Skip to content

Commit

Permalink
feat: invoice returns
Browse files Browse the repository at this point in the history
  • Loading branch information
akshayitzme committed Jun 27, 2023
1 parent 1722a96 commit 410da26
Show file tree
Hide file tree
Showing 14 changed files with 370 additions and 31 deletions.
63 changes: 63 additions & 0 deletions backend/database/bespoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
import { ModelNameEnum } from '../../models/types';
import DatabaseCore from './core';
import { BespokeFunction } from './types';
import { safeParseFloat } from 'utils/index';
import { DocValueMap } from 'fyo/core/types';

export class BespokeQueries {
[key: string]: BespokeFunction;
Expand Down Expand Up @@ -180,4 +182,65 @@ export class BespokeQueries {

return value[0][Object.keys(value[0])[0]];
}

static async getInvoiceReturnBalanceItemsQty(
db: DatabaseCore,
schemaName: string,
invoice: string
): Promise<DocValueMap[] | undefined> {
const invoiceItems = (await db.knex!(`${schemaName}Item`)
.select('item')
.where('parent', invoice)
.groupBy('item')
.sum({ quantity: 'quantity' })) as DocValueMap[];

const returnInvoiceNames = (
await db.knex!(schemaName)
.select('name')
.where('returnAgainst', invoice)
.andWhere('submitted', true)
.andWhere('cancelled', false)
).map((i) => i.name);

if (!returnInvoiceNames.length) {
return;
}

const returnedItems = (await db.knex!(`${schemaName}Item`)
.select('item')
.sum({ quantity: 'quantity' })
.whereIn('parent', returnInvoiceNames)
.groupBy('item')) as DocValueMap[];

if (!returnedItems.length) {
return;
}

const returnBalanceItemQty = [];

for (const item of returnedItems) {
const invoiceItem = invoiceItems.filter(
(invItem) => invItem.item === item.item
)[0];

if (!invoiceItem) {
continue;
}

let balanceQty = safeParseFloat(
(invoiceItem.quantity as number) - Math.abs(item.quantity as number)
);

if (balanceQty === 0) {
continue;
}

if (balanceQty > 0) {
balanceQty *= -1;
}
returnBalanceItemQty.push({ ...item, quantity: balanceQty });
}

return returnBalanceItemQty;
}
}
11 changes: 11 additions & 0 deletions fyo/core/dbHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,17 @@ export class DatabaseHandler extends DatabaseBase {
)) as number | null;
}

async getInvoiceReturnBalanceItemsQty(
schemaName: string,
invoice: string
): Promise<DocValueMap[] | undefined> {
return (await this.#demux.callBespoke(
'getInvoiceReturnBalanceItemsQty',
schemaName,
invoice
)) as DocValueMap[] | undefined;
}

/**
* Internal methods
*/
Expand Down
4 changes: 4 additions & 0 deletions models/baseModels/AccountingSettings/AccountingSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { getCountryInfo } from 'utils/misc';

