mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-04-15 09:14:08 +00:00
Fix global tax recalculation and fractional cent totals
Global percentage taxes are now recalculated when items or discount change, preventing stale tax amounts. Math.round() applied to sub_total, total, and tax in invoice/estimate submit payloads to ensure the backend always receives whole-cent integers.
This commit is contained in:
@@ -202,9 +202,9 @@ async function submitForm(): Promise<void> {
|
||||
|
||||
const data: Record<string, unknown> = {
|
||||
...cloneDeep(estimateStore.newEstimate),
|
||||
sub_total: estimateStore.getSubTotal,
|
||||
total: estimateStore.getTotal,
|
||||
tax: estimateStore.getTotalTax,
|
||||
sub_total: Math.round(estimateStore.getSubTotal),
|
||||
total: Math.round(estimateStore.getTotal),
|
||||
tax: Math.round(estimateStore.getTotalTax),
|
||||
}
|
||||
|
||||
const items = data.items as Array<Record<string, unknown>>
|
||||
|
||||
@@ -309,9 +309,9 @@ async function submitForm(): Promise<void> {
|
||||
customFields: invoiceData.customFields,
|
||||
|
||||
// Calculated totals
|
||||
sub_total: invoiceStore.getSubTotal,
|
||||
total: invoiceStore.getTotal,
|
||||
tax: invoiceStore.getTotalTax,
|
||||
sub_total: Math.round(invoiceStore.getSubTotal),
|
||||
total: Math.round(invoiceStore.getTotal),
|
||||
tax: Math.round(invoiceStore.getTotalTax),
|
||||
}
|
||||
|
||||
let response
|
||||
@@ -325,9 +325,9 @@ async function submitForm(): Promise<void> {
|
||||
} else {
|
||||
const data: Record<string, unknown> = {
|
||||
...cloneDeep(invoiceStore.newInvoice),
|
||||
sub_total: invoiceStore.getSubTotal,
|
||||
total: invoiceStore.getTotal,
|
||||
tax: invoiceStore.getTotalTax,
|
||||
sub_total: Math.round(invoiceStore.getSubTotal),
|
||||
total: Math.round(invoiceStore.getTotal),
|
||||
tax: Math.round(invoiceStore.getTotalTax),
|
||||
}
|
||||
|
||||
const items = data.items as Array<Record<string, unknown>>
|
||||
|
||||
@@ -192,6 +192,9 @@
|
||||
:store-prop="storeProp"
|
||||
:store="store"
|
||||
:type="taxPopupType"
|
||||
:tax-types="availableTaxTypes"
|
||||
:company-currency="companyCurrency"
|
||||
:can-create-tax-type="canCreateTaxType"
|
||||
@select:tax-type="onSelectTax"
|
||||
/>
|
||||
</div>
|
||||
@@ -228,10 +231,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import TaxSelectPopup from './TaxSelectPopup.vue'
|
||||
import { useCompanyStore } from '../../../stores/company.store'
|
||||
import { useUserStore } from '../../../stores/user.store'
|
||||
import { taxTypeService } from '../../../api/services/tax-type.service'
|
||||
import { generateClientId } from '../../../utils'
|
||||
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'
|
||||
@@ -254,7 +260,22 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
})
|
||||
|
||||
const companyStore = useCompanyStore()
|
||||
const userStore = useUserStore()
|
||||
const taxModal = ref<HTMLElement | null>(null)
|
||||
const availableTaxTypes = ref<TaxType[]>([])
|
||||
|
||||
const canCreateTaxType = computed<boolean>(() => {
|
||||
return userStore.hasAbilities(ABILITIES.CREATE_TAX_TYPE)
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const response = await taxTypeService.list({ limit: 'all' as unknown as number })
|
||||
availableTaxTypes.value = response.data
|
||||
} catch {
|
||||
// Silently fail
|
||||
}
|
||||
})
|
||||
|
||||
const formData = computed<DocumentFormData>(() => {
|
||||
return props.store[props.storeProp] as DocumentFormData
|
||||
@@ -285,7 +306,10 @@ const baseCurrencyGrandTotal = computed<number>(() => {
|
||||
|
||||
watch(
|
||||
() => formData.value.items,
|
||||
() => setDiscount(),
|
||||
() => {
|
||||
setDiscount()
|
||||
recalculateGlobalTaxes()
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
@@ -294,6 +318,7 @@ const totalDiscount = computed<number>({
|
||||
set: (newValue: number) => {
|
||||
formData.value.discount = newValue
|
||||
setDiscount()
|
||||
recalculateGlobalTaxes()
|
||||
},
|
||||
})
|
||||
|
||||
@@ -350,6 +375,18 @@ function setDiscount(): void {
|
||||
formData.value.discount_val = Math.round(newValue * 100)
|
||||
}
|
||||
|
||||
function recalculateGlobalTaxes(): void {
|
||||
if (formData.value.tax_per_item === 'YES') return
|
||||
|
||||
const subtotalWithDiscount = props.store.getSubtotalWithDiscount
|
||||
formData.value.taxes.forEach((tax: DocumentTax) => {
|
||||
if (tax.calculation_type === 'percentage' && tax.percent) {
|
||||
tax.amount = Math.round((subtotalWithDiscount * tax.percent) / 100)
|
||||
}
|
||||
// Fixed taxes keep their amount as-is
|
||||
})
|
||||
}
|
||||
|
||||
function selectFixed(): void {
|
||||
if (formData.value.discount_type === 'fixed') return
|
||||
formData.value.discount_val = Math.round(formData.value.discount * 100)
|
||||
|
||||
Reference in New Issue
Block a user