mirror of
https://github.com/frappe/books.git
synced 2026-08-24 02:24:17 -05:00
feat : added Batch Autogeneration
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import { ReadOnlyMap, ValidationMap } from 'fyo/model/types';
|
||||
import { ValidationError } from 'fyo/utils/errors';
|
||||
|
||||
const invalidNumberSeries = /[/\=\?\&\%]/;
|
||||
|
||||
function getPaddedName(prefix: string, next: number, padZeros: number): string {
|
||||
return prefix + next.toString().padStart(padZeros ?? 4, '0');
|
||||
}
|
||||
|
||||
export default class BatchSeries extends Doc {
|
||||
validations: ValidationMap = {
|
||||
name: (value) => {
|
||||
if (typeof value !== 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (invalidNumberSeries.test(value)) {
|
||||
throw new ValidationError(
|
||||
this.fyo
|
||||
.t`The following characters cannot be used ${'/, ?, &, =, %'} in a Number Series name.`
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
setCurrent() {
|
||||
let current = this.get('current') as number | null;
|
||||
|
||||
if (!current) {
|
||||
current = this.get('start') as number;
|
||||
} else {
|
||||
current = current + 1;
|
||||
}
|
||||
|
||||
this.current = current;
|
||||
}
|
||||
|
||||
async next(schemaName: string) {
|
||||
this.setCurrent();
|
||||
const exists = await this.checkIfCurrentExists(schemaName);
|
||||
|
||||
if (exists) {
|
||||
this.current = (this.current as number) + 1;
|
||||
}
|
||||
|
||||
await this.sync();
|
||||
return this.getPaddedName(this.current as number);
|
||||
}
|
||||
|
||||
async checkIfCurrentExists(schemaName: string) {
|
||||
if (!schemaName) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const name = this.getPaddedName(this.current as number);
|
||||
return await this.fyo.db.exists(schemaName, name);
|
||||
}
|
||||
|
||||
getPaddedName(next: number): string {
|
||||
return getPaddedName(this.name as string, next, this.padZeros as number);
|
||||
}
|
||||
|
||||
readOnly: ReadOnlyMap = {
|
||||
referenceType: () => this.inserted,
|
||||
padZeros: () => this.inserted,
|
||||
start: () => this.inserted,
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ModelMap } from 'fyo/model/types';
|
||||
import BatchSeries from './BatchSeries';
|
||||
import NumberSeries from './NumberSeries';
|
||||
import SerialNumberSeries from './SerialNumberSeries';
|
||||
import SystemSettings from './SystemSettings';
|
||||
@@ -6,6 +7,7 @@ import { CustomField } from './CustomField';
|
||||
import { CustomForm } from './CustomForm';
|
||||
|
||||
export const coreModels = {
|
||||
BatchSeries,
|
||||
NumberSeries,
|
||||
SerialNumberSeries,
|
||||
SystemSettings,
|
||||
|
||||
@@ -21,6 +21,13 @@ import { isPesa } from 'fyo/utils';
|
||||
import { PricingRule } from '../PricingRule/PricingRule';
|
||||
import { getItemRateFromPriceList, getPricingRule } from 'models/helpers';
|
||||
import { SalesInvoice } from '../SalesInvoice/SalesInvoice';
|
||||
import { getSuggestedBatchName } from 'models/inventory/helpers';
|
||||
import { ValuationMethod } from 'models/inventory/types';
|
||||
import {
|
||||
getRawStockLedgerEntries,
|
||||
getStockLedgerEntries,
|
||||
getStockBalanceEntries,
|
||||
} from 'reports/inventory/helpers';
|
||||
|
||||
export abstract class InvoiceItem extends Doc {
|
||||
item?: string;
|
||||
@@ -606,15 +613,122 @@ export abstract class InvoiceItem extends Doc {
|
||||
filters: { uom: value as string, parent: this.item },
|
||||
});
|
||||
|
||||
if (item.length < 1)
|
||||
if (item.length < 1) {
|
||||
throw new ValidationError(
|
||||
t`Transfer Unit ${value as string} is not applicable for Item ${
|
||||
this.item
|
||||
}`
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
qty: async (value: DocValue) => {
|
||||
const requiredQuantity = Math.abs(value as number);
|
||||
|
||||
if (!this.item || requiredQuantity <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isSales) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.fyo.singles.InventorySettings?.enableBatches) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.batch) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.validateBatchQuantity(this.batch, requiredQuantity);
|
||||
},
|
||||
|
||||
batch: async (value: DocValue) => {
|
||||
if (!value || !this.item) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isSales) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.fyo.singles.InventorySettings?.enableBatches) {
|
||||
return;
|
||||
}
|
||||
|
||||
const requiredQuantity = this.quantity ?? 0;
|
||||
|
||||
if (requiredQuantity > 0) {
|
||||
await this.validateBatchQuantity(value as string, requiredQuantity);
|
||||
} else if (requiredQuantity < 0) {
|
||||
await this.validateBatchQuantity(
|
||||
value as string,
|
||||
Math.abs(requiredQuantity)
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
async validateBatchQuantity(
|
||||
batchName: string,
|
||||
requiredQuantity: number
|
||||
): Promise<void> {
|
||||
try {
|
||||
let inventoryLocation: string | undefined;
|
||||
|
||||
if (this.location) {
|
||||
inventoryLocation = this.location as string;
|
||||
} else {
|
||||
const posProfileName = this.fyo.singles.POSSettings?.posProfile;
|
||||
if (posProfileName) {
|
||||
const posProfile = await this.fyo.doc.getDoc(
|
||||
ModelNameEnum.POSProfile,
|
||||
posProfileName as string
|
||||
);
|
||||
inventoryLocation = posProfile?.inventory as string | undefined;
|
||||
} else {
|
||||
inventoryLocation = this.fyo.singles.POSSettings?.inventory;
|
||||
}
|
||||
}
|
||||
|
||||
const valuationMethod =
|
||||
(this.fyo.singles.InventorySettings
|
||||
?.valuationMethod as ValuationMethod) ?? ValuationMethod.FIFO;
|
||||
|
||||
const rawSLEs = await getRawStockLedgerEntries(this.fyo);
|
||||
const computedSLEs = getStockLedgerEntries(rawSLEs, valuationMethod);
|
||||
|
||||
const stockBalance = getStockBalanceEntries(computedSLEs, {
|
||||
item: this.item!,
|
||||
location: inventoryLocation,
|
||||
batch: batchName,
|
||||
});
|
||||
|
||||
const availableQuantity = stockBalance.reduce(
|
||||
(sum, entry) => sum + (entry.balanceQuantity || 0),
|
||||
0
|
||||
);
|
||||
|
||||
if (requiredQuantity > availableQuantity) {
|
||||
// ✅ dynamic import just like your example
|
||||
const { showToast } = await import('src/utils/interactive');
|
||||
|
||||
showToast({
|
||||
type: 'warning',
|
||||
message: this.fyo.t`
|
||||
Batch ${batchName} only has ${availableQuantity} quantity available
|
||||
but ${requiredQuantity} is required
|
||||
`,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof ValidationError) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hidden: HiddenMap = {
|
||||
itemDiscountedTotal: () => {
|
||||
if (!this.enableDiscounting) {
|
||||
@@ -655,15 +769,93 @@ export abstract class InvoiceItem extends Doc {
|
||||
return { for: ['not in', [itemNotFor]] };
|
||||
},
|
||||
batch: async (doc: Doc) => {
|
||||
const batches = await doc.fyo.db.getAll(ModelNameEnum.Batch, {
|
||||
fields: ['name'],
|
||||
filters: { item: doc.item as string },
|
||||
});
|
||||
const batchName = batches.map((b) => b.name) as string[];
|
||||
const hasBatch = await doc.fyo.getValue(
|
||||
ModelNameEnum.Item,
|
||||
doc.item as string,
|
||||
'hasBatch'
|
||||
);
|
||||
|
||||
return {
|
||||
name: ['in', batchName],
|
||||
};
|
||||
if (!hasBatch) {
|
||||
return { name: ['in', []] };
|
||||
}
|
||||
|
||||
if (!doc.isSales) {
|
||||
const batchName = await getSuggestedBatchName(
|
||||
doc.fyo,
|
||||
doc.item as string
|
||||
);
|
||||
|
||||
if (batchName) {
|
||||
await doc.set('batch', batchName);
|
||||
|
||||
return {
|
||||
name: ['in', [batchName]],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
let inventoryLocation: string | undefined;
|
||||
|
||||
if (doc.location) {
|
||||
inventoryLocation = doc.location as string;
|
||||
} else {
|
||||
const posProfileName = doc.fyo.singles.POSSettings?.posProfile;
|
||||
if (posProfileName) {
|
||||
const posProfile = await doc.fyo.doc.getDoc(
|
||||
ModelNameEnum.POSProfile,
|
||||
posProfileName as string
|
||||
);
|
||||
inventoryLocation = posProfile?.inventory as string | undefined;
|
||||
} else {
|
||||
inventoryLocation = doc.fyo.singles.POSSettings?.inventory;
|
||||
}
|
||||
}
|
||||
|
||||
const rawSLEs = await getRawStockLedgerEntries(doc.fyo);
|
||||
|
||||
const valuationMethod =
|
||||
(doc.fyo.singles.InventorySettings
|
||||
?.valuationMethod as ValuationMethod) ?? ValuationMethod.FIFO;
|
||||
|
||||
const computedSLEs = getStockLedgerEntries(rawSLEs, valuationMethod);
|
||||
|
||||
const stockBalance = getStockBalanceEntries(computedSLEs, {
|
||||
item: doc.item as string,
|
||||
location: inventoryLocation,
|
||||
});
|
||||
|
||||
const batchesWithStock = stockBalance
|
||||
.filter((entry) => entry.batch && entry.balanceQuantity > 0)
|
||||
.map((entry) => entry.batch);
|
||||
|
||||
if (batchesWithStock.length === 0) {
|
||||
const allBatchesWithStock = stockBalance
|
||||
.filter((entry) => entry.batch && entry.balanceQuantity > 0)
|
||||
.map((entry) => entry.batch);
|
||||
|
||||
if (allBatchesWithStock.length > 0) {
|
||||
return {
|
||||
name: ['in', allBatchesWithStock],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: ['in', batchesWithStock],
|
||||
};
|
||||
} catch (error) {
|
||||
// Fallback to all batches for the item
|
||||
const batches = await doc.fyo.db.getAll(ModelNameEnum.Batch, {
|
||||
fields: ['name'],
|
||||
filters: { item: doc.item as string },
|
||||
});
|
||||
const batchName = batches.map((b) => b.name) as string[];
|
||||
|
||||
return {
|
||||
name: ['in', batchName],
|
||||
};
|
||||
}
|
||||
},
|
||||
transferUnit: async (doc: Doc) => {
|
||||
const conversionItems = await doc.fyo.db.getAll(
|
||||
|
||||
@@ -14,6 +14,10 @@ import { ValidationError } from 'fyo/utils/errors';
|
||||
import { Money } from 'pesa';
|
||||
import { AccountRootTypeEnum, AccountTypeEnum } from '../Account/types';
|
||||
|
||||
function getPaddedName(prefix: string, next: number, padZeros: number): string {
|
||||
return prefix + next.toString().padStart(padZeros ?? 4, '0');
|
||||
}
|
||||
|
||||
interface UOMConversionItem {
|
||||
name: string;
|
||||
uom: string;
|
||||
@@ -26,6 +30,7 @@ export class Item extends Doc {
|
||||
itemType?: 'Product' | 'Service';
|
||||
for?: 'Purchases' | 'Sales' | 'Both';
|
||||
hasBatch?: boolean;
|
||||
batchSeries?: string;
|
||||
itemGroup?: string;
|
||||
hsnCode?: number;
|
||||
hasSerialNumber?: boolean;
|
||||
@@ -100,6 +105,13 @@ export class Item extends Doc {
|
||||
this.serialNumberSeries = series + '-';
|
||||
}
|
||||
}
|
||||
|
||||
if (this.batchSeries && this.hasBatch) {
|
||||
const series = this.batchSeries.trim();
|
||||
if (series && !series.endsWith('-')) {
|
||||
this.batchSeries = series + '-';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async afterSync(): Promise<void> {
|
||||
@@ -125,6 +137,46 @@ export class Item extends Doc {
|
||||
.sync();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.hasBatch && this.batchSeries) {
|
||||
const seriesName = this.batchSeries?.trim();
|
||||
|
||||
if (!seriesName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const exists = await this.fyo.db.exists('BatchSeries', seriesName);
|
||||
|
||||
if (!exists) {
|
||||
await this.fyo.doc
|
||||
.getNewDoc('BatchSeries', {
|
||||
name: seriesName,
|
||||
start: 1001,
|
||||
padZeros: 4,
|
||||
current: 1001,
|
||||
})
|
||||
.sync();
|
||||
|
||||
const batchSeriesDoc = await this.fyo.doc.getDoc(
|
||||
'BatchSeries',
|
||||
seriesName
|
||||
);
|
||||
const start = (batchSeriesDoc?.start as number) ?? 1001;
|
||||
const padZeros = (batchSeriesDoc?.padZeros as number) ?? 4;
|
||||
const batchName = getPaddedName(seriesName, start, padZeros);
|
||||
|
||||
const batchExists = await this.fyo.db.exists('Batch', batchName);
|
||||
|
||||
if (!batchExists) {
|
||||
await this.fyo.doc
|
||||
.getNewDoc('Batch', {
|
||||
name: batchName,
|
||||
item: this.name as string,
|
||||
})
|
||||
.sync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static filters: FiltersMap = {
|
||||
@@ -173,6 +225,21 @@ export class Item extends Doc {
|
||||
);
|
||||
}
|
||||
},
|
||||
batchSeries: (value: DocValue) => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const series = (value as string).trim();
|
||||
const invalidChars = /[/\=\?\&\%]/;
|
||||
|
||||
if (invalidChars.test(series)) {
|
||||
throw new ValidationError(
|
||||
this.fyo
|
||||
.t`Batch Series cannot contain the following characters: /, ?, &, =, %`
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
static getActions(fyo: Fyo): Action[] {
|
||||
@@ -226,6 +293,7 @@ export class Item extends Doc {
|
||||
this.fyo.singles.InventorySettings?.enableSerialNumber && this.trackItem
|
||||
),
|
||||
serialNumberSeries: () => !this.hasSerialNumber,
|
||||
batchSeries: () => !this.hasBatch,
|
||||
uomConversions: () =>
|
||||
!this.fyo.singles.InventorySettings?.enableUomConversions,
|
||||
itemGroup: () => !this.fyo.singles.AccountingSettings?.enableitemGroup,
|
||||
|
||||
@@ -5,10 +5,44 @@ import { ModelNameEnum } from 'models/types';
|
||||
import { getInvoiceActions, getTransactionStatusColumn } from '../../helpers';
|
||||
import { Invoice } from '../Invoice/Invoice';
|
||||
import { PurchaseInvoiceItem } from '../PurchaseInvoiceItem/PurchaseInvoiceItem';
|
||||
import { createBatch } from 'models/inventory/helpers';
|
||||
|
||||
export class PurchaseInvoice extends Invoice {
|
||||
items?: PurchaseInvoiceItem[];
|
||||
|
||||
async beforeSubmit(): Promise<void> {
|
||||
await super.beforeSubmit();
|
||||
|
||||
if (this.isReturn) {
|
||||
return;
|
||||
}
|
||||
|
||||
const batchesToCreate: { item: string; batch: string }[] = [];
|
||||
|
||||
for (const item of this.items ?? []) {
|
||||
if (!item.item || !item.batch) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const hasBatch = await this.fyo.getValue(
|
||||
ModelNameEnum.Item,
|
||||
item.item,
|
||||
'hasBatch'
|
||||
);
|
||||
|
||||
if (hasBatch) {
|
||||
batchesToCreate.push({
|
||||
item: item.item,
|
||||
batch: item.batch,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const { item, batch } of batchesToCreate) {
|
||||
await createBatch(this.fyo, item, batch);
|
||||
}
|
||||
}
|
||||
|
||||
async getPosting() {
|
||||
const exchangeRate = this.exchangeRate ?? 1;
|
||||
const posting: LedgerPosting = new LedgerPosting(this, this.fyo);
|
||||
|
||||
+11
-1
@@ -48,7 +48,10 @@ import {
|
||||
getStockLedgerEntries,
|
||||
} from 'reports/inventory/helpers';
|
||||
import { LoyaltyPointEntry } from './baseModels/LoyaltyPointEntry/LoyaltyPointEntry';
|
||||
import { generateSerialNumbersForItem } from './inventory/helpers';
|
||||
import {
|
||||
generateSerialNumbersForItem,
|
||||
generateBatchForItem,
|
||||
} from './inventory/helpers';
|
||||
|
||||
export function getQuoteActions(
|
||||
fyo: Fyo,
|
||||
@@ -761,6 +764,13 @@ export async function addItem<M extends ModelsWithItems>(name: string, doc: M) {
|
||||
|
||||
await item.set('item', name);
|
||||
|
||||
if (doc instanceof Invoice && !doc.isSales) {
|
||||
const batchName = await generateBatchForItem(doc.fyo, name);
|
||||
if (batchName) {
|
||||
await item.set('batch', batchName);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
doc instanceof StockTransfer &&
|
||||
doc.schemaName === ModelNameEnum.PurchaseReceipt
|
||||
|
||||
@@ -11,6 +11,7 @@ import type { StockTransferItem } from './StockTransferItem';
|
||||
import { Transfer } from './Transfer';
|
||||
import { TransferItem } from './TransferItem';
|
||||
import type { SerialNumberStatus } from './types';
|
||||
import BatchSeries from 'fyo/models/BatchSeries';
|
||||
import SerialNumberSeries from 'fyo/models/SerialNumberSeries';
|
||||
|
||||
export async function validateBatch(
|
||||
@@ -19,6 +20,33 @@ export async function validateBatch(
|
||||
if (doc.schemaName === ModelNameEnum.SalesQuote) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
doc.schemaName === ModelNameEnum.PurchaseInvoice ||
|
||||
doc.schemaName === ModelNameEnum.PurchaseReceipt
|
||||
) {
|
||||
for (const row of doc.items ?? []) {
|
||||
if (row.item && row.batch) {
|
||||
const hasBatch = await doc.fyo.getValue(
|
||||
ModelNameEnum.Item,
|
||||
row.item,
|
||||
'hasBatch'
|
||||
);
|
||||
|
||||
if (hasBatch) {
|
||||
const batchExists = await doc.fyo.db.exists(
|
||||
ModelNameEnum.Batch,
|
||||
row.batch
|
||||
);
|
||||
|
||||
if (!batchExists) {
|
||||
await createBatch(doc.fyo, row.item, row.batch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const row of doc.items ?? []) {
|
||||
await validateItemRowBatch(row);
|
||||
}
|
||||
@@ -531,3 +559,140 @@ export async function getExistingActiveSerialNumbersForItem(
|
||||
|
||||
return selectedSerialNumbers.join('\n');
|
||||
}
|
||||
|
||||
export async function getSuggestedBatchName(
|
||||
fyo: Fyo,
|
||||
itemName: string
|
||||
): Promise<string | undefined> {
|
||||
try {
|
||||
const batchSeries = await fyo.getValue(
|
||||
ModelNameEnum.Item,
|
||||
itemName,
|
||||
'batchSeries'
|
||||
);
|
||||
|
||||
if (!batchSeries) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const seriesName = (batchSeries as string).trim();
|
||||
const seriesExists = await fyo.db.exists('BatchSeries', seriesName);
|
||||
|
||||
if (!seriesExists) {
|
||||
await fyo.doc
|
||||
.getNewDoc('BatchSeries', {
|
||||
name: seriesName,
|
||||
start: 1001,
|
||||
padZeros: 4,
|
||||
current: 1001,
|
||||
})
|
||||
.sync();
|
||||
}
|
||||
|
||||
const batchSeriesDoc = (await fyo.doc.getDoc(
|
||||
'BatchSeries',
|
||||
seriesName
|
||||
)) as BatchSeries;
|
||||
|
||||
const padZeros = (batchSeriesDoc.padZeros as number) ?? 4;
|
||||
|
||||
const prefix = seriesName.endsWith('-') ? seriesName : seriesName + '-';
|
||||
const existingBatches = (await fyo.db.getAllRaw(ModelNameEnum.Batch, {
|
||||
fields: ['name'],
|
||||
filters: { item: itemName },
|
||||
})) as { name: string }[];
|
||||
|
||||
let nextNumber: number;
|
||||
|
||||
if (existingBatches && existingBatches.length > 0) {
|
||||
let highestNumber = -1;
|
||||
|
||||
for (const batch of existingBatches) {
|
||||
const batchName = batch.name;
|
||||
if (batchName.startsWith(prefix)) {
|
||||
const numericPart = batchName.substring(prefix.length);
|
||||
const num = parseInt(numericPart, 10);
|
||||
|
||||
if (!isNaN(num) && num > highestNumber) {
|
||||
highestNumber = num;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (highestNumber >= 0) {
|
||||
nextNumber = highestNumber + 1;
|
||||
} else {
|
||||
nextNumber = (batchSeriesDoc.start as number) ?? 1001;
|
||||
}
|
||||
} else {
|
||||
nextNumber = (batchSeriesDoc.start as number) ?? 1001;
|
||||
}
|
||||
|
||||
const batchName = prefix + nextNumber.toString().padStart(padZeros, '0');
|
||||
|
||||
return batchName;
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function createBatch(
|
||||
fyo: Fyo,
|
||||
itemName: string,
|
||||
batchName: string
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const batchExists = await fyo.db.exists(ModelNameEnum.Batch, batchName);
|
||||
if (batchExists) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const batchDoc = fyo.doc.getNewDoc('Batch', {
|
||||
name: batchName,
|
||||
item: itemName,
|
||||
});
|
||||
|
||||
await batchDoc.sync();
|
||||
|
||||
const batchSeries = await fyo.getValue(
|
||||
ModelNameEnum.Item,
|
||||
itemName,
|
||||
'batchSeries'
|
||||
);
|
||||
|
||||
if (batchSeries) {
|
||||
const seriesName = (batchSeries as string).trim();
|
||||
const batchSeriesDoc = (await fyo.doc.getDoc(
|
||||
'BatchSeries',
|
||||
seriesName
|
||||
)) as BatchSeries;
|
||||
|
||||
const prefix = seriesName.endsWith('-') ? seriesName : seriesName + '-';
|
||||
if (batchName.startsWith(prefix)) {
|
||||
const numericPart = batchName.substring(prefix.length);
|
||||
const num = parseInt(numericPart, 10);
|
||||
if (!isNaN(num)) {
|
||||
await batchSeriesDoc.set('current', num);
|
||||
await batchSeriesDoc.sync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateBatchForItem(
|
||||
fyo: Fyo,
|
||||
itemName: string
|
||||
): Promise<string | undefined> {
|
||||
const batchName = await getSuggestedBatchName(fyo, itemName);
|
||||
if (!batchName) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const success = await createBatch(fyo, itemName, batchName);
|
||||
return success ? batchName : undefined;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ export enum ModelNameEnum {
|
||||
AccountingSettings = 'AccountingSettings',
|
||||
Address = 'Address',
|
||||
Batch = 'Batch',
|
||||
BatchSeries = 'BatchSeries',
|
||||
Color = 'Color',
|
||||
Currency = 'Currency',
|
||||
GetStarted = 'GetStarted',
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "BatchSeries",
|
||||
"label": "Batch Series",
|
||||
"naming": "manual",
|
||||
"isSingle": false,
|
||||
"isChild": false,
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "name",
|
||||
"label": "Prefix",
|
||||
"fieldtype": "Data",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"fieldname": "start",
|
||||
"label": "Start",
|
||||
"fieldtype": "Int",
|
||||
"default": 1001,
|
||||
"required": true,
|
||||
"minvalue": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "padZeros",
|
||||
"label": "Pad Zeros",
|
||||
"fieldtype": "Int",
|
||||
"default": 4,
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"fieldname": "current",
|
||||
"label": "Current",
|
||||
"fieldtype": "Int",
|
||||
"required": true,
|
||||
"readOnly": true
|
||||
}
|
||||
],
|
||||
"quickEditFields": ["start", "padZeros"]
|
||||
}
|
||||
@@ -153,6 +153,12 @@
|
||||
"default": false,
|
||||
"section": "Inventory"
|
||||
},
|
||||
{
|
||||
"fieldname": "batchSeries",
|
||||
"label": "Batch Series",
|
||||
"fieldtype": "Data",
|
||||
"section": "Inventory"
|
||||
},
|
||||
{
|
||||
"fieldname": "hasSerialNumber",
|
||||
"label": "Has Serial Number",
|
||||
|
||||
@@ -3,6 +3,7 @@ import AccountingLedgerEntry from './app/AccountingLedgerEntry.json';
|
||||
import AccountingSettings from './app/AccountingSettings.json';
|
||||
import Address from './app/Address.json';
|
||||
import Batch from './app/Batch.json';
|
||||
import BatchSeries from './app/BatchSeries.json';
|
||||
import Color from './app/Color.json';
|
||||
import Currency from './app/Currency.json';
|
||||
import Defaults from './app/Defaults.json';
|
||||
@@ -108,6 +109,7 @@ export const appSchemas: Schema[] | SchemaStub[] = [
|
||||
Defaults as Schema,
|
||||
NumberSeries as Schema,
|
||||
SerialNumberSeries as Schema,
|
||||
BatchSeries as Schema,
|
||||
|
||||
PrintSettings as Schema,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user