export class AccountingSettings extends Doc {
enableDiscounting?: boolean;
enableInvoiceReturns?: boolean;
enableInventory?: boolean;
enablePriceList?: boolean;

Expand Down Expand Up @@ -43,6 +44,9 @@ export class AccountingSettings extends Doc {
enableDiscounting: () => {
return !!this.enableDiscounting;
},
enableInvoiceReturns: () => {
return !!this.enableInvoiceReturns;
},
enableInventory: () => {
return !!this.enableInventory;
},
Expand Down
148 changes: 142 additions & 6 deletions models/baseModels/Invoice/Invoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { Party } from '../Party/Party';
import { Payment } from '../Payment/Payment';
import { Tax } from '../Tax/Tax';
import { TaxSummary } from '../TaxSummary/TaxSummary';
import { isPesa } from 'fyo/utils';

export abstract class Invoice extends Transactional {
_taxes: Record<string, Tax> = {};
Expand All @@ -47,6 +48,10 @@ export abstract class Invoice extends Transactional {
stockNotTransferred?: number;
backReference?: string;

isItemsReturned?: boolean;
returnAgainst?: string;
returnCompleted?: boolean;

submitted?: boolean;
cancelled?: boolean;
makeAutoPayment?: boolean;
Expand Down Expand Up @@ -159,12 +164,16 @@ export abstract class Invoice extends Transactional {
await stockTransfer?.submit();
await this.load();
}
await this._updateIsItemsReturned();
await this._updateReturnInvoiceOutStanding();
}

async afterCancel() {
await super.afterCancel();
await this._cancelPayments();
await this._updatePartyOutStanding();
await this._updateIsItemsReturned();
await this._updateReturnInvoiceOutStanding();
}

async _cancelPayments() {
Expand Down Expand Up @@ -368,6 +377,123 @@ export abstract class Invoice extends Transactional {
return discountAmount;
}

async getReturnDoc() {
if (!this.items?.length) {
return;
}

const invoiceData = this.getValidDict(true, true);
const invoiceItems = invoiceData.items as InvoiceItem[];
const returnInvoiceItems: InvoiceItem[] = [];

const returnBalanceItemsQty =
await this.fyo.db.getInvoiceReturnBalanceItemsQty(
this.schema.name,
this.name!
);

for (const item of invoiceItems) {
if (!item.quantity) {
continue;
}

let quantity = -1 * item.quantity!;

if (returnBalanceItemsQty) {
const balanceItemQty = returnBalanceItemsQty.filter(
(i) => i.item === item.item
)[0];

if (!balanceItemQty) {
continue;
}
quantity = balanceItemQty.quantity as number;
}

item.quantity = safeParseFloat(quantity);
delete item.name;
returnInvoiceItems.push(item);
}

const returnDocData = {
...invoiceData,
name: null,
date: new Date(),
items: returnInvoiceItems as InvoiceItem[],
isReturn: true,
returnAgainst: invoiceData.name,
netTotal: this.fyo.pesa(0),
baseGrandTotal: this.fyo.pesa(0),
grandTotal: this.fyo.pesa(0),
outstandingAmount: this.fyo.pesa(0),
};

const rawReturnDoc = this.fyo.doc.getNewDoc(
this.schema.name,
returnDocData
);

rawReturnDoc.once('beforeSync', async () => {
rawReturnDoc.runFormulas();
});
return rawReturnDoc;
}

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 isItemsReturned = returnInvoices.length;

await this.fyo.db.update(this.schemaName, {
name: this.returnAgainst as string,
isItemsReturned,
});
}

async _updateReturnInvoiceOutStanding() {
if (!this.isReturn || !this.returnAgainst || !this.grandTotal) {
return;
}

const invoiceOutstandingAmount = Object.values(
await this.fyo.db.get(
this.schema.name,
this.returnAgainst,
'outstandingAmount'
)
)[0];

if (!isPesa(invoiceOutstandingAmount)) {
return;
}

const returnInvoiceGrandTotal = this.grandTotal.abs();
let outstandingAmount = this.fyo.pesa(0);

if (this.submitted && !this.cancelled) {
outstandingAmount = invoiceOutstandingAmount.sub(returnInvoiceGrandTotal);
}

if (this.isCancelled) {
outstandingAmount = invoiceOutstandingAmount.add(returnInvoiceGrandTotal);
}

await this.fyo.db.update(this.schemaName, {
name: this.returnAgainst as string,
outstandingAmount,
});
}

formulas: FormulaMap = {
account: {
formula: async () => {
Expand Down Expand Up @@ -447,6 +573,9 @@ export abstract class Invoice extends Transactional {
!!this.autoStockTransferLocation,
dependsOn: [],
},
returnCompleted: {
formula: () => false,
},
};

getStockTransferred() {
Expand Down Expand Up @@ -518,6 +647,8 @@ export abstract class Invoice extends Transactional {
!(this.attachment || !(this.isSubmitted || this.isCancelled)),
backReference: () => !this.backReference,
priceList: () => !this.fyo.singles.AccountingSettings?.enablePriceList,
isReturn: () => !this.fyo.singles.AccountingSettings?.enableInvoiceReturns,
returnAgainst: () => !this.isReturn,
};

static defaults: DefaultMap = {
Expand Down Expand Up @@ -552,6 +683,12 @@ export abstract class Invoice extends Transactional {
isEnabled: true,
...(doc.isSales ? { isSales: true } : { isPurchase: true }),
}),
returnAgainst: () => ({
submitted: true,
isReturn: false,
cancelled: false,
returnCompleted: false,
}),
};

static createFilters: FiltersMap = {
Expand Down Expand Up @@ -591,16 +728,15 @@ export abstract class Invoice extends Transactional {
return null;
}

if (this.outstandingAmount?.isZero()) {
return null;
}

const accountField = this.isSales ? 'account' : 'paymentAccount';
const data = {
party: this.party,
date: new Date().toISOString().slice(0, 10),
paymentType: this.isSales ? 'Receive' : 'Pay',
amount: this.outstandingAmount,
paymentType:
this.isSales && !this.outstandingAmount?.isNegative()
? 'Receive'
: 'Pay',
amount: this.outstandingAmount?.abs(),
[accountField]: this.account,
for: [
{
Expand Down
15 changes: 15 additions & 0 deletions models/baseModels/InvoiceItem/InvoiceItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ export abstract class InvoiceItem extends Doc {
return this.parentdoc?.isMultiCurrency ?? false;
}

get isReturn() {
return !!this.parentdoc?.isReturn;
}

constructor(schema: Schema, data: DocValueMap, fyo: Fyo) {
super(schema, data, fyo);
this._setGetCurrencies();
Expand Down Expand Up @@ -210,6 +214,15 @@ export abstract class InvoiceItem extends Doc {
const unitDoc = itemDoc.getLink('uom');

let quantity: number = this.quantity ?? 1;

if (this.isReturn && quantity > 0) {
quantity *= -1;
}

if (!this.isReturn && quantity < 0) {
quantity *= -1;
}

if (fieldname === 'transferQuantity') {
quantity = this.transferQuantity! * this.unitConversionFactor!;
}
Expand All @@ -225,6 +238,8 @@ export abstract class InvoiceItem extends Doc {
'transferQuantity',
'transferUnit',
'unitConversionFactor',
'item',
'isReturn',
],
},
unitConversionFactor: {
Expand Down
5 changes: 3 additions & 2 deletions models/baseModels/Payment/Payment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ export class Payment extends Transactional {
)) as Invoice;

outstandingAmount = outstandingAmount.add(
referenceDoc.outstandingAmount ?? 0
referenceDoc.outstandingAmount?.abs() ?? 0
);
}

Expand Down Expand Up @@ -507,6 +507,7 @@ export class Payment extends Transactional {
}

if (outstanding?.isPositive()) {
console.log('positive');
return 'Receive';
}
return 'Pay';
Expand Down Expand Up @@ -534,7 +535,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(
Expand Down
Loading

0 comments on commit 410da26

Please sign in to comment.