mirror of
				https://github.com/mokuappio/serverless-invoices.git
				synced 2025-10-31 18:01:07 -04:00 
			
		
		
		
	Separated client form into tabs. Fixed deleting client. Implemented custom fields for client and invoice client fields. Removed "hardcoded" client_reg_no and client_vat_no fields from client and invoice model - can be replaced with custom fields. Abstracted invoice-rows management to separate store namespace. Invoice client fields are prefilled from selected client.
This commit is contained in:
		
							
								
								
									
										31
									
								
								src/store/client-fields.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/store/client-fields.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| import ClientField from '@/store/models/client-field'; | ||||
|  | ||||
| export default { | ||||
|   namespaced: true, | ||||
|   state: {}, | ||||
|   mutations: {}, | ||||
|   actions: { | ||||
|     init() {}, | ||||
|     terminate() {}, | ||||
|     async clientFieldProps(store, payload) { | ||||
|       return ClientField.update({ | ||||
|         where: payload.fieldId, | ||||
|         data: payload.props, | ||||
|       }); | ||||
|     }, | ||||
|     async updateClientField({ dispatch }, payload) { | ||||
|       await dispatch('clientFieldProps', payload); | ||||
|       return dispatch('clients/updateClient', null, { root: true }); // TODO: pass clientId to make generic | ||||
|     }, | ||||
|     async addNewField(store, clientId) { | ||||
|       const field = await ClientField.createNew(); | ||||
|       field.$update({ | ||||
|         client_id: clientId, | ||||
|       }); | ||||
|     }, | ||||
|     async deleteClientField({ dispatch }, fieldId) { | ||||
|       await ClientField.delete(fieldId); | ||||
|       return dispatch('clients/updateClient', null, { root: true }); // TODO: pass clientId to make generic | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| @ -47,11 +47,15 @@ export default { | ||||
|       }); | ||||
|     }, | ||||
|     async updateClient({ getters, dispatch }, props) { | ||||
|       await dispatch('clientProps', props); | ||||
|       if (props) { | ||||
|         await dispatch('clientProps', props); | ||||
|       } | ||||
|       return ClientService.updateClient(getters.client); | ||||
|     }, | ||||
|     async updateClientById(store, payload) { | ||||
|       const client = Client.find(payload.clientId); | ||||
|       const client = Client.query() | ||||
|         .with('fields') | ||||
|         .find(payload.clientId); | ||||
|       client.$update(payload.props); | ||||
|       return ClientService.updateClient(client); | ||||
|     }, | ||||
| @ -60,24 +64,22 @@ export default { | ||||
|       commit('clientId', client.id); | ||||
|       commit('isModalOpen', true); | ||||
|     }, | ||||
|     async deleteClient(clientId) { | ||||
|     async deleteClient(store, clientId) { | ||||
|       const res = await ClientService.deleteClient(clientId); | ||||
|       if ('client_id' in res) { | ||||
|         Client.delete(res.client_id); | ||||
|       } | ||||
|       await Client.delete(clientId); | ||||
|       return res; | ||||
|     }, | ||||
|   }, | ||||
|   getters: { | ||||
|     client(state) { | ||||
|       return Client.query() | ||||
|         .with(['bank_account']) | ||||
|         .with(['bank_account', 'fields']) | ||||
|         .find(state.clientId); | ||||
|     }, | ||||
|     all() { | ||||
|       return Client.query() | ||||
|         .where('$isNew', false) | ||||
|         .with(['bank_account']) | ||||
|         .with(['bank_account', 'fields']) | ||||
|         .get(); | ||||
|     }, | ||||
|   }, | ||||
|  | ||||
							
								
								
									
										34
									
								
								src/store/invoice-client-fields.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/store/invoice-client-fields.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | ||||
