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:
HenriT
2021-04-08 19:54:45 +03:00
parent af22213b9b
commit 292fafe3a5
20 changed files with 465 additions and 138 deletions

View File

@ -0,0 +1,75 @@
.nav-tabs {
overflow-x: auto;
overflow-y: hidden;
white-space: nowrap;
// scrollbar styles
scrollbar-color: var(--text-caption);
scrollbar-width: 6px;
scrollbar-gutter: always;
&::-webkit-scrollbar-track {
background: var(--shade);
border-radius: 6px;
}
&::-webkit-scrollbar-thumb {
background: var(--text-caption);
border-radius: 6px;
&:hover {
background: var(--text-secondary);
}
}
&::-webkit-scrollbar {
width: 6px;
height: 6px;
}
> li {
display: inline-block;
float: none;
white-space: normal;
}
&--simple {
&.nav-tabs {
> li {
> a {
border-bottom: 1px solid transparent;
&.active {
background-color: transparent;
border-left-color: transparent;
border-top-color: transparent;
border-right-color: transparent;
border-bottom-color: var(--text-primary);
color: $nav-tabs-link-active-color;
}
&:hover, &:focus, &:active {
background-color: transparent;
border-left-color: transparent;
border-top-color: transparent;
border-right-color: transparent;
}
}
}
> li {
> a {
border-bottom-width: 2px;
min-width: 90px;
text-align: center;
font-weight: 500;
color: $nav-tabs-link-active-color;
}
}
}
.tab-content {
padding-top: 16px;
}
}
}

View File

@ -13,6 +13,7 @@
@import "snackbars"; @import "snackbars";
@import "surfaces"; @import "surfaces";
@import "tables"; @import "tables";
@import "tabs";
@import "transitions"; @import "transitions";
@import "type"; @import "type";
@import "utilities"; @import "utilities";

View File

@ -0,0 +1,70 @@
<template>
<div>
<div v-for="field in client.fields" :key="field.id" class="col-sm-6">
<AppEditable :value="field.label"
placeholder="Label"
@change="updateFieldProp({ label: $event }, field)"/>
<i class="material-icons md-18 float-right pointer" @click="removeField(field)">close</i>
<AppInput :value="field.value" @change="updateFieldProp({ value: $event }, field)"
:placeholder="field.label"/>
</div>
<div class="col-12">
<button class="btn btn-sm btn-secondary" @click="addNewField">
<i class="material-icons md-18">add</i>
Field
</button>
</div>
</div>
</template>
<script>
import NotificationService from '@/services/notification.service';
import AppInput from '@/components/form/AppInput';
import AppEditable from '@/components/form/AppEditable';
export default {
props: ['client'],
components: {
AppEditable,
AppInput,
},
computed: {
isNew() {
return this.client && this.client.$isNew;
},
},
methods: {
addNewField() {
this.$store.dispatch('clientFields/addNewField', this.client.id);
},
async removeField(field) {
const confirmed = await this.$bvModal.msgBoxConfirm(`Delete field ${field.label}?`, {
okTitle: 'Delete',
okVariant: 'danger',
cancelTitle: 'Dismiss',
cancelVariant: 'btn-link',
contentClass: 'bg-base dp--24',
});
if (confirmed) {
await this.$store.dispatch('clientFields/deleteClientField', field.id);
try {
NotificationService.success('Deleted');
} catch (err) {
NotificationService.error(err.message);
}
}
},
updateFieldProp(props, field) {
if (this.isNew) {
return this.$store.dispatch('clientFields/clientFieldProps', {
props,
fieldId: field.id,
});
}
this.$store.dispatch('clientFields/updateClientField', {
props,
fieldId: field.id,
});
},
},
};
</script>

View File

