Skip to content

Commit

Permalink
frappe#755 Accounting for taxes on payments
Browse files Browse the repository at this point in the history
When defining taxes, it is possible to define an additional payment
account that will be used during payments to move taxes from the
original tax account to this new payment tax account. This allows to
account for taxes only when payment is received.

Now payments can reference tax summary objects that will reference the
two accounts to move funds between when the payment is committed. Reuse
some of the Invoice code to generate these tax summary objects.
  • Loading branch information
mildred committed Dec 2, 2023
1 parent 80eec59 commit d12c74e
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 22 deletions.
65 changes: 45 additions & 20 deletions models/baseModels/Invoice/Invoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ import { Payment } from '../Payment/Payment';
import { Tax } from '../Tax/Tax';
import { TaxSummary } from '../TaxSummary/TaxSummary';

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<string, Tax> = {};
taxes?: TaxSummary[];
Expand Down Expand Up @@ -230,30 +239,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(): InvoiceTaxItem[] {
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 (
Expand All @@ -264,11 +258,42 @@ export abstract class Invoice extends Transactional {
amount = item.itemDiscountedTotal!;
}

const taxAmount = amount.mul(rate / 100);
taxes[account].amount = taxes[account].amount.add(taxAmount);
let 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;
Expand Down
92 changes: 92 additions & 0 deletions models/baseModels/Payment/Payment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<AccountTypeEnum, string[] | undefined>;

export class Payment extends Transactional {
taxes?: TaxSummary[];
party?: string;
amount?: Money;
writeoff?: Money;
Expand Down Expand Up @@ -220,6 +222,80 @@ 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
Expand All @@ -243,6 +319,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;
}
Expand Down Expand Up @@ -520,6 +610,7 @@ export class Payment extends Transactional {
formula: () => this.amount!.sub(this.writeoff!),
dependsOn: ['amount', 'writeoff', 'for'],
},
taxes: { formula: async () => await this.getTaxSummary() },
};

validations: ValidationMap = {
Expand Down Expand Up @@ -562,6 +653,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 = {
Expand Down
8 changes: 8 additions & 0 deletions schemas/app/Payment.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 10 additions & 2 deletions schemas/app/TaxDetail.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -20,5 +28,5 @@
"placeholder": "0%"
}
],
"tableFields": ["account", "rate"]
"tableFields": ["account", "payment_account", "rate"]
}
8 changes: 8 additions & 0 deletions schemas/app/TaxSummary.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit d12c74e

Please sign in to comment.