mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-04-07 13:41:23 +00:00
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:
@@ -169,6 +169,9 @@ const taxAmount = computed(() => {
|
||||
if (taxPerItemEnabled && !discountPerItemEnabled){
|
||||
return getTaxAmount()
|
||||
}
|
||||
if (props.store[props.storeProp].tax_included) {
|
||||
return Math.round(props.discountedTotal - (props.discountedTotal / (1 + (localTax.percent / 100))))
|
||||
}
|
||||
return (props.discountedTotal * localTax.percent) / 100
|
||||
}
|
||||
return 0
|
||||
@@ -261,6 +264,7 @@ function getTaxAmount() {
|
||||
const itemTotal = props.discountedTotal
|
||||
const modelDiscount = props.store[props.storeProp].discount ? props.store[props.storeProp].discount : 0
|
||||
const type = props.store[props.storeProp].discount_type
|
||||
let discountedTotal = props.discountedTotal
|
||||
if (modelDiscount > 0) {
|
||||
props.store[props.storeProp].items.forEach((_i) => {
|
||||
total += _i.total
|
||||
@@ -268,10 +272,14 @@ function getTaxAmount() {
|
||||
const proportion = (itemTotal / total).toFixed(2)
|
||||
discount = type === 'fixed' ? modelDiscount * 100 : (total * modelDiscount) / 100
|
||||
const itemDiscount = Math.round(discount * proportion)
|
||||
const discounted = itemTotal - itemDiscount
|
||||
return Math.round((discounted * localTax.percent) / 100)
|
||||
discountedTotal = itemTotal - itemDiscount
|
||||
}
|
||||
return Math.round((props.discountedTotal * localTax.percent) / 100)
|
||||
|
||||
if (props.store[props.storeProp].tax_included) {
|
||||
return Math.round(discountedTotal - (discountedTotal / (1 + (localTax.percent / 100))))
|
||||
}
|
||||
|
||||
return Math.round((discountedTotal * localTax.percent) / 100)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,4 +1,27 @@
|
||||
<template>
|
||||
<!-- Tax Included -->
|
||||
<div
|
||||
v-if="companyStore.selectedCompanySettings.tax_included === 'YES'"
|
||||
class="
|
||||
flex
|
||||
items-center
|
||||
justify-end
|
||||
w-full
|
||||
px-6
|
||||
text-base
|
||||
border border-b-0 border-gray-200 border-solid
|
||||
cursor-pointer
|
||||
text-primary-400
|
||||
bg-white
|
||||
"
|
||||
>
|
||||
<BaseSwitchSection
|
||||
v-model="taxIncludedField"
|
||||
:title="$t('settings.tax_types.tax_included')"
|
||||
:store="store"
|
||||
:store-prop="storeProp"
|
||||
/>
|
||||
</div>
|
||||
<table class="text-center item-table min-w-full">
|
||||
<colgroup>
|
||||
<col style="width: 40%; min-width: 280px" />
|
||||
@@ -191,4 +214,14 @@ const defaultCurrency = computed(() => {
|
||||
return companyStore.selectedCompanyCurrency
|
||||
}
|
||||
})
|
||||
|
||||
const taxIncludedField = computed({
|
||||
get: () => {
|
||||
return props.store[props.storeProp].tax_included
|
||||
},
|
||||
set: async (value) => {
|
||||
props.store[props.storeProp].tax_included = value
|
||||
},
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
@@ -38,6 +38,17 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="store[storeProp].tax_per_item === 'YES'"
|
||||
>
|
||||
<NetTotal
|
||||
:currency="currency"
|
||||
:store="store"
|
||||
:storeProp="storeProp"
|
||||
:isLoading="isLoading"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="tax in itemWiseTaxes"
|
||||
:key="tax.tax_type_id"
|
||||
@@ -135,6 +146,21 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="
|
||||
store[storeProp].tax_per_item === 'NO' ||
|
||||
store[storeProp].tax_per_item === null
|
||||
"
|
||||
class="flex items-center justify-between w-full mt-2"
|
||||
>
|
||||
<NetTotal
|
||||
:currency="currency"
|
||||
:store="store"
|
||||
:storeProp="storeProp"
|
||||
:isLoading="isLoading"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="
|
||||
store[storeProp].tax_per_item === 'NO' ||
|
||||
@@ -149,6 +175,7 @@
|
||||
:taxes="taxes"
|
||||
:currency="currency"
|
||||
:store="store"
|
||||
:storeProp="storeProp"
|
||||
@remove="removeTax"
|
||||
@update="updateTax"
|
||||
/>
|
||||
@@ -198,6 +225,7 @@
|
||||
<script setup>
|
||||
import { computed, inject, ref, watch } from 'vue'
|
||||
import Guid from 'guid'
|
||||
import NetTotal from './NetTotal.vue'
|
||||
import Tax from './CreateTotalTaxes.vue'
|
||||
import TaxStub from '@/scripts/admin/stub/abilities'
|
||||
import SelectTaxPopup from './SelectTaxPopup.vue'
|
||||
|
||||
@@ -42,6 +42,10 @@ const props = defineProps({
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
storeProp: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
data: {
|
||||
type: String,
|
||||
default: '',
|
||||
@@ -64,6 +68,13 @@ const taxAmount = computed(() => {
|
||||
100
|
||||
)
|
||||
}
|
||||
if (props.store.getSubtotalWithDiscount && props.tax.percent && props.store[props.storeProp].tax_included) {
|
||||
return Math.round(
|
||||
props.store.getSubtotalWithDiscount - (
|
||||
props.store.getSubtotalWithDiscount / (1 + (props.tax.percent / 100))
|
||||
)
|
||||
)
|
||||
}
|
||||
if (props.store.getSubtotalWithDiscount && props.tax.percent) {
|
||||
return Math.round(
|
||||
(props.store.getSubtotalWithDiscount * props.tax.percent) / 100
|
||||
@@ -81,6 +92,13 @@ watchEffect(() => {
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.store[props.storeProp].tax_included,
|
||||
(val) => {
|
||||
updateTax()
|
||||
}, { deep: true },
|
||||
)
|
||||
|
||||
function updateTax() {
|
||||
emit('update', {
|
||||
...props.tax,
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="store[storeProp].tax_included"
|
||||
class="flex items-center justify-between w-full"
|
||||
>
|
||||
<BaseContentPlaceholders v-if="isLoading">
|
||||
<BaseContentPlaceholdersText :lines="1" class="w-16 h-5" />
|
||||
</BaseContentPlaceholders>
|
||||
<label
|
||||
v-else
|
||||
class="text-sm font-semibold leading-5 text-gray-500 uppercase"
|
||||
>
|
||||
{{ $t('estimates.net_total') }}
|
||||
</label>
|
||||
|
||||
<BaseContentPlaceholders v-if="isLoading">
|
||||
<BaseContentPlaceholdersText :lines="1" class="w-16 h-5" />
|
||||
</BaseContentPlaceholders>
|
||||
|
||||
<label
|
||||
v-else
|
||||
class="flex items-center justify-center m-0 text-lg text-black uppercase "
|
||||
>
|
||||
<BaseFormatMoney
|
||||
:amount="store.getNetTotal"
|
||||
:currency="currency"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import BaseContentPlaceholdersText from '@/scripts/components/base/BaseContentPlaceholdersText.vue'
|
||||
import BaseContentPlaceholders from '@/scripts/components/base/BaseContentPlaceholders.vue'
|
||||
import BaseFormatMoney from '@/scripts/components/base/BaseFormatMoney.vue'
|
||||
|
||||
const props = defineProps({
|
||||
store: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
storeProp: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
currency: {
|
||||
type: [Object, String],
|
||||
default: '',
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user