diff --git a/fyo/model/doc.ts b/fyo/model/doc.ts index 31fa0d07..e4d8cccf 100644 --- a/fyo/model/doc.ts +++ b/fyo/model/doc.ts @@ -857,6 +857,7 @@ export class Doc extends Observable { return; } + await this.trigger('beforeCancel'); await this.trigger('beforeCancel'); await this.setAndSync('cancelled', true); await this.trigger('afterCancel'); @@ -908,6 +909,7 @@ export class Doc extends Observable { if (convertToFloat) { return sum.float; } + return sum; } diff --git a/models/Transactional/Transactional.ts b/models/Transactional/Transactional.ts index cef4116e..76337a5d 100644 --- a/models/Transactional/Transactional.ts +++ b/models/Transactional/Transactional.ts @@ -26,28 +26,56 @@ export abstract class Transactional extends Doc { return true; } - abstract getPosting(): Promise; + abstract getPosting(): Promise; async validate() { await super.validate(); + if (!this.isTransactional) { + return; + } + const posting = await this.getPosting(); + if (posting === null) { + return; + } + posting.validate(); } async afterSubmit(): Promise { await super.afterSubmit(); + if (!this.isTransactional) { + return; + } + const posting = await this.getPosting(); + if (posting === null) { + return; + } + await posting.post(); } async afterCancel(): Promise { await super.afterCancel(); + if (!this.isTransactional) { + return; + } + const posting = await this.getPosting(); + if (posting === null) { + return; + } + await posting.postReverse(); } async afterDelete(): Promise { await super.afterDelete(); + if (!this.isTransactional) { + return; + } + const ledgerEntryIds = (await this.fyo.db.getAll( ModelNameEnum.AccountingLedgerEntry, { diff --git a/models/inventory/InventorySettings.ts b/models/inventory/InventorySettings.ts index a9d08429..1bcd6c0a 100644 --- a/models/inventory/InventorySettings.ts +++ b/models/inventory/InventorySettings.ts @@ -1,12 +1,13 @@ import { Doc } from 'fyo/model/doc'; import { FiltersMap } from 'fyo/model/types'; import { AccountTypeEnum } from 'models/baseModels/Account/types'; -import { valuationMethod } from './types'; +import { ValuationMethod } from './types'; export class InventorySettings extends Doc { stockInHand?: string; - valuationMethod?: valuationMethod; - stockInReceivedButNotBilled?: string; + valuationMethod?: ValuationMethod; + stockReceivedButNotBilled?: string; + costOfGoodsSold?: string; static filters: FiltersMap = { stockInHand: () => ({ @@ -17,5 +18,9 @@ export class InventorySettings extends Doc { isGroup: false, accountType: AccountTypeEnum['Stock Received But Not Billed'], }), + costOfGoodsSold: () => ({ + isGroup: false, + accountType: AccountTypeEnum['Cost of Goods Sold'], + }), }; } diff --git a/models/inventory/StockManager.ts b/models/inventory/StockManager.ts index 6a24fb70..20d86813 100644 --- a/models/inventory/StockManager.ts +++ b/models/inventory/StockManager.ts @@ -1,5 +1,6 @@ import { Fyo, t } from 'fyo'; import { ValidationError } from 'fyo/utils/errors'; +import { DateTime } from 'luxon'; import { ModelNameEnum } from 'models/types'; import { Money } from 'pesa'; import { StockLedgerEntry } from './StockLedgerEntry'; @@ -53,6 +54,19 @@ export class StockManager { }); } + async validateCancel(transferDetails: SMTransferDetails[]) { + const reverseTransferDetails = transferDetails.map( + ({ item, rate, quantity, fromLocation, toLocation }) => ({ + item, + rate, + quantity, + fromLocation: toLocation, + toLocation: fromLocation, + }) + ); + await this.validateTransfers(reverseTransferDetails); + } + async #sync() { for (const item of this.items) { await item.sync(); @@ -117,15 +131,21 @@ export class StockManager { return; } - const quantityBefore = + const date = details.date.toISOString(); + let quantityBefore = (await this.fyo.db.getStockQuantity( details.item, details.fromLocation, undefined, - details.date.toISOString() + date )) ?? 0; + const formattedDate = this.fyo.format(details.date, 'Datetime'); + if (this.isCancelled) { + quantityBefore += details.quantity; + } + if (quantityBefore < details.quantity) { throw new ValidationError( [ diff --git a/models/inventory/StockMovement.ts b/models/inventory/StockMovement.ts index ac31ce25..44eec5e0 100644 --- a/models/inventory/StockMovement.ts +++ b/models/inventory/StockMovement.ts @@ -1,18 +1,18 @@ -import { Doc } from 'fyo/model/doc'; import { DefaultMap, FiltersMap, FormulaMap, - ListViewSettings + ListViewSettings, } from 'fyo/model/types'; import { getDocStatusListColumn } from 'models/helpers'; +import { LedgerPosting } from 'models/Transactional/LedgerPosting'; import { ModelNameEnum } from 'models/types'; import { Money } from 'pesa'; -import { StockManager } from './StockManager'; import { StockMovementItem } from './StockMovementItem'; +import { Transfer } from './Transfer'; import { MovementType } from './types'; -export class StockMovement extends Doc { +export class StockMovement extends Transfer { name?: string; date?: Date; numberSeries?: string; @@ -20,6 +20,14 @@ export class StockMovement extends Doc { items?: StockMovementItem[]; amount?: Money; + override get isTransactional(): boolean { + return false; + } + + override async getPosting(): Promise { + return null; + } + formulas: FormulaMap = { amount: { formula: () => { @@ -46,23 +54,6 @@ export class StockMovement extends Doc { }; } - async beforeSubmit(): Promise { - await super.beforeSubmit(); - const transferDetails = this._getTransferDetails(); - await this._getStockManager().validateTransfers(transferDetails); - } - - async afterSubmit(): Promise { - await super.afterSubmit(); - const transferDetails = this._getTransferDetails(); - await this._getStockManager().createTransfers(transferDetails); - } - - async afterCancel(): Promise { - await super.afterCancel(); - await this._getStockManager().cancelTransfers(); - } - _getTransferDetails() { return (this.items ?? []).map((row) => ({ item: row.item!, @@ -72,16 +63,4 @@ export class StockMovement extends Doc { toLocation: row.toLocation, })); } - - _getStockManager(): StockManager { - return new StockManager( - { - date: this.date!, - referenceName: this.name!, - referenceType: this.schemaName, - }, - this.isCancelled, - this.fyo - ); - } } diff --git a/models/inventory/StockTransfer.ts b/models/inventory/StockTransfer.ts index 1d1088c6..800882f8 100644 --- a/models/inventory/StockTransfer.ts +++ b/models/inventory/StockTransfer.ts @@ -1,10 +1,136 @@ +import { t } from 'fyo'; import { Attachment } from 'fyo/core/types'; import { Doc } from 'fyo/model/doc'; +import { DefaultMap, FiltersMap, FormulaMap } from 'fyo/model/types'; +import { NotFoundError, ValidationError } from 'fyo/utils/errors'; +import { Defaults } from 'models/baseModels/Defaults/Defaults'; +import { getNumberSeries } from 'models/helpers'; +import { LedgerPosting } from 'models/Transactional/LedgerPosting'; +import { ModelNameEnum } from 'models/types'; +import { Money } from 'pesa'; +import { StockTransferItem } from './StockTransferItem'; +import { Transfer } from './Transfer'; -export abstract class StockTransfer extends Doc { +export abstract class StockTransfer extends Transfer { name?: string; - date?: string; + date?: Date; party?: string; terms?: string; attachment?: Attachment; + grandTotal?: Money; + items?: StockTransferItem[]; + + get isSales() { + return this.schemaName === ModelNameEnum.Shipment; + } + + formulas: FormulaMap = { + grandTotal: { + formula: () => this.getSum('items', 'amount', false), + dependsOn: ['items'], + }, + }; + + static defaults: DefaultMap = { + numberSeries: (doc) => getNumberSeries(doc.schemaName, doc.fyo), + terms: (doc) => { + const defaults = doc.fyo.singles.Defaults as Defaults | undefined; + if (doc.schemaName === ModelNameEnum.Shipment) { + return defaults?.shipmentTerms ?? ''; + } + + return defaults?.purchaseReceiptTerms ?? ''; + }, + date: () => new Date().toISOString().slice(0, 10), + }; + + static filters: FiltersMap = { + party: (doc: Doc) => ({ + role: ['in', [doc.isSales ? 'Customer' : 'Supplier', 'Both']], + }), + numberSeries: (doc: Doc) => ({ referenceType: doc.schemaName }), + }; + + override _getTransferDetails() { + return (this.items ?? []).map((row) => { + let fromLocation = undefined; + let toLocation = undefined; + + if (this.isSales) { + fromLocation = row.location; + } else { + toLocation = row.location; + } + + return { + item: row.item!, + rate: row.rate!, + quantity: row.quantity!, + fromLocation, + toLocation, + }; + }); + } + + override async getPosting(): Promise { + await this.validateAccounts(); + const stockInHand = (await this.fyo.getValue( + ModelNameEnum.InventorySettings, + 'stockInHand' + )) as string; + + const amount = this.grandTotal ?? this.fyo.pesa(0); + const posting = new LedgerPosting(this, this.fyo); + + if (this.isSales) { + const costOfGoodsSold = (await this.fyo.getValue( + ModelNameEnum.InventorySettings, + 'costOfGoodsSold' + )) as string; + + await posting.debit(costOfGoodsSold, amount); + await posting.credit(stockInHand, amount); + } else { + const stockReceivedButNotBilled = (await this.fyo.getValue( + ModelNameEnum.InventorySettings, + 'stockReceivedButNotBilled' + )) as string; + + await posting.debit(stockInHand, amount); + await posting.credit(stockReceivedButNotBilled, amount); + } + + await posting.makeRoundOffEntry() + return posting; + } + + async validateAccounts() { + const settings: string[] = ['stockInHand']; + if (this.isSales) { + settings.push('costOfGoodsSold'); + } else { + settings.push('stockReceivedButNotBilled'); + } + + const messages: string[] = []; + for (const setting of settings) { + const value = this.fyo.singles.InventorySettings?.[setting] as + | string + | undefined; + const field = this.fyo.getField(ModelNameEnum.InventorySettings, setting); + if (!value) { + messages.push(t`${field.label} account not set in Inventory Settings.`); + continue; + } + + const exists = await this.fyo.db.exists(ModelNameEnum.Account, value); + if (!exists) { + messages.push(t`Account ${value} does not exist.`); + } + } + + if (messages.length) { + throw new ValidationError(messages.join(' ')); + } + } } diff --git a/models/inventory/StockTransferItem.ts b/models/inventory/StockTransferItem.ts index 44601a02..e09f6bb6 100644 --- a/models/inventory/StockTransferItem.ts +++ b/models/inventory/StockTransferItem.ts @@ -1,5 +1,8 @@ import { Doc } from 'fyo/model/doc'; +import { FiltersMap, FormulaMap } from 'fyo/model/types'; +import { ModelNameEnum } from 'models/types'; import { Money } from 'pesa'; +import { locationFilter } from './helpers'; export class StockTransferItem extends Doc { item?: string; @@ -10,4 +13,98 @@ export class StockTransferItem extends Doc { unit?: string; description?: string; hsnCode?: number; + + formulas: FormulaMap = { + description: { + formula: async () => + (await this.fyo.getValue( + 'Item', + this.item as string, + 'description' + )) as string, + dependsOn: ['item'], + }, + unit: { + formula: async () => + (await this.fyo.getValue( + 'Item', + this.item as string, + 'unit' + )) as string, + dependsOn: ['item'], + }, + hsnCode: { + formula: async () => + (await this.fyo.getValue( + 'Item', + this.item as string, + 'hsnCode' + )) as string, + dependsOn: ['item'], + }, + amount: { + formula: () => { + return this.rate?.mul(this.quantity ?? 0) ?? this.fyo.pesa(0); + }, + dependsOn: ['rate', 'quantity'], + }, + rate: { + formula: async (fieldname) => { + const rate = (await this.fyo.getValue( + 'Item', + this.item as string, + 'rate' + )) as undefined | Money; + + if (!rate?.float && this.rate?.float) { + return this.rate; + } + + return rate ?? this.fyo.pesa(0); + }, + dependsOn: ['item'], + }, + quantity: { + formula: async () => { + if (!this.item) { + return this.quantity as number; + } + + const itemDoc = await this.fyo.doc.getDoc( + ModelNameEnum.Item, + this.item as string + ); + + const unitDoc = itemDoc.getLink('unit'); + if (unitDoc?.isWhole) { + return Math.round(this.quantity as number); + } + + return this.quantity as number; + }, + dependsOn: ['quantity'], + }, + account: { + formula: () => { + let accountType = 'expenseAccount'; + if (this.isSales) { + accountType = 'incomeAccount'; + } + return this.fyo.getValue('Item', this.item as string, accountType); + }, + dependsOn: ['item'], + }, + }; + + static filters: FiltersMap = { + item: (doc: Doc) => { + let itemNotFor = 'Sales'; + if (doc.isSales) { + itemNotFor = 'Purchases'; + } + + return { for: ['not in', [itemNotFor]], trackItem: true }; + }, + location: locationFilter, + }; } diff --git a/models/inventory/Transfer.ts b/models/inventory/Transfer.ts new file mode 100644 index 00000000..375ab40a --- /dev/null +++ b/models/inventory/Transfer.ts @@ -0,0 +1,51 @@ +import { Transactional } from 'models/Transactional/Transactional'; +import { StockManager } from './StockManager'; +import { SMTransferDetails } from './types'; + +export abstract class Transfer extends Transactional { + date?: Date; + + async beforeSubmit(): Promise { + await super.beforeSubmit(); + const transferDetails = this._getTransferDetails(); + await this._getStockManager().validateTransfers(transferDetails); + } + + async afterSubmit(): Promise { + await super.afterSubmit(); + const transferDetails = this._getTransferDetails(); + await this._getStockManager().createTransfers(transferDetails); + } + + async beforeCancel(): Promise { + await super.beforeCancel(); + const transferDetails = this._getTransferDetails(); + const stockManager = this._getStockManager(); + stockManager.isCancelled = true; + await stockManager.validateCancel(transferDetails); + } + + async afterCancel(): Promise { + await super.afterCancel(); + await this._getStockManager().cancelTransfers(); + } + + _getStockManager(): StockManager { + let date = this.date!; + if (typeof date === 'string') { + date = new Date(date); + } + + return new StockManager( + { + date, + referenceName: this.name!, + referenceType: this.schemaName, + }, + this.isCancelled, + this.fyo + ); + } + + abstract _getTransferDetails(): SMTransferDetails[]; +} diff --git a/models/inventory/tests/helpers.ts b/models/inventory/tests/helpers.ts index e6011aa8..f37d8ffd 100644 --- a/models/inventory/tests/helpers.ts +++ b/models/inventory/tests/helpers.ts @@ -1,8 +1,18 @@ import { Fyo } from 'fyo'; import { ModelNameEnum } from 'models/types'; import { StockMovement } from '../StockMovement'; +import { StockTransfer } from '../StockTransfer'; import { MovementType } from '../types'; +type ALE = { + date: string; + account: string; + party: string; + debit: string; + credit: string; + reverted: number; +}; + type SLE = { date: string; name: string; @@ -20,10 +30,28 @@ type Transfer = { rate: number; }; +interface TransferTwo extends Omit { + location: string; +} + export function getItem(name: string, rate: number) { return { name, rate, trackItem: true }; } +export async function getStockTransfer( + schemaName: ModelNameEnum.PurchaseReceipt | ModelNameEnum.Shipment, + party: string, + date: Date, + transfers: TransferTwo[], + fyo: Fyo +): Promise { + const doc = fyo.doc.getNewDoc(schemaName, { party, date }) as StockTransfer; + for (const { item, location, quantity, rate } of transfers) { + await doc.append('items', { item, location, quantity, rate }); + } + return doc; +} + export async function getStockMovement( movementType: MovementType, date: Date, @@ -64,3 +92,14 @@ export async function getSLEs( fields: ['date', 'name', 'item', 'location', 'rate', 'quantity'], })) as SLE[]; } + +export async function getALEs( + referenceName: string, + referenceType: string, + fyo: Fyo +) { + return (await fyo.db.getAllRaw(ModelNameEnum.AccountingLedgerEntry, { + filters: { referenceName, referenceType }, + fields: ['date', 'account', 'party', 'debit', 'credit', 'reverted'], + })) as ALE[]; +} diff --git a/models/inventory/tests/testStockTransfer.spec.ts b/models/inventory/tests/testStockTransfer.spec.ts new file mode 100644 index 00000000..c62c6c91 --- /dev/null +++ b/models/inventory/tests/testStockTransfer.spec.ts @@ -0,0 +1,288 @@ +import { + assertDoesNotThrow, + assertThrows +} from 'backend/database/tests/helpers'; +import { ModelNameEnum } from 'models/types'; +import { RawValue } from 'schemas/types'; +import test from 'tape'; +import { closeTestFyo, getTestFyo, setupTestFyo } from 'tests/helpers'; +import { InventorySettings } from '../InventorySettings'; +import { ValuationMethod } from '../types'; +import { getALEs, getItem, getSLEs, getStockTransfer } from './helpers'; + +const fyo = getTestFyo(); +setupTestFyo(fyo, __filename); + +const item = 'Pen'; +const location = 'Common'; +const party = 'Someone'; +const testDocs = { + Item: { + [item]: getItem(item, 100), + }, + Location: { + [location]: { name: location }, + }, + Party: { [party]: { name: party, Role: 'Both' } }, +} as Record>; + +test('insert test docs', async (t) => { + for (const schemaName in testDocs) { + for (const name in testDocs[schemaName]) { + await fyo.doc.getNewDoc(schemaName, testDocs[schemaName][name]).sync(); + } + } + + t.ok(await fyo.db.exists(ModelNameEnum.Party, party), 'party created'); + t.ok( + await fyo.db.exists(ModelNameEnum.Location, location), + 'location created' + ); + t.ok(await fyo.db.exists(ModelNameEnum.Item, item), 'item created'); +}); + +test('inventory settings', async (t) => { + const doc = (await fyo.doc.getDoc( + ModelNameEnum.InventorySettings + )) as InventorySettings; + + t.equal(doc.valuationMethod, ValuationMethod.FIFO, 'fifo valuation set'); + t.ok(doc.stockInHand, 'stock in hand set'); + t.ok(doc.stockReceivedButNotBilled, 'stock rec. but not billed set'); +}); + +test('PurchaseReceipt, create inward stock movement', async (t) => { + const date = new Date('2022-01-01'); + const rate = (testDocs['Item'][item].rate as number) ?? 0; + const quantity = 10; + const doc = await getStockTransfer( + ModelNameEnum.PurchaseReceipt, + party, + date, + [ + { + item, + location, + quantity, + rate, + }, + ], + fyo + ); + + await doc.sync(); + const grandTotal = quantity * rate; + t.equal(doc.grandTotal?.float, quantity * rate); + + await doc.submit(); + + t.equal( + (await fyo.db.getAllRaw(ModelNameEnum.PurchaseReceipt)).length, + 1, + 'purchase receipt created' + ); + t.equal( + (await getSLEs(doc.name!, doc.schemaName, fyo)).length, + 1, + 'sle created' + ); + t.equal( + await fyo.db.getStockQuantity(item, location), + quantity, + 'stock purchased' + ); + t.ok(doc.name?.startsWith('PREC-')); + + const ales = await getALEs(doc.name!, doc.schemaName, fyo); + for (const ale of ales) { + t.equal(ale.party, party, 'party matches'); + if (ale.account === 'Stock Received But Not Billed') { + t.equal(parseFloat(ale.debit), 0); + t.equal(parseFloat(ale.credit), grandTotal); + } else { + t.equal(parseFloat(ale.credit), 0); + t.equal(parseFloat(ale.debit), grandTotal); + } + } +}); + +test('Shipment, create outward stock movement', async (t) => { + const date = new Date('2022-01-02'); + const rate = (testDocs['Item'][item].rate as number) ?? 0; + const quantity = 5; + const doc = await getStockTransfer( + ModelNameEnum.Shipment, + party, + date, + [ + { + item, + location, + quantity, + rate, + }, + ], + fyo + ); + + await doc.sync(); + const grandTotal = quantity * rate; + t.equal(doc.grandTotal?.float, grandTotal); + + await doc.submit(); + + t.equal( + (await fyo.db.getAllRaw(ModelNameEnum.Shipment)).length, + 1, + 'shipment created' + ); + t.equal( + (await getSLEs(doc.name!, doc.schemaName, fyo)).length, + 1, + 'sle created' + ); + t.equal( + await fyo.db.getStockQuantity(item, location), + 10 - quantity, + 'stock purchased' + ); + t.ok(doc.name?.startsWith('SHPM-')); + + const ales = await getALEs(doc.name!, doc.schemaName, fyo); + for (const ale of ales) { + t.equal(ale.party, party, 'party matches'); + if (ale.account === 'Cost of Goods Sold') { + t.equal(parseFloat(ale.debit), grandTotal); + t.equal(parseFloat(ale.credit), 0); + } else { + t.equal(parseFloat(ale.debit), 0); + t.equal(parseFloat(ale.credit), grandTotal); + } + } +}); + +test('Shipment, invalid', async (t) => { + const date = new Date('2022-01-03'); + const rate = (testDocs['Item'][item].rate as number) ?? 0; + const quantity = 10; + const doc = await getStockTransfer( + ModelNameEnum.Shipment, + party, + date, + [ + { + item, + location, + quantity, + rate, + }, + ], + fyo + ); + + await doc.sync(); + const grandTotal = quantity * rate; + + t.equal(await fyo.db.getStockQuantity(item, location), 5, 'stock unchanged'); + t.equal(doc.grandTotal?.float, grandTotal); + await assertThrows(async () => await doc.submit()); + + t.equal( + (await getSLEs(doc.name!, doc.schemaName, fyo)).length, + 0, + 'sles not created' + ); + t.equal( + (await getALEs(doc.name!, doc.schemaName, fyo)).length, + 0, + 'ales not created' + ); +}); + +test('Stock Transfer, invalid cancellation', async (t) => { + const { name } = + ( + (await fyo.db.getAllRaw(ModelNameEnum.PurchaseReceipt)) as { + name: string; + }[] + )[0] ?? {}; + + t.ok(name?.startsWith('PREC-')); + const doc = await fyo.doc.getDoc(ModelNameEnum.PurchaseReceipt, name); + await assertThrows(async () => await doc.cancel()); + t.equal(await fyo.db.getStockQuantity(item, location), 5, 'stock unchanged'); + t.equal( + (await getSLEs(name, doc.schemaName, fyo)).length, + 1, + 'sle unchanged' + ); + const ales = await getALEs(name, doc.schemaName, fyo); + t.ok(ales.every((i) => !i.reverted) && ales.length === 2, 'ale unchanged'); +}); + +test('Shipment, cancel and delete', async (t) => { + const { name } = + ( + (await fyo.db.getAllRaw(ModelNameEnum.Shipment, { order: 'asc' })) as { + name: string; + }[] + )[0] ?? {}; + + t.ok(name?.startsWith('SHPM-'), 'number series matches'); + const doc = await fyo.doc.getDoc(ModelNameEnum.Shipment, name); + t.ok(doc.isSubmitted, `doc ${name} is submitted`); + await assertDoesNotThrow(async () => await doc.cancel()); + t.ok(doc.isCancelled), `doc is cancelled`; + + t.equal(await fyo.db.getStockQuantity(item, location), 10, 'stock changed'); + t.equal((await getSLEs(name, doc.schemaName, fyo)).length, 0, 'sle deleted'); + const ales = await getALEs(name, doc.schemaName, fyo); + t.ok(ales.every((i) => !!i.reverted) && ales.length === 4, 'ale reverted'); + + await doc.delete(); + t.equal((await getALEs(name, doc.schemaName, fyo)).length, 0, 'ales deleted'); + t.equal( + ( + await fyo.db.getAllRaw(ModelNameEnum.Shipment, { + filters: { name: name }, + }) + ).length, + 0, + 'doc deleted' + ); +}); + +test('Purchase Receipt, cancel and delete', async (t) => { + const { name } = + ( + (await fyo.db.getAllRaw(ModelNameEnum.PurchaseReceipt, { + order: 'asc', + })) as { + name: string; + }[] + )[0] ?? {}; + + t.ok(name?.startsWith('PREC-'), 'number series matches'); + const doc = await fyo.doc.getDoc(ModelNameEnum.PurchaseReceipt, name); + t.ok(doc.isSubmitted, `doc ${name} is submitted`); + await assertDoesNotThrow(async () => await doc.cancel()); + t.ok(doc.isCancelled), `doc is cancelled`; + + t.equal(await fyo.db.getStockQuantity(item, location), null, 'stock changed'); + t.equal((await getSLEs(name, doc.schemaName, fyo)).length, 0, 'sle deleted'); + const ales = await getALEs(name, doc.schemaName, fyo); + t.ok(ales.every((i) => !!i.reverted) && ales.length === 4, 'ale reverted'); + + await doc.delete(); + t.equal((await getALEs(name, doc.schemaName, fyo)).length, 0, 'ales deleted'); + t.equal( + ( + await fyo.db.getAllRaw(ModelNameEnum.Shipment, { + filters: { name: name }, + }) + ).length, + 0, + 'doc deleted' + ); +}); +closeTestFyo(fyo, __filename); diff --git a/models/inventory/types.ts b/models/inventory/types.ts index 16ee2de3..6a846b1a 100644 --- a/models/inventory/types.ts +++ b/models/inventory/types.ts @@ -1,5 +1,10 @@ import { Money } from 'pesa'; +export enum ValuationMethod { + 'FIFO' = 'FIFO', + 'MovingAverage' = 'MovingAverage', +} + export enum MovementType { 'MaterialIssue' = 'MaterialIssue', 'MaterialReceipt' = 'MaterialReceipt', diff --git a/reports/inventory/StockLedger.ts b/reports/inventory/StockLedger.ts index efcc72cc..0f170bf7 100644 --- a/reports/inventory/StockLedger.ts +++ b/reports/inventory/StockLedger.ts @@ -94,12 +94,10 @@ export class StockLedger extends Report { } if (toDate && row.date > toDate) { - console.log('here'); continue; } if (fromDate && row.date < fromDate) { - console.log('here'); continue; } diff --git a/schemas/app/inventory/InventorySettings.json b/schemas/app/inventory/InventorySettings.json index e43eb909..3230a77e 100644 --- a/schemas/app/inventory/InventorySettings.json +++ b/schemas/app/inventory/InventorySettings.json @@ -25,15 +25,19 @@ "fieldname": "stockInHand", "label": "Stock In Hand Acc.", "fieldtype": "Link", - "target": "Account", - "create": true + "target": "Account" }, { "fieldname": "stockReceivedButNotBilled", "label": "Stock Received But Not Billed Acc.", "fieldtype": "Link", - "target": "Account", - "create": true + "target": "Account" + }, + { + "fieldname": "costOfGoodsSold", + "label": "Cost Of Goods Sold", + "fieldtype": "Link", + "target": "Account" } ] } diff --git a/src/components/Controls/FormControl.vue b/src/components/Controls/FormControl.vue index 555c9858..02230c01 100644 --- a/src/components/Controls/FormControl.vue +++ b/src/components/Controls/FormControl.vue @@ -38,9 +38,6 @@ const components = { export default { name: 'FormControl', render() { - if (!this.$attrs.df) { - console.log(this); - } const fieldtype = this.$attrs.df.fieldtype; const component = components[fieldtype] ?? Data; diff --git a/src/pages/Settings/TabBase.vue b/src/pages/Settings/TabBase.vue index 43b9ad0d..ae402618 100644 --- a/src/pages/Settings/TabBase.vue +++ b/src/pages/Settings/TabBase.vue @@ -52,10 +52,6 @@ export default defineComponent({ }, computed: { fields() { - console.log( - 'changed', - this.doc?.schema.fields.map(({ fieldname }) => fieldname).join(',') - ); return this.doc?.schema.fields; }, }, diff --git a/src/setup/setupInstance.ts b/src/setup/setupInstance.ts index 5ed80e11..c626aed5 100644 --- a/src/setup/setupInstance.ts +++ b/src/setup/setupInstance.ts @@ -15,7 +15,7 @@ import { import { AccountingSettings } from 'models/baseModels/AccountingSettings/AccountingSettings'; import { numberSeriesDefaultsMap } from 'models/baseModels/Defaults/Defaults'; import { InventorySettings } from 'models/inventory/InventorySettings'; -import { valuationMethod } from 'models/inventory/types'; +import { ValuationMethod } from 'models/inventory/types'; import { ModelNameEnum } from 'models/types'; import { createRegionalRecords } from 'src/regional'; import { @@ -346,32 +346,26 @@ async function updateInventorySettings(fyo: Fyo) { )) as InventorySettings; if (!inventorySettings.valuationMethod) { - await inventorySettings.set('valuationMethod', valuationMethod.FIFO); + await inventorySettings.set('valuationMethod', ValuationMethod.FIFO); } + const accountTypeDefaultMap = { + [AccountTypeEnum.Stock]: 'stockInHand', + [AccountTypeEnum['Stock Received But Not Billed']]: + 'stockReceivedButNotBilled', + [AccountTypeEnum['Cost of Goods Sold']]: 'costOfGoodsSold', + } as Record; - const stockAccounts = (await fyo.db.getAllRaw('Account', { - filters: { accountType: AccountTypeEnum.Stock, isGroup: false }, - })) as { name: string }[]; + for (const accountType in accountTypeDefaultMap) { + const accounts = (await fyo.db.getAllRaw('Account', { + filters: { accountType, isGroup: false }, + })) as { name: string }[]; - if (stockAccounts.length && !inventorySettings.stockInHand) { - await inventorySettings.set('stockInHand', stockAccounts[0].name); - } + if (!accounts.length) { + continue; + } - const stockReceivedButNotBilled = (await fyo.db.getAllRaw('Account', { - filters: { - accountType: AccountTypeEnum['Stock Received But Not Billed'], - isGroup: false, - }, - })) as { name: string }[]; - - if ( - stockReceivedButNotBilled.length && - !inventorySettings.stockInReceivedButNotBilled - ) { - await inventorySettings.set( - 'stockInReceivedButNotBilled', - stockAccounts[0].name - ); + const settingName = accountTypeDefaultMap[accountType]!; + inventorySettings.set(settingName, accounts[0].name); } await inventorySettings.sync();