diff --git a/models/baseModels/Invoice/Invoice.ts b/models/baseModels/Invoice/Invoice.ts index 53a95486..368c5f17 100644 --- a/models/baseModels/Invoice/Invoice.ts +++ b/models/baseModels/Invoice/Invoice.ts @@ -45,6 +45,7 @@ export abstract class Invoice extends Transactional { discountPercent?: number; discountAfterTax?: boolean; stockNotTransferred?: number; + backReference?: string; submitted?: boolean; cancelled?: boolean; @@ -479,6 +480,7 @@ export abstract class Invoice extends Transactional { terms: () => !(this.terms || !(this.isSubmitted || this.isCancelled)), attachment: () => !(this.attachment || !(this.isSubmitted || this.isCancelled)), + backReference: () => !this.backReference, }; static defaults: DefaultMap = { @@ -585,16 +587,20 @@ export abstract class Invoice extends Transactional { const defaults = (this.fyo.singles.Defaults as Defaults) ?? {}; let terms; + let numberSeries; if (this.isSales) { terms = defaults.shipmentTerms ?? ''; + numberSeries = defaults.shipmentNumberSeries ?? undefined; } else { terms = defaults.purchaseReceiptTerms ?? ''; + numberSeries = defaults.purchaseReceiptNumberSeries ?? undefined; } const data = { party: this.party, date: new Date().toISOString(), terms, + numberSeries, backReference: this.name, }; @@ -613,6 +619,8 @@ export abstract class Invoice extends Transactional { const quantity = row.stockNotTransferred; const trackItem = itemDoc.trackItem; const batch = row.batch || null; + const description = row.description; + const hsnCode = row.hsnCode; let rate = row.rate as Money; if (this.exchangeRate && this.exchangeRate > 1) { @@ -629,6 +637,8 @@ export abstract class Invoice extends Transactional { location, rate, batch, + description, + hsnCode, }); } diff --git a/models/baseModels/InvoiceItem/InvoiceItem.ts b/models/baseModels/InvoiceItem/InvoiceItem.ts index 670bbc13..1b48534d 100644 --- a/models/baseModels/InvoiceItem/InvoiceItem.ts +++ b/models/baseModels/InvoiceItem/InvoiceItem.ts @@ -16,6 +16,7 @@ import { FieldTypeEnum, Schema } from 'schemas/types'; import { safeParseFloat } from 'utils/index'; import { Invoice } from '../Invoice/Invoice'; import { Item } from '../Item/Item'; +import { StockTransfer } from 'models/inventory/StockTransfer'; export abstract class InvoiceItem extends Doc { item?: string; @@ -24,6 +25,9 @@ export abstract class InvoiceItem extends Doc { parentdoc?: Invoice; rate?: Money; + description?: string; + hsnCode?: number; + unit?: string; transferUnit?: string; quantity?: number; @@ -347,7 +351,26 @@ export abstract class InvoiceItem extends Doc { return 0; } - return this.quantity; + const { backReference, stockTransferSchemaName } = this.parentdoc ?? {}; + if ( + !backReference || + !stockTransferSchemaName || + typeof this.quantity !== 'number' + ) { + return this.quantity; + } + + const refdoc = (await this.fyo.doc.getDoc( + stockTransferSchemaName, + backReference + )) as StockTransfer; + + const transferred = + refdoc.items + ?.filter((i) => i.item === this.item) + .reduce((acc, i) => i.quantity ?? 0 + acc, 0) ?? 0; + + return Math.max(0, this.quantity - transferred); }, dependsOn: ['item', 'quantity'], }, diff --git a/models/helpers.ts b/models/helpers.ts index 8e5ab995..3f75c3c1 100644 --- a/models/helpers.ts +++ b/models/helpers.ts @@ -29,6 +29,17 @@ export function getInvoiceActions( ]; } +export function getStockTransferActions( + fyo: Fyo, + schemaName: ModelNameEnum.Shipment | ModelNameEnum.PurchaseReceipt +): Action[] { + return [ + getMakeInvoiceAction(fyo, schemaName), + getLedgerLinkAction(fyo, false), + getLedgerLinkAction(fyo, true), + ]; +} + export function getMakeStockTransferAction( fyo: Fyo, schemaName: ModelNameEnum.SalesInvoice | ModelNameEnum.PurchaseInvoice @@ -55,6 +66,32 @@ export function getMakeStockTransferAction( }; } +export function getMakeInvoiceAction( + fyo: Fyo, + schemaName: ModelNameEnum.Shipment | ModelNameEnum.PurchaseReceipt +): Action { + let label = fyo.t`Sales Invoice`; + if (schemaName === ModelNameEnum.PurchaseReceipt) { + label = fyo.t`Purchase Invoice`; + } + + return { + label, + group: fyo.t`Create`, + condition: (doc: Doc) => doc.isSubmitted && !doc.backReference, + action: async (doc: Doc) => { + const invoice = await (doc as StockTransfer).getInvoice(); + if (!invoice) { + return; + } + + const { routeTo } = await import('src/utils/ui'); + const path = `/edit/${invoice.schemaName}/${invoice.name}`; + await routeTo(path); + }, + }; +} + export function getMakePaymentAction(fyo: Fyo): Action { return { label: fyo.t`Payment`, diff --git a/models/inventory/PurchaseReceipt.ts b/models/inventory/PurchaseReceipt.ts index 6da16e02..f0136346 100644 --- a/models/inventory/PurchaseReceipt.ts +++ b/models/inventory/PurchaseReceipt.ts @@ -1,7 +1,12 @@ -import { ListViewSettings } from 'fyo/model/types'; -import { getTransactionStatusColumn } from 'models/helpers'; +import { Action, ListViewSettings } from 'fyo/model/types'; +import { + getStockTransferActions, + getTransactionStatusColumn, +} from 'models/helpers'; import { PurchaseReceiptItem } from './PurchaseReceiptItem'; import { StockTransfer } from './StockTransfer'; +import { Fyo } from 'fyo'; +import { ModelNameEnum } from 'models/types'; export class PurchaseReceipt extends StockTransfer { items?: PurchaseReceiptItem[]; @@ -17,4 +22,8 @@ export class PurchaseReceipt extends StockTransfer { ], }; } + + static getActions(fyo: Fyo): Action[] { + return getStockTransferActions(fyo, ModelNameEnum.Shipment); + } } diff --git a/models/inventory/Shipment.ts b/models/inventory/Shipment.ts index fa559d4e..1fb89cd4 100644 --- a/models/inventory/Shipment.ts +++ b/models/inventory/Shipment.ts @@ -1,5 +1,10 @@ -import { ListViewSettings } from 'fyo/model/types'; -import { getTransactionStatusColumn } from 'models/helpers'; +import { Fyo } from 'fyo'; +import { Action, ListViewSettings } from 'fyo/model/types'; +import { + getStockTransferActions, + getTransactionStatusColumn, +} from 'models/helpers'; +import { ModelNameEnum } from 'models/types'; import { ShipmentItem } from './ShipmentItem'; import { StockTransfer } from './StockTransfer'; @@ -17,4 +22,8 @@ export class Shipment extends StockTransfer { ], }; } + + static getActions(fyo: Fyo): Action[] { + return getStockTransferActions(fyo, ModelNameEnum.Shipment); + } } diff --git a/models/inventory/StockTransfer.ts b/models/inventory/StockTransfer.ts index 700bcd87..ec55f334 100644 --- a/models/inventory/StockTransfer.ts +++ b/models/inventory/StockTransfer.ts @@ -1,8 +1,7 @@ -import { Fyo, t } from 'fyo'; +import { t } from 'fyo'; import { Attachment } from 'fyo/core/types'; import { Doc } from 'fyo/model/doc'; import { - Action, ChangeArg, DefaultMap, FiltersMap, @@ -13,7 +12,7 @@ import { ValidationError } from 'fyo/utils/errors'; import { LedgerPosting } from 'models/Transactional/LedgerPosting'; import { Defaults } from 'models/baseModels/Defaults/Defaults'; import { Invoice } from 'models/baseModels/Invoice/Invoice'; -import { addItem, getLedgerLinkAction, getNumberSeries } from 'models/helpers'; +import { addItem, getNumberSeries } from 'models/helpers'; import { ModelNameEnum } from 'models/types'; import { Money } from 'pesa'; import { TargetField } from 'schemas/types'; @@ -27,6 +26,7 @@ import { validateBatch, validateSerialNumber, } from './helpers'; +import { Item } from 'models/baseModels/Item/Item'; export abstract class StockTransfer extends Transfer { name?: string; @@ -42,6 +42,13 @@ export abstract class StockTransfer extends Transfer { return this.schemaName === ModelNameEnum.Shipment; } + get invoiceSchemaName() { + if (this.isSales) { + return ModelNameEnum.SalesInvoice; + } + return ModelNameEnum.PurchaseInvoice; + } + formulas: FormulaMap = { grandTotal: { formula: () => this.getSum('items', 'amount', false), @@ -174,10 +181,6 @@ export abstract class StockTransfer extends Transfer { await validateSerialNumberStatus(this); } - static getActions(fyo: Fyo): Action[] { - return [getLedgerLinkAction(fyo, false), getLedgerLinkAction(fyo, true)]; - } - async afterSubmit() { await super.afterSubmit(); await updateSerialNumbers(this, false); @@ -309,6 +312,68 @@ export abstract class StockTransfer extends Transfer { await this.set('date', stDoc.date); await this.set('items', stDoc.items); } + + async getInvoice(): Promise { + if (!this.isSubmitted || this.backReference) { + return null; + } + + const schemaName = this.invoiceSchemaName; + + const defaults = (this.fyo.singles.Defaults as Defaults) ?? {}; + let terms; + let numberSeries; + if (this.isSales) { + terms = defaults.salesInvoiceTerms ?? ''; + numberSeries = defaults.salesInvoiceNumberSeries ?? undefined; + } else { + terms = defaults.purchaseInvoiceTerms ?? ''; + numberSeries = defaults.purchaseInvoiceNumberSeries ?? undefined; + } + + const data = { + party: this.party, + date: new Date().toISOString(), + terms, + numberSeries, + backReference: this.name, + }; + + const invoice = this.fyo.doc.getNewDoc(schemaName, data) as Invoice; + for (const row of this.items ?? []) { + if (!row.item) { + continue; + } + + const item = row.item; + const unit = row.unit; + const quantity = row.quantity; + const batch = row.batch || null; + const rate = row.rate ?? this.fyo.pesa(0); + const description = row.description; + const hsnCode = row.hsnCode; + + if (!quantity) { + continue; + } + + await invoice.append('items', { + item, + quantity, + unit, + rate, + batch, + hsnCode, + description, + }); + } + + if (!invoice.items?.length) { + return null; + } + + return invoice; + } } async function validateSerialNumberStatus(doc: StockTransfer) { diff --git a/schemas/app/Invoice.json b/schemas/app/Invoice.json index 9295cd4b..29c10053 100644 --- a/schemas/app/Invoice.json +++ b/schemas/app/Invoice.json @@ -174,6 +174,11 @@ "label": "Attachment", "fieldtype": "Attachment", "section": "References" + }, + { + "abstract": true, + "fieldname": "backReference", + "section": "References" } ], "keywordFields": ["name", "party"] diff --git a/schemas/app/PurchaseInvoice.json b/schemas/app/PurchaseInvoice.json index e6ab7d53..fca1587b 100644 --- a/schemas/app/PurchaseInvoice.json +++ b/schemas/app/PurchaseInvoice.json @@ -15,6 +15,13 @@ "default": "PINV-", "section": "Default" }, + { + "fieldname": "backReference", + "label": "Back Reference", + "fieldtype": "Link", + "target": "PurchaseReceipt", + "section": "References" + }, { "fieldname": "items", "label": "Items", diff --git a/schemas/app/SalesInvoice.json b/schemas/app/SalesInvoice.json index c3e63336..34e3d66e 100644 --- a/schemas/app/SalesInvoice.json +++ b/schemas/app/SalesInvoice.json @@ -15,6 +15,13 @@ "default": "SINV-", "section": "Default" }, + { + "fieldname": "backReference", + "label": "Back Reference", + "fieldtype": "Link", + "target": "Shipment", + "section": "References" + }, { "fieldname": "items", "label": "Items",