2
0
mirror of https://github.com/frappe/books.git synced 2024-11-10 07:40:55 +00:00
books/models/baseModels/Invoice/Invoice.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

209 lines
5.5 KiB
TypeScript
Raw Normal View History

import { DocValue } from 'fyo/core/types';
import { Doc } from 'fyo/model/doc';
import { DefaultMap, FiltersMap, FormulaMap } from 'fyo/model/types';
import { getExchangeRate } from 'models/helpers';
import { LedgerPosting } from 'models/ledgerPosting/ledgerPosting';
2022-04-14 08:01:33 +00:00
import Money from 'pesa/dist/types/src/money';
import { Party } from '../Party/Party';
import { Payment } from '../Payment/Payment';
import { Tax } from '../Tax/Tax';
import { TaxSummary } from '../TaxSummary/TaxSummary';
2022-04-14 08:01:33 +00:00
export abstract class Invoice extends Doc {
2022-04-14 08:01:33 +00:00
_taxes: Record<string, Tax> = {};
taxes?: TaxSummary[];
2022-04-14 08:01:33 +00:00
party?: string;
account?: string;
currency?: string;
netTotal?: Money;
baseGrandTotal?: Money;
2022-04-20 06:38:47 +00:00
outstandingAmount?: Money;
exchangeRate?: number;
2022-04-20 06:38:47 +00:00
submitted?: boolean;
cancelled?: boolean;
abstract getPosting(): Promise<LedgerPosting>;
get isSales() {
return this.schemaName === 'SalesInvoice';
}
2022-04-14 08:01:33 +00:00
async getPayments() {
const payments = await this.fyo.db.getAll('PaymentFor', {
2022-04-14 08:01:33 +00:00
fields: ['parent'],
filters: { referenceName: this.name! },
2022-04-14 08:01:33 +00:00
orderBy: 'name',
});
if (payments.length != 0) {
return payments;
}
return [];
}
async beforeSync() {
2022-04-14 08:01:33 +00:00
const entries = await this.getPosting();
await entries.validateEntries();
}
async afterSubmit() {
// post ledger entries
const entries = await this.getPosting();
await entries.post();
// update outstanding amounts
await this.fyo.db.update(this.schemaName, {
2022-04-14 08:01:33 +00:00
name: this.name as string,
outstandingAmount: this.baseGrandTotal!,
2022-04-14 08:01:33 +00:00
});
const party = (await this.fyo.doc.getDoc('Party', this.party!)) as Party;
2022-04-14 08:01:33 +00:00
await party.updateOutstandingAmount();
}
async afterRevert() {
const paymentRefList = await this.getPayments();
for (const paymentFor of paymentRefList) {
const paymentReference = paymentFor.parent;
const payment = (await this.fyo.doc.getDoc(
2022-04-14 08:01:33 +00:00
'Payment',
paymentReference as string
)) as Payment;
const paymentEntries = await payment.getPosting();
for (const entry of paymentEntries) {
await entry.postReverse();
}
// To set the payment status as unsubmitted.
await this.fyo.db.update('Payment', {
2022-04-14 08:01:33 +00:00
name: paymentReference,
submitted: false,
cancelled: true,
});
}
const entries = await this.getPosting();
await entries.postReverse();
}
async getExchangeRate() {
if (!this.currency) return 1.0;
const accountingSettings = await this.fyo.doc.getSingle(
'AccountingSettings'
);
2022-04-14 08:01:33 +00:00
const companyCurrency = accountingSettings.currency;
if (this.currency === companyCurrency) {
return 1.0;
}
return await getExchangeRate({
fromCurrency: this.currency!,
2022-04-14 08:01:33 +00:00
toCurrency: companyCurrency as string,
});
}
async getTaxSummary() {
const taxes: Record<
string,
{
account: string;
rate: number;
amount: Money;
baseAmount: Money;
[key: string]: DocValue;
}
2022-04-14 08:01:33 +00:00
> = {};
for (const row of this.items as Doc[]) {
if (!row.tax) {
continue;
}
const tax = await this.getTax(row.tax as string);
for (const d of tax.details as Doc[]) {
const account = d.account as string;
const rate = d.rate as number;
taxes[account] = taxes[account] || {
account,
rate,
amount: this.fyo.pesa(0),
baseAmount: this.fyo.pesa(0),
2022-04-14 08:01:33 +00:00
};
const amount = (row.amount as Money).mul(rate).div(100);
taxes[account].amount = taxes[account].amount.add(amount);
}
}
return Object.keys(taxes)
.map((account) => {
const tax = taxes[account];
tax.baseAmount = tax.amount.mul(this.exchangeRate!);
2022-04-14 08:01:33 +00:00
return tax;
})
.filter((tax) => !tax.amount.isZero());
}
async getTax(tax: string) {
if (!this._taxes![tax]) {
this._taxes[tax] = await this.fyo.doc.getDoc('Tax', tax);
2022-04-14 08:01:33 +00:00
}
return this._taxes[tax];
}
async getGrandTotal() {
return ((this.taxes ?? []) as Doc[])
.map((doc) => doc.amount as Money)
.reduce((a, b) => a.add(b), this.netTotal!);
2022-04-14 08:01:33 +00:00
}
formulas: FormulaMap = {
account: {
formula: async () =>
this.getFrom('Party', this.party!, 'defaultAccount') as string,
dependsOn: ['party'],
},
currency: {
formula: async () =>
(this.getFrom('Party', this.party!, 'currency') as string) ||
(this.fyo.singles.AccountingSettings!.currency as string),
dependsOn: ['party'],
},
exchangeRate: { formula: async () => await this.getExchangeRate() },
netTotal: { formula: async () => this.getSum('items', 'amount', false) },
baseNetTotal: {
formula: async () => this.netTotal!.mul(this.exchangeRate!),
},
taxes: { formula: async () => await this.getTaxSummary() },
grandTotal: { formula: async () => await this.getGrandTotal() },
baseGrandTotal: {
formula: async () => (this.grandTotal as Money).mul(this.exchangeRate!),
},
outstandingAmount: {
formula: async () => {
if (this.submitted) {
return;
}
return this.baseGrandTotal!;
},
},
};
2022-04-25 06:33:31 +00:00
static defaults: DefaultMap = {
date: () => new Date().toISOString().slice(0, 10),
};
static filters: FiltersMap = {
account: (doc: Doc) => ({
isGroup: false,
accountType: doc.isSales ? 'Receivable' : 'Payable',
}),
numberSeries: (doc: Doc) => ({ referenceType: doc.schemaName }),
};
2022-04-14 08:01:33 +00:00
}