2
0
mirror of https://github.com/frappe/books.git synced 2024-11-09 15:20:56 +00:00

Merge pull request #789 from frappe/mildred/755-taxes-on-payments

feat: #755 Accounting for taxes on payments
This commit is contained in:
Mildred Ki'Lya 2023-12-27 11:05:42 +01:00 committed by GitHub
commit 0da912e324
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 178 additions and 27 deletions

View File

@ -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[];
@ -242,6 +255,38 @@ export abstract class Invoice extends Transactional {
return safeParseFloat(exchangeRate.toFixed(2));
}
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 details of (tax.details ?? []) as TaxDetail[]) {
let amount = item.amount!;
if (
this.enableDiscounting &&
!this.discountAfterTax &&
!item.itemDiscountedTotal?.isZero()
) {
amount = item.itemDiscountedTotal!;
}
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,
@ -252,33 +297,16 @@ export abstract class Invoice extends Transactional {
}
> = {};
type TaxDetail = { account: string; rate: number };
for (const { details, taxAmount } of await this.getTaxItems()) {
const account = details.account;
for (const item of this.items ?? []) {
if (!item.tax) {
continue;
}
taxes[account] ??= {
account,
rate: details.rate,
amount: this.fyo.pesa(0),
};
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),
};
let amount = item.amount!;
if (
this.enableDiscounting &&
!this.discountAfterTax &&
!item.itemDiscountedTotal?.isZero()
) {
amount = item.itemDiscountedTotal!;
}
const taxAmount = amount.mul(rate / 100);
taxes[account].amount = taxes[account].amount.add(taxAmount);
}
taxes[account].amount = taxes[account].amount.add(taxAmount);
}
type Summary = typeof taxes[string] & { idx: number };

View File

@ -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;
@ -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 = {

View File

@ -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;

View File

@ -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",

View File

@ -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"]
}

View File

@ -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",