diff --git a/models/baseModels/Invoice/Invoice.ts b/models/baseModels/Invoice/Invoice.ts index 7c8d451f8..04679097d 100644 --- a/models/baseModels/Invoice/Invoice.ts +++ b/models/baseModels/Invoice/Invoice.ts @@ -28,6 +28,19 @@ import { TaxSummary } from '../TaxSummary/TaxSummary'; import { ReturnDocItem } from 'models/inventory/types'; import { AccountFieldEnum, PaymentTypeEnum } from '../Payment/types'; +export type TaxDetail = { + account: string; + payment_account?: string; + rate: number; +}; + +export type InvoiceTaxItem = { + details: TaxDetail; + exchangeRate?: number; + fullAmount: Money; + taxAmount: Money; +}; + export abstract class Invoice extends Transactional { _taxes: Record = {}; taxes?: TaxSummary[]; @@ -242,31 +255,15 @@ export abstract class Invoice extends Transactional { return safeParseFloat(exchangeRate.toFixed(2)); } - async getTaxSummary() { - const taxes: Record< - string, - { - account: string; - rate: number; - amount: Money; - } - > = {}; - - type TaxDetail = { account: string; rate: number }; - + async getTaxItems(): Promise { + const taxItems: InvoiceTaxItem[] = []; for (const item of this.items ?? []) { if (!item.tax) { continue; } const tax = await this.getTax(item.tax); - for (const { account, rate } of (tax.details ?? []) as TaxDetail[]) { - taxes[account] ??= { - account, - rate, - amount: this.fyo.pesa(0), - }; - + for (const details of (tax.details ?? []) as TaxDetail[]) { let amount = item.amount!; if ( this.enableDiscounting && @@ -276,11 +273,42 @@ export abstract class Invoice extends Transactional { amount = item.itemDiscountedTotal!; } - const taxAmount = amount.mul(rate / 100); - taxes[account].amount = taxes[account].amount.add(taxAmount); + const taxItem: InvoiceTaxItem = { + details, + exchangeRate: this.exchangeRate ?? 1, + fullAmount: amount, + taxAmount: amount.mul(details.rate / 100), + }; + + taxItems.push(taxItem); } } + return taxItems; + } + + async getTaxSummary() { + const taxes: Record< + string, + { + account: string; + rate: number; + amount: Money; + } + > = {}; + + for (const { details, taxAmount } of await this.getTaxItems()) { + const account = details.account; + + taxes[account] ??= { + account, + rate: details.rate, + amount: this.fyo.pesa(0), + }; + + taxes[account].amount = taxes[account].amount.add(taxAmount); + } + type Summary = typeof taxes[string] & { idx: number }; const taxArr: Summary[] = []; let idx = 0; diff --git a/models/baseModels/Payment/Payment.ts b/models/baseModels/Payment/Payment.ts index f1b91b1f9..38ee4418c 100644 --- a/models/baseModels/Payment/Payment.ts +++ b/models/baseModels/Payment/Payment.ts @@ -28,10 +28,12 @@ import { Invoice } from '../Invoice/Invoice'; import { Party } from '../Party/Party'; import { PaymentFor } from '../PaymentFor/PaymentFor'; import { PaymentMethod, PaymentType } from './types'; +import { TaxSummary } from '../TaxSummary/TaxSummary'; type AccountTypeMap = Record; export class Payment extends Transactional { + taxes?: TaxSummary[]; party?: string; amount?: Money; writeoff?: Money; @@ -221,6 +223,86 @@ export class Payment extends Transactional { ); } + async getTaxSummary() { + const taxes: Record< + string, + Record< + string, + { + account: string; + from_account: string; + rate: number; + amount: Money; + } + > + > = {}; + + for (const childDoc of this.for ?? []) { + const referenceName = childDoc.referenceName; + const referenceType = childDoc.referenceType; + + const refDoc = (await this.fyo.doc.getDoc( + childDoc.referenceType!, + childDoc.referenceName + )) as Invoice; + + if (referenceName && referenceType && !refDoc) { + throw new ValidationError( + t`${referenceType} of type ${ + this.fyo.schemaMap?.[referenceType]?.label ?? referenceType + } does not exist` + ); + } + + if (!refDoc) { + continue; + } + + for (const { + details, + taxAmount, + exchangeRate, + } of await refDoc.getTaxItems()) { + const { account, payment_account } = details; + if (!payment_account) { + continue; + } + + taxes[payment_account] ??= {}; + taxes[payment_account][account] ??= { + account: payment_account, + from_account: account, + rate: details.rate, + amount: this.fyo.pesa(0), + }; + + taxes[payment_account][account].amount = taxes[payment_account][ + account + ].amount.add(taxAmount.mul(exchangeRate ?? 1)); + } + } + + type Summary = typeof taxes[string][string] & { idx: number }; + const taxArr: Summary[] = []; + let idx = 0; + for (const payment_account in taxes) { + for (const account in taxes[payment_account]) { + const tax = taxes[payment_account][account]; + if (tax.amount.isZero()) { + continue; + } + + taxArr.push({ + ...tax, + idx, + }); + idx += 1; + } + } + + return taxArr; + } + async getPosting() { /** * account : From Account @@ -244,6 +326,20 @@ export class Payment extends Transactional { await posting.debit(paymentAccount, amount); await posting.credit(account, amount); + if (this.taxes) { + if (this.paymentType === 'Receive') { + for (const tax of this.taxes) { + await posting.debit(tax.from_account!, tax.amount!); + await posting.credit(tax.account!, tax.amount!); + } + } else if (this.paymentType === 'Pay') { + for (const tax of this.taxes) { + await posting.credit(tax.from_account!, tax.amount!); + await posting.debit(tax.account!, tax.amount!); + } + } + } + await this.applyWriteOffPosting(posting); return posting; } @@ -546,6 +642,7 @@ export class Payment extends Transactional { return this.for![0].referenceType; }, }, + taxes: { formula: async () => await this.getTaxSummary() }, }; validations: ValidationMap = { @@ -588,6 +685,7 @@ export class Payment extends Transactional { attachment: () => !(this.attachment || !(this.isSubmitted || this.isCancelled)), for: () => !!((this.isSubmitted || this.isCancelled) && !this.for?.length), + taxes: () => !this.taxes?.length, }; static filters: FiltersMap = { diff --git a/models/baseModels/TaxSummary/TaxSummary.ts b/models/baseModels/TaxSummary/TaxSummary.ts index bb9438e49..ad5bc6b39 100644 --- a/models/baseModels/TaxSummary/TaxSummary.ts +++ b/models/baseModels/TaxSummary/TaxSummary.ts @@ -9,6 +9,7 @@ import { Invoice } from '../Invoice/Invoice'; export class TaxSummary extends Doc { account?: string; + from_account?: string; rate?: number; amount?: Money; parentdoc?: Invoice; diff --git a/schemas/app/Payment.json b/schemas/app/Payment.json index 780c8c55f..9f30e2d8c 100644 --- a/schemas/app/Payment.json +++ b/schemas/app/Payment.json @@ -141,6 +141,14 @@ "computed": true, "section": "Amounts" }, + { + "fieldname": "taxes", + "label": "Taxes", + "fieldtype": "Table", + "target": "TaxSummary", + "readOnly": true, + "section": "Amounts" + }, { "fieldname": "for", "label": "Payment Reference", diff --git a/schemas/app/TaxDetail.json b/schemas/app/TaxDetail.json index ca74c6355..91e5f2f9a 100644 --- a/schemas/app/TaxDetail.json +++ b/schemas/app/TaxDetail.json @@ -6,12 +6,20 @@ "fields": [ { "fieldname": "account", - "label": "Tax Account", + "label": "Tax Invoice Account", "fieldtype": "Link", "target": "Account", "create": true, "required": true }, + { + "fieldname": "payment_account", + "label": "Tax Payment Account", + "fieldtype": "Link", + "target": "Account", + "create": true, + "required": false + }, { "fieldname": "rate", "label": "Rate", @@ -20,5 +28,5 @@ "placeholder": "0%" } ], - "tableFields": ["account", "rate"] + "tableFields": ["account", "payment_account", "rate"] } diff --git a/schemas/app/TaxSummary.json b/schemas/app/TaxSummary.json index d0d2893f5..00823bf75 100644 --- a/schemas/app/TaxSummary.json +++ b/schemas/app/TaxSummary.json @@ -10,6 +10,14 @@ "target": "Account", "required": true }, + { + "fieldname": "from_account", + "label": "Tax Invoice Account", + "fieldtype": "Link", + "target": "Account", + "required": false, + "hidden": true + }, { "fieldname": "rate", "label": "Tax Rate",