mirror of
https://github.com/mokuappio/serverless-invoices.git
synced 2026-02-08 17:02:38 -05:00
Init commit
This commit is contained in:
67
src/store/bank-accounts.js
Normal file
67
src/store/bank-accounts.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import BankAccountService from '@/services/bank-account.service';
|
||||
import BankAccount from '@/store/models/bank-account';
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
bankAccountId: null,
|
||||
isModalOpen: null,
|
||||
},
|
||||
mutations: {
|
||||
bankAccountId(state, bankAccountId) {
|
||||
state.bankAccountId = bankAccountId;
|
||||
},
|
||||
isModalOpen(state, isOpen) {
|
||||
state.isModalOpen = isOpen;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
init({ dispatch }) {
|
||||
return dispatch('getBankAccounts');
|
||||
},
|
||||
terminate() {
|
||||
return BankAccount.deleteAll();
|
||||
},
|
||||
async getBankAccounts() {
|
||||
const accounts = await BankAccountService.getBankAccounts();
|
||||
await BankAccount.create({ data: accounts });
|
||||
return accounts;
|
||||
},
|
||||
async getBankAccount({ commit }, bankAccountId) {
|
||||
const bankAccount = await BankAccountService.getBankAccount(bankAccountId);
|
||||
commit('bankAccountId', bankAccount.id);
|
||||
return BankAccount.insert({ data: bankAccount });
|
||||
},
|
||||
async createNewBankAccount(store, bankAccount) {
|
||||
const res = await BankAccountService.createBankAccount(bankAccount);
|
||||
await BankAccount.insert({ data: res });
|
||||
return BankAccount.find(res.id);
|
||||
},
|
||||
bankAccountProps({ state }, props) {
|
||||
return BankAccount.update({
|
||||
where: state.bankAccountId,
|
||||
data: props,
|
||||
});
|
||||
},
|
||||
async updateBankAccount({ getters, dispatch }, props) {
|
||||
await dispatch('bankAccountProps', props);
|
||||
return BankAccountService.updateBankAccount(getters.bankAccount);
|
||||
},
|
||||
async openNewBankAccountModal({ commit }) {
|
||||
const bankAccount = await BankAccount.createNew();
|
||||
commit('bankAccountId', bankAccount.id);
|
||||
commit('isModalOpen', true);
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
bankAccount(state) {
|
||||
return BankAccount.query()
|
||||
.find(state.bankAccountId);
|
||||
},
|
||||
all() {
|
||||
return BankAccount.query()
|
||||
.where('$isNew', false)
|
||||
.get();
|
||||
},
|
||||
},
|
||||
};
|
||||
86
src/store/clients.js
Normal file
86
src/store/clients.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import ClientService from '@/services/client.service';
|
||||
import Client from '@/store/models/client';
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
clientId: null,
|
||||
isModalOpen: false,
|
||||
},
|
||||
mutations: {
|
||||
clientId(state, clientId) {
|
||||
state.clientId = clientId;
|
||||
},
|
||||
isModalOpen(state, isOpen) {
|
||||
state.isModalOpen = isOpen;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
init({ dispatch }) {
|
||||
return dispatch('getClients');
|
||||
},
|
||||
terminate() {
|
||||
return Client.deleteAll();
|
||||
},
|
||||
async getClients() {
|
||||
const clients = await ClientService.getClients();
|
||||
await Client.create({ data: clients });
|
||||
return clients;
|
||||
},
|
||||
async getClient({ commit }, clientId) {
|
||||
const client = await ClientService.getClient(clientId);
|
||||
commit('clientId', client.id);
|
||||
Client.insert({ data: client });
|
||||
},
|
||||
async createNewClient(store, client) {
|
||||
if (!client.hasOwnProperty('id')) {
|
||||
client = new Client(client);
|
||||
}
|
||||
const res = await ClientService.createClient(client);
|
||||
await Client.insert({ data: res });
|
||||
return Client.find(res.id);
|
||||
},
|
||||
clientProps({ state }, props) {
|
||||
return Client.update({
|
||||
where: state.clientId,
|
||||
data: props,
|
||||
});
|
||||
},
|
||||
async updateClient({ getters, dispatch }, props) {
|
||||
await dispatch('clientProps', props);
|
||||
return ClientService.updateClient(getters.client);
|
||||
},
|
||||
async updateClientById(payload) {
|
||||
const client = await Client.update({
|
||||
where: payload.clientId,
|
||||
data: payload.props,
|
||||
});
|
||||
return ClientService.updateClient(client);
|
||||
},
|
||||
async openNewClientModal({ commit }) {
|
||||
const client = await Client.createNew();
|
||||
commit('clientId', client.id);
|
||||
commit('isModalOpen', true);
|
||||
},
|
||||
async deleteClient(clientId) {
|
||||
const res = await ClientService.deleteClient(clientId);
|
||||
if ('client_id' in res) {
|
||||
Client.delete(res.client_id);
|
||||
}
|
||||
return res;
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
client(state) {
|
||||
return Client.query()
|
||||
.with(['bank_account'])
|
||||
.find(state.clientId);
|
||||
},
|
||||
all() {
|
||||
return Client.query()
|
||||
.where('$isNew', false)
|
||||
.with(['bank_account'])
|
||||
.get();
|
||||
},
|
||||
},
|
||||
};
|
||||
197
src/store/invoices.js
Normal file
197
src/store/invoices.js
Normal file
@@ -0,0 +1,197 @@
|
||||
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';
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
errors: new Errors(),
|
||||
invoiceId: null,
|
||||
isSendModalOpen: false,
|
||||
},
|
||||
mutations: {
|
||||
invoiceId(state, invoiceId) {
|
||||
state.invoiceId = invoiceId;
|
||||
},
|
||||
isSendModalOpen(state, isSendModalOpen) {
|
||||
state.isSendModalOpen = isSendModalOpen;
|
||||
},
|
||||
setErrors(state, errors) {
|
||||
state.errors.set(errors);
|
||||
},
|
||||
clearErrors(state) {
|
||||
state.errors.clear();
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
init({ dispatch }) {
|
||||
dispatch('getInvoices');
|
||||
},
|
||||
terminate() {
|
||||
return Invoice.deleteAll();
|
||||
},
|
||||
async getInvoices() {
|
||||
const invoices = await InvoiceService.getInvoices();
|
||||
await Invoice.create({ data: invoices });
|
||||
return invoices;
|
||||
},
|
||||
async getInvoice({ commit }, invoiceId) {
|
||||
const invoice = await InvoiceService.getInvoice(invoiceId);
|
||||
await Invoice.insert({ data: invoice });
|
||||
commit('invoiceId', invoiceId);
|
||||
return invoice;
|
||||
},
|
||||
async createNewInvoice() {
|
||||
const invoice = await Invoice.createNew();
|
||||
await InvoiceService.createInvoice(invoice);
|
||||
return invoice.id;
|
||||
},
|
||||
invoiceProps({ state }, props) {
|
||||
return Invoice.update({
|
||||
where: state.invoiceId,
|
||||
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
|
||||
const clientProps = pick(props, {
|
||||
bank_account_id: 'bank_account_id',
|
||||
client_name: 'company_name',
|
||||
client_address: 'company_address',
|
||||
client_postal_code: 'company_postal_code',
|
||||
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',
|
||||
});
|
||||
if ('vat_rate' in props) {
|
||||
clientProps.has_vat = props.vat_rate > 0;
|
||||
}
|
||||
|
||||
if (Object.keys(clientProps).length > 0 && getters.invoice.client_id) {
|
||||
dispatch('clients/updateClientById', {
|
||||
props: clientProps,
|
||||
clientId: getters.invoice.client_id,
|
||||
}, { root: true });
|
||||
}
|
||||
|
||||
const teamProps = pick(props, {
|
||||
late_fee: 'invoice_late_fee',
|
||||
from_name: 'company_name',
|
||||
from_address: 'company_address',
|
||||
from_postal_code: 'company_postal_code',
|
||||
from_city: 'company_city',
|
||||
from_country: 'company_country',
|
||||
from_county: 'company_county',
|
||||
from_reg_no: 'company_reg_no',
|
||||
from_vat_no: 'company_vat_no',
|
||||
from_website: 'website',
|
||||
from_email: 'contact_email',
|
||||
from_phone: 'contact_phone',
|
||||
vat_rate: 'vat_rate',
|
||||
});
|
||||
if ('due_at' in props || 'issued_at' in props) {
|
||||
teamProps.invoice_due_days = dayjs(getters.invoice.due_at)
|
||||
.diff(getters.invoice.issued_at, 'days');
|
||||
}
|
||||
if ('vat_rate' in props) {
|
||||
// You can only set VAT to 0 if setting it directly under settings
|
||||
// This is to avoid setting general VAT to 0, if only changing per invoice
|
||||
if (parseFloat(teamProps.vat_rate) === 0) {
|
||||
delete teamProps.vat_rate;
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(teamProps).length > 0) {
|
||||
dispatch('teams/updateTeam', teamProps, { root: true });
|
||||
}
|
||||
|
||||
commit('clearErrors');
|
||||
|
||||
return InvoiceService.updateInvoice(getters.invoice)
|
||||
.catch(err => commit('setErrors', err.response.data.errors));
|
||||
},
|
||||
async updateInvoiceRow({ getters, dispatch, commit }, payload) {
|
||||
await dispatch('invoiceRowProps', payload);
|
||||
|
||||
commit('clearErrors');
|
||||
|
||||
return InvoiceService.updateInvoice(getters.invoice)
|
||||
.catch(err => commit('setErrors', err.response.data.errors));
|
||||
},
|
||||
async deleteInvoice(invoice) {
|
||||
const res = await InvoiceService.deleteInvoice(invoice.id);
|
||||
if ('invoice_id' in res) {
|
||||
Invoice.delete(res.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 sendInvoice({ state, dispatch }, message) {
|
||||
const res = await InvoiceService.sendInvoice(state.invoiceId, message);
|
||||
dispatch('invoiceProps', {
|
||||
status: 'sent',
|
||||
});
|
||||
return res;
|
||||
},
|
||||
async bookInvoice({ getters, commit, dispatch }) {
|
||||
commit('clearErrors');
|
||||
|
||||
try {
|
||||
const res = await InvoiceService.bookInvoice(getters.invoice);
|
||||
return dispatch('getInvoice', res.invoice_id);
|
||||
} catch (err) {
|
||||
commit('setErrors', err.response.data.errors);
|
||||
}
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
invoice(state) {
|
||||
return Invoice.query()
|
||||
.with(['client', 'project', 'team.logos'])
|
||||
.with('rows', query => query.orderBy('order', 'asc'))
|
||||
.find(state.invoiceId);
|
||||
},
|
||||
all() {
|
||||
return Invoice.query()
|
||||
.where('$isNew', false)
|
||||
.with(['client'])
|
||||
.with('rows', query => query.orderBy('order', 'asc')) // TODO: do we need this?
|
||||
.orderBy('issued_at', 'desc')
|
||||
.orderBy('number', 'desc')
|
||||
.get();
|
||||
},
|
||||
subTotal(state, getters) {
|
||||
return getters.invoice.rows.reduce((carr, row) => (row.quantity * row.price) + carr, 0);
|
||||
},
|
||||
total(state, getters) {
|
||||
return getters.subTotal + getters.totalVat;
|
||||
},
|
||||
totalVat(state, getters) {
|
||||
return (getters.invoice.vat_rate / 100) * getters.subTotal;
|
||||
},
|
||||
},
|
||||
};
|
||||
17
src/store/models/bank-account.js
Normal file
17
src/store/models/bank-account.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Model } from '@vuex-orm/core';
|
||||
import { uuidv4 } from '@/utils/helpers';
|
||||
|
||||
export default class BankAccount extends Model {
|
||||
// This is the name used as module name of the Vuex Store.
|
||||
static entity = 'bank_accounts';
|
||||
|
||||
static fields() {
|
||||
return {
|
||||
id: this.attr(() => uuidv4()),
|
||||
account_no: this.attr(''),
|
||||
bank_name: this.attr(''),
|
||||
updated_at: this.attr(''),
|
||||
created_at: this.attr(''),
|
||||
};
|
||||
}
|
||||
}
|
||||
30
src/store/models/client.js
Normal file
30
src/store/models/client.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Model } from '@vuex-orm/core';
|
||||
import { uuidv4 } from '@/utils/helpers';
|
||||
import BankAccount from '@/store/models/bank-account';
|
||||
|
||||
export default class Client extends Model {
|
||||
// This is the name used as module name of the Vuex Store.
|
||||
static entity = 'clients';
|
||||
|
||||
static fields() {
|
||||
return {
|
||||
id: this.attr(() => uuidv4()),
|
||||
company_name: this.attr(''),
|
||||
company_address: this.attr(''),
|
||||
company_postal_code: this.attr(''),
|
||||
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'),
|
||||
updated_at: this.attr(''),
|
||||
created_at: this.attr(''),
|
||||
};
|
||||
}
|
||||
}
|
||||
23
src/store/models/invoice-row.js
Normal file
23
src/store/models/invoice-row.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Model } from '@vuex-orm/core';
|
||||
import { uuidv4 } from '@/utils/helpers';
|
||||
import Invoice from '@/store/models/invoice';
|
||||
|
||||
export default class InvoiceRow extends Model {
|
||||
// This is the name used as module name of the Vuex Store.
|
||||
static entity = 'invoice_rows';
|
||||
|
||||
static fields() {
|
||||
return {
|
||||
id: this.attr(() => uuidv4()),
|
||||
invoice_id: this.attr(null),
|
||||
invoice: this.belongsTo(Invoice, 'invoice_id'),
|
||||
item: this.attr(''),
|
||||
quantity: this.attr(null),
|
||||
price: this.attr(null),
|
||||
unit: this.attr(''),
|
||||
order: this.attr(null),
|
||||
updated_at: this.attr(''),
|
||||
created_at: this.attr(''),
|
||||
};
|
||||
}
|
||||
}
|
||||
51
src/store/models/invoice.js
Normal file
51
src/store/models/invoice.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Model } from '@vuex-orm/core';
|
||||
import { uuidv4 } from '@/utils/helpers';
|
||||
import Client from '@/store/models/client';
|
||||
import InvoiceRow from '@/store/models/invoice-row';
|
||||
|
||||
export default class Invoice extends Model {
|
||||
// This is the name used as module name of the Vuex Store.
|
||||
static entity = 'invoices';
|
||||
|
||||
static fields() {
|
||||
return {
|
||||
id: this.attr(() => uuidv4()),
|
||||
number: this.attr(''),
|
||||
status: this.attr('draft'),
|
||||
issued_at: this.attr(''),
|
||||
due_at: this.attr(''),
|
||||
late_fee: this.attr(''),
|
||||
vat_rate: this.attr(''),
|
||||
currency: this.attr(''),
|
||||
from_name: this.attr(''),
|
||||
from_address: this.attr(''),
|
||||
from_postal_code: this.attr(''),
|
||||
from_city: this.attr(''),
|
||||
from_country: this.attr(''),
|
||||
from_county: this.attr(''),
|
||||
from_reg_no: this.attr(''),
|
||||
from_vat_no: this.attr(''),
|
||||
from_website: this.attr(''),
|
||||
from_email: this.attr(''),
|
||||
from_phone: this.attr(''),
|
||||
bank_name: this.attr(''),
|
||||
bank_account_no: this.attr(''),
|
||||
client_name: this.attr(''),
|
||||
client_address: this.attr(''),
|
||||
client_postal_code: this.attr(''),
|
||||
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'),
|
||||
rows: this.hasMany(InvoiceRow, 'invoice_id'),
|
||||
notes: this.attr(''),
|
||||
updated_at: this.attr(''),
|
||||
created_at: this.attr(''),
|
||||
total: this.attr(null), // Only used in lists.
|
||||
};
|
||||
}
|
||||
}
|
||||
30
src/store/models/team.js
Normal file
30
src/store/models/team.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Model } from '@vuex-orm/core';
|
||||
import { uuidv4 } from '@/utils/helpers';
|
||||
|
||||
export default class Team extends Model {
|
||||
// This is the name used as module name of the Vuex Store.
|
||||
static entity = 'teams';
|
||||
|
||||
static fields() {
|
||||
return {
|
||||
id: this.attr(() => uuidv4()),
|
||||
company_name: this.attr(''),
|
||||
company_address: this.attr(''),
|
||||
company_postal_code: this.attr(''),
|
||||
company_country: this.attr(''),
|
||||
company_county: this.attr(''),
|
||||
company_city: this.attr(''),
|
||||
company_reg_no: this.attr(''),
|
||||
company_vat_no: this.attr(''),
|
||||
website: this.attr(''),
|
||||
contact_email: this.attr(''),
|
||||
contact_phone: this.attr(''),
|
||||
vat_rate: this.attr(null),
|
||||
invoice_late_fee: this.attr(null),
|
||||
invoice_due_days: this.attr(null),
|
||||
updated_at: this.attr(''),
|
||||
created_at: this.attr(''),
|
||||
logo_url: this.attr(''),
|
||||
};
|
||||
}
|
||||
}
|
||||
39
src/store/store.js
Normal file
39
src/store/store.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import VuexORM from '@vuex-orm/core';
|
||||
import VuexORMisDirtyPlugin from '@vuex-orm/plugin-change-flags';
|
||||
import BankAccount from '@/store/models/bank-account';
|
||||
import Client from '@/store/models/client';
|
||||
import Invoice from '@/store/models/invoice';
|
||||
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 invoices from '@/store/invoices';
|
||||
import teams from '@/store/teams';
|
||||
import themes from '@/store/themes';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
VuexORM.use(VuexORMisDirtyPlugin);
|
||||
const database = new VuexORM.Database();
|
||||
|
||||
database.register(Team);
|
||||
database.register(Client);
|
||||
database.register(Invoice);
|
||||
database.register(InvoiceRow);
|
||||
database.register(BankAccount);
|
||||
|
||||
export default new Vuex.Store({
|
||||
plugins: [VuexORM.install(database)],
|
||||
modules: {
|
||||
bankAccounts,
|
||||
clients,
|
||||
invoices,
|
||||
teams,
|
||||
themes,
|
||||
},
|
||||
state: {},
|
||||
mutations: {},
|
||||
actions: {},
|
||||
});
|
||||
49
src/store/teams.js
Normal file
49
src/store/teams.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import TeamService from '@/services/team.service';
|
||||
import Team from '@/store/models/team';
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: {},
|
||||
mutations: {},
|
||||
actions: {
|
||||
async init({ dispatch }) {
|
||||
dispatch('clients/terminate', null, { root: true });
|
||||
dispatch('bankAccounts/terminate', null, { root: true });
|
||||
dispatch('invoices/terminate', null, { root: true });
|
||||
|
||||
await dispatch('getTeam');
|
||||
|
||||
dispatch('clients/init', null, { root: true });
|
||||
dispatch('bankAccounts/init', null, { root: true });
|
||||
dispatch('invoices/init', null, { root: true });
|
||||
},
|
||||
async getTeam() {
|
||||
const team = await TeamService.getTeam();
|
||||
await Team.create({ data: team });
|
||||
return team;
|
||||
},
|
||||
async teamProps({ state }, props) {
|
||||
return Team.update({
|
||||
where: state.teamId,
|
||||
data: props,
|
||||
});
|
||||
},
|
||||
async updateTeam({ getters, dispatch }, props) {
|
||||
if (props) {
|
||||
await dispatch('teamProps', props);
|
||||
}
|
||||
return TeamService.updateTeam(getters.team);
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
team() {
|
||||
return Team.query().first();
|
||||
},
|
||||
all() {
|
||||
return Team.query()
|
||||
.with(['logos'])
|
||||
.where('$isNew', false)
|
||||
.get();
|
||||
},
|
||||
},
|
||||
};
|
||||
11
src/store/themes.js
Normal file
11
src/store/themes.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
theme: 'light',
|
||||
},
|
||||
mutations: {
|
||||
theme(state, theme) {
|
||||
state.theme = theme;
|
||||
},
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user