mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-09-03 22:01:00 +00:00
Add compound tax support (#753)
* test(taxes): cover compound_tax API semantics and validate it as boolean * feat(taxes): restore the compound tax toggle in tax type settings * feat(taxes): compute document-level compound tax amounts Two-pass recalculation: simple taxes on the discounted subtotal, compound taxes on the subtotal plus all simple taxes (v2 parity; branch order fixed -> compound -> inclusive back-out -> simple). Amounts now also recalculate on tax add/remove and on the tax-inclusive toggle, which previously never triggered recalculation. * feat(taxes): support compound taxes in per-item mode Per-item tax rows now carry the compound flag and charge compound taxes on the item's discounted base plus its simple taxes, through the same calcTaxAmount branch order as document-level taxes. The item row splits its tax sums into simple and compound so a compound row can never widen its own base. Also fixes two pre-existing bugs: the per-item tax dropdown read window.__taxTypes, which nothing ever assigned, so it always rendered empty (tax types are now fetched by the items table and passed down); and removing a tax row hard-zeroed the item's totals instead of re-syncing them. * fix(invoices): skip placeholder tax rows when persisting item taxes The document form keeps one empty placeholder tax row per item in per-item tax mode. Its empty name is nullified by the framework's empty-string middleware, so inserting it violated the NOT NULL constraint and turned every per-item save into a 500. The equivalent v2 guard keyed on a null amount, which the v3 stub (amount: 0) evades; keying on the missing tax_type_id catches it.
This commit is contained in:
@@ -171,11 +171,12 @@
|
||||
:tax-data="tax"
|
||||
:taxes="itemData.taxes ?? []"
|
||||
:discounted-total="total"
|
||||
:total-tax="totalSimpleTax"
|
||||
:total-simple-tax="totalSimpleTax"
|
||||
:total="subtotal"
|
||||
:currency="currency"
|
||||
:update-items="syncItemToStore"
|
||||
:ability="'create-invoice'"
|
||||
:tax-types="taxTypes"
|
||||
:can-add-tax="canAddTax"
|
||||
:store="store"
|
||||
:store-prop="storeProp"
|
||||
:discount="discount"
|
||||
@@ -199,6 +200,7 @@ import DocumentItemRowTax from './DocumentItemRowTax.vue'
|
||||
import DragIcon from '@/scripts/components/icons/DragIcon.vue'
|
||||
import { generateClientId } from '../../../utils'
|
||||
import type { Currency } from '../../../types/domain/currency'
|
||||
import type { TaxType } from '../../../types/domain/tax'
|
||||
import type { DocumentItem, DocumentFormData, DocumentTax } from './use-document-calculations'
|
||||
|
||||
interface Props {
|
||||
@@ -215,6 +217,8 @@ interface Props {
|
||||
currency: Currency | Record<string, unknown>
|
||||
invoiceItems: DocumentItem[]
|
||||
itemValidationScope?: string
|
||||
taxTypes?: TaxType[]
|
||||
canAddTax?: boolean
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
@@ -227,6 +231,8 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
type: '',
|
||||
loading: false,
|
||||
itemValidationScope: '',
|
||||
taxTypes: () => [],
|
||||
canAddTax: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
@@ -286,16 +292,33 @@ const showRemoveButton = computed<boolean>(() => {
|
||||
return formData.value.items.length > 1
|
||||
})
|
||||
|
||||
// Base handed down to the tax rows: only the non-compound taxes count, so a
|
||||
// compound row can never widen its own base through this value.
|
||||
const totalSimpleTax = computed<number>(() => {
|
||||
const taxes = props.itemData.taxes ?? []
|
||||
return Math.round(
|
||||
taxes.reduce((sum: number, tax: Partial<DocumentTax>) => {
|
||||
if (tax.compound_tax) {
|
||||
return sum
|
||||
}
|
||||
return sum + (tax.amount ?? 0)
|
||||
}, 0),
|
||||
)
|
||||
})
|
||||
|
||||
const totalTax = computed<number>(() => totalSimpleTax.value)
|
||||
const totalCompoundTax = computed<number>(() => {
|
||||
const taxes = props.itemData.taxes ?? []
|
||||
return Math.round(
|
||||
taxes.reduce((sum: number, tax: Partial<DocumentTax>) => {
|
||||
if (tax.compound_tax) {
|
||||
return sum + (tax.amount ?? 0)
|
||||
}
|
||||
return sum
|
||||
}, 0),
|
||||
)
|
||||
})
|
||||
|
||||
const totalTax = computed<number>(() => totalSimpleTax.value + totalCompoundTax.value)
|
||||
|
||||
const companyCurrency = computed(() => companyStore.selectedCompanyCurrency)
|
||||
|
||||
@@ -436,6 +459,7 @@ function syncItemToStore(): void {
|
||||
total: total.value,
|
||||
sub_total: subtotal.value,
|
||||
totalSimpleTax: totalSimpleTax.value,
|
||||
totalCompoundTax: totalCompoundTax.value,
|
||||
totalTax: totalTax.value,
|
||||
tax: totalTax.value,
|
||||
taxes: [...itemTaxes],
|
||||
|
||||
@@ -37,6 +37,9 @@
|
||||
<template v-else>
|
||||
{{ option.percent }} %
|
||||
</template>
|
||||
<BaseBadge v-if="option.compound_tax" class="ml-2 text-xs">
|
||||
{{ $t('tax_types.compound_tax') }}
|
||||
</BaseBadge>
|
||||
</template>
|
||||
|
||||
<template v-if="canAddTax" #action>
|
||||
@@ -77,16 +80,9 @@ import { useModalStore } from '../../../stores/modal.store'
|
||||
import type { TaxType } from '../../../types/domain/tax'
|
||||
import type { Currency } from '../../../types/domain/currency'
|
||||
import type { DocumentFormData, DocumentTax } from './use-document-calculations'
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__taxTypes?: TaxType[]
|
||||
__userHasAbility?: (ability: string) => boolean
|
||||
}
|
||||
}
|
||||
import { calcTaxAmount } from './use-document-calculations'
|
||||
|
||||
interface Props {
|
||||
ability: string
|
||||
store: Record<string, unknown>
|
||||
storeProp: string
|
||||
itemIndex: number
|
||||
@@ -94,10 +90,13 @@ interface Props {
|
||||
taxData: DocumentTax
|
||||
taxes: DocumentTax[]
|
||||
total: number
|
||||
totalTax: number
|
||||
/** Sum of the item's non-compound tax amounts, i.e. the compound base widener. */
|
||||
totalSimpleTax: number
|
||||
discountedTotal: number
|
||||
currency: Currency | Record<string, unknown>
|
||||
updateItems: () => void
|
||||
taxTypes?: TaxType[]
|
||||
canAddTax?: boolean
|
||||
discount?: number
|
||||
}
|
||||
|
||||
@@ -107,7 +106,8 @@ interface Emits {
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
ability: '',
|
||||
taxTypes: () => [],
|
||||
canAddTax: false,
|
||||
discount: 0,
|
||||
})
|
||||
|
||||
@@ -116,23 +116,13 @@ const emit = defineEmits<Emits>()
|
||||
const { t } = useI18n()
|
||||
const modalStore = useModalStore()
|
||||
|
||||
const taxTypes = computed<TaxType[]>(() => {
|
||||
return (window.__taxTypes ?? []).filter(
|
||||
(taxType) => taxType.transaction_type === 'sales',
|
||||
)
|
||||
})
|
||||
|
||||
const canAddTax = computed(() => {
|
||||
return window.__userHasAbility?.(props.ability) ?? false
|
||||
})
|
||||
|
||||
const selectedTax = ref<TaxType | null>(null)
|
||||
const localTax = reactive<DocumentTax>({ ...props.taxData })
|
||||
|
||||
const storeData = computed(() => props.store[props.storeProp] as DocumentFormData)
|
||||
|
||||
const filteredTypes = computed<(TaxType & { disabled?: boolean })[]>(() => {
|
||||
const clonedTypes = taxTypes.value.map((a) => ({ ...a, disabled: false }))
|
||||
const clonedTypes = props.taxTypes.map((a) => ({ ...a, disabled: false }))
|
||||
|
||||
return clonedTypes.map((taxType) => {
|
||||
const found = props.taxes.find((tax) => tax.tax_type_id === taxType.id)
|
||||
@@ -141,27 +131,52 @@ const filteredTypes = computed<(TaxType & { disabled?: boolean })[]>(() => {
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* The item's taxable base, shared by every tax branch.
|
||||
*
|
||||
* With a per-item discount the item total is already net of it. With a
|
||||
* document-level discount the item carries its proportional share of that
|
||||
* discount instead.
|
||||
*/
|
||||
const effectiveBase = computed<number>(() => {
|
||||
if (storeData.value.discount_per_item === 'YES') {
|
||||
return props.discountedTotal
|
||||
}
|
||||
|
||||
const modelDiscount = storeData.value.discount ?? 0
|
||||
|
||||
if (modelDiscount <= 0) {
|
||||
return props.discountedTotal
|
||||
}
|
||||
|
||||
const itemsTotal = storeData.value.items.reduce(
|
||||
(sum: number, item) => sum + (item.total ?? 0),
|
||||
0,
|
||||
)
|
||||
|
||||
if (!itemsTotal) {
|
||||
return props.discountedTotal
|
||||
}
|
||||
|
||||
const proportion = parseFloat((props.discountedTotal / itemsTotal).toFixed(2))
|
||||
const discount =
|
||||
storeData.value.discount_type === 'fixed'
|
||||
? modelDiscount * 100
|
||||
: (itemsTotal * modelDiscount) / 100
|
||||
|
||||
return props.discountedTotal - Math.round(discount * proportion)
|
||||
})
|
||||
|
||||
const taxAmount = computed<number>(() => {
|
||||
if (localTax.calculation_type === 'fixed') {
|
||||
return localTax.fixed_amount
|
||||
}
|
||||
|
||||
if (props.discountedTotal) {
|
||||
const taxPerItemEnabled = storeData.value.tax_per_item === 'YES'
|
||||
const discountPerItemEnabled = storeData.value.discount_per_item === 'YES'
|
||||
|
||||
if (taxPerItemEnabled && !discountPerItemEnabled) {
|
||||
return getTaxAmount()
|
||||
}
|
||||
if (storeData.value.tax_included) {
|
||||
return Math.round(
|
||||
props.discountedTotal -
|
||||
props.discountedTotal / (1 + (localTax.percent ?? 0) / 100),
|
||||
)
|
||||
}
|
||||
return Math.round((props.discountedTotal * (localTax.percent ?? 0)) / 100)
|
||||
}
|
||||
return 0
|
||||
return calcTaxAmount(
|
||||
effectiveBase.value,
|
||||
localTax.percent,
|
||||
localTax.fixed_amount,
|
||||
localTax.calculation_type,
|
||||
storeData.value.tax_included ?? false,
|
||||
localTax.compound_tax ?? false,
|
||||
props.totalSimpleTax,
|
||||
)
|
||||
})
|
||||
|
||||
watch(
|
||||
@@ -169,8 +184,9 @@ watch(
|
||||
() => updateRowTax(),
|
||||
)
|
||||
|
||||
// A sibling simple tax landing later widens this row's base when it is compound.
|
||||
watch(
|
||||
() => props.totalTax,
|
||||
() => props.totalSimpleTax,
|
||||
() => updateRowTax(),
|
||||
)
|
||||
|
||||
@@ -179,11 +195,18 @@ watch(
|
||||
() => updateRowTax(),
|
||||
)
|
||||
|
||||
// Initialize selected tax if editing
|
||||
if (props.taxData.tax_type_id > 0) {
|
||||
selectedTax.value =
|
||||
taxTypes.value.find((_type) => _type.id === props.taxData.tax_type_id) ?? null
|
||||
}
|
||||
// Resolve the selected tax type when editing. The list is fetched by the parent
|
||||
// table, so it usually arrives after this row has been set up.
|
||||
watch(
|
||||
() => props.taxTypes,
|
||||
(types) => {
|
||||
if (localTax.tax_type_id > 0) {
|
||||
selectedTax.value =
|
||||
types.find((_type) => _type.id === localTax.tax_type_id) ?? selectedTax.value
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
updateRowTax()
|
||||
|
||||
@@ -194,6 +217,7 @@ function onSelectTax(val: TaxType): void {
|
||||
val.calculation_type === 'fixed' ? val.fixed_amount : 0
|
||||
localTax.tax_type_id = val.id
|
||||
localTax.name = val.name
|
||||
localTax.compound_tax = val.compound_tax ?? false
|
||||
|
||||
updateRowTax()
|
||||
}
|
||||
@@ -229,43 +253,9 @@ function removeTax(index: number): void {
|
||||
const store = props.store as Record<string, Record<string, unknown>>
|
||||
const formData = store[props.storeProp] as DocumentFormData
|
||||
formData.items[props.itemIndex].taxes?.splice(index, 1)
|
||||
const item = formData.items[props.itemIndex]
|
||||
item.tax = 0
|
||||
item.totalTax = 0
|
||||
}
|
||||
|
||||
function getTaxAmount(): number {
|
||||
if (localTax.calculation_type === 'fixed') {
|
||||
return localTax.fixed_amount
|
||||
}
|
||||
|
||||
let itemsTotal = 0
|
||||
let discount = 0
|
||||
const itemTotal = props.discountedTotal
|
||||
const modelDiscount = storeData.value.discount ?? 0
|
||||
const type = storeData.value.discount_type
|
||||
let discountedTotal = props.discountedTotal
|
||||
|
||||
if (modelDiscount > 0) {
|
||||
storeData.value.items.forEach((item) => {
|
||||
itemsTotal += item.total ?? 0
|
||||
})
|
||||
const proportion = parseFloat((itemTotal / itemsTotal).toFixed(2))
|
||||
discount =
|
||||
type === 'fixed'
|
||||
? modelDiscount * 100
|
||||
: (itemsTotal * modelDiscount) / 100
|
||||
const itemDiscount = Math.round(discount * proportion)
|
||||
discountedTotal = itemTotal - itemDiscount
|
||||
}
|
||||
|
||||
if (storeData.value.tax_included) {
|
||||
return Math.round(
|
||||
discountedTotal -
|
||||
discountedTotal / (1 + (localTax.percent ?? 0) / 100),
|
||||
)
|
||||
}
|
||||
|
||||
return Math.round((discountedTotal * (localTax.percent ?? 0)) / 100)
|
||||
// Re-sync the item so the remaining rows re-base off the new simple total
|
||||
// instead of leaving stale `tax` / `totalTax` values behind.
|
||||
props.updateItems()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -92,6 +92,8 @@
|
||||
:currency="defaultCurrency"
|
||||
:item-validation-scope="itemValidationScope"
|
||||
:invoice-items="formData.items"
|
||||
:tax-types="availableTaxTypes"
|
||||
:can-add-tax="canAddTax"
|
||||
:store="store"
|
||||
:store-prop="storeProp"
|
||||
/>
|
||||
@@ -110,11 +112,15 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import draggable from 'vuedraggable'
|
||||
import DocumentItemRow from './DocumentItemRow.vue'
|
||||
import ItemModal from '@/scripts/features/company/items/components/ItemModal.vue'
|
||||
import { useUserStore } from '../../../stores/user.store'
|
||||
import { taxTypeService } from '../../../api/services/tax-type.service'
|
||||
import { ABILITIES } from '../../../config/abilities'
|
||||
import type { Currency } from '../../../types/domain/currency'
|
||||
import type { TaxType } from '../../../types/domain/tax'
|
||||
import type { DocumentFormData } from './use-document-calculations'
|
||||
|
||||
interface Props {
|
||||
@@ -134,6 +140,25 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
taxIncludedSetting: 'NO',
|
||||
})
|
||||
|
||||
const userStore = useUserStore()
|
||||
const availableTaxTypes = ref<TaxType[]>([])
|
||||
|
||||
const canAddTax = computed<boolean>(() => {
|
||||
return userStore.hasAbilities(ABILITIES.CREATE_TAX_TYPE)
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const response = await taxTypeService.list({
|
||||
limit: 'all',
|
||||
transaction_type: 'sales',
|
||||
})
|
||||
availableTaxTypes.value = response.data
|
||||
} catch {
|
||||
// Silently fail
|
||||
}
|
||||
})
|
||||
|
||||
const formData = computed<DocumentFormData>(() => {
|
||||
return props.store[props.storeProp] as DocumentFormData
|
||||
})
|
||||
|
||||
@@ -241,6 +241,7 @@ import { ABILITIES } from '../../../config/abilities'
|
||||
import type { Currency } from '../../../types/domain/currency'
|
||||
import type { TaxType } from '../../../types/domain/tax'
|
||||
import type { DocumentFormData, DocumentTax, DocumentStore, DocumentItem } from './use-document-calculations'
|
||||
import { calcTaxAmount } from './use-document-calculations'
|
||||
|
||||
interface Props {
|
||||
store: DocumentStore & {
|
||||
@@ -316,6 +317,13 @@ watch(
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
watch(
|
||||
() => formData.value.tax_included,
|
||||
() => {
|
||||
recalculateGlobalTaxes()
|
||||
},
|
||||
)
|
||||
|
||||
const totalDiscount = computed<number>({
|
||||
get: () => formData.value.discount,
|
||||
set: (newValue: number) => {
|
||||
@@ -341,6 +349,7 @@ interface AggregatedTax {
|
||||
name: string
|
||||
calculation_type: string | null
|
||||
fixed_amount: number
|
||||
compound_tax: boolean
|
||||
}
|
||||
|
||||
const itemWiseTaxes = computed<AggregatedTax[]>(() => {
|
||||
@@ -359,6 +368,7 @@ const itemWiseTaxes = computed<AggregatedTax[]>(() => {
|
||||
name: tax.name ?? '',
|
||||
calculation_type: tax.calculation_type ?? null,
|
||||
fixed_amount: tax.fixed_amount ?? 0,
|
||||
compound_tax: tax.compound_tax ?? false,
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -382,11 +392,39 @@ function recalculateGlobalTaxes(): void {
|
||||
if (formData.value.tax_per_item === 'YES') return
|
||||
|
||||
const subtotalWithDiscount = props.store.getSubtotalWithDiscount
|
||||
const taxIncluded = formData.value.tax_included ?? false
|
||||
|
||||
// Pass 1: simple (non-compound) taxes are charged on the discounted subtotal.
|
||||
let simpleTotal = 0
|
||||
formData.value.taxes.forEach((tax: DocumentTax) => {
|
||||
if (tax.compound_tax) return
|
||||
if (tax.calculation_type === 'percentage' && tax.percent) {
|
||||
tax.amount = Math.round((subtotalWithDiscount * tax.percent) / 100)
|
||||
tax.amount = calcTaxAmount(
|
||||
subtotalWithDiscount,
|
||||
tax.percent,
|
||||
null,
|
||||
'percentage',
|
||||
taxIncluded,
|
||||
)
|
||||
}
|
||||
// Fixed taxes keep their amount as-is, but still count toward the compound base
|
||||
simpleTotal += tax.amount ?? 0
|
||||
})
|
||||
|
||||
// Pass 2: compound taxes are charged on the discounted subtotal plus the simple taxes.
|
||||
formData.value.taxes.forEach((tax: DocumentTax) => {
|
||||
if (!tax.compound_tax) return
|
||||
if (tax.calculation_type === 'percentage' && tax.percent) {
|
||||
tax.amount = calcTaxAmount(
|
||||
subtotalWithDiscount,
|
||||
tax.percent,
|
||||
null,
|
||||
'percentage',
|
||||
taxIncluded,
|
||||
true,
|
||||
simpleTotal,
|
||||
)
|
||||
}
|
||||
// Fixed taxes keep their amount as-is
|
||||
})
|
||||
}
|
||||
|
||||
@@ -404,18 +442,15 @@ function selectPercentage(): void {
|
||||
}
|
||||
|
||||
function onSelectTax(selectedTax: TaxType): void {
|
||||
let amount = 0
|
||||
if (
|
||||
selectedTax.calculation_type === 'percentage' &&
|
||||
props.store.getSubtotalWithDiscount &&
|
||||
selectedTax.percent
|
||||
) {
|
||||
amount = Math.round(
|
||||
(props.store.getSubtotalWithDiscount * selectedTax.percent) / 100,
|
||||
)
|
||||
} else if (selectedTax.calculation_type === 'fixed') {
|
||||
amount = selectedTax.fixed_amount
|
||||
}
|
||||
const amount = calcTaxAmount(
|
||||
props.store.getSubtotalWithDiscount,
|
||||
selectedTax.percent,
|
||||
selectedTax.fixed_amount,
|
||||
selectedTax.calculation_type,
|
||||
formData.value.tax_included ?? false,
|
||||
selectedTax.compound_tax ?? false,
|
||||
props.store.getTotalSimpleTax,
|
||||
)
|
||||
|
||||
const data: DocumentTax = {
|
||||
id: generateClientId(),
|
||||
@@ -431,6 +466,10 @@ function onSelectTax(selectedTax: TaxType): void {
|
||||
props.store.$patch((state: Record<string, unknown>) => {
|
||||
;(state[props.storeProp] as DocumentFormData).taxes.push({ ...data })
|
||||
})
|
||||
|
||||
// Adding a simple tax widens the base of any compound tax already present,
|
||||
// so the insertion order must not matter.
|
||||
recalculateGlobalTaxes()
|
||||
}
|
||||
|
||||
function updateTax(data: DocumentTax): void {
|
||||
@@ -445,5 +484,8 @@ function removeTax(id: number | string): void {
|
||||
props.store.$patch((state: Record<string, unknown>) => {
|
||||
;(state[props.storeProp] as DocumentFormData).taxes.splice(index, 1)
|
||||
})
|
||||
|
||||
// Removing a simple tax shrinks the base of any remaining compound tax.
|
||||
recalculateGlobalTaxes()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -64,6 +64,9 @@
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ taxType.percent }} %
|
||||
<BaseBadge v-if="taxType.compound_tax" class="text-xs">
|
||||
{{ $t('tax_types.compound_tax') }}
|
||||
</BaseBadge>
|
||||
</template>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -161,18 +161,36 @@ export function calcItemTotal(subtotal: number, discountVal: number): number {
|
||||
return subtotal - discountVal
|
||||
}
|
||||
|
||||
/** Calculate tax amount for a given total and tax config */
|
||||
/**
|
||||
* Calculate tax amount for a given total and tax config.
|
||||
*
|
||||
* A compound tax is charged on the base plus every simple (non-compound) tax
|
||||
* already applied, and is never backed out of a tax-inclusive total.
|
||||
*
|
||||
* @param total Base amount in cents (document subtotal after discount, or an item total after discount)
|
||||
* @param percent Percentage rate, when the tax is percentage based
|
||||
* @param fixedAmount Flat amount in cents, when the tax is fixed
|
||||
* @param calculationType `'fixed'` or `'percentage'`
|
||||
* @param taxIncluded Whether the base already includes the tax (back it out)
|
||||
* @param compoundTax Whether the tax is charged on top of the simple taxes
|
||||
* @param simpleTaxTotal Sum of the non-compound tax amounts in cents
|
||||
*/
|
||||
export function calcTaxAmount(
|
||||
total: number,
|
||||
percent: number | null,
|
||||
fixedAmount: number | null,
|
||||
calculationType: string | null,
|
||||
taxIncluded: boolean | null,
|
||||
compoundTax = false,
|
||||
simpleTaxTotal = 0,
|
||||
): number {
|
||||
if (calculationType === 'fixed' && fixedAmount != null) {
|
||||
return fixedAmount
|
||||
}
|
||||
if (!total || !percent) return 0
|
||||
if (compoundTax) {
|
||||
return Math.round(((total + simpleTaxTotal) * percent) / 100)
|
||||
}
|
||||
if (taxIncluded) {
|
||||
return Math.round(total - total / (1 + percent / 100))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user