Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: #755 Accounting for taxes on payments #789

Merged
merged 1 commit into from
Dec 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 49 additions & 21 deletions models/baseModels/Invoice/Invoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, Tax> = {};
taxes?: TaxSummary[];
Expand Down Expand Up @@ -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<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 (
this.enableDiscounting &&
Expand All @@ -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;
Expand Down
98 changes: 98 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 @@ -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
Expand All @@ -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;
}
Expand Down Expand Up @@ -546,6 +642,7 @@ export class Payment extends Transactional {
return this.for![0].referenceType;
},
},
taxes: { formula: async () => await this.getTaxSummary() },
};

validations: ValidationMap = {
Expand Down Expand Up @@ -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 = {
Expand Down
1 change: 1 addition & 0 deletions models/baseModels/TaxSummary/TaxSummary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
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
Loading