feat: Tax included (#370)

* feat: Tax included

* Added a toggle switch in tax settings to enable the feature.
* Database migration adding tax_included field into estimates, invoices
  and recurring invoices table.
* Toggle switch to enable and store the tax_included by estimates,
  invoices and recurring invoices.
* In case of tax included enabled, total taxes will be recalculated and
  the invoices, estimates and recurring invoices total won't be sum with
  taxes.
* Apply tax included when discount_per_item/tax_per_item item is enabled.
* Custom component to show the net total when tax included is enabled.
* Update invoice and estimates pdfs with net total.

* chore: Tax included by default

A switch button inside the tax settings to enable the tax included by
default in invoices, estimates and recurring invoices.
This commit is contained in:
Fabio Ribeiro
2025-08-28 10:28:24 +02:00
committed by GitHub
parent 08e1bb2e22
commit d69a56e2d5
32 changed files with 582 additions and 83 deletions

View File

@@ -37,17 +37,56 @@ export const useRecurringInvoiceStore = (useWindow = false) => {
},
frequencies: [
{ label: global.t('recurring_invoices.frequency.every_minute'), value: '* * * * *' },
{ label: global.t('recurring_invoices.frequency.every_30_minute'), value: '*/30 * * * *' },
{ label: global.t('recurring_invoices.frequency.every_hour'), value: '0 * * * *' },
{ label: global.t('recurring_invoices.frequency.every_2_hour'), value: '0 */2 * * *' },
{ label: global.t('recurring_invoices.frequency.every_day_at_midnight'), value: '0 0 * * *' },
{ label: global.t('recurring_invoices.frequency.every_week'), value: '0 0 * * 0' },
{ label: global.t('recurring_invoices.frequency.every_15_days_at_midnight'), value: '0 5 */15 * *' },
{ label: global.t('recurring_invoices.frequency.on_the_first_day_of_every_month_at_midnight'), value: '0 0 1 * *' },
{ label: global.t('recurring_invoices.frequency.every_6_month'), value: '0 0 1 */6 *' },
{ label: global.t('recurring_invoices.frequency.every_year_on_the_first_day_of_january_at_midnight'), value: '0 0 1 1 *' },
{ label: global.t('recurring_invoices.frequency.custom'), value: 'CUSTOM' },
{
label: global.t('recurring_invoices.frequency.every_minute'),
value: '* * * * *',
},
{
label: global.t('recurring_invoices.frequency.every_30_minute'),
value: '*/30 * * * *',
},
{
label: global.t('recurring_invoices.frequency.every_hour'),
value: '0 * * * *',
},
{
label: global.t('recurring_invoices.frequency.every_2_hour'),
value: '0 */2 * * *',
},
{
label: global.t('recurring_invoices.frequency.every_day_at_midnight'),
value: '0 0 * * *',
},
{
label: global.t('recurring_invoices.frequency.every_week'),
value: '0 0 * * 0',
},
{
label: global.t(
'recurring_invoices.frequency.every_15_days_at_midnight',
),
value: '0 5 */15 * *',
},
{
label: global.t(
'recurring_invoices.frequency.on_the_first_day_of_every_month_at_midnight',
),
value: '0 0 1 * *',
},
{
label: global.t('recurring_invoices.frequency.every_6_month'),
value: '0 0 1 */6 *',
},
{
label: global.t(
'recurring_invoices.frequency.every_year_on_the_first_day_of_january_at_midnight',
),
value: '0 0 1 1 *',
},
{
label: global.t('recurring_invoices.frequency.custom'),
value: 'CUSTOM',
},
],
}),
@@ -60,6 +99,10 @@ export const useRecurringInvoiceStore = (useWindow = false) => {
)
},
getNetTotal() {
return this.getSubtotalWithDiscount - this.getTotalTax
},
getTotalSimpleTax() {
return _.sumBy(this.newRecurringInvoice.taxes, function (tax) {
if (!tax.compound_tax) {
@@ -95,6 +138,9 @@ export const useRecurringInvoiceStore = (useWindow = false) => {
},
getTotal() {
if (this.newRecurringInvoice.tax_included) {
return this.getSubtotalWithDiscount
}
return this.getSubtotalWithDiscount + this.getTotalTax
},
},
@@ -174,7 +220,7 @@ export const useRecurringInvoiceStore = (useWindow = false) => {
})
let pos = this.recurringInvoices.findIndex(
(invoice) => invoice.id === response.data.data.id
(invoice) => invoice.id === response.data.data.id,
)
this.recurringInvoices[pos] = response.data.data
@@ -240,7 +286,7 @@ export const useRecurringInvoiceStore = (useWindow = false) => {
.post(`/api/v1/recurring-invoices/delete`, id)
.then((response) => {
let index = this.recurringInvoices.findIndex(
(invoice) => invoice.id === id
(invoice) => invoice.id === id,
)
this.recurringInvoices.splice(index, 1)
resolve(response)
@@ -265,7 +311,7 @@ export const useRecurringInvoiceStore = (useWindow = false) => {
.then((response) => {
this.selectedRecurringInvoices.forEach((invoice) => {
let index = this.recurringInvoices.findIndex(
(_inv) => _inv.id === invoice.id
(_inv) => _inv.id === invoice.id,
)
this.recurringInvoices.splice(index, 1)
})
@@ -305,7 +351,7 @@ export const useRecurringInvoiceStore = (useWindow = false) => {
this.selectAllField = false
} else {
let allInvoiceIds = this.recurringInvoices.map(
(invoice) => invoice.id
(invoice) => invoice.id,
)
this.selectedRecurringInvoices = allInvoiceIds
this.selectAllField = true
@@ -351,13 +397,16 @@ export const useRecurringInvoiceStore = (useWindow = false) => {
// on create
if (!isEdit) {
await notesStore.fetchNotes()
this.newRecurringInvoice.notes = notesStore.getDefaultNoteForType('Invoice')?.notes
this.newRecurringInvoice.notes =
notesStore.getDefaultNoteForType('Invoice')?.notes
this.newRecurringInvoice.tax_per_item =
companyStore.selectedCompanySettings.tax_per_item
this.newRecurringInvoice.discount_per_item =
companyStore.selectedCompanySettings.discount_per_item
this.newRecurringInvoice.sales_tax_type = companyStore.selectedCompanySettings.sales_tax_type
this.newRecurringInvoice.sales_tax_address_type = companyStore.selectedCompanySettings.sales_tax_address_type
this.newRecurringInvoice.sales_tax_type =
companyStore.selectedCompanySettings.sales_tax_type
this.newRecurringInvoice.sales_tax_address_type =
companyStore.selectedCompanySettings.sales_tax_address_type
this.newRecurringInvoice.starts_at = moment().format('YYYY-MM-DD')
this.newRecurringInvoice.next_invoice_date = moment()
.add(7, 'days')
@@ -399,7 +448,7 @@ export const useRecurringInvoiceStore = (useWindow = false) => {
this.isFetchingInitialSettings = false
})
.catch((err) => {
console.log(err);
console.log(err)
handleError(err)
})
},
@@ -407,7 +456,9 @@ export const useRecurringInvoiceStore = (useWindow = false) => {
addSalesTaxUs() {
const taxTypeStore = useTaxTypeStore()
let salesTax = { ...TaxStub }
let found = this.newRecurringInvoice.taxes.find((_t) => _t.name === 'Sales Tax' && _t.type === 'MODULE')
let found = this.newRecurringInvoice.taxes.find(
(_t) => _t.name === 'Sales Tax' && _t.type === 'MODULE',
)
if (found) {
for (const key in found) {
if (Object.prototype.hasOwnProperty.call(salesTax, key)) {
@@ -424,14 +475,15 @@ export const useRecurringInvoiceStore = (useWindow = false) => {
},
setSelectedFrequency() {
let data = this.frequencies.find(
(frequency) => {
return frequency.value === this.newRecurringInvoice.frequency
}
)
data ? this.newRecurringInvoice.selectedFrequency = data
: this.newRecurringInvoice.selectedFrequency = { label: 'Custom', value: 'CUSTOM' }
let data = this.frequencies.find((frequency) => {
return frequency.value === this.newRecurringInvoice.frequency
})
data
? (this.newRecurringInvoice.selectedFrequency = data)
: (this.newRecurringInvoice.selectedFrequency = {
label: 'Custom',
value: 'CUSTOM',
})
},
resetSelectedNote() {