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:
commit
0da912e324
@ -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 };
|
||||
|
@ -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 = {
|
||||
|
@ -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;
|
||||
|
@ -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",
|
||||
|
@ -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"]
|
||||
}
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user