@ -1,26 +1,60 @@
<template> <template>
<div> <div>
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12 d-flex justify-content-between">
<h4>Client</h4> <h4>Client</h4>
<div v-if="client">
<div v-if="!isNew">
<b-dropdown variant="link" size="sm" no-caret right>
<template slot="button-content">
<i class="material-icons">more_vert</i>
</template>
<b-dropdown-item-button @click="deleteClient">Delete</b-dropdown-item-button>
</b-dropdown>
<button class="btn btn-sm btn-primary"
@click="$emit('done')">Done
</button>
</div>
<button v-else class="btn btn-primary ml-2"
:disabled="loading"
@click="createClient">Create
</button>
</div>
</div> </div>
</div> </div>
<div v-if="client" class="row"> <b-tabs v-if="client" nav-class="nav-tabs--simple mb-4" active-tab-class="active" class="row">
<div class="col-12"> <b-tab title="General" class="col-12">
<h5>General</h5> <div class="row">
</div>
<AppInput :value="client.company_name" @change="updateProp({ company_name: $event })" <AppInput :value="client.company_name" @change="updateProp({ company_name: $event })"
label="Company Name" field="company_name" :errors="errors" class="col-12"/> label="Company Name" field="company_name" :errors="errors" class="col-12"/>
<AppInput :value="client.invoice_email" @change="updateProp({ invoice_email: $event })" <AppInput :value="client.invoice_email" @change="updateProp({ invoice_email: $event })"
label="Email" field="invoice_email" :errors="errors" class="col-sm-7"/> label="Email" field="invoice_email" :errors="errors" class="col-sm-7"/>
<AppInput :value="client.company_reg_no" @change="updateProp({ company_reg_no: $event })"
label="Company reg no" field="company_reg_no" :errors="errors" class="col-sm-5"/>
<div class="col-12">
<h5>Invoice Settings</h5>
<h6>Address</h6>
</div> </div>
<ClientFields class="row" :client="client"/>
</b-tab>
<b-tab title="Invoicing" class="col-12">
<div class="row">
<AppInput :value="client.currency" @change="updateProp({ currency: $event })"
label="Currency" field="currency" :errors="errors" class="col-sm-4"/>
<AppInput :value="client.rate" @change="updateProp({ rate: $event })"
label="Hourly rate" field="rate" :errors="errors" class="col-sm-4"/>
<AppCheckbox :value="client.has_vat" @input="updateProp({ has_vat: $event })"
label="Apply VAT" field="has_vat" :errors="errors" class="col-sm-4"/>
<AppSelect :value="client.bank_account"
track-by="id"
label="Bank account"
label-field="bank_name"
:options="bankAccounts || []"
@input="bankAccountChanged"
class="col-12"/>
</div>
</b-tab>
<b-tab title="Address" class="col-12">
<div class="row">
<AppInput :value="client.company_address" @change="updateProp({ company_address: $event })" <AppInput :value="client.company_address" @change="updateProp({ company_address: $event })"
label="Company Address" field="company_address" :errors="errors" label="Company Address" field="company_address" :errors="errors"
class="col-12"/> class="col-12"/>
@ -34,61 +68,36 @@
label="County/State" field="company_county" :errors="errors" class="col-sm-6"/> label="County/State" field="company_county" :errors="errors" class="col-sm-6"/>
<AppInput :value="client.company_country" @change="updateProp({ company_country: $event })" <AppInput :value="client.company_country" @change="updateProp({ company_country: $event })"
label="Country" field="company_country" :errors="errors" class="col-sm-6"/> label="Country" field="company_country" :errors="errors" class="col-sm-6"/>
<AppInput :value="client.currency" @change="updateProp({ currency: $event })" </div>
label="Currency" field="currency" :errors="errors" class="col-sm-4"/> </b-tab>
<AppInput :value="client.rate" @change="updateProp({ rate: $event })"
label="Hourly rate" field="rate" :errors="errors" class="col-sm-4"/>
<div class="col-12"> </b-tabs>
<h6>VAT</h6>
</div>
<AppInput :value="client.company_vat_no" @change="updateProp({ company_vat_no: $event })"
label="Company VAT no" field="company_vat_no" :errors="errors" class="col-sm-8"/>
<AppCheckbox :value="client.has_vat" @input="updateProp({ has_vat: $event })"
label="Apply VAT" field="has_vat" :errors="errors" class="col-sm-4"/>
<div class="col-12">
<h6>Banking details</h6>
</div>
<AppSelect :value="client.bank_account"
track-by="id"
label="Bank account"
label-field="bank_name"
:options="bankAccounts || []"
@input="bankAccountChanged"
class="col-12"/>
</div>
<div v-if="!client">Loading</div> <div v-if="!client">Loading</div>
<div class="row mt-3 text-right" v-if="client">
<div class="col-12">
<div v-if="!isNew">
<button class="btn btn-outline-danger mr-2" @click="deleteClient(client.id)">Delete</button>
<button class="btn btn-primary"
@click="$emit('done')">Done
</button>
</div>
<button v-else class="btn btn-primary ml-2"
:disabled="loading"
@click="createClient">Create
</button>
</div>
</div>
</div> </div>
</template> </template>
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import {
BTab, BTabs, BDropdownItemButton, BDropdown,
} from 'bootstrap-vue';
import NotificationService from '@/services/notification.service'; import NotificationService from '@/services/notification.service';
import AppInput from '@/components/form/AppInput'; import AppInput from '@/components/form/AppInput';
import AppSelect from '@/components/form/AppSelect'; import AppSelect from '@/components/form/AppSelect';
import Errors from '@/utils/errors'; import Errors from '@/utils/errors';
import AppCheckbox from '@/components/form/AppCheckbox'; import AppCheckbox from '@/components/form/AppCheckbox';
import ClientFields from '@/components/clients/ClientFields';
export default { export default {
components: { components: {
ClientFields,
AppCheckbox, AppCheckbox,
AppInput, AppInput,
AppSelect, AppSelect,
BTab,
BTabs,
BDropdown,
BDropdownItemButton,
}, },
data() { data() {
return { return {
@ -148,7 +157,7 @@ export default {
this.loading = false; this.loading = false;
}); });
}, },
async deleteClient(clientId) { async deleteClient() {
const confirmed = await this.$bvModal.msgBoxConfirm(`Delete client ${this.client.company_name}?`, { const confirmed = await this.$bvModal.msgBoxConfirm(`Delete client ${this.client.company_name}?`, {
okTitle: 'Delete', okTitle: 'Delete',
okVariant: 'danger', okVariant: 'danger',
@ -158,7 +167,7 @@ export default {
}); });
if (confirmed) { if (confirmed) {
this.$emit('done'); this.$emit('done');
await this.$store.dispatch('clients/deleteClient', clientId); await this.$store.dispatch('clients/deleteClient', this.client.id);
try { try {
NotificationService.success('Deleted'); NotificationService.success('Deleted');
} catch (err) { } catch (err) {

View File

@ -31,20 +31,8 @@
<AppError :errors="errors" field="client_county"/> <AppError :errors="errors" field="client_county"/>
<AppError :errors="errors" field="client_country"/> <AppError :errors="errors" field="client_country"/>
<span :class="{'d-print-none': !invoice.client_reg_no }">Reg no: </span> <InvoiceClientFields :invoice="invoice"/>
<AppEditable :value="invoice.client_reg_no"
:errors="errors"
field="client_reg_no"
placeholder="Enter reg no"
class="break-line"
@change="updateProp({ client_reg_no: $event })"/>
<span :class="{'d-print-none': !invoice.client_vat_no }">VAT no: </span>
<AppEditable :value="invoice.client_vat_no"
:errors="errors"
field="client_vat_no"
placeholder="Enter vat no"
class="break-line"
@change="updateProp({ client_vat_no: $event })"/>
<AppEditable :value="invoice.client_email" <AppEditable :value="invoice.client_email"
:errors="errors" :errors="errors"
field="client_email" field="client_email"
@ -58,6 +46,7 @@ import { mapGetters } from 'vuex';
import AppError from '@/components/form/AppError'; import AppError from '@/components/form/AppError';
import AppEditable from '@/components/form/AppEditable'; import AppEditable from '@/components/form/AppEditable';
import ClientSelector from '@/components/clients/ClientSelector'; import ClientSelector from '@/components/clients/ClientSelector';
import InvoiceClientFields from '@/components/invoices/InvoiceClientFields';
export default { export default {
props: ['invoice', 'errors'], props: ['invoice', 'errors'],
@ -65,6 +54,7 @@ export default {
AppError, AppError,
ClientSelector, ClientSelector,
AppEditable, AppEditable,
InvoiceClientFields,
}, },
computed: { computed: {
...mapGetters({ ...mapGetters({
@ -82,6 +72,8 @@ export default {
this.prefillClient(client); this.prefillClient(client);
}, },
prefillClient(client) { prefillClient(client) {
this.prefillClientFields(client);
return this.updateProp({ return this.updateProp({
client_id: client.id, client_id: client.id,
client_name: client.company_name, client_name: client.company_name,
@ -90,8 +82,6 @@ export default {
client_city: client.company_city, client_city: client.company_city,
client_county: client.company_county, client_county: client.company_county,
client_country: client.company_country, client_country: client.company_country,
client_reg_no: client.company_reg_no,
client_vat_no: client.company_vat_no,
client_email: client.invoice_email, client_email: client.invoice_email,
currency: client.currency || 'USD', currency: client.currency || 'USD',
vat_rate: client.has_vat ? this.team.vat_rate : 0, vat_rate: client.has_vat ? this.team.vat_rate : 0,
@ -99,6 +89,20 @@ export default {
bank_account_no: client.bank_account ? client.bank_account.account_no : null, bank_account_no: client.bank_account ? client.bank_account.account_no : null,
}); });
}, },
prefillClientFields(client) {
this.$store.dispatch('invoiceClientFields/removeInvoiceClientFields', this.invoice.id);
client.fields.forEach((field) => {
this.$store.dispatch('invoiceClientFields/addInvoiceClientField', {
invoiceId: this.invoice.id,
props: {
label: field.label,
value: field.value,
client_field_id: field.id,
},
});
});
},
}, },
}; };
</script> </script>

View File

@ -0,0 +1,29 @@
<template>
<div>
<div v-for="field in invoice.client_fields">
<span :class="{'d-print-none': !field.value }">{{ field.label }}: </span>
<AppEditable :value="field.value"
:placeholder="field.label"
class="break-line"
@change="updateProp({ value: $event }, field)"/>
</div>
</div>
</template>
<script>
import AppEditable from '@/components/form/AppEditable';
export default {
props: ['invoice'],
components: {
AppEditable,
},
methods: {
updateProp(props, field) {
this.$store.dispatch('invoiceClientFields/updateInvoiceClientField', {
props,
fieldId: field.id,
});
},
},
};
</script>

View File

@ -137,7 +137,7 @@ export default {
this.$store.dispatch('invoices/updateInvoice', props); this.$store.dispatch('invoices/updateInvoice', props);
}, },
addRow() { addRow() {
this.$store.dispatch('invoices/addRow'); this.$store.dispatch('invoiceRows/addRow', this.invoice.id);
}, },
updateTeam(props) { updateTeam(props) {
this.$store.dispatch('teams/updateTeam', props); this.$store.dispatch('teams/updateTeam', props);

View File

@ -52,13 +52,13 @@ export default {
}, },
methods: { methods: {
updateProp(props) { updateProp(props) {
this.$store.dispatch('invoices/updateInvoiceRow', { this.$store.dispatch('invoiceRows/updateInvoiceRow', {
props, props,
id: this.row.id, id: this.row.id,
}); });
}, },
async removeRow(row) { async removeRow(row) {
await this.$store.dispatch('invoices/removeRow', row); await this.$store.dispatch('invoiceRows/removeRow', row.id);
this.updateProp(); this.updateProp();
}, },
}, },

View 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
},
},
};

View File

@ -47,11 +47,15 @@ export default {
}); });
}, },
async updateClient({ getters, dispatch }, props) { async updateClient({ getters, dispatch }, props) {
if (props) {
await dispatch('clientProps', props); await dispatch('clientProps', props);
}
return ClientService.updateClient(getters.client); return ClientService.updateClient(getters.client);
}, },
async updateClientById(store, payload) { async updateClientById(store, payload) {
const client = Client.find(payload.clientId); const client = Client.query()
.with('fields')
.find(payload.clientId);
client.$update(payload.props); client.$update(payload.props);
return ClientService.updateClient(client); return ClientService.updateClient(client);
}, },
@ -60,24 +64,22 @@ export default {
commit('clientId', client.id); commit('clientId', client.id);
commit('isModalOpen', true); commit('isModalOpen', true);
}, },
async deleteClient(clientId) { async deleteClient(store, clientId) {
const res = await ClientService.deleteClient(clientId); const res = await ClientService.deleteClient(clientId);
if ('client_id' in res) { await Client.delete(clientId);
Client.delete(res.client_id);
}
return res; return res;
}, },
}, },
getters: { getters: {
client(state) { client(state) {
return Client.query() return Client.query()
.with(['bank_account']) .with(['bank_account', 'fields'])
.find(state.clientId); .find(state.clientId);
}, },
all() { all() {
return Client.query() return Client.query()
.where('$isNew', false) .where('$isNew', false)
.with(['bank_account']) .with(['bank_account', 'fields'])
.get(); .get();
}, },
}, },

View 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
View 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);
},
},
};

