Skip to content

Commit

Permalink
Merge branch 'master' into nepali-translate
Browse files Browse the repository at this point in the history
  • Loading branch information
SumanTwati authored Dec 2, 2023
2 parents 85dfe74 + 814845f commit 62a9e89
Show file tree
Hide file tree
Showing 21 changed files with 1,672 additions and 38 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
> [!IMPORTANT]
>
> Frappe in search of a maintainer for Frappe Books. For more details check
> this issue: [#775](https://github.com/frappe/books/issues/775)
---

<div align="center" markdown="1">

<img src="https://user-images.githubusercontent.com/29507195/207267672-d422db6c-d89a-4bbe-9822-468a55c15053.png" alt="Frappe Books logo" width="384"/>
Expand Down
43 changes: 31 additions & 12 deletions backend/database/bespoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ export class BespokeQueries {

static async getReturnBalanceItemsQty(
db: DatabaseCore,
schemaName: string,
schemaName: ModelNameEnum,
docName: string
): Promise<Record<string, ReturnDocItem> | undefined> {
const returnDocNames = (
Expand All @@ -200,21 +200,41 @@ export class BespokeQueries {
return;
}

const returnedItems: DocItem[] = await db.knex!(`${schemaName}Item`)
.select('item', 'batch', 'serialNumber')
const returnedItemsQuery = db.knex!(`${schemaName}Item`)
.sum({ quantity: 'quantity' })
.whereIn('parent', returnDocNames)
.groupBy('item', 'batch', 'serialNumber');
.whereIn('parent', returnDocNames);

const docItemsQuery = db.knex!(`${schemaName}Item`)
.where('parent', docName)
.sum({ quantity: 'quantity' });

if (
[ModelNameEnum.SalesInvoice, ModelNameEnum.PurchaseInvoice].includes(
schemaName
)
) {
returnedItemsQuery.select('item', 'batch').groupBy('item', 'batch');
docItemsQuery.select('name', 'item', 'batch').groupBy('item', 'batch');
}

if (
[ModelNameEnum.Shipment, ModelNameEnum.PurchaseReceipt].includes(
schemaName
)
) {
returnedItemsQuery
.select('item', 'batch', 'serialNumber')
.groupBy('item', 'batch', 'serialNumber');
docItemsQuery
.select('name', 'item', 'batch', 'serialNumber')
.groupBy('item', 'batch', 'serialNumber');
}

const returnedItems = (await returnedItemsQuery) as DocItem[];
if (!returnedItems.length) {
return;
}

const docItems: DocItem[] = await db.knex!(`${schemaName}Item`)
.select('name', 'item', 'batch', 'serialNumber')
.where('parent', docName)
.groupBy('item', 'batch', 'serialNumber')
.sum({ quantity: 'quantity' });
const docItems = (await docItemsQuery) as DocItem[];

const docItemsMap = BespokeQueries.#getDocItemMap(docItems);
const returnedItemsMap = BespokeQueries.#getDocItemMap(returnedItems);
Expand All @@ -223,7 +243,6 @@ export class BespokeQueries {
docItemsMap,
returnedItemsMap
);

return returnBalanceItems;
}

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 @@ -16,6 +16,7 @@ export class AccountingSettings extends Doc {
enableInventory?: boolean;
enablePriceList?: boolean;
enableFormCustomization?: boolean;
enableInvoiceReturns?: boolean;

static filters: FiltersMap = {
writeOffAccount: () => ({
Expand Down Expand Up @@ -47,6 +48,9 @@ export class AccountingSettings extends Doc {
enableInventory: () => {
return !!this.enableInventory;
},
enableInvoiceReturns: () => {
return !!this.enableInvoiceReturns;
},
};

override hidden: HiddenMap = {
Expand Down
166 changes: 163 additions & 3 deletions models/baseModels/Invoice/Invoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> = {};
Expand Down Expand Up @@ -52,6 +54,9 @@ export abstract class Invoice extends Transactional {
makeAutoPayment?: boolean;
makeAutoStockTransfer?: boolean;

isReturned?: boolean;
returnAgainst?: string;

get isSales() {
return this.schemaName === 'SalesInvoice';
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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: [
{
Expand Down Expand Up @@ -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> {
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
Loading

0 comments on commit 62a9e89

Please sign in to comment.