mirror of
https://github.com/frappe/books.git
synced 2024-11-08 23:00:56 +00:00
Merge pull request #562 from frappe/fix-batchwise-inventory
fix: issues with Batch-wise Inventory
This commit is contained in:
commit
ee495bb174
@ -138,7 +138,7 @@ export class BespokeQueries {
|
|||||||
location?: string,
|
location?: string,
|
||||||
fromDate?: string,
|
fromDate?: string,
|
||||||
toDate?: string,
|
toDate?: string,
|
||||||
batchNumber?: string
|
batch?: string
|
||||||
): Promise<number | null> {
|
): Promise<number | null> {
|
||||||
const query = db.knex!(ModelNameEnum.StockLedgerEntry)
|
const query = db.knex!(ModelNameEnum.StockLedgerEntry)
|
||||||
.sum('quantity')
|
.sum('quantity')
|
||||||
@ -148,8 +148,8 @@ export class BespokeQueries {
|
|||||||
query.andWhere('location', location);
|
query.andWhere('location', location);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (batchNumber) {
|
if (batch) {
|
||||||
query.andWhere('batchNumber', batchNumber);
|
query.andWhere('batch', batch);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fromDate) {
|
if (fromDate) {
|
||||||
|
@ -313,7 +313,7 @@ export class DatabaseHandler extends DatabaseBase {
|
|||||||
location?: string,
|
location?: string,
|
||||||
fromDate?: string,
|
fromDate?: string,
|
||||||
toDate?: string,
|
toDate?: string,
|
||||||
batchNumber?: string
|
batch?: string
|
||||||
): Promise<number | null> {
|
): Promise<number | null> {
|
||||||
return (await this.#demux.callBespoke(
|
return (await this.#demux.callBespoke(
|
||||||
'getStockQuantity',
|
'getStockQuantity',
|
||||||
@ -321,7 +321,7 @@ export class DatabaseHandler extends DatabaseBase {
|
|||||||
location,
|
location,
|
||||||
fromDate,
|
fromDate,
|
||||||
toDate,
|
toDate,
|
||||||
batchNumber
|
batch
|
||||||
)) as number | null;
|
)) as number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
import { DEFAULT_CURRENCY } from 'fyo/utils/consts';
|
import { DEFAULT_CURRENCY } from 'fyo/utils/consts';
|
||||||
import { ValidationError } from 'fyo/utils/errors';
|
import { ValidationError } from 'fyo/utils/errors';
|
||||||
import { addItem, getExchangeRate, getNumberSeries } from 'models/helpers';
|
import { addItem, getExchangeRate, getNumberSeries } from 'models/helpers';
|
||||||
|
import { validateBatch } from 'models/inventory/helpers';
|
||||||
import { InventorySettings } from 'models/inventory/InventorySettings';
|
import { InventorySettings } from 'models/inventory/InventorySettings';
|
||||||
import { StockTransfer } from 'models/inventory/StockTransfer';
|
import { StockTransfer } from 'models/inventory/StockTransfer';
|
||||||
import { Transactional } from 'models/Transactional/Transactional';
|
import { Transactional } from 'models/Transactional/Transactional';
|
||||||
@ -545,6 +546,7 @@ export abstract class Invoice extends Transactional {
|
|||||||
const item = row.item;
|
const item = row.item;
|
||||||
const quantity = row.stockNotTransferred;
|
const quantity = row.stockNotTransferred;
|
||||||
const trackItem = itemDoc.trackItem;
|
const trackItem = itemDoc.trackItem;
|
||||||
|
const batch = row.batch || null;
|
||||||
let rate = row.rate as Money;
|
let rate = row.rate as Money;
|
||||||
|
|
||||||
if (this.exchangeRate && this.exchangeRate > 1) {
|
if (this.exchangeRate && this.exchangeRate > 1) {
|
||||||
@ -560,6 +562,7 @@ export abstract class Invoice extends Transactional {
|
|||||||
quantity,
|
quantity,
|
||||||
location,
|
location,
|
||||||
rate,
|
rate,
|
||||||
|
batch,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ export abstract class InvoiceItem extends Doc {
|
|||||||
quantity?: number;
|
quantity?: number;
|
||||||
transferQuantity?: number;
|
transferQuantity?: number;
|
||||||
unitConversionFactor?: number;
|
unitConversionFactor?: number;
|
||||||
|
batch?: string;
|
||||||
|
|
||||||
tax?: string;
|
tax?: string;
|
||||||
stockNotTransferred?: number;
|
stockNotTransferred?: number;
|
||||||
@ -430,9 +431,13 @@ export abstract class InvoiceItem extends Doc {
|
|||||||
!(this.enableDiscounting && !!this.setItemDiscountAmount),
|
!(this.enableDiscounting && !!this.setItemDiscountAmount),
|
||||||
itemDiscountPercent: () =>
|
itemDiscountPercent: () =>
|
||||||
!(this.enableDiscounting && !this.setItemDiscountAmount),
|
!(this.enableDiscounting && !this.setItemDiscountAmount),
|
||||||
transferUnit: () => !this.enableInventory,
|
batch: () => !this.fyo.singles.InventorySettings?.enableBatches,
|
||||||
transferQuantity: () => !this.enableInventory,
|
transferUnit: () =>
|
||||||
unitConversionFactor: () => !this.enableInventory,
|
!this.fyo.singles.InventorySettings?.enableUomConversions,
|
||||||
|
transferQuantity: () =>
|
||||||
|
!this.fyo.singles.InventorySettings?.enableUomConversions,
|
||||||
|
unitConversionFactor: () =>
|
||||||
|
!this.fyo.singles.InventorySettings?.enableUomConversions,
|
||||||
};
|
};
|
||||||
|
|
||||||
static filters: FiltersMap = {
|
static filters: FiltersMap = {
|
||||||
|
@ -19,7 +19,7 @@ export class Item extends Doc {
|
|||||||
trackItem?: boolean;
|
trackItem?: boolean;
|
||||||
itemType?: 'Product' | 'Service';
|
itemType?: 'Product' | 'Service';
|
||||||
for?: 'Purchases' | 'Sales' | 'Both';
|
for?: 'Purchases' | 'Sales' | 'Both';
|
||||||
hasBatchNumber?: boolean;
|
hasBatch?: boolean;
|
||||||
|
|
||||||
formulas: FormulaMap = {
|
formulas: FormulaMap = {
|
||||||
incomeAccount: {
|
incomeAccount: {
|
||||||
@ -76,29 +76,6 @@ export class Item extends Doc {
|
|||||||
throw new ValidationError(this.fyo.t`Rate can't be negative.`);
|
throw new ValidationError(this.fyo.t`Rate can't be negative.`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
hasBatchNumber: async (value: DocValue) => {
|
|
||||||
const { hasBatchNumber } = await this.fyo.db.get(
|
|
||||||
'Item',
|
|
||||||
this.name!,
|
|
||||||
'hasBatchNumber'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (hasBatchNumber && value !== hasBatchNumber) {
|
|
||||||
const itemEntriesInSLE = await this.fyo.db.count(
|
|
||||||
ModelNameEnum.StockLedgerEntry,
|
|
||||||
{
|
|
||||||
filters: { item: this.name! },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (itemEntriesInSLE > 0) {
|
|
||||||
throw new ValidationError(
|
|
||||||
this.fyo.t`Cannot change value of Has Batch No as Item ${this
|
|
||||||
.name!} already has transactions against it. `
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static getActions(fyo: Fyo): Action[] {
|
static getActions(fyo: Fyo): Action[] {
|
||||||
@ -146,15 +123,17 @@ export class Item extends Doc {
|
|||||||
!this.fyo.singles.AccountingSettings?.enableInventory ||
|
!this.fyo.singles.AccountingSettings?.enableInventory ||
|
||||||
this.itemType !== 'Product' ||
|
this.itemType !== 'Product' ||
|
||||||
(this.inserted && !this.trackItem),
|
(this.inserted && !this.trackItem),
|
||||||
hasBatchNumber: () => !this.trackItem,
|
|
||||||
barcode: () => !this.fyo.singles.InventorySettings?.enableBarcodes,
|
barcode: () => !this.fyo.singles.InventorySettings?.enableBarcodes,
|
||||||
uomConversions: () => !this.fyo.singles.AccountingSettings?.enableInventory,
|
hasBatch: () =>
|
||||||
|
!(this.fyo.singles.InventorySettings?.enableBatches && this.trackItem),
|
||||||
|
uomConversions: () =>
|
||||||
|
!this.fyo.singles.InventorySettings?.enableUomConversions,
|
||||||
};
|
};
|
||||||
|
|
||||||
readOnly: ReadOnlyMap = {
|
readOnly: ReadOnlyMap = {
|
||||||
unit: () => this.inserted,
|
unit: () => this.inserted,
|
||||||
itemType: () => this.inserted,
|
itemType: () => this.inserted,
|
||||||
trackItem: () => this.inserted,
|
trackItem: () => this.inserted,
|
||||||
hasBatchNumber: () => this.inserted,
|
hasBatch: () => this.inserted,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -26,14 +26,14 @@ import { ShipmentItem } from './inventory/ShipmentItem';
|
|||||||
import { StockLedgerEntry } from './inventory/StockLedgerEntry';
|
import { StockLedgerEntry } from './inventory/StockLedgerEntry';
|
||||||
import { StockMovement } from './inventory/StockMovement';
|
import { StockMovement } from './inventory/StockMovement';
|
||||||
import { StockMovementItem } from './inventory/StockMovementItem';
|
import { StockMovementItem } from './inventory/StockMovementItem';
|
||||||
import { BatchNumber } from './baseModels/BatchNumber/BatchNumber';
|
import { Batch } from './inventory/Batch';
|
||||||
|
|
||||||
export const models = {
|
export const models = {
|
||||||
Account,
|
Account,
|
||||||
AccountingLedgerEntry,
|
AccountingLedgerEntry,
|
||||||
AccountingSettings,
|
AccountingSettings,
|
||||||
Address,
|
Address,
|
||||||
BatchNumber,
|
Batch,
|
||||||
Defaults,
|
Defaults,
|
||||||
Item,
|
Item,
|
||||||
JournalEntry,
|
JournalEntry,
|
||||||
|
@ -3,7 +3,7 @@ import {
|
|||||||
ListViewSettings,
|
ListViewSettings,
|
||||||
} from 'fyo/model/types';
|
} from 'fyo/model/types';
|
||||||
|
|
||||||
export class BatchNumber extends Doc {
|
export class Batch extends Doc {
|
||||||
static getListViewSettings(): ListViewSettings {
|
static getListViewSettings(): ListViewSettings {
|
||||||
return {
|
return {
|
||||||
columns: ["name", "expiryDate", "manufactureDate"],
|
columns: ["name", "expiryDate", "manufactureDate"],
|
@ -1,5 +1,5 @@
|
|||||||
import { Doc } from 'fyo/model/doc';
|
import { Doc } from 'fyo/model/doc';
|
||||||
import { FiltersMap } from 'fyo/model/types';
|
import { FiltersMap, ReadOnlyMap } from 'fyo/model/types';
|
||||||
import { AccountTypeEnum } from 'models/baseModels/Account/types';
|
import { AccountTypeEnum } from 'models/baseModels/Account/types';
|
||||||
import { ValuationMethod } from './types';
|
import { ValuationMethod } from './types';
|
||||||
|
|
||||||
@ -10,6 +10,8 @@ export class InventorySettings extends Doc {
|
|||||||
stockReceivedButNotBilled?: string;
|
stockReceivedButNotBilled?: string;
|
||||||
costOfGoodsSold?: string;
|
costOfGoodsSold?: string;
|
||||||
enableBarcodes?: boolean;
|
enableBarcodes?: boolean;
|
||||||
|
enableBatches?: boolean;
|
||||||
|
enableUomConversions?: boolean;
|
||||||
|
|
||||||
static filters: FiltersMap = {
|
static filters: FiltersMap = {
|
||||||
stockInHand: () => ({
|
stockInHand: () => ({
|
||||||
@ -25,4 +27,16 @@ export class InventorySettings extends Doc {
|
|||||||
accountType: AccountTypeEnum['Cost of Goods Sold'],
|
accountType: AccountTypeEnum['Cost of Goods Sold'],
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
readOnly: ReadOnlyMap = {
|
||||||
|
enableBarcodes: () => {
|
||||||
|
return !!this.enableBarcodes;
|
||||||
|
},
|
||||||
|
enableBatches: () => {
|
||||||
|
return !!this.enableBatches;
|
||||||
|
},
|
||||||
|
enableUomConversions: () => {
|
||||||
|
return !!this.enableUomConversions;
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -10,5 +10,5 @@ export class StockLedgerEntry extends Doc {
|
|||||||
location?: string;
|
location?: string;
|
||||||
referenceName?: string;
|
referenceName?: string;
|
||||||
referenceType?: string;
|
referenceType?: string;
|
||||||
batchNumber?: string;
|
batch?: string;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Fyo, t } from 'fyo';
|
import { Fyo, t } from 'fyo';
|
||||||
import { ValidationError } from 'fyo/utils/errors';
|
import { ValidationError } from 'fyo/utils/errors';
|
||||||
import { DateTime } from 'luxon';
|
|
||||||
import { ModelNameEnum } from 'models/types';
|
import { ModelNameEnum } from 'models/types';
|
||||||
import { Money } from 'pesa';
|
import { Money } from 'pesa';
|
||||||
import { StockLedgerEntry } from './StockLedgerEntry';
|
import { StockLedgerEntry } from './StockLedgerEntry';
|
||||||
@ -133,43 +132,7 @@ export class StockManager {
|
|||||||
|
|
||||||
const date = details.date.toISOString();
|
const date = details.date.toISOString();
|
||||||
const formattedDate = this.fyo.format(details.date, 'Datetime');
|
const formattedDate = this.fyo.format(details.date, 'Datetime');
|
||||||
const isItemHasBatchNumber = await this.fyo.getValue(
|
const batch = details.batch || undefined;
|
||||||
'Item',
|
|
||||||
details.item,
|
|
||||||
'hasBatchNumber'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isItemHasBatchNumber && !this.isCancelled) {
|
|
||||||
if (!details.batchNumber) {
|
|
||||||
throw new ValidationError(
|
|
||||||
t`Please enter Batch Number for ${details.item}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const itemsInBatch =
|
|
||||||
(await this.fyo.db.getStockQuantity(
|
|
||||||
details.item,
|
|
||||||
details.fromLocation,
|
|
||||||
undefined,
|
|
||||||
date,
|
|
||||||
details.batchNumber
|
|
||||||
)) ?? 0;
|
|
||||||
|
|
||||||
if (details.quantity <= itemsInBatch) return;
|
|
||||||
|
|
||||||
throw new ValidationError(
|
|
||||||
[
|
|
||||||
t`Insufficient Quantity`,
|
|
||||||
t`Additional quantity (${
|
|
||||||
details.quantity - itemsInBatch
|
|
||||||
}) is required in batch ${
|
|
||||||
details.batchNumber
|
|
||||||
} to make the outward transfer of item ${details.item} from ${
|
|
||||||
details.fromLocation
|
|
||||||
} on ${formattedDate}`,
|
|
||||||
].join('\n')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let quantityBefore =
|
let quantityBefore =
|
||||||
(await this.fyo.db.getStockQuantity(
|
(await this.fyo.db.getStockQuantity(
|
||||||
@ -177,22 +140,24 @@ export class StockManager {
|
|||||||
details.fromLocation,
|
details.fromLocation,
|
||||||
undefined,
|
undefined,
|
||||||
date,
|
date,
|
||||||
undefined
|
batch
|
||||||
)) ?? 0;
|
)) ?? 0;
|
||||||
|
|
||||||
if (this.isCancelled) {
|
if (this.isCancelled) {
|
||||||
quantityBefore += details.quantity;
|
quantityBefore += details.quantity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const batchMessage = !!batch ? t` in Batch ${batch}` : '';
|
||||||
|
|
||||||
if (quantityBefore < details.quantity) {
|
if (quantityBefore < details.quantity) {
|
||||||
throw new ValidationError(
|
throw new ValidationError(
|
||||||
[
|
[
|
||||||
t`Insufficient Quantity.`,
|
t`Insufficient Quantity.`,
|
||||||
t`Additional quantity (${
|
t`Additional quantity (${
|
||||||
details.quantity - quantityBefore
|
details.quantity - quantityBefore
|
||||||
}) required to make outward transfer of item ${details.item} from ${
|
}) required${batchMessage} to make outward transfer of item ${
|
||||||
details.fromLocation
|
details.item
|
||||||
} on ${formattedDate}`,
|
} from ${details.fromLocation} on ${formattedDate}`,
|
||||||
].join('\n')
|
].join('\n')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -202,8 +167,9 @@ export class StockManager {
|
|||||||
details.fromLocation,
|
details.fromLocation,
|
||||||
details.date.toISOString(),
|
details.date.toISOString(),
|
||||||
undefined,
|
undefined,
|
||||||
undefined
|
batch
|
||||||
);
|
);
|
||||||
|
|
||||||
if (quantityAfter === null) {
|
if (quantityAfter === null) {
|
||||||
// No future transactions
|
// No future transactions
|
||||||
return;
|
return;
|
||||||
@ -217,9 +183,9 @@ export class StockManager {
|
|||||||
t`Transfer will cause future entries to have negative stock.`,
|
t`Transfer will cause future entries to have negative stock.`,
|
||||||
t`Additional quantity (${
|
t`Additional quantity (${
|
||||||
quantityAfter - quantityRemaining
|
quantityAfter - quantityRemaining
|
||||||
}) required to make outward transfer of item ${details.item} from ${
|
}) required${batchMessage} to make outward transfer of item ${
|
||||||
details.fromLocation
|
details.item
|
||||||
} on ${formattedDate}`,
|
} from ${details.fromLocation} on ${formattedDate}`,
|
||||||
].join('\n')
|
].join('\n')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -244,7 +210,7 @@ class StockManagerItem {
|
|||||||
referenceType: string;
|
referenceType: string;
|
||||||
fromLocation?: string;
|
fromLocation?: string;
|
||||||
toLocation?: string;
|
toLocation?: string;
|
||||||
batchNumber?: string;
|
batch?: string;
|
||||||
|
|
||||||
stockLedgerEntries?: StockLedgerEntry[];
|
stockLedgerEntries?: StockLedgerEntry[];
|
||||||
|
|
||||||
@ -259,7 +225,7 @@ class StockManagerItem {
|
|||||||
this.toLocation = details.toLocation;
|
this.toLocation = details.toLocation;
|
||||||
this.referenceName = details.referenceName;
|
this.referenceName = details.referenceName;
|
||||||
this.referenceType = details.referenceType;
|
this.referenceType = details.referenceType;
|
||||||
this.batchNumber = details.batchNumber;
|
this.batch = details.batch;
|
||||||
|
|
||||||
this.fyo = fyo;
|
this.fyo = fyo;
|
||||||
}
|
}
|
||||||
@ -312,7 +278,7 @@ class StockManagerItem {
|
|||||||
date: this.date,
|
date: this.date,
|
||||||
item: this.item,
|
item: this.item,
|
||||||
rate: this.rate,
|
rate: this.rate,
|
||||||
batchNumber: this.batchNumber,
|
batch: this.batch || null,
|
||||||
quantity,
|
quantity,
|
||||||
location,
|
location,
|
||||||
referenceName: this.referenceName,
|
referenceName: this.referenceName,
|
||||||
|
@ -15,6 +15,7 @@ import {
|
|||||||
import { LedgerPosting } from 'models/Transactional/LedgerPosting';
|
import { LedgerPosting } from 'models/Transactional/LedgerPosting';
|
||||||
import { ModelNameEnum } from 'models/types';
|
import { ModelNameEnum } from 'models/types';
|
||||||
import { Money } from 'pesa';
|
import { Money } from 'pesa';
|
||||||
|
import { validateBatch } from './helpers';
|
||||||
import { StockMovementItem } from './StockMovementItem';
|
import { StockMovementItem } from './StockMovementItem';
|
||||||
import { Transfer } from './Transfer';
|
import { Transfer } from './Transfer';
|
||||||
import { MovementType } from './types';
|
import { MovementType } from './types';
|
||||||
@ -49,6 +50,11 @@ export class StockMovement extends Transfer {
|
|||||||
|
|
||||||
async validate() {
|
async validate() {
|
||||||
await super.validate();
|
await super.validate();
|
||||||
|
this.validateManufacture();
|
||||||
|
await validateBatch(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
validateManufacture() {
|
||||||
if (this.movementType !== MovementType.Manufacture) {
|
if (this.movementType !== MovementType.Manufacture) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -109,7 +115,7 @@ export class StockMovement extends Transfer {
|
|||||||
item: row.item!,
|
item: row.item!,
|
||||||
rate: row.rate!,
|
rate: row.rate!,
|
||||||
quantity: row.quantity!,
|
quantity: row.quantity!,
|
||||||
batchNumber: row.batchNumber!,
|
batch: row.batch!,
|
||||||
fromLocation: row.fromLocation,
|
fromLocation: row.fromLocation,
|
||||||
toLocation: row.toLocation,
|
toLocation: row.toLocation,
|
||||||
}));
|
}));
|
||||||
|
@ -4,6 +4,7 @@ import { Doc } from 'fyo/model/doc';
|
|||||||
import {
|
import {
|
||||||
FiltersMap,
|
FiltersMap,
|
||||||
FormulaMap,
|
FormulaMap,
|
||||||
|
HiddenMap,
|
||||||
ReadOnlyMap,
|
ReadOnlyMap,
|
||||||
RequiredMap,
|
RequiredMap,
|
||||||
ValidationMap,
|
ValidationMap,
|
||||||
@ -30,7 +31,7 @@ export class StockMovementItem extends Doc {
|
|||||||
rate?: Money;
|
rate?: Money;
|
||||||
amount?: Money;
|
amount?: Money;
|
||||||
parentdoc?: StockMovement;
|
parentdoc?: StockMovement;
|
||||||
batchNumber?: string;
|
batch?: string;
|
||||||
|
|
||||||
get isIssue() {
|
get isIssue() {
|
||||||
return this.parentdoc?.movementType === MovementType.MaterialIssue;
|
return this.parentdoc?.movementType === MovementType.MaterialIssue;
|
||||||
@ -233,6 +234,16 @@ export class StockMovementItem extends Doc {
|
|||||||
toLocation: () => this.isIssue,
|
toLocation: () => this.isIssue,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
override hidden: HiddenMap = {
|
||||||
|
batch: () => !this.fyo.singles.InventorySettings?.enableBatches,
|
||||||
|
transferUnit: () =>
|
||||||
|
!this.fyo.singles.InventorySettings?.enableUomConversions,
|
||||||
|
transferQuantity: () =>
|
||||||
|
!this.fyo.singles.InventorySettings?.enableUomConversions,
|
||||||
|
unitConversionFactor: () =>
|
||||||
|
!this.fyo.singles.InventorySettings?.enableUomConversions,
|
||||||
|
};
|
||||||
|
|
||||||
static createFilters: FiltersMap = {
|
static createFilters: FiltersMap = {
|
||||||
item: () => ({ trackItem: true, itemType: 'Product' }),
|
item: () => ({ trackItem: true, itemType: 'Product' }),
|
||||||
};
|
};
|
||||||
|
@ -15,6 +15,7 @@ import { addItem, getLedgerLinkAction, getNumberSeries } from 'models/helpers';
|
|||||||
import { LedgerPosting } from 'models/Transactional/LedgerPosting';
|
import { LedgerPosting } from 'models/Transactional/LedgerPosting';
|
||||||
import { ModelNameEnum } from 'models/types';
|
import { ModelNameEnum } from 'models/types';
|
||||||
import { Money } from 'pesa';
|
import { Money } from 'pesa';
|
||||||
|
import { validateBatch } from './helpers';
|
||||||
import { StockTransferItem } from './StockTransferItem';
|
import { StockTransferItem } from './StockTransferItem';
|
||||||
import { Transfer } from './Transfer';
|
import { Transfer } from './Transfer';
|
||||||
|
|
||||||
@ -82,7 +83,7 @@ export abstract class StockTransfer extends Transfer {
|
|||||||
item: row.item!,
|
item: row.item!,
|
||||||
rate: row.rate!,
|
rate: row.rate!,
|
||||||
quantity: row.quantity!,
|
quantity: row.quantity!,
|
||||||
batchNumber: row.batchNumber!,
|
batch: row.batch!,
|
||||||
fromLocation,
|
fromLocation,
|
||||||
toLocation,
|
toLocation,
|
||||||
};
|
};
|
||||||
@ -151,6 +152,11 @@ export abstract class StockTransfer extends Transfer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override async validate(): Promise<void> {
|
||||||
|
await super.validate();
|
||||||
|
await validateBatch(this);
|
||||||
|
}
|
||||||
|
|
||||||
static getActions(fyo: Fyo): Action[] {
|
static getActions(fyo: Fyo): Action[] {
|
||||||
return [getLedgerLinkAction(fyo, false), getLedgerLinkAction(fyo, true)];
|
return [getLedgerLinkAction(fyo, false), getLedgerLinkAction(fyo, true)];
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
import { t } from 'fyo';
|
import { t } from 'fyo';
|
||||||
import { DocValue } from 'fyo/core/types';
|
import { DocValue } from 'fyo/core/types';
|
||||||
import { Doc } from 'fyo/model/doc';
|
import { Doc } from 'fyo/model/doc';
|
||||||
import { FiltersMap, FormulaMap, ValidationMap } from 'fyo/model/types';
|
import {
|
||||||
|
FiltersMap,
|
||||||
|
FormulaMap,
|
||||||
|
HiddenMap,
|
||||||
|
ValidationMap,
|
||||||
|
} from 'fyo/model/types';
|
||||||
import { ValidationError } from 'fyo/utils/errors';
|
import { ValidationError } from 'fyo/utils/errors';
|
||||||
import { ModelNameEnum } from 'models/types';
|
import { ModelNameEnum } from 'models/types';
|
||||||
import { Money } from 'pesa';
|
import { Money } from 'pesa';
|
||||||
@ -21,7 +26,7 @@ export class StockTransferItem extends Doc {
|
|||||||
amount?: Money;
|
amount?: Money;
|
||||||
description?: string;
|
description?: string;
|
||||||
hsnCode?: number;
|
hsnCode?: number;
|
||||||
batchNumber?: string
|
batch?: string;
|
||||||
|
|
||||||
formulas: FormulaMap = {
|
formulas: FormulaMap = {
|
||||||
description: {
|
description: {
|
||||||
@ -200,4 +205,14 @@ export class StockTransferItem extends Doc {
|
|||||||
return { for: ['not in', [itemNotFor]], trackItem: true };
|
return { for: ['not in', [itemNotFor]], trackItem: true };
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
override hidden: HiddenMap = {
|
||||||
|
batch: () => !this.fyo.singles.InventorySettings?.enableBatches,
|
||||||
|
transferUnit: () =>
|
||||||
|
!this.fyo.singles.InventorySettings?.enableUomConversions,
|
||||||
|
transferQuantity: () =>
|
||||||
|
!this.fyo.singles.InventorySettings?.enableUomConversions,
|
||||||
|
unitConversionFactor: () =>
|
||||||
|
!this.fyo.singles.InventorySettings?.enableUomConversions,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -1 +1,51 @@
|
|||||||
|
import { ValidationError } from 'fyo/utils/errors';
|
||||||
|
import { Invoice } from 'models/baseModels/Invoice/Invoice';
|
||||||
|
import { InvoiceItem } from 'models/baseModels/InvoiceItem/InvoiceItem';
|
||||||
|
import { ModelNameEnum } from 'models/types';
|
||||||
|
import { StockMovement } from './StockMovement';
|
||||||
|
import { StockMovementItem } from './StockMovementItem';
|
||||||
|
import { StockTransfer } from './StockTransfer';
|
||||||
|
import { StockTransferItem } from './StockTransferItem';
|
||||||
|
|
||||||
|
export async function validateBatch(
|
||||||
|
doc: StockMovement | StockTransfer | Invoice
|
||||||
|
) {
|
||||||
|
for (const row of doc.items ?? []) {
|
||||||
|
await validateItemRowBatch(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function validateItemRowBatch(
|
||||||
|
doc: StockMovementItem | StockTransferItem | InvoiceItem
|
||||||
|
) {
|
||||||
|
const idx = doc.idx ?? 0 + 1;
|
||||||
|
const item = doc.item;
|
||||||
|
const batch = doc.batch;
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasBatch = await doc.fyo.getValue(
|
||||||
|
ModelNameEnum.Item,
|
||||||
|
item,
|
||||||
|
'hasBatch'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasBatch && batch) {
|
||||||
|
throw new ValidationError(
|
||||||
|
[
|
||||||
|
doc.fyo.t`Batch set for row ${idx}.`,
|
||||||
|
doc.fyo.t`Item ${item} is not a batched item`,
|
||||||
|
].join(' ')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasBatch && !batch) {
|
||||||
|
throw new ValidationError(
|
||||||
|
[
|
||||||
|
doc.fyo.t`Batch not set for row ${idx}.`,
|
||||||
|
doc.fyo.t`Item ${item} is a batched item`,
|
||||||
|
].join(' ')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Fyo } from 'fyo';
|
import { Fyo } from 'fyo';
|
||||||
import { BatchNumber } from 'models/baseModels/BatchNumber/BatchNumber';
|
import { Batch } from 'models/inventory/Batch';
|
||||||
import { ModelNameEnum } from 'models/types';
|
import { ModelNameEnum } from 'models/types';
|
||||||
import { StockMovement } from '../StockMovement';
|
import { StockMovement } from '../StockMovement';
|
||||||
import { StockTransfer } from '../StockTransfer';
|
import { StockTransfer } from '../StockTransfer';
|
||||||
@ -27,7 +27,7 @@ type Transfer = {
|
|||||||
item: string;
|
item: string;
|
||||||
from?: string;
|
from?: string;
|
||||||
to?: string;
|
to?: string;
|
||||||
batchNumber?: string;
|
batch?: string;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
rate: number;
|
rate: number;
|
||||||
};
|
};
|
||||||
@ -36,22 +36,22 @@ interface TransferTwo extends Omit<Transfer, 'from' | 'to'> {
|
|||||||
location: string;
|
location: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getItem(name: string, rate: number, hasBatchNumber?: boolean) {
|
export function getItem(name: string, rate: number, hasBatch: boolean = false) {
|
||||||
return { name, rate, trackItem: true, hasBatchNumber };
|
return { name, rate, trackItem: true, hasBatch };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getBatchNumber(
|
export async function getBatch(
|
||||||
schemaName: ModelNameEnum.BatchNumber,
|
schemaName: ModelNameEnum.Batch,
|
||||||
batchNumber: string,
|
batch: string,
|
||||||
expiryDate: Date,
|
expiryDate: Date,
|
||||||
manufactureDate: Date,
|
manufactureDate: Date,
|
||||||
fyo: Fyo
|
fyo: Fyo
|
||||||
): Promise<BatchNumber> {
|
): Promise<Batch> {
|
||||||
const doc = fyo.doc.getNewDoc(schemaName, {
|
const doc = fyo.doc.getNewDoc(schemaName, {
|
||||||
batchNumber,
|
batch,
|
||||||
expiryDate,
|
expiryDate,
|
||||||
manufactureDate,
|
manufactureDate,
|
||||||
}) as BatchNumber;
|
}) as Batch;
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ export async function getStockMovement(
|
|||||||
item,
|
item,
|
||||||
from: fromLocation,
|
from: fromLocation,
|
||||||
to: toLocation,
|
to: toLocation,
|
||||||
batchNumber: batchNumber,
|
batch,
|
||||||
quantity,
|
quantity,
|
||||||
rate,
|
rate,
|
||||||
} of transfers) {
|
} of transfers) {
|
||||||
@ -91,7 +91,7 @@ export async function getStockMovement(
|
|||||||
item,
|
item,
|
||||||
fromLocation,
|
fromLocation,
|
||||||
toLocation,
|
toLocation,
|
||||||
batchNumber,
|
batch,
|
||||||
rate,
|
rate,
|
||||||
quantity,
|
quantity,
|
||||||
});
|
});
|
||||||
|
288
models/inventory/tests/testBatches.spec.ts
Normal file
288
models/inventory/tests/testBatches.spec.ts
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
import { assertThrows } from 'backend/database/tests/helpers';
|
||||||
|
import { ModelNameEnum } from 'models/types';
|
||||||
|
import test from 'tape';
|
||||||
|
import { closeTestFyo, getTestFyo, setupTestFyo } from 'tests/helpers';
|
||||||
|
import { MovementType } from '../types';
|
||||||
|
import { getItem, getSLEs, getStockMovement } from './helpers';
|
||||||
|
|
||||||
|
const fyo = getTestFyo();
|
||||||
|
|
||||||
|
setupTestFyo(fyo, __filename);
|
||||||
|
|
||||||
|
const itemMap = {
|
||||||
|
Pen: {
|
||||||
|
name: 'Pen',
|
||||||
|
rate: 700,
|
||||||
|
},
|
||||||
|
Ink: {
|
||||||
|
name: 'Ink',
|
||||||
|
rate: 50,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const locationMap = {
|
||||||
|
LocationOne: 'LocationOne',
|
||||||
|
LocationTwo: 'LocationTwo',
|
||||||
|
};
|
||||||
|
|
||||||
|
const batchMap = {
|
||||||
|
batchOne: {
|
||||||
|
name: 'PN-AB001',
|
||||||
|
manufactureDate: '2022-11-03T09:57:04.528',
|
||||||
|
},
|
||||||
|
batchTwo: {
|
||||||
|
name: 'PN-AB002',
|
||||||
|
manufactureDate: '2022-10-03T09:57:04.528',
|
||||||
|
},
|
||||||
|
batchThree: {
|
||||||
|
name: 'PN-AB003',
|
||||||
|
manufactureDate: '2022-10-03T09:57:04.528',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
test('create dummy items, locations & batches', async (t) => {
|
||||||
|
// Create Items
|
||||||
|
for (const { name, rate } of Object.values(itemMap)) {
|
||||||
|
const item = getItem(name, rate, true);
|
||||||
|
await fyo.doc.getNewDoc(ModelNameEnum.Item, item).sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Locations
|
||||||
|
for (const name of Object.values(locationMap)) {
|
||||||
|
await fyo.doc.getNewDoc(ModelNameEnum.Location, { name }).sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Batches
|
||||||
|
for (const batch of Object.values(batchMap)) {
|
||||||
|
const doc = fyo.doc.getNewDoc(ModelNameEnum.Batch, batch);
|
||||||
|
await doc.sync();
|
||||||
|
|
||||||
|
const exists = await fyo.db.exists(ModelNameEnum.Batch, batch.name);
|
||||||
|
t.ok(exists, `${batch.name} exists`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('batched item, create stock movement, material receipt', async (t) => {
|
||||||
|
const { rate } = itemMap.Pen;
|
||||||
|
const stockMovement = await getStockMovement(
|
||||||
|
MovementType.MaterialReceipt,
|
||||||
|
new Date('2022-11-03T09:57:04.528'),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
item: itemMap.Pen.name,
|
||||||
|
to: locationMap.LocationOne,
|
||||||
|
quantity: 2,
|
||||||
|
batch: batchMap.batchOne.name,
|
||||||
|
rate,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
item: itemMap.Pen.name,
|
||||||
|
to: locationMap.LocationOne,
|
||||||
|
quantity: 1,
|
||||||
|
batch: batchMap.batchTwo.name,
|
||||||
|
rate,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
fyo
|
||||||
|
);
|
||||||
|
|
||||||
|
await (await stockMovement.sync()).submit();
|
||||||
|
t.equal(
|
||||||
|
await fyo.db.getStockQuantity(
|
||||||
|
itemMap.Pen.name,
|
||||||
|
locationMap.LocationOne,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
batchMap.batchOne.name
|
||||||
|
),
|
||||||
|
2,
|
||||||
|
'batch one has quantity two'
|
||||||
|
);
|
||||||
|
|
||||||
|
t.equal(
|
||||||
|
await fyo.db.getStockQuantity(
|
||||||
|
itemMap.Pen.name,
|
||||||
|
locationMap.LocationOne,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
batchMap.batchTwo.name
|
||||||
|
),
|
||||||
|
1,
|
||||||
|
'batch two has quantity one'
|
||||||
|
);
|
||||||
|
|
||||||
|
t.equal(
|
||||||
|
await fyo.db.getStockQuantity(
|
||||||
|
itemMap.Pen.name,
|
||||||
|
locationMap.LocationOne,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
batchMap.batchThree.name
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
'batch three has no quantity'
|
||||||
|
);
|
||||||
|
|
||||||
|
t.equal(
|
||||||
|
await fyo.db.getStockQuantity(
|
||||||
|
itemMap.Ink.name,
|
||||||
|
locationMap.LocationOne,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
batchMap.batchOne.name
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
'non transacted item has no quantity'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('batched item, create stock movement, material issue', async (t) => {
|
||||||
|
const { rate } = itemMap.Pen;
|
||||||
|
const quantity = 2;
|
||||||
|
const batch = batchMap.batchOne.name;
|
||||||
|
|
||||||
|
const stockMovement = await getStockMovement(
|
||||||
|
MovementType.MaterialIssue,
|
||||||
|
new Date('2022-11-03T10:00:00.528'),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
item: itemMap.Pen.name,
|
||||||
|
from: locationMap.LocationOne,
|
||||||
|
batch,
|
||||||
|
quantity,
|
||||||
|
rate,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
fyo
|
||||||
|
);
|
||||||
|
|
||||||
|
await (await stockMovement.sync()).submit();
|
||||||
|
t.equal(
|
||||||
|
await fyo.db.getStockQuantity(
|
||||||
|
itemMap.Pen.name,
|
||||||
|
locationMap.LocationOne,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
batch
|
||||||
|
),
|
||||||
|
0,
|
||||||
|
'batch one quantity transacted out'
|
||||||
|
);
|
||||||
|
|
||||||
|
t.equal(
|
||||||
|
await fyo.db.getStockQuantity(
|
||||||
|
itemMap.Pen.name,
|
||||||
|
locationMap.LocationOne,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
batchMap.batchTwo.name
|
||||||
|
),
|
||||||
|
1,
|
||||||
|
'batch two quantity intact'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('batched item, create stock movement, material transfer', async (t) => {
|
||||||
|
const { rate } = itemMap.Pen;
|
||||||
|
const quantity = 1;
|
||||||
|
const batch = batchMap.batchTwo.name;
|
||||||
|
|
||||||
|
const stockMovement = await getStockMovement(
|
||||||
|
MovementType.MaterialTransfer,
|
||||||
|
new Date('2022-11-03T09:58:04.528'),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
item: itemMap.Pen.name,
|
||||||
|
from: locationMap.LocationOne,
|
||||||
|
to: locationMap.LocationTwo,
|
||||||
|
batch,
|
||||||
|
quantity,
|
||||||
|
rate,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
fyo
|
||||||
|
);
|
||||||
|
|
||||||
|
await (await stockMovement.sync()).submit();
|
||||||
|
t.equal(
|
||||||
|
await fyo.db.getStockQuantity(
|
||||||
|
itemMap.Pen.name,
|
||||||
|
locationMap.LocationOne,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
batch
|
||||||
|
),
|
||||||
|
0,
|
||||||
|
'location one batch transacted out'
|
||||||
|
);
|
||||||
|
|
||||||
|
t.equal(
|
||||||
|
await fyo.db.getStockQuantity(
|
||||||
|
itemMap.Pen.name,
|
||||||
|
locationMap.LocationTwo,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
batch
|
||||||
|
),
|
||||||
|
quantity,
|
||||||
|
'location two batch transacted in'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('batched item, create invalid stock movements', async (t) => {
|
||||||
|
const { name, rate } = itemMap.Pen;
|
||||||
|
const quantity = await fyo.db.getStockQuantity(
|
||||||
|
itemMap.Pen.name,
|
||||||
|
locationMap.LocationTwo,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
batchMap.batchTwo.name
|
||||||
|
);
|
||||||
|
|
||||||
|
t.equal(quantity, 1, 'location two, batch one has quantity');
|
||||||
|
if (!quantity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let stockMovement = await getStockMovement(
|
||||||
|
MovementType.MaterialIssue,
|
||||||
|
new Date('2022-11-03T09:59:04.528'),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
item: itemMap.Pen.name,
|
||||||
|
from: locationMap.LocationTwo,
|
||||||
|
batch: batchMap.batchOne.name,
|
||||||
|
quantity,
|
||||||
|
rate,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
fyo
|
||||||
|
);
|
||||||
|
|
||||||
|
await assertThrows(
|
||||||
|
async () => (await stockMovement.sync()).submit(),
|
||||||
|
'invalid stockMovement with insufficient quantity did not throw'
|
||||||
|
);
|
||||||
|
|
||||||
|
stockMovement = await getStockMovement(
|
||||||
|
MovementType.MaterialIssue,
|
||||||
|
new Date('2022-11-03T09:59:04.528'),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
item: itemMap.Pen.name,
|
||||||
|
from: locationMap.LocationTwo,
|
||||||
|
quantity,
|
||||||
|
rate,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
fyo
|
||||||
|
);
|
||||||
|
|
||||||
|
await assertThrows(
|
||||||
|
async () => (await stockMovement.sync()).submit(),
|
||||||
|
'invalid stockMovement without batch did not throw'
|
||||||
|
);
|
||||||
|
t.equal(await fyo.db.getStockQuantity(name), 1, 'item still has quantity');
|
||||||
|
});
|
||||||
|
|
||||||
|
closeTestFyo(fyo, __filename);
|
@ -17,12 +17,10 @@ const itemMap = {
|
|||||||
Pen: {
|
Pen: {
|
||||||
name: 'Pen',
|
name: 'Pen',
|
||||||
rate: 700,
|
rate: 700,
|
||||||
hasBatchNumber: true,
|
|
||||||
},
|
},
|
||||||
Ink: {
|
Ink: {
|
||||||
name: 'Ink',
|
name: 'Ink',
|
||||||
rate: 50,
|
rate: 50,
|
||||||
hasBatchNumber: false,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -31,22 +29,14 @@ const locationMap = {
|
|||||||
LocationTwo: 'LocationTwo',
|
LocationTwo: 'LocationTwo',
|
||||||
};
|
};
|
||||||
|
|
||||||
const batchNumberMap = {
|
|
||||||
batchNumberOne: {
|
|
||||||
name: 'IK-AB001',
|
|
||||||
manufactureDate: '2022-11-03T09:57:04.528',
|
|
||||||
expiryDate: '2023-11-03T09:57:04.528',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Section 1: Test Creation of Items and Locations
|
* Section 1: Test Creation of Items and Locations
|
||||||
*/
|
*/
|
||||||
|
|
||||||
test('create dummy items & locations', async (t) => {
|
test('create dummy items & locations', async (t) => {
|
||||||
// Create Items
|
// Create Items
|
||||||
for (const { name, rate, hasBatchNumber } of Object.values(itemMap)) {
|
for (const { name, rate } of Object.values(itemMap)) {
|
||||||
const item = getItem(name, rate, hasBatchNumber);
|
const item = getItem(name, rate);
|
||||||
await fyo.doc.getNewDoc(ModelNameEnum.Item, item).sync();
|
await fyo.doc.getNewDoc(ModelNameEnum.Item, item).sync();
|
||||||
t.ok(await fyo.db.exists(ModelNameEnum.Item, name), `${name} exists`);
|
t.ok(await fyo.db.exists(ModelNameEnum.Item, name), `${name} exists`);
|
||||||
}
|
}
|
||||||
@ -58,25 +48,6 @@ test('create dummy items & locations', async (t) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('create dummy batch numbers', async (t) => {
|
|
||||||
// create batchNumber
|
|
||||||
for (const { name, manufactureDate, expiryDate } of Object.values(
|
|
||||||
batchNumberMap
|
|
||||||
)) {
|
|
||||||
await fyo.doc
|
|
||||||
.getNewDoc(ModelNameEnum.BatchNumber, {
|
|
||||||
name,
|
|
||||||
expiryDate,
|
|
||||||
manufactureDate,
|
|
||||||
})
|
|
||||||
.sync();
|
|
||||||
t.ok(
|
|
||||||
await fyo.db.exists(ModelNameEnum.BatchNumber, name),
|
|
||||||
`${name} exists`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Section 2: Test Creation of Stock Movements
|
* Section 2: Test Creation of Stock Movements
|
||||||
*/
|
*/
|
||||||
@ -119,55 +90,6 @@ test('create stock movement, material receipt', async (t) => {
|
|||||||
t.equal(await fyo.db.getStockQuantity(itemMap.Ink.name), quantity);
|
t.equal(await fyo.db.getStockQuantity(itemMap.Ink.name), quantity);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Batch Enabled : create stock movement, material receipt', async (t) => {
|
|
||||||
const { rate } = itemMap.Pen;
|
|
||||||
const quantity = 2;
|
|
||||||
const batchNumber = batchNumberMap.batchNumberOne.name;
|
|
||||||
const amount = rate * quantity;
|
|
||||||
const stockMovement = await getStockMovement(
|
|
||||||
MovementType.MaterialReceipt,
|
|
||||||
new Date('2022-11-03T09:57:04.528'),
|
|
||||||
[
|
|
||||||
{
|
|
||||||
item: itemMap.Pen.name,
|
|
||||||
to: locationMap.LocationOne,
|
|
||||||
quantity,
|
|
||||||
batchNumber,
|
|
||||||
rate,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
fyo
|
|
||||||
);
|
|
||||||
|
|
||||||
await (await stockMovement.sync()).submit();
|
|
||||||
t.ok(stockMovement.name?.startsWith('SMOV-'));
|
|
||||||
t.equal(stockMovement.amount?.float, amount);
|
|
||||||
t.equal(stockMovement.items?.[0].amount?.float, amount);
|
|
||||||
|
|
||||||
const name = stockMovement.name!;
|
|
||||||
|
|
||||||
const sles = await getSLEs(name, ModelNameEnum.StockMovement, fyo);
|
|
||||||
t.equal(sles.length, 1);
|
|
||||||
|
|
||||||
const sle = sles[0];
|
|
||||||
t.notEqual(new Date(sle.date).toString(), 'Invalid Date');
|
|
||||||
t.equal(parseInt(sle.name), 2);
|
|
||||||
t.equal(sle.item, itemMap.Pen.name);
|
|
||||||
t.equal(parseFloat(sle.rate), rate);
|
|
||||||
t.equal(sle.quantity, quantity);
|
|
||||||
t.equal(sle.location, locationMap.LocationOne);
|
|
||||||
t.equal(
|
|
||||||
await fyo.db.getStockQuantity(
|
|
||||||
itemMap.Pen.name,
|
|
||||||
locationMap.LocationOne,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
batchNumber
|
|
||||||
),
|
|
||||||
quantity
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('create stock movement, material transfer', async (t) => {
|
test('create stock movement, material transfer', async (t) => {
|
||||||
const { rate } = itemMap.Ink;
|
const { rate } = itemMap.Ink;
|
||||||
const quantity = 2;
|
const quantity = 2;
|
||||||
@ -214,69 +136,6 @@ test('create stock movement, material transfer', async (t) => {
|
|||||||
t.equal(await fyo.db.getStockQuantity(itemMap.Ink.name), quantity);
|
t.equal(await fyo.db.getStockQuantity(itemMap.Ink.name), quantity);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Batch Enabled create stock movement, material transfer', async (t) => {
|
|
||||||
const { rate } = itemMap.Pen;
|
|
||||||
const quantity = 2;
|
|
||||||
const batchNumber = batchNumberMap.batchNumberOne.name;
|
|
||||||
|
|
||||||
const stockMovement = await getStockMovement(
|
|
||||||
MovementType.MaterialTransfer,
|
|
||||||
new Date('2022-11-03T09:58:04.528'),
|
|
||||||
[
|
|
||||||
{
|
|
||||||
item: itemMap.Pen.name,
|
|
||||||
from: locationMap.LocationOne,
|
|
||||||
to: locationMap.LocationTwo,
|
|
||||||
batchNumber,
|
|
||||||
quantity,
|
|
||||||
rate,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
fyo
|
|
||||||
);
|
|
||||||
|
|
||||||
await (await stockMovement.sync()).submit();
|
|
||||||
const name = stockMovement.name!;
|
|
||||||
|
|
||||||
const sles = await getSLEs(name, ModelNameEnum.StockMovement, fyo);
|
|
||||||
t.equal(sles.length, 2);
|
|
||||||
|
|
||||||
for (const sle of sles) {
|
|
||||||
t.notEqual(new Date(sle.date).toString(), 'Invalid Date');
|
|
||||||
t.equal(sle.item, itemMap.Pen.name);
|
|
||||||
t.equal(parseFloat(sle.rate), rate);
|
|
||||||
|
|
||||||
if (sle.location === locationMap.LocationOne) {
|
|
||||||
t.equal(sle.quantity, -quantity);
|
|
||||||
} else if (sle.location === locationMap.LocationTwo) {
|
|
||||||
t.equal(sle.quantity, quantity);
|
|
||||||
} else {
|
|
||||||
t.ok(false, 'no-op');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.equal(
|
|
||||||
await fyo.db.getStockQuantity(
|
|
||||||
itemMap.Pen.name,
|
|
||||||
locationMap.LocationOne,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
batchNumber
|
|
||||||
),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
t.equal(
|
|
||||||
await fyo.db.getStockQuantity(
|
|
||||||
itemMap.Pen.name,
|
|
||||||
locationMap.LocationTwo,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
batchNumber
|
|
||||||
),
|
|
||||||
quantity
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('create stock movement, material issue', async (t) => {
|
test('create stock movement, material issue', async (t) => {
|
||||||
const { rate } = itemMap.Ink;
|
const { rate } = itemMap.Ink;
|
||||||
const quantity = 2;
|
const quantity = 2;
|
||||||
@ -310,50 +169,6 @@ test('create stock movement, material issue', async (t) => {
|
|||||||
t.equal(await fyo.db.getStockQuantity(itemMap.Ink.name), 0);
|
t.equal(await fyo.db.getStockQuantity(itemMap.Ink.name), 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Batch Enabled create stock movement, material issue', async (t) => {
|
|
||||||
const { rate } = itemMap.Pen;
|
|
||||||
const quantity = 2;
|
|
||||||
const batchNumber = batchNumberMap.batchNumberOne.name;
|
|
||||||
|
|
||||||
const stockMovement = await getStockMovement(
|
|
||||||
MovementType.MaterialIssue,
|
|
||||||
new Date('2022-11-03T09:59:04.528'),
|
|
||||||
[
|
|
||||||
{
|
|
||||||
item: itemMap.Pen.name,
|
|
||||||
from: locationMap.LocationTwo,
|
|
||||||
batchNumber,
|
|
||||||
quantity,
|
|
||||||
rate,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
fyo
|
|
||||||
);
|
|
||||||
|
|
||||||
await (await stockMovement.sync()).submit();
|
|
||||||
const name = stockMovement.name!;
|
|
||||||
|
|
||||||
const sles = await getSLEs(name, ModelNameEnum.StockMovement, fyo);
|
|
||||||
t.equal(sles.length, 1);
|
|
||||||
|
|
||||||
const sle = sles[0];
|
|
||||||
t.notEqual(new Date(sle.date).toString(), 'Invalid Date');
|
|
||||||
t.equal(sle.item, itemMap.Pen.name);
|
|
||||||
t.equal(parseFloat(sle.rate), rate);
|
|
||||||
t.equal(sle.quantity, -quantity);
|
|
||||||
t.equal(sle.location, locationMap.LocationTwo);
|
|
||||||
t.equal(
|
|
||||||
await fyo.db.getStockQuantity(
|
|
||||||
itemMap.Pen.name,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
batchNumber
|
|
||||||
),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Section 3: Test Cancellation of Stock Movements
|
* Section 3: Test Cancellation of Stock Movements
|
||||||
*/
|
*/
|
||||||
@ -399,7 +214,6 @@ async function runEntries(
|
|||||||
item: string;
|
item: string;
|
||||||
to?: string;
|
to?: string;
|
||||||
from?: string;
|
from?: string;
|
||||||
batchNumber?: string;
|
|
||||||
quantity: number;
|
quantity: number;
|
||||||
rate: number;
|
rate: number;
|
||||||
}[];
|
}[];
|
||||||
@ -435,7 +249,6 @@ test('create stock movements, invalid entries, in sequence', async (t) => {
|
|||||||
{
|
{
|
||||||
item,
|
item,
|
||||||
to: locationMap.LocationOne,
|
to: locationMap.LocationOne,
|
||||||
batchNumber: batchNumberMap.batchNumberOne.name,
|
|
||||||
quantity,
|
quantity,
|
||||||
rate,
|
rate,
|
||||||
},
|
},
|
||||||
@ -451,7 +264,6 @@ test('create stock movements, invalid entries, in sequence', async (t) => {
|
|||||||
item,
|
item,
|
||||||
from: locationMap.LocationOne,
|
from: locationMap.LocationOne,
|
||||||
to: locationMap.LocationTwo,
|
to: locationMap.LocationTwo,
|
||||||
batchNumber: batchNumberMap.batchNumberOne.name,
|
|
||||||
quantity: quantity + 1,
|
quantity: quantity + 1,
|
||||||
rate,
|
rate,
|
||||||
},
|
},
|
||||||
@ -466,7 +278,6 @@ test('create stock movements, invalid entries, in sequence', async (t) => {
|
|||||||
{
|
{
|
||||||
item,
|
item,
|
||||||
from: locationMap.LocationOne,
|
from: locationMap.LocationOne,
|
||||||
batchNumber: batchNumberMap.batchNumberOne.name,
|
|
||||||
quantity: quantity + 1,
|
quantity: quantity + 1,
|
||||||
rate,
|
rate,
|
||||||
},
|
},
|
||||||
@ -481,7 +292,6 @@ test('create stock movements, invalid entries, in sequence', async (t) => {
|
|||||||
{
|
{
|
||||||
item,
|
item,
|
||||||
from: locationMap.LocationOne,
|
from: locationMap.LocationOne,
|
||||||
batchNumber: batchNumberMap.batchNumberOne.name,
|
|
||||||
to: locationMap.LocationTwo,
|
to: locationMap.LocationTwo,
|
||||||
quantity,
|
quantity,
|
||||||
rate,
|
rate,
|
||||||
@ -497,7 +307,6 @@ test('create stock movements, invalid entries, in sequence', async (t) => {
|
|||||||
{
|
{
|
||||||
item,
|
item,
|
||||||
from: locationMap.LocationTwo,
|
from: locationMap.LocationTwo,
|
||||||
batchNumber: batchNumberMap.batchNumberOne.name,
|
|
||||||
quantity,
|
quantity,
|
||||||
rate,
|
rate,
|
||||||
},
|
},
|
||||||
@ -562,28 +371,4 @@ test('create stock movements, invalid entries, out of sequence', async (t) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('create stock movements, material issue, insufficient quantity', async (t) => {
|
|
||||||
const { name, rate } = itemMap.Pen;
|
|
||||||
const quantity = 2;
|
|
||||||
const batchNumber = batchNumberMap.batchNumberOne.name;
|
|
||||||
|
|
||||||
const stockMovement = await getStockMovement(
|
|
||||||
MovementType.MaterialIssue,
|
|
||||||
new Date('2022-11-03T09:59:04.528'),
|
|
||||||
[
|
|
||||||
{
|
|
||||||
item: itemMap.Pen.name,
|
|
||||||
from: locationMap.LocationTwo,
|
|
||||||
batchNumber,
|
|
||||||
quantity,
|
|
||||||
rate,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
fyo
|
|
||||||
);
|
|
||||||
|
|
||||||
await assertThrows(async () => (await stockMovement.sync()).submit());
|
|
||||||
t.equal(await fyo.db.getStockQuantity(name), 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
closeTestFyo(fyo, __filename);
|
closeTestFyo(fyo, __filename);
|
||||||
|
@ -22,7 +22,7 @@ export interface SMTransferDetails {
|
|||||||
item: string;
|
item: string;
|
||||||
rate: Money;
|
rate: Money;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
batchNumber?: string;
|
batch?: string;
|
||||||
fromLocation?: string;
|
fromLocation?: string;
|
||||||
toLocation?: string;
|
toLocation?: string;
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ export enum ModelNameEnum {
|
|||||||
AccountingLedgerEntry = 'AccountingLedgerEntry',
|
AccountingLedgerEntry = 'AccountingLedgerEntry',
|
||||||
AccountingSettings = 'AccountingSettings',
|
AccountingSettings = 'AccountingSettings',
|
||||||
Address = 'Address',
|
Address = 'Address',
|
||||||
BatchNumber= 'BatchNumber',
|
Batch= 'Batch',
|
||||||
Color = 'Color',
|
Color = 'Color',
|
||||||
CompanySettings = 'CompanySettings',
|
CompanySettings = 'CompanySettings',
|
||||||
Currency = 'Currency',
|
Currency = 'Currency',
|
||||||
|
@ -25,9 +25,11 @@ export class StockBalance extends StockLedger {
|
|||||||
const filters = {
|
const filters = {
|
||||||
item: this.item,
|
item: this.item,
|
||||||
location: this.location,
|
location: this.location,
|
||||||
|
batch: this.batch,
|
||||||
fromDate: this.fromDate,
|
fromDate: this.fromDate,
|
||||||
toDate: this.toDate,
|
toDate: this.toDate,
|
||||||
};
|
};
|
||||||
|
|
||||||
const rawData = getStockBalanceEntries(this._rawData ?? [], filters);
|
const rawData = getStockBalanceEntries(this._rawData ?? [], filters);
|
||||||
|
|
||||||
return rawData.map((sbe, i) => {
|
return rawData.map((sbe, i) => {
|
||||||
@ -41,7 +43,7 @@ export class StockBalance extends StockLedger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getFilters(): Field[] {
|
getFilters(): Field[] {
|
||||||
return [
|
const filters = [
|
||||||
{
|
{
|
||||||
fieldtype: 'Link',
|
fieldtype: 'Link',
|
||||||
target: 'Item',
|
target: 'Item',
|
||||||
@ -56,6 +58,17 @@ export class StockBalance extends StockLedger {
|
|||||||
label: t`Location`,
|
label: t`Location`,
|
||||||
fieldname: 'location',
|
fieldname: 'location',
|
||||||
},
|
},
|
||||||
|
...(this.hasBatches
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
fieldtype: 'Link',
|
||||||
|
target: 'Batch',
|
||||||
|
placeholder: t`Batch`,
|
||||||
|
label: t`Batch`,
|
||||||
|
fieldname: 'batch',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
{
|
{
|
||||||
fieldtype: 'Date',
|
fieldtype: 'Date',
|
||||||
placeholder: t`From Date`,
|
placeholder: t`From Date`,
|
||||||
@ -69,6 +82,8 @@ export class StockBalance extends StockLedger {
|
|||||||
fieldname: 'toDate',
|
fieldname: 'toDate',
|
||||||
},
|
},
|
||||||
] as Field[];
|
] as Field[];
|
||||||
|
|
||||||
|
return filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
getColumns(): ColumnField[] {
|
getColumns(): ColumnField[] {
|
||||||
@ -89,6 +104,11 @@ export class StockBalance extends StockLedger {
|
|||||||
label: 'Location',
|
label: 'Location',
|
||||||
fieldtype: 'Link',
|
fieldtype: 'Link',
|
||||||
},
|
},
|
||||||
|
...(this.hasBatches
|
||||||
|
? ([
|
||||||
|
{ fieldname: 'batch', label: 'Batch', fieldtype: 'Link' },
|
||||||
|
] as ColumnField[])
|
||||||
|
: []),
|
||||||
{
|
{
|
||||||
fieldname: 'balanceQuantity',
|
fieldname: 'balanceQuantity',
|
||||||
label: 'Balance Qty.',
|
label: 'Balance Qty.',
|
||||||
|
@ -3,6 +3,7 @@ import { RawValueMap } from 'fyo/core/types';
|
|||||||
import { Action } from 'fyo/model/types';
|
import { Action } from 'fyo/model/types';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
import { InventorySettings } from 'models/inventory/InventorySettings';
|
||||||
import { ValuationMethod } from 'models/inventory/types';
|
import { ValuationMethod } from 'models/inventory/types';
|
||||||
import { ModelNameEnum } from 'models/types';
|
import { ModelNameEnum } from 'models/types';
|
||||||
import getCommonExportActions from 'reports/commonExporter';
|
import getCommonExportActions from 'reports/commonExporter';
|
||||||
@ -26,6 +27,7 @@ export class StockLedger extends Report {
|
|||||||
|
|
||||||
item?: string;
|
item?: string;
|
||||||
location?: string;
|
location?: string;
|
||||||
|
batch?: string;
|
||||||
fromDate?: string;
|
fromDate?: string;
|
||||||
toDate?: string;
|
toDate?: string;
|
||||||
ascending?: boolean;
|
ascending?: boolean;
|
||||||
@ -34,6 +36,11 @@ export class StockLedger extends Report {
|
|||||||
|
|
||||||
groupBy: 'none' | 'item' | 'location' = 'none';
|
groupBy: 'none' | 'item' | 'location' = 'none';
|
||||||
|
|
||||||
|
get hasBatches(): boolean {
|
||||||
|
return !!(this.fyo.singles.InventorySettings as InventorySettings)
|
||||||
|
.enableBatches;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(fyo: Fyo) {
|
constructor(fyo: Fyo) {
|
||||||
super(fyo);
|
super(fyo);
|
||||||
this._setObservers();
|
this._setObservers();
|
||||||
@ -110,6 +117,10 @@ export class StockLedger extends Report {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.batch && row.batch !== this.batch) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const date = row.date.valueOf();
|
const date = row.date.valueOf();
|
||||||
if (toDate && date > toDate) {
|
if (toDate && date > toDate) {
|
||||||
continue;
|
continue;
|
||||||
@ -260,11 +271,11 @@ export class StockLedger extends Report {
|
|||||||
label: 'Location',
|
label: 'Location',
|
||||||
fieldtype: 'Link',
|
fieldtype: 'Link',
|
||||||
},
|
},
|
||||||
{
|
...(this.hasBatches
|
||||||
fieldname: 'batchNumber',
|
? ([
|
||||||
label: 'Batch No.',
|
{ fieldname: 'batch', label: 'Batch', fieldtype: 'Link' },
|
||||||
fieldtype: 'Link',
|
] as ColumnField[])
|
||||||
},
|
: []),
|
||||||
{
|
{
|
||||||
fieldname: 'quantity',
|
fieldname: 'quantity',
|
||||||
label: 'Quantity',
|
label: 'Quantity',
|
||||||
@ -344,6 +355,17 @@ export class StockLedger extends Report {
|
|||||||
label: t`Location`,
|
label: t`Location`,
|
||||||
fieldname: 'location',
|
fieldname: 'location',
|
||||||
},
|
},
|
||||||
|
...(this.hasBatches
|
||||||
|
? ([
|
||||||
|
{
|
||||||
|
fieldtype: 'Link',
|
||||||
|
target: 'Batch',
|
||||||
|
placeholder: t`Batch`,
|
||||||
|
label: t`Batch`,
|
||||||
|
fieldname: 'batch',
|
||||||
|
},
|
||||||
|
] as Field[])
|
||||||
|
: []),
|
||||||
{
|
{
|
||||||
fieldtype: 'Date',
|
fieldtype: 'Date',
|
||||||
placeholder: t`From Date`,
|
placeholder: t`From Date`,
|
||||||
|
@ -11,13 +11,14 @@ import {
|
|||||||
|
|
||||||
type Item = string;
|
type Item = string;
|
||||||
type Location = string;
|
type Location = string;
|
||||||
|
type Batch = string;
|
||||||
|
|
||||||
export async function getRawStockLedgerEntries(fyo: Fyo) {
|
export async function getRawStockLedgerEntries(fyo: Fyo) {
|
||||||
const fieldnames = [
|
const fieldnames = [
|
||||||
'name',
|
'name',
|
||||||
'date',
|
'date',
|
||||||
'item',
|
'item',
|
||||||
'batchNumber',
|
'batch',
|
||||||
'rate',
|
'rate',
|
||||||
'quantity',
|
'quantity',
|
||||||
'location',
|
'location',
|
||||||
@ -37,29 +38,27 @@ export function getStockLedgerEntries(
|
|||||||
valuationMethod: ValuationMethod
|
valuationMethod: ValuationMethod
|
||||||
): ComputedStockLedgerEntry[] {
|
): ComputedStockLedgerEntry[] {
|
||||||
const computedSLEs: ComputedStockLedgerEntry[] = [];
|
const computedSLEs: ComputedStockLedgerEntry[] = [];
|
||||||
const stockQueues: Record<Item, Record<Location, StockQueue>> = {};
|
const stockQueues: Record<
|
||||||
|
Item,
|
||||||
|
Record<Location, Record<Batch, StockQueue>>
|
||||||
|
> = {};
|
||||||
|
|
||||||
for (const sle of rawSLEs) {
|
for (const sle of rawSLEs) {
|
||||||
const name = safeParseInt(sle.name);
|
const name = safeParseInt(sle.name);
|
||||||
const date = new Date(sle.date);
|
const date = new Date(sle.date);
|
||||||
const rate = safeParseFloat(sle.rate);
|
const rate = safeParseFloat(sle.rate);
|
||||||
const {
|
const { item, location, quantity, referenceName, referenceType } = sle;
|
||||||
item,
|
const batch = sle.batch ?? '';
|
||||||
location,
|
|
||||||
batchNumber,
|
|
||||||
quantity,
|
|
||||||
referenceName,
|
|
||||||
referenceType,
|
|
||||||
} = sle;
|
|
||||||
|
|
||||||
if (quantity === 0) {
|
if (quantity === 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
stockQueues[item] ??= {};
|
stockQueues[item] ??= {};
|
||||||
stockQueues[item][location] ??= new StockQueue();
|
stockQueues[item][location] ??= {};
|
||||||
|
stockQueues[item][location][batch] ??= new StockQueue();
|
||||||
|
|
||||||
const q = stockQueues[item][location];
|
const q = stockQueues[item][location][batch];
|
||||||
const initialValue = q.value;
|
const initialValue = q.value;
|
||||||
|
|
||||||
let incomingRate: number | null;
|
let incomingRate: number | null;
|
||||||
@ -88,7 +87,7 @@ export function getStockLedgerEntries(
|
|||||||
|
|
||||||
item,
|
item,
|
||||||
location,
|
location,
|
||||||
batchNumber,
|
batch,
|
||||||
|
|
||||||
quantity,
|
quantity,
|
||||||
balanceQuantity,
|
balanceQuantity,
|
||||||
@ -116,9 +115,13 @@ export function getStockBalanceEntries(
|
|||||||
location?: string;
|
location?: string;
|
||||||
fromDate?: string;
|
fromDate?: string;
|
||||||
toDate?: string;
|
toDate?: string;
|
||||||
|
batch?: string;
|
||||||
}
|
}
|
||||||
): StockBalanceEntry[] {
|
): StockBalanceEntry[] {
|
||||||
const sbeMap: Record<Item, Record<Location, StockBalanceEntry>> = {};
|
const sbeMap: Record<
|
||||||
|
Item,
|
||||||
|
Record<Location, Record<Batch, StockBalanceEntry>>
|
||||||
|
> = {};
|
||||||
|
|
||||||
const fromDate = filters.fromDate ? Date.parse(filters.fromDate) : null;
|
const fromDate = filters.fromDate ? Date.parse(filters.fromDate) : null;
|
||||||
const toDate = filters.toDate ? Date.parse(filters.toDate) : null;
|
const toDate = filters.toDate ? Date.parse(filters.toDate) : null;
|
||||||
@ -132,16 +135,23 @@ export function getStockBalanceEntries(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filters.batch && sle.batch !== filters.batch) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const batch = sle.batch || '';
|
||||||
|
|
||||||
sbeMap[sle.item] ??= {};
|
sbeMap[sle.item] ??= {};
|
||||||
sbeMap[sle.item][sle.location] ??= getSBE(
|
sbeMap[sle.item][sle.location] ??= {};
|
||||||
|
sbeMap[sle.item][sle.location][batch] ??= getSBE(
|
||||||
sle.item,
|
sle.item,
|
||||||
sle.location,
|
sle.location,
|
||||||
sle.batchNumber
|
batch
|
||||||
);
|
);
|
||||||
const date = sle.date.valueOf();
|
const date = sle.date.valueOf();
|
||||||
|
|
||||||
if (fromDate && date < fromDate) {
|
if (fromDate && date < fromDate) {
|
||||||
const sbe = sbeMap[sle.item][sle.location]!;
|
const sbe = sbeMap[sle.item][sle.location][batch];
|
||||||
updateOpeningBalances(sbe, sle);
|
updateOpeningBalances(sbe, sle);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -150,26 +160,28 @@ export function getStockBalanceEntries(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sbe = sbeMap[sle.item][sle.location]!;
|
const sbe = sbeMap[sle.item][sle.location][batch];
|
||||||
updateCurrentBalances(sbe, sle);
|
updateCurrentBalances(sbe, sle);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.values(sbeMap)
|
return Object.values(sbeMap)
|
||||||
.map((sbes) => Object.values(sbes))
|
.map((sbeBatched) =>
|
||||||
.flat();
|
Object.values(sbeBatched).map((sbes) => Object.values(sbes))
|
||||||
|
)
|
||||||
|
.flat(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSBE(
|
function getSBE(
|
||||||
item: string,
|
item: string,
|
||||||
location: string,
|
location: string,
|
||||||
batchNumber: string
|
batch: string
|
||||||
): StockBalanceEntry {
|
): StockBalanceEntry {
|
||||||
return {
|
return {
|
||||||
name: 0,
|
name: 0,
|
||||||
|
|
||||||
item,
|
item,
|
||||||
location,
|
location,
|
||||||
batchNumber,
|
batch,
|
||||||
|
|
||||||
balanceQuantity: 0,
|
balanceQuantity: 0,
|
||||||
balanceValue: 0,
|
balanceValue: 0,
|
||||||
|
@ -5,7 +5,7 @@ export interface RawStockLedgerEntry {
|
|||||||
date: string;
|
date: string;
|
||||||
item: string;
|
item: string;
|
||||||
rate: string;
|
rate: string;
|
||||||
batchNumber: string;
|
batch: string | null;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
location: string;
|
location: string;
|
||||||
referenceName: string;
|
referenceName: string;
|
||||||
@ -20,7 +20,7 @@ export interface ComputedStockLedgerEntry{
|
|||||||
|
|
||||||
item: string;
|
item: string;
|
||||||
location:string;
|
location:string;
|
||||||
batchNumber: string;
|
batch: string;
|
||||||
|
|
||||||
quantity: number;
|
quantity: number;
|
||||||
balanceQuantity: number;
|
balanceQuantity: number;
|
||||||
@ -41,7 +41,7 @@ export interface StockBalanceEntry{
|
|||||||
|
|
||||||
item: string;
|
item: string;
|
||||||
location:string;
|
location:string;
|
||||||
batchNumber: string;
|
batch: string;
|
||||||
|
|
||||||
balanceQuantity: number;
|
balanceQuantity: number;
|
||||||
balanceValue: number;
|
balanceValue: number;
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "BatchNumber",
|
"name": "Batch",
|
||||||
"label": "Batch Number",
|
"label": "Batch",
|
||||||
"naming": "manual",
|
"naming": "manual",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldname": "name",
|
"fieldname": "name",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Batch Number",
|
"label": "Batch",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
@ -48,12 +48,12 @@
|
|||||||
"readOnly": true
|
"readOnly": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "batchNumber",
|
"fieldname": "batch",
|
||||||
"label": "Batch No",
|
"label": "Batch",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"create": true,
|
"create": true,
|
||||||
"target": "BatchNumber",
|
"target": "Batch",
|
||||||
"placeholder": "Batch No"
|
"placeholder": "Batch"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "quantity",
|
"fieldname": "quantity",
|
||||||
@ -134,7 +134,7 @@
|
|||||||
"readOnly": true
|
"readOnly": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tableFields": ["item", "tax", "batchNumber", "quantity", "rate", "amount"],
|
"tableFields": ["item", "tax", "quantity", "rate", "amount"],
|
||||||
"keywordFields": ["item", "tax"],
|
"keywordFields": ["item", "tax"],
|
||||||
"quickEditFields": [
|
"quickEditFields": [
|
||||||
"item",
|
"item",
|
||||||
@ -146,7 +146,7 @@
|
|||||||
|
|
||||||
"transferQuantity",
|
"transferQuantity",
|
||||||
"transferUnit",
|
"transferUnit",
|
||||||
"batchNumber",
|
"batch",
|
||||||
"quantity",
|
"quantity",
|
||||||
"unit",
|
"unit",
|
||||||
"unitConversionFactor",
|
"unitConversionFactor",
|
||||||
|
@ -132,8 +132,8 @@
|
|||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "hasBatchNumber",
|
"fieldname": "hasBatch",
|
||||||
"label": "Has Batch No",
|
"label": "Has Batch",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"default": false,
|
"default": false,
|
||||||
"section": "Inventory"
|
"section": "Inventory"
|
||||||
@ -158,7 +158,6 @@
|
|||||||
"barcode",
|
"barcode",
|
||||||
"hsnCode",
|
"hsnCode",
|
||||||
"trackItem",
|
"trackItem",
|
||||||
"hasBatchNumber",
|
|
||||||
"uom"
|
"uom"
|
||||||
],
|
],
|
||||||
"keywordFields": ["name", "itemType", "for"]
|
"keywordFields": ["name", "itemType", "for"]
|
||||||
|
@ -30,8 +30,8 @@
|
|||||||
"fieldtype": "Check"
|
"fieldtype": "Check"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "displayBatchNumber",
|
"fieldname": "displayBatch",
|
||||||
"label": "Display Batch Number",
|
"label": "Display Batch",
|
||||||
"fieldtype": "Check"
|
"fieldtype": "Check"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -143,7 +143,7 @@
|
|||||||
"logo",
|
"logo",
|
||||||
"displayLogo",
|
"displayLogo",
|
||||||
"displayTaxInvoice",
|
"displayTaxInvoice",
|
||||||
"displayBatchNumber",
|
"displayBatch",
|
||||||
"template",
|
"template",
|
||||||
"color",
|
"color",
|
||||||
"font",
|
"font",
|
||||||
|
@ -50,6 +50,16 @@
|
|||||||
"fieldname": "enableBarcodes",
|
"fieldname": "enableBarcodes",
|
||||||
"label": "Enable Barcodes",
|
"label": "Enable Barcodes",
|
||||||
"fieldtype": "Check"
|
"fieldtype": "Check"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "enableBatches",
|
||||||
|
"label": "Enable Batches",
|
||||||
|
"fieldtype": "Check"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "enableUomConversions",
|
||||||
|
"label": "Enable UOM Conversion",
|
||||||
|
"fieldtype": "Check"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -51,10 +51,10 @@
|
|||||||
"readOnly": true
|
"readOnly": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "batchNumber",
|
"fieldname": "batch",
|
||||||
"label": "Batch No",
|
"label": "Batch",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"target": "BatchNumber",
|
"target": "Batch",
|
||||||
"readOnly": true
|
"readOnly": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -51,10 +51,10 @@
|
|||||||
"readOnly": true
|
"readOnly": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "batchNumber",
|
"fieldname": "batch",
|
||||||
"label": "Batch No",
|
"label": "Batch",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"target": "BatchNumber",
|
"target": "Batch",
|
||||||
"create": true
|
"create": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -84,7 +84,7 @@
|
|||||||
"readOnly": true
|
"readOnly": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tableFields": ["item", "fromLocation", "toLocation", "batchNumber", "quantity", "rate"],
|
"tableFields": ["item", "fromLocation", "toLocation", "quantity", "rate"],
|
||||||
"keywordFields": ["item"],
|
"keywordFields": ["item"],
|
||||||
"quickEditFields": [
|
"quickEditFields": [
|
||||||
"item",
|
"item",
|
||||||
@ -93,7 +93,7 @@
|
|||||||
|
|
||||||
"transferQuantity",
|
"transferQuantity",
|
||||||
"transferUnit",
|
"transferUnit",
|
||||||
"batchNumber",
|
"batch",
|
||||||
"quantity",
|
"quantity",
|
||||||
"unit",
|
"unit",
|
||||||
"unitConversionFactor",
|
"unitConversionFactor",
|
||||||
|
@ -42,10 +42,10 @@
|
|||||||
"readOnly": true
|
"readOnly": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "batchNumber",
|
"fieldname": "batch",
|
||||||
"label": "Batch No",
|
"label": "Batch",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"target": "BatchNumber"
|
"target": "Batch"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "quantity",
|
"fieldname": "quantity",
|
||||||
@ -86,13 +86,13 @@
|
|||||||
"placeholder": "HSN/SAC Code"
|
"placeholder": "HSN/SAC Code"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tableFields": ["item", "location", "batchNumber", "quantity", "rate", "amount"],
|
"tableFields": ["item", "location", "quantity", "rate", "amount"],
|
||||||
"quickEditFields": [
|
"quickEditFields": [
|
||||||
"item",
|
"item",
|
||||||
|
|
||||||
"transferQuantity",
|
"transferQuantity",
|
||||||
"transferUnit",
|
"transferUnit",
|
||||||
"batchNumber",
|
"batch",
|
||||||
"quantity",
|
"quantity",
|
||||||
"unit",
|
"unit",
|
||||||
"unitConversionFactor",
|
"unitConversionFactor",
|
||||||
|
@ -47,7 +47,7 @@ import submittable from './meta/submittable.json';
|
|||||||
import tree from './meta/tree.json';
|
import tree from './meta/tree.json';
|
||||||
import { Schema, SchemaStub } from './types';
|
import { Schema, SchemaStub } from './types';
|
||||||
import InventorySettings from './app/inventory/InventorySettings.json';
|
import InventorySettings from './app/inventory/InventorySettings.json';
|
||||||
import BatchNumber from './app/BatchNumber.json'
|
import Batch from './app/Batch.json'
|
||||||
|
|
||||||
export const coreSchemas: Schema[] = [
|
export const coreSchemas: Schema[] = [
|
||||||
PatchRun as Schema,
|
PatchRun as Schema,
|
||||||
@ -116,5 +116,5 @@ export const appSchemas: Schema[] | SchemaStub[] = [
|
|||||||
PurchaseReceipt as Schema,
|
PurchaseReceipt as Schema,
|
||||||
PurchaseReceiptItem as Schema,
|
PurchaseReceiptItem as Schema,
|
||||||
|
|
||||||
BatchNumber as Schema
|
Batch as Schema
|
||||||
];
|
];
|
||||||
|
@ -83,7 +83,7 @@ export default {
|
|||||||
showHSN: this.showHSN,
|
showHSN: this.showHSN,
|
||||||
displayLogo: this.printSettings.displayLogo,
|
displayLogo: this.printSettings.displayLogo,
|
||||||
displayTaxInvoice: this.printSettings.displayTaxInvoice,
|
displayTaxInvoice: this.printSettings.displayTaxInvoice,
|
||||||
displayBatchNumber: this.printSettings.displayBatchNumber,
|
displayBatch: this.printSettings.displayBatch,
|
||||||
discountAfterTax: this.doc.discountAfterTax,
|
discountAfterTax: this.doc.discountAfterTax,
|
||||||
logo: this.printSettings.logo,
|
logo: this.printSettings.logo,
|
||||||
companyName: this.fyo.singles.AccountingSettings.companyName,
|
companyName: this.fyo.singles.AccountingSettings.companyName,
|
||||||
|
@ -74,9 +74,9 @@
|
|||||||
<div class="py-4 text-end w-2/12">Quantity</div>
|
<div class="py-4 text-end w-2/12">Quantity</div>
|
||||||
<div
|
<div
|
||||||
class="w-3/12 text-end py-4"
|
class="w-3/12 text-end py-4"
|
||||||
v-if="printObject.displayBatchNumber"
|
v-if="printObject.displayBatch"
|
||||||
>
|
>
|
||||||
Batch No
|
Batch
|
||||||
</div>
|
</div>
|
||||||
<div class="py-4 text-end w-3/12">Rate</div>
|
<div class="py-4 text-end w-3/12">Rate</div>
|
||||||
<div class="py-4 text-end w-3/12">Amount</div>
|
<div class="py-4 text-end w-3/12">Amount</div>
|
||||||
@ -93,9 +93,9 @@
|
|||||||
<div class="w-2/12 text-end py-4">{{ row.quantity }}</div>
|
<div class="w-2/12 text-end py-4">{{ row.quantity }}</div>
|
||||||
<div
|
<div
|
||||||
class="w-3/12 text-end py-4"
|
class="w-3/12 text-end py-4"
|
||||||
v-if="printObject.displayBatchNumber"
|
v-if="printObject.displayBatch"
|
||||||
>
|
>
|
||||||
{{ row.batchNumber }}
|
{{ row.batch }}
|
||||||
</div>
|
</div>
|
||||||
<div class="w-3/12 text-end py-4">{{ row.rate }}</div>
|
<div class="w-3/12 text-end py-4">{{ row.rate }}</div>
|
||||||
<div class="w-3/12 text-end py-4">{{ row.amount }}</div>
|
<div class="w-3/12 text-end py-4">{{ row.amount }}</div>
|
||||||
|
@ -68,8 +68,8 @@
|
|||||||
<div class="w-4/12">Item</div>
|
<div class="w-4/12">Item</div>
|
||||||
<div class="w-2/12 text-end" v-if="printObject.showHSN">HSN/SAC</div>
|
<div class="w-2/12 text-end" v-if="printObject.showHSN">HSN/SAC</div>
|
||||||
<div class="w-2/12 text-end">Quantity</div>
|
<div class="w-2/12 text-end">Quantity</div>
|
||||||
<div class="w-3/12 text-end" v-if="printObject.displayBatchNumber">
|
<div class="w-3/12 text-end" v-if="printObject.displayBatch">
|
||||||
Batch No
|
Batch
|
||||||
</div>
|
</div>
|
||||||
<div class="w-3/12 text-end">Rate</div>
|
<div class="w-3/12 text-end">Rate</div>
|
||||||
<div class="w-3/12 text-end">Amount</div>
|
<div class="w-3/12 text-end">Amount</div>
|
||||||
@ -84,8 +84,8 @@
|
|||||||
{{ row.hsnCode }}
|
{{ row.hsnCode }}
|
||||||
</div>
|
</div>
|
||||||
<div class="w-2/12 text-end">{{ row.quantity }}</div>
|
<div class="w-2/12 text-end">{{ row.quantity }}</div>
|
||||||
<div class="w-3/12 text-end" v-if="printObject.displayBatchNumber">
|
<div class="w-3/12 text-end" v-if="printObject.displayBatch">
|
||||||
{{ row.batchNumber }}
|
{{ row.batch }}
|
||||||
</div>
|
</div>
|
||||||
<div class="w-3/12 text-end">{{ row.rate }}</div>
|
<div class="w-3/12 text-end">{{ row.rate }}</div>
|
||||||
<div class="w-3/12 text-end">{{ row.amount }}</div>
|
<div class="w-3/12 text-end">{{ row.amount }}</div>
|
||||||
|
@ -112,8 +112,8 @@
|
|||||||
<div class="w-4/12">Item</div>
|
<div class="w-4/12">Item</div>
|
||||||
<div class="w-2/12 text-end" v-if="printObject.showHSN">HSN/SAC</div>
|
<div class="w-2/12 text-end" v-if="printObject.showHSN">HSN/SAC</div>
|
||||||
<div class="w-2/12 text-end">Quantity</div>
|
<div class="w-2/12 text-end">Quantity</div>
|
||||||
<div class="w-3/12 text-end" v-if="printObject.displayBatchNumber">
|
<div class="w-3/12 text-end" v-if="printObject.displayBatch">
|
||||||
Batch No
|
Batch
|
||||||
</div>
|
</div>
|
||||||
<div class="w-3/12 text-end">Rate</div>
|
<div class="w-3/12 text-end">Rate</div>
|
||||||
<div class="w-3/12 text-end">Amount</div>
|
<div class="w-3/12 text-end">Amount</div>
|
||||||
@ -128,8 +128,8 @@
|
|||||||
{{ row.hsnCode }}
|
{{ row.hsnCode }}
|
||||||
</div>
|
</div>
|
||||||
<div class="w-2/12 text-end">{{ row.quantity }}</div>
|
<div class="w-2/12 text-end">{{ row.quantity }}</div>
|
||||||
<div class="w-3/12 text-end" v-if="printObject.displayBatchNumber">
|
<div class="w-3/12 text-end" v-if="printObject.displayBatch">
|
||||||
{{ row.batchNumber }}
|
{{ row.batch }}
|
||||||
</div>
|
</div>
|
||||||
<div class="w-3/12 text-end">{{ row.rate }}</div>
|
<div class="w-3/12 text-end">{{ row.rate }}</div>
|
||||||
<div class="w-3/12 text-end">{{ row.amount }}</div>
|
<div class="w-3/12 text-end">{{ row.amount }}</div>
|
||||||
|
@ -120,16 +120,26 @@ export default {
|
|||||||
if (this.fieldsChanged.length === 0) {
|
if (this.fieldsChanged.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const fieldnames = this.fieldsChanged.map(({ fieldname }) => fieldname);
|
|
||||||
|
|
||||||
if (
|
const shouleShowReload = this.fieldsChanged
|
||||||
fieldnames.includes('displayPrecision') ||
|
.map(({ fieldname }) => fieldname)
|
||||||
fieldnames.includes('hideGetStarted') ||
|
.some((f) => {
|
||||||
fieldnames.includes('displayPrecision') ||
|
if (f.startsWith('enable')) {
|
||||||
fieldnames.includes('enableDiscounting') ||
|
return true;
|
||||||
fieldnames.includes('enableInventory') ||
|
}
|
||||||
fieldnames.includes('enableBarcodes')
|
|
||||||
) {
|
if (f === 'displayPrecision') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (f === 'hideGetStarted') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (shouleShowReload) {
|
||||||
this.showReloadToast();
|
this.showReloadToast();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -108,6 +108,7 @@ export const docsPathMap: Record<string, string | undefined> = {
|
|||||||
[ModelNameEnum.PurchaseReceipt]: 'inventory/purchase-receipt',
|
[ModelNameEnum.PurchaseReceipt]: 'inventory/purchase-receipt',
|
||||||
StockLedger: 'inventory/stock-ledger',
|
StockLedger: 'inventory/stock-ledger',
|
||||||
StockBalance: 'inventory/stock-balance',
|
StockBalance: 'inventory/stock-balance',
|
||||||
|
[ModelNameEnum.Batch]: 'inventory/batches',
|
||||||
|
|
||||||
// Entries
|
// Entries
|
||||||
Entries: 'entries/entries',
|
Entries: 'entries/entries',
|
||||||
|
@ -230,10 +230,16 @@ function getListViewList(fyo: Fyo): SearchItem[] {
|
|||||||
schemaNames.push(
|
schemaNames.push(
|
||||||
ModelNameEnum.StockMovement,
|
ModelNameEnum.StockMovement,
|
||||||
ModelNameEnum.Shipment,
|
ModelNameEnum.Shipment,
|
||||||
ModelNameEnum.PurchaseReceipt
|
ModelNameEnum.PurchaseReceipt,
|
||||||
|
ModelNameEnum.Location
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasBatch = fyo.doc.singles.InventorySettings?.enableBatches;
|
||||||
|
if (hasBatch) {
|
||||||
|
schemaNames.push(ModelNameEnum.Batch);
|
||||||
|
}
|
||||||
|
|
||||||
if (fyo.store.isDevelopment) {
|
if (fyo.store.isDevelopment) {
|
||||||
schemaNames = Object.keys(fyo.schemaMap) as ModelNameEnum[];
|
schemaNames = Object.keys(fyo.schemaMap) as ModelNameEnum[];
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user