View File

@ -1,6 +1,5 @@
import InvoiceService from '@/services/invoice.service'; import InvoiceService from '@/services/invoice.service';
import Invoice from '@/store/models/invoice'; import Invoice from '@/store/models/invoice';
import InvoiceRow from '@/store/models/invoice-row';
import { pick } from '@/utils/helpers'; import { pick } from '@/utils/helpers';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import Errors from '@/utils/errors'; import Errors from '@/utils/errors';
@ -55,16 +54,7 @@ export default {
data: props, data: props,
}); });
}, },
invoiceRowProps(store, payload) { async updateClient({ getters, dispatch }, props) {
return InvoiceRow.update({
where: payload.id,
data: payload.props,
});
},
async updateInvoice({ getters, dispatch, commit }, props) {
await dispatch('invoiceProps', props);
// Update client
const clientProps = pick(props, { const clientProps = pick(props, {
bank_account_id: 'bank_account_id', bank_account_id: 'bank_account_id',
client_name: 'company_name', client_name: 'company_name',
@ -73,8 +63,6 @@ export default {
client_country: 'company_country', client_country: 'company_country',
client_county: 'company_county', client_county: 'company_county',
client_city: 'company_city', client_city: 'company_city',
client_reg_no: 'company_reg_no',
client_vat_no: 'company_vat_no',
client_email: 'invoice_email', client_email: 'invoice_email',
currency: 'currency', currency: 'currency',
}); });
@ -88,7 +76,8 @@ export default {
clientId: getters.invoice.client_id, clientId: getters.invoice.client_id,
}, { root: true }); }, { root: true });
} }
},
async updateTeam({ getters, dispatch }, props) {
const teamProps = pick(props, { const teamProps = pick(props, {
late_fee: 'invoice_late_fee', late_fee: 'invoice_late_fee',
from_name: 'company_name', from_name: 'company_name',
@ -119,37 +108,23 @@ export default {
if (Object.keys(teamProps).length > 0) { if (Object.keys(teamProps).length > 0) {
dispatch('teams/updateTeam', teamProps, { root: true }); 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) { async updateInvoice({ dispatch, commit, getters }, props) {
await dispatch('invoiceRowProps', payload); if (props) {
await dispatch('invoiceProps', props);
await dispatch('updateClient', props);
await dispatch('updateTeam', props);
}
commit('clearErrors'); commit('clearErrors');
return InvoiceService.updateInvoice(getters.invoice) return InvoiceService.updateInvoice(getters.invoice)
.catch(err => commit('setErrors', err.errors)); .catch(err => commit('setErrors', err.errors));
}, },
async deleteInvoice(store, invoice) { async deleteInvoice(store, invoice) {
const res = await InvoiceService.deleteInvoice(invoice.id); const res = await InvoiceService.deleteInvoice(invoice.id);
if ('invoice_id' in res) { await Invoice.delete(invoice.id);
Invoice.delete(res.invoice_id);
}
return res; 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 }) { async bookInvoice({ getters, commit, dispatch }) {
commit('clearErrors'); commit('clearErrors');
@ -164,7 +139,7 @@ export default {
getters: { getters: {
invoice(state) { invoice(state) {
return Invoice.query() return Invoice.query()
.with(['client']) .with(['client', 'client_fields'])
.with('rows', query => query.orderBy('order', 'asc')) .with('rows', query => query.orderBy('order', 'asc'))
.find(state.invoiceId); .find(state.invoiceId);
}, },

View 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(''),
};
}
}

View File

@ -1,6 +1,7 @@
import { Model } from '@vuex-orm/core'; import { Model } from '@vuex-orm/core';
import { uuidv4 } from '@/utils/helpers'; import { uuidv4 } from '@/utils/helpers';
import BankAccount from '@/store/models/bank-account'; import BankAccount from '@/store/models/bank-account';
import ClientField from '@/store/models/client-field';
export default class Client extends Model { export default class Client extends Model {
// This is the name used as module name of the Vuex Store. // 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_country: this.attr(''),
company_county: this.attr(''), company_county: this.attr(''),
company_city: this.attr(''), company_city: this.attr(''),
company_reg_no: this.attr(''),
company_vat_no: this.attr(''),
has_vat: this.attr(null), has_vat: this.attr(null),
currency: this.attr(null), currency: this.attr(null),
rate: this.attr(null), rate: this.attr(null),
invoice_email: this.attr(''), invoice_email: this.attr(''),
bank_account_id: this.attr(null), 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(''), updated_at: this.attr(''),
created_at: this.attr(''), created_at: this.attr(''),
}; };

View 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(''),
};
}
}

View File

@ -2,6 +2,7 @@ import { Model } from '@vuex-orm/core';
import { uuidv4 } from '@/utils/helpers'; import { uuidv4 } from '@/utils/helpers';
import Client from '@/store/models/client'; import Client from '@/store/models/client';
import InvoiceRow from '@/store/models/invoice-row'; import InvoiceRow from '@/store/models/invoice-row';
import InvoiceClientField from '@/store/models/invoice-client-field';
export default class Invoice extends Model { export default class Invoice extends Model {
// This is the name used as module name of the Vuex Store. // 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_country: this.attr(''),
client_county: this.attr(''), client_county: this.attr(''),
client_city: this.attr(''), client_city: this.attr(''),
client_reg_no: this.attr(''),
client_vat_no: this.attr(''),
client_email: this.attr(''), client_email: this.attr(''),
client_id: this.attr(null), client_id: this.attr(null),
client: this.belongsTo(Client, 'client_id'), client: this.belongsTo(Client, 'client_id'),
@ -46,6 +45,7 @@ export default class Invoice extends Model {
updated_at: this.attr(''), updated_at: this.attr(''),
created_at: this.attr(''), created_at: this.attr(''),
total: this.attr(null), // Only used in lists. total: this.attr(null), // Only used in lists.
client_fields: this.hasMany(InvoiceClientField, 'invoice_id'),
}; };
} }

View 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(''),
};
}
}

View File

@ -1,5 +1,6 @@
import { Model } from '@vuex-orm/core'; import { Model } from '@vuex-orm/core';
import { uuidv4 } from '@/utils/helpers'; import { uuidv4 } from '@/utils/helpers';
import TeamField from '@/store/models/team-field';
export default class Team extends Model { export default class Team extends Model {
// This is the name used as module name of the Vuex Store. // 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), vat_rate: this.attr(null),
invoice_late_fee: this.attr(null), invoice_late_fee: this.attr(null),
invoice_due_days: this.attr(null), invoice_due_days: this.attr(null),
fields: this.hasMany(TeamField, 'team_id'),
updated_at: this.attr(''), updated_at: this.attr(''),
created_at: this.attr(''), created_at: this.attr(''),
logo_url: this.attr(''), logo_url: this.attr(''),

View File

@ -9,10 +9,16 @@ import InvoiceRow from '@/store/models/invoice-row';
import Team from '@/store/models/team'; import Team from '@/store/models/team';
import bankAccounts from '@/store/bank-accounts'; import bankAccounts from '@/store/bank-accounts';
import clients from '@/store/clients'; import clients from '@/store/clients';
import clientFields from '@/store/client-fields';
import invoices from '@/store/invoices'; import invoices from '@/store/invoices';
import invoiceRows from '@/store/invoice-rows';
import invoiceClientFields from '@/store/invoice-client-fields';
import teams from '@/store/teams'; import teams from '@/store/teams';
import themes from '@/store/themes'; import themes from '@/store/themes';
import data from '@/store/data'; 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); Vue.use(Vuex);
@ -20,8 +26,11 @@ VuexORM.use(VuexORMisDirtyPlugin);
const database = new VuexORM.Database(); const database = new VuexORM.Database();
database.register(Team); database.register(Team);
database.register(TeamField);
database.register(Client); database.register(Client);
database.register(ClientField);
database.register(Invoice); database.register(Invoice);
database.register(InvoiceClientField);
database.register(InvoiceRow); database.register(InvoiceRow);
database.register(BankAccount); database.register(BankAccount);
@ -30,7 +39,10 @@ export default new Vuex.Store({
modules: { modules: {
bankAccounts, bankAccounts,
clients, clients,
clientFields,
invoices, invoices,
invoiceRows,
invoiceClientFields,
teams, teams,
themes, themes,
data, data,