mirror of
https://github.com/frappe/books.git
synced 2025-01-03 15:17:30 +00:00
feat: schema changes for invoice return
This commit is contained in:
parent
3199121432
commit
8cac1ef45f
@ -16,6 +16,7 @@ export class AccountingSettings extends Doc {
|
||||
enableInventory?: boolean;
|
||||
enablePriceList?: boolean;
|
||||
enableFormCustomization?: boolean;
|
||||
enableInvoiceReturns?: boolean;
|
||||
|
||||
static filters: FiltersMap = {
|
||||
writeOffAccount: () => ({
|
||||
@ -47,6 +48,9 @@ export class AccountingSettings extends Doc {
|
||||
enableInventory: () => {
|
||||
return !!this.enableInventory;
|
||||
},
|
||||
enableInvoiceReturns: () => {
|
||||
return !!this.enableInvoiceReturns;
|
||||
},
|
||||
};
|
||||
|
||||
override hidden: HiddenMap = {
|
||||
|
@ -25,6 +25,8 @@ import { Party } from '../Party/Party';
|
||||
import { Payment } from '../Payment/Payment';
|
||||
import { Tax } from '../Tax/Tax';
|
||||
import { TaxSummary } from '../TaxSummary/TaxSummary';
|
||||
import { ReturnDocItem } from 'models/inventory/types';
|
||||
import { AccountFieldEnum, PaymentTypeEnum } from '../Payment/types';
|
||||
|
||||
export abstract class Invoice extends Transactional {
|
||||
_taxes: Record<string, Tax> = {};
|
||||
@ -52,6 +54,9 @@ export abstract class Invoice extends Transactional {
|
||||
makeAutoPayment?: boolean;
|
||||
makeAutoStockTransfer?: boolean;
|
||||
|
||||
isReturned?: boolean;
|
||||
returnAgainst?: string;
|
||||
|
||||
get isSales() {
|
||||
return this.schemaName === 'SalesInvoice';
|
||||
}
|
||||
@ -118,6 +123,10 @@ export abstract class Invoice extends Transactional {
|
||||
return null;
|
||||
}
|
||||
|
||||
get isReturn(): boolean {
|
||||
return !!this.returnAgainst;
|
||||
}
|
||||
|
||||
constructor(schema: Schema, data: DocValueMap, fyo: Fyo) {
|
||||
super(schema, data, fyo);
|
||||
this._setGetCurrencies();
|
||||
@ -159,12 +168,15 @@ export abstract class Invoice extends Transactional {
|
||||
await stockTransfer?.submit();
|
||||
await this.load();
|
||||
}
|
||||
|
||||
await this._updateIsItemsReturned();
|
||||
}
|
||||
|
||||
async afterCancel() {
|
||||
await super.afterCancel();
|
||||
await this._cancelPayments();
|
||||
await this._updatePartyOutStanding();
|
||||
await this._updateIsItemsReturned();
|
||||
}
|
||||
|
||||
async _cancelPayments() {
|
||||
@ -368,6 +380,134 @@ export abstract class Invoice extends Transactional {
|
||||
return discountAmount;
|
||||
}
|
||||
|
||||
async getReturnDoc(): Promise<Invoice | undefined> {
|
||||
if (!this.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const docData = this.getValidDict(true, true);
|
||||
const docItems = docData.items as DocValueMap[];
|
||||
|
||||
if (!docItems) {
|
||||
return;
|
||||
}
|
||||
|
||||
let returnDocItems: DocValueMap[] = [];
|
||||
|
||||
const returnBalanceItemsQty = await this.fyo.db.getReturnBalanceItemsQty(
|
||||
this.schemaName,
|
||||
this.name
|
||||
);
|
||||
|
||||
for (const item of docItems) {
|
||||
if (!returnBalanceItemsQty) {
|
||||
returnDocItems = docItems;
|
||||
returnDocItems.map((row) => {
|
||||
row.name = undefined;
|
||||
(row.quantity as number) *= -1;
|
||||
return row;
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
const isItemExist = !!returnDocItems.filter(
|
||||
(balanceItem) => balanceItem.item === item.item
|
||||
).length;
|
||||
|
||||
if (isItemExist) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const returnedItem: ReturnDocItem | undefined =
|
||||
returnBalanceItemsQty[item.item as string];
|
||||
|
||||
let quantity = returnedItem.quantity;
|
||||
let serialNumber: string | undefined =
|
||||
returnedItem.serialNumbers?.join('\n');
|
||||
|
||||
if (
|
||||
item.batch &&
|
||||
returnedItem.batches &&
|
||||
returnedItem.batches[item.batch as string]
|
||||
) {
|
||||
quantity = returnedItem.batches[item.batch as string].quantity;
|
||||
|
||||
if (returnedItem.batches[item.batch as string].serialNumbers) {
|
||||
serialNumber =
|
||||
returnedItem.batches[item.batch as string].serialNumbers?.join(
|
||||
'\n'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
returnDocItems.push({
|
||||
...item,
|
||||
serialNumber,
|
||||
name: undefined,
|
||||
quantity: quantity,
|
||||
});
|
||||
}
|
||||
const returnDocData = {
|
||||
...docData,
|
||||
name: undefined,
|
||||
date: new Date(),
|
||||
items: returnDocItems,
|
||||
returnAgainst: docData.name,
|
||||
} as DocValueMap;
|
||||
|
||||
const newReturnDoc = this.fyo.doc.getNewDoc(
|
||||
this.schema.name,
|
||||
returnDocData
|
||||
) as Invoice;
|
||||
|
||||
await newReturnDoc.runFormulas();
|
||||
return newReturnDoc;
|
||||
}
|
||||
|
||||
async _updateIsItemsReturned() {
|
||||
if (!this.isReturn || !this.returnAgainst) {
|
||||
return;
|
||||
}
|
||||
|
||||
const returnInvoices = await this.fyo.db.getAll(this.schema.name, {
|
||||
filters: {
|
||||
submitted: true,
|
||||
cancelled: false,
|
||||
returnAgainst: this.returnAgainst,
|
||||
},
|
||||
});
|
||||
|
||||
const isReturned = !!returnInvoices.length;
|
||||
const invoiceDoc = await this.fyo.doc.getDoc(
|
||||
this.schemaName,
|
||||
this.returnAgainst
|
||||
);
|
||||
await invoiceDoc.setAndSync({ isReturned });
|
||||
await invoiceDoc.submit();
|
||||
}
|
||||
|
||||
async _validateHasLinkedReturnInvoices() {
|
||||
if (!this.name || this.isReturn) {
|
||||
return;
|
||||
}
|
||||
|
||||
const returnInvoices = await this.fyo.db.getAll(this.schemaName, {
|
||||
filters: {
|
||||
returnAgainst: this.name,
|
||||
},
|
||||
});
|
||||
|
||||
if (!returnInvoices.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const names = returnInvoices.map(({ name }) => name).join(', ');
|
||||
throw new ValidationError(
|
||||
this.fyo
|
||||
.t`Cannot cancel ${this.name} because of the following ${this.schema.label}: ${names}`
|
||||
);
|
||||
}
|
||||
|
||||
formulas: FormulaMap = {
|
||||
account: {
|
||||
formula: async () => {
|
||||
@ -518,6 +658,8 @@ export abstract class Invoice extends Transactional {
|
||||
!(this.attachment || !(this.isSubmitted || this.isCancelled)),
|
||||
backReference: () => !this.backReference,
|
||||
priceList: () => !this.fyo.singles.AccountingSettings?.enablePriceList,
|
||||
returnAgainst: () =>
|
||||
(this.isSubmitted || this.isCancelled) && !this.returnAgainst,
|
||||
};
|
||||
|
||||
static defaults: DefaultMap = {
|
||||
@ -595,12 +737,29 @@ export abstract class Invoice extends Transactional {
|
||||
return null;
|
||||
}
|
||||
|
||||
const accountField = this.isSales ? 'account' : 'paymentAccount';
|
||||
let accountField: AccountFieldEnum = AccountFieldEnum.Account;
|
||||
let paymentType: PaymentTypeEnum = PaymentTypeEnum.Receive;
|
||||
|
||||
if (this.isSales && this.isReturn) {
|
||||
accountField = AccountFieldEnum.PaymentAccount;
|
||||
paymentType = PaymentTypeEnum.Pay;
|
||||
}
|
||||
|
||||
if (!this.isSales) {
|
||||
accountField = AccountFieldEnum.PaymentAccount;
|
||||
paymentType = PaymentTypeEnum.Pay;
|
||||
|
||||
if (this.isReturn) {
|
||||
accountField = AccountFieldEnum.Account;
|
||||
paymentType = PaymentTypeEnum.Receive;
|
||||
}
|
||||
}
|
||||
|
||||
const data = {
|
||||
party: this.party,
|
||||
date: new Date().toISOString().slice(0, 10),
|
||||
paymentType: this.isSales ? 'Receive' : 'Pay',
|
||||
amount: this.outstandingAmount,
|
||||
paymentType,
|
||||
amount: this.outstandingAmount?.abs(),
|
||||
[accountField]: this.account,
|
||||
for: [
|
||||
{
|
||||
@ -720,6 +879,7 @@ export abstract class Invoice extends Transactional {
|
||||
async beforeCancel(): Promise<void> {
|
||||
await super.beforeCancel();
|
||||
await this._validateStockTransferCancelled();
|
||||
await this._validateHasLinkedReturnInvoices();
|
||||
}
|
||||
|
||||
async beforeDelete(): Promise<void> {
|
||||
|
@ -300,7 +300,7 @@ export class Payment extends Transactional {
|
||||
)) as Invoice;
|
||||
|
||||
outstandingAmount = outstandingAmount.add(
|
||||
referenceDoc.outstandingAmount ?? 0
|
||||
referenceDoc.outstandingAmount?.abs() ?? 0
|
||||
);
|
||||
}
|
||||
|
||||
@ -438,6 +438,15 @@ export class Payment extends Transactional {
|
||||
'referenceName'
|
||||
)) as Invoice | null;
|
||||
|
||||
if (
|
||||
refDoc &&
|
||||
refDoc.schema.name === ModelNameEnum.SalesInvoice &&
|
||||
refDoc.isReturned
|
||||
) {
|
||||
const accountsMap = await this._getAccountsMap();
|
||||
return accountsMap[AccountTypeEnum.Cash]?.[0];
|
||||
}
|
||||
|
||||
return refDoc?.account ?? null;
|
||||
}
|
||||
|
||||
@ -534,7 +543,7 @@ export class Payment extends Transactional {
|
||||
return;
|
||||
}
|
||||
|
||||
const amount = this.getSum('for', 'amount', false);
|
||||
const amount = (this.getSum('for', 'amount', false) as Money).abs();
|
||||
|
||||
if ((value as Money).gt(amount)) {
|
||||
throw new ValidationError(
|
||||
|
@ -12,14 +12,26 @@ export class SalesInvoice extends Invoice {
|
||||
async getPosting() {
|
||||
const exchangeRate = this.exchangeRate ?? 1;
|
||||
const posting: LedgerPosting = new LedgerPosting(this, this.fyo);
|
||||
if (this.isReturn) {
|
||||
await posting.credit(this.account!, this.baseGrandTotal!);
|
||||
} else {
|
||||
await posting.debit(this.account!, this.baseGrandTotal!);
|
||||
}
|
||||
|
||||
for (const item of this.items!) {
|
||||
if (this.isReturn) {
|
||||
await posting.debit(item.account!, item.amount!.mul(exchangeRate));
|
||||
continue;
|
||||
}
|
||||
await posting.credit(item.account!, item.amount!.mul(exchangeRate));
|
||||
}
|
||||
|
||||
if (this.taxes) {
|
||||
for (const tax of this.taxes) {
|
||||
if (this.isReturn) {
|
||||
await posting.debit(tax.account!, tax.amount!.mul(exchangeRate));
|
||||
continue;
|
||||
}
|
||||
await posting.credit(tax.account!, tax.amount!.mul(exchangeRate));
|
||||
}
|
||||
}
|
||||
@ -28,8 +40,12 @@ export class SalesInvoice extends Invoice {
|
||||
const discountAccount = this.fyo.singles.AccountingSettings
|
||||
?.discountAccount as string | undefined;
|
||||
if (discountAccount && discountAmount.isPositive()) {
|
||||
if (this.isReturn) {
|
||||
await posting.credit(discountAccount, discountAmount.mul(exchangeRate));
|
||||
} else {
|
||||
await posting.debit(discountAccount, discountAmount.mul(exchangeRate));
|
||||
}
|
||||
}
|
||||
|
||||
await posting.makeRoundOffEntry();
|
||||
return posting;
|
||||
|
Loading…
Reference in New Issue
Block a user