| import InvoiceClientField from '@/store/models/invoice-client-field'; | ||||
|  | ||||
| export default { | ||||
|   namespaced: true, | ||||
|   state: {}, | ||||
|   mutations: {}, | ||||
|   actions: { | ||||
|     init() {}, | ||||
|     terminate() {}, | ||||
|     invoiceClientFieldProps(store, payload) { | ||||
|       return InvoiceClientField.update({ | ||||
|         where: payload.fieldId, | ||||
|         data: payload.props, | ||||
|       }); | ||||
|     }, | ||||
|     async updateInvoiceClientField({ dispatch }, payload) { | ||||
|       await dispatch('invoiceClientFieldProps', payload); | ||||
|       return dispatch('invoices/updateInvoice', null, { root: true }); | ||||
|     }, | ||||
|     async removeInvoiceClientFields(store, invoiceId) { | ||||
|       return InvoiceClientField.delete(field => field.invoice_id === invoiceId); | ||||
|     }, | ||||
|     async addInvoiceClientField(store, payload) { | ||||
|       const field = await InvoiceClientField.createNew(); | ||||
|       await field.$update({ | ||||
|         ...payload.props, | ||||
|         invoice_id: payload.invoiceId, | ||||
|       }); | ||||
|     }, | ||||
|     async removeInvoiceClientField(store, fieldId) { | ||||
|       await InvoiceClientField.delete(fieldId); | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
							
								
								
									
										34
									
								
								src/store/invoice-rows.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/store/invoice-rows.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | ||||
| import InvoiceRow from '@/store/models/invoice-row'; | ||||
|  | ||||
| export default { | ||||
|   namespaced: true, | ||||
|   state: { | ||||
|   }, | ||||
|   mutations: { | ||||
|   }, | ||||
|   actions: { | ||||
|     init() {}, | ||||
|     terminate() {}, | ||||
|     invoiceRowProps(store, payload) { | ||||
|       return InvoiceRow.update({ | ||||
|         where: payload.id, | ||||
|         data: payload.props, | ||||
|       }); | ||||
|     }, | ||||
|     async updateInvoiceRow({ dispatch }, payload) { | ||||
|       await dispatch('invoiceRowProps', payload); | ||||
|       return dispatch('invoices/updateInvoice', null, { root: true }); | ||||
|     }, | ||||
|     async addRow(store, invoiceId) { | ||||
|       const row = await InvoiceRow.createNew(); | ||||
|       const rowCount = InvoiceRow.query().where('invoice_id', invoiceId).count(); | ||||
|       row.$update({ | ||||
|         invoice_id: invoiceId, | ||||
|         order: rowCount, | ||||
|       }); | ||||
|     }, | ||||
|     async removeRow(store, rowId) { | ||||
|       await InvoiceRow.delete(rowId); | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| @ -1,6 +1,5 @@ | ||||
| import InvoiceService from '@/services/invoice.service'; | ||||
| import Invoice from '@/store/models/invoice'; | ||||
| import InvoiceRow from '@/store/models/invoice-row'; | ||||
| import { pick } from '@/utils/helpers'; | ||||
| import dayjs from 'dayjs'; | ||||
| import Errors from '@/utils/errors'; | ||||
| @ -55,16 +54,7 @@ export default { | ||||
|         data: props, | ||||
|       }); | ||||
|     }, | ||||
|     invoiceRowProps(store, payload) { | ||||
|       return InvoiceRow.update({ | ||||
|         where: payload.id, | ||||
|         data: payload.props, | ||||
|       }); | ||||
|     }, | ||||
|     async updateInvoice({ getters, dispatch, commit }, props) { | ||||
|       await dispatch('invoiceProps', props); | ||||
|  | ||||
|       // Update client | ||||
|     async updateClient({ getters, dispatch }, props) { | ||||
|       const clientProps = pick(props, { | ||||
|         bank_account_id: 'bank_account_id', | ||||
|         client_name: 'company_name', | ||||
| @ -73,8 +63,6 @@ export default { | ||||
|         client_country: 'company_country', | ||||
|         client_county: 'company_county', | ||||
|         client_city: 'company_city', | ||||
|         client_reg_no: 'company_reg_no', | ||||
|         client_vat_no: 'company_vat_no', | ||||
|         client_email: 'invoice_email', | ||||
|         currency: 'currency', | ||||
|       }); | ||||
| @ -88,7 +76,8 @@ export default { | ||||
|           clientId: getters.invoice.client_id, | ||||
|         }, { root: true }); | ||||
|       } | ||||
|  | ||||
|     }, | ||||
|     async updateTeam({ getters, dispatch }, props) { | ||||
|       const teamProps = pick(props, { | ||||
|         late_fee: 'invoice_late_fee', | ||||
|         from_name: 'company_name', | ||||
| @ -119,37 +108,23 @@ export default { | ||||
|       if (Object.keys(teamProps).length > 0) { | ||||
|         dispatch('teams/updateTeam', teamProps, { root: true }); | ||||
|       } | ||||
|  | ||||
|       commit('clearErrors'); | ||||
|  | ||||
|       return InvoiceService.updateInvoice(getters.invoice) | ||||
|         .catch(err => commit('setErrors', err.errors)); | ||||
|     }, | ||||
|     async updateInvoiceRow({ getters, dispatch, commit }, payload) { | ||||
|       await dispatch('invoiceRowProps', payload); | ||||
|     async updateInvoice({ dispatch, commit, getters }, props) { | ||||
|       if (props) { | ||||
|         await dispatch('invoiceProps', props); | ||||
|         await dispatch('updateClient', props); | ||||
|         await dispatch('updateTeam', props); | ||||
|       } | ||||
|  | ||||
|       commit('clearErrors'); | ||||
|  | ||||
|       return InvoiceService.updateInvoice(getters.invoice) | ||||
|         .catch(err => commit('setErrors', err.errors)); | ||||
|     }, | ||||
|     async deleteInvoice(store, invoice) { | ||||
|       const res = await InvoiceService.deleteInvoice(invoice.id); | ||||
|       if ('invoice_id' in res) { | ||||
|         Invoice.delete(res.invoice_id); | ||||
|       } | ||||
|       await Invoice.delete(invoice.id); | ||||
|       return res; | ||||
|     }, | ||||
|     async addRow({ state, getters }) { | ||||
|       const row = await InvoiceRow.createNew(); | ||||
|       row.$update({ | ||||
|         invoice_id: state.invoiceId, | ||||
|         order: getters.invoice.rows.length, | ||||
|       }); | ||||
|     }, | ||||
|     async removeRow(store, row) { | ||||
|       await InvoiceRow.delete(row.id); | ||||
|     }, | ||||
|     async bookInvoice({ getters, commit, dispatch }) { | ||||
|       commit('clearErrors'); | ||||
|  | ||||
| @ -164,7 +139,7 @@ export default { | ||||
|   getters: { | ||||
|     invoice(state) { | ||||
|       return Invoice.query() | ||||
|         .with(['client']) | ||||
|         .with(['client', 'client_fields']) | ||||
|         .with('rows', query => query.orderBy('order', 'asc')) | ||||
|         .find(state.invoiceId); | ||||
|     }, | ||||
|  | ||||
							
								
								
									
										16
									
								
								src/store/models/client-field.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/store/models/client-field.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| import { Model } from '@vuex-orm/core'; | ||||
| import { uuidv4 } from '@/utils/helpers'; | ||||
|  | ||||
| export default class ClientField extends Model { | ||||
|   // This is the name used as module name of the Vuex Store. | ||||
|   static entity = 'client_fields'; | ||||
|  | ||||
|   static fields() { | ||||
|     return { | ||||
|       id: this.attr(() => uuidv4()), | ||||
|       client_id: this.attr(null), | ||||
|       label: this.attr(''), | ||||
|       value: this.attr(''), | ||||
|     }; | ||||
|   } | ||||
| } | ||||
| @ -1,6 +1,7 @@ | ||||
| import { Model } from '@vuex-orm/core'; | ||||
| import { uuidv4 } from '@/utils/helpers'; | ||||
| import BankAccount from '@/store/models/bank-account'; | ||||
| import ClientField from '@/store/models/client-field'; | ||||
|  | ||||
| export default class Client extends Model { | ||||
|   // This is the name used as module name of the Vuex Store. | ||||
| @ -15,14 +16,13 @@ export default class Client extends Model { | ||||
|       company_country: this.attr(''), | ||||
|       company_county: this.attr(''), | ||||
|       company_city: this.attr(''), | ||||
|       company_reg_no: this.attr(''), | ||||
|       company_vat_no: this.attr(''), | ||||
|       has_vat: this.attr(null), | ||||
|       currency: this.attr(null), | ||||
|       rate: this.attr(null), | ||||
|       invoice_email: this.attr(''), | ||||
|       bank_account_id: this.attr(null), | ||||
|       bank_account: this.belongsTo(BankAccount, 'bank_account_id', 'id'), | ||||
|       bank_account: this.belongsTo(BankAccount, 'bank_account_id'), | ||||
|       fields: this.hasMany(ClientField, 'client_id'), | ||||
|       updated_at: this.attr(''), | ||||
|       created_at: this.attr(''), | ||||
|     }; | ||||
|  | ||||
							
								
								
									
										17
									
								
								src/store/models/invoice-client-field.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/store/models/invoice-client-field.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| import { Model } from '@vuex-orm/core'; | ||||
| import { uuidv4 } from '@/utils/helpers'; | ||||
|  | ||||
| export default class InvoiceClientField extends Model { | ||||
|   // This is the name used as module name of the Vuex Store. | ||||
|   static entity = 'invoice_client_fields'; | ||||
|  | ||||
|   static fields() { | ||||
|     return { | ||||
|       id: this.attr(() => uuidv4()), | ||||
|       invoice_id: this.attr(null), | ||||
|       client_field_id: this.attr(null), | ||||
|       label: this.attr(''), | ||||
|       value: this.attr(''), | ||||
|     }; | ||||
|   } | ||||
| } | ||||
| @ -2,6 +2,7 @@ import { Model } from '@vuex-orm/core'; | ||||
| import { uuidv4 } from '@/utils/helpers'; | ||||
| import Client from '@/store/models/client'; | ||||
| import InvoiceRow from '@/store/models/invoice-row'; | ||||
| import InvoiceClientField from '@/store/models/invoice-client-field'; | ||||
|  | ||||
| export default class Invoice extends Model { | ||||
|   // This is the name used as module name of the Vuex Store. | ||||
| @ -36,8 +37,6 @@ export default class Invoice extends Model { | ||||
|       client_country: this.attr(''), | ||||
|       client_county: this.attr(''), | ||||
|       client_city: this.attr(''), | ||||
|       client_reg_no: this.attr(''), | ||||
|       client_vat_no: this.attr(''), | ||||
|       client_email: this.attr(''), | ||||
|       client_id: this.attr(null), | ||||
|       client: this.belongsTo(Client, 'client_id'), | ||||
| @ -46,6 +45,7 @@ export default class Invoice extends Model { | ||||
|       updated_at: this.attr(''), | ||||
|       created_at: this.attr(''), | ||||
|       total: this.attr(null), // Only used in lists. | ||||
|       client_fields: this.hasMany(InvoiceClientField, 'invoice_id'), | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|  | ||||
							
								
								
									
										16
									
								
								src/store/models/team-field.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/store/models/team-field.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| import { Model } from '@vuex-orm/core'; | ||||
| import { uuidv4 } from '@/utils/helpers'; | ||||
|  | ||||
| export default class TeamField extends Model { | ||||
|   // This is the name used as module name of the Vuex Store. | ||||
|   static entity = 'team_fields'; | ||||
|  | ||||
|   static fields() { | ||||
|     return { | ||||
|       id: this.attr(() => uuidv4()), | ||||
|       team_id: this.attr(null), | ||||
|       label: this.attr(''), | ||||
|       value: this.attr(''), | ||||
|     }; | ||||
|   } | ||||
| } | ||||
| @ -1,5 +1,6 @@ | ||||
| import { Model } from '@vuex-orm/core'; | ||||
| import { uuidv4 } from '@/utils/helpers'; | ||||
| import TeamField from '@/store/models/team-field'; | ||||
|  | ||||
| export default class Team extends Model { | ||||
|   // This is the name used as module name of the Vuex Store. | ||||
| @ -22,6 +23,7 @@ export default class Team extends Model { | ||||
|       vat_rate: this.attr(null), | ||||
|       invoice_late_fee: this.attr(null), | ||||
|       invoice_due_days: this.attr(null), | ||||
|       fields: this.hasMany(TeamField, 'team_id'), | ||||
|       updated_at: this.attr(''), | ||||
|       created_at: this.attr(''), | ||||
|       logo_url: this.attr(''), | ||||
|  | ||||
| @ -9,10 +9,16 @@ import InvoiceRow from '@/store/models/invoice-row'; | ||||
| import Team from '@/store/models/team'; | ||||
| import bankAccounts from '@/store/bank-accounts'; | ||||
| import clients from '@/store/clients'; | ||||
| import clientFields from '@/store/client-fields'; | ||||
| import invoices from '@/store/invoices'; | ||||
| import invoiceRows from '@/store/invoice-rows'; | ||||
| import invoiceClientFields from '@/store/invoice-client-fields'; | ||||
| import teams from '@/store/teams'; | ||||
| import themes from '@/store/themes'; | ||||
| import data from '@/store/data'; | ||||
| import ClientField from '@/store/models/client-field'; | ||||
| import TeamField from '@/store/models/team-field'; | ||||
| import InvoiceClientField from '@/store/models/invoice-client-field'; | ||||
|  | ||||
| Vue.use(Vuex); | ||||
|  | ||||
| @ -20,8 +26,11 @@ VuexORM.use(VuexORMisDirtyPlugin); | ||||
| const database = new VuexORM.Database(); | ||||
|  | ||||
| database.register(Team); | ||||
| database.register(TeamField); | ||||
| database.register(Client); | ||||
| database.register(ClientField); | ||||
| database.register(Invoice); | ||||
| database.register(InvoiceClientField); | ||||
| database.register(InvoiceRow); | ||||
| database.register(BankAccount); | ||||
|  | ||||
| @ -30,7 +39,10 @@ export default new Vuex.Store({ | ||||
|   modules: { | ||||
|     bankAccounts, | ||||
|     clients, | ||||
|     clientFields, | ||||
|     invoices, | ||||
|     invoiceRows, | ||||
|     invoiceClientFields, | ||||
|     teams, | ||||
|     themes, | ||||
|     data, | ||||
|  | ||||
		Reference in New Issue
	
	Block a user