2
0
mirror of https://github.com/frappe/books.git synced 2024-11-14 09:24:04 +00:00
books/models/inventory/helpers.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

334 lines
8.2 KiB
TypeScript
Raw Normal View History

2023-05-04 10:45:12 +00:00
import { Fyo, t } from 'fyo';
2023-02-27 13:09:18 +00:00
import { ValidationError } from 'fyo/utils/errors';
2023-05-04 10:45:12 +00:00
import type { Invoice } from 'models/baseModels/Invoice/Invoice';
import type { InvoiceItem } from 'models/baseModels/InvoiceItem/InvoiceItem';
2023-02-27 13:09:18 +00:00
import { ModelNameEnum } from 'models/types';
2023-05-04 10:45:12 +00:00
import { SerialNumber } from './SerialNumber';
import type { StockMovement } from './StockMovement';
import type { StockMovementItem } from './StockMovementItem';
import type { StockTransfer } from './StockTransfer';
import type { StockTransferItem } from './StockTransferItem';
import { Transfer } from './Transfer';
import { TransferItem } from './TransferItem';
2023-05-04 10:45:12 +00:00
import type { SerialNumberStatus } from './types';
2022-10-28 08:04:08 +00:00
2023-02-28 06:01:04 +00:00
export async function validateBatch(
2023-02-27 13:09:18 +00:00
doc: StockMovement | StockTransfer | Invoice
) {
for (const row of doc.items ?? []) {
2023-02-28 06:01:04 +00:00
await validateItemRowBatch(row);
2023-02-27 13:09:18 +00:00
}
}
2023-02-28 06:01:04 +00:00
async function validateItemRowBatch(
2023-02-27 13:09:18 +00:00
doc: StockMovementItem | StockTransferItem | InvoiceItem
) {
2023-05-04 10:45:12 +00:00
const idx = doc.idx ?? 0;
2023-02-27 13:09:18 +00:00
const item = doc.item;
2023-02-28 06:01:04 +00:00
const batch = doc.batch;
2023-02-27 13:46:04 +00:00
if (!item) {
2023-02-27 13:09:18 +00:00
return;
}
2023-04-25 07:07:29 +00:00
const hasBatch = await doc.fyo.getValue(ModelNameEnum.Item, item, 'hasBatch');
2023-02-27 13:09:18 +00:00
2023-02-28 06:01:04 +00:00
if (!hasBatch && batch) {
2023-02-27 13:46:04 +00:00
throw new ValidationError(
[
2023-05-04 10:45:12 +00:00
doc.fyo.t`Batch set for row ${idx + 1}.`,
2023-02-27 13:46:04 +00:00
doc.fyo.t`Item ${item} is not a batched item`,
].join(' ')
);
2023-02-27 13:09:18 +00:00
}
2023-02-28 06:01:04 +00:00
if (hasBatch && !batch) {
2023-02-27 13:46:04 +00:00
throw new ValidationError(
[
2023-05-04 10:45:12 +00:00
doc.fyo.t`Batch not set for row ${idx + 1}.`,
2023-02-27 13:46:04 +00:00
doc.fyo.t`Item ${item} is a batched item`,
].join(' ')
);
}
2023-02-27 13:09:18 +00:00
}
2023-04-25 07:07:29 +00:00
2023-05-04 10:45:12 +00:00
export async function validateSerialNumber(doc: StockMovement | StockTransfer) {
2023-05-08 09:37:13 +00:00
if (doc.isCancelled) {
return;
}
2023-04-25 07:07:29 +00:00
for (const row of doc.items ?? []) {
2023-05-04 10:45:12 +00:00
await validateItemRowSerialNumber(row);
2023-04-25 07:07:29 +00:00
}
}
2023-05-04 10:45:12 +00:00
async function validateItemRowSerialNumber(
row: StockMovementItem | StockTransferItem
2023-04-25 07:07:29 +00:00
) {
2023-05-04 10:45:12 +00:00
const idx = row.idx ?? 0;
const item = row.item;
2023-04-25 07:07:29 +00:00
if (!item) {
return;
}
2023-05-04 10:45:12 +00:00
const hasSerialNumber = await row.fyo.getValue(
2023-04-25 07:07:29 +00:00
ModelNameEnum.Item,
item,
2023-05-04 10:45:12 +00:00
'hasSerialNumber'
2023-04-25 07:07:29 +00:00
);
2023-05-04 10:45:12 +00:00
if (hasSerialNumber && !row.serialNumber) {
2023-04-25 07:07:29 +00:00
throw new ValidationError(
[
2023-05-04 10:45:12 +00:00
row.fyo.t`Serial Number not set for row ${idx + 1}.`,
row.fyo.t`Serial Number is enabled for Item ${item}`,
2023-04-25 07:07:29 +00:00
].join(' ')
);
}
2023-05-04 10:45:12 +00:00
if (!hasSerialNumber && row.serialNumber) {
2023-04-25 07:07:29 +00:00
throw new ValidationError(
[
2023-05-04 10:45:12 +00:00
row.fyo.t`Serial Number set for row ${idx + 1}.`,
row.fyo.t`Serial Number is not enabled for Item ${item}`,
2023-04-25 07:07:29 +00:00
].join(' ')
);
}
2023-05-04 10:45:12 +00:00
const serialNumber = row.serialNumber;
if (!hasSerialNumber || typeof serialNumber !== 'string') {
return;
}
const serialNumbers = getSerialNumbers(serialNumber);
2023-04-25 07:07:29 +00:00
const quantity = Math.abs(row.quantity ?? 0);
2023-05-04 10:45:12 +00:00
if (serialNumbers.length !== quantity) {
2023-04-25 07:07:29 +00:00
throw new ValidationError(
2023-05-04 10:45:12 +00:00
t`Additional ${
quantity - serialNumbers.length
} Serial Numbers required for ${quantity} quantity of ${item}.`
2023-04-25 07:07:29 +00:00
);
}
const nonExistingIncomingSerialNumbers: string[] = [];
for (const serialNumber of serialNumbers) {
if (await row.fyo.db.exists(ModelNameEnum.SerialNumber, serialNumber)) {
continue;
}
if (isSerialNumberIncoming(row)) {
nonExistingIncomingSerialNumbers.push(serialNumber);
continue;
}
throw new ValidationError(t`Serial Number ${serialNumber} does not exist.`);
}
2023-05-04 10:45:12 +00:00
for (const serialNumber of serialNumbers) {
if (nonExistingIncomingSerialNumbers.includes(serialNumber)) {
continue;
}
2023-05-04 10:45:12 +00:00
const snDoc = await row.fyo.doc.getDoc(
ModelNameEnum.SerialNumber,
serialNumber
2023-04-25 07:07:29 +00:00
);
2023-05-04 10:45:12 +00:00
if (!(snDoc instanceof SerialNumber)) {
continue;
2023-04-25 07:07:29 +00:00
}
2023-05-04 10:45:12 +00:00
if (snDoc.item !== item) {
throw new ValidationError(
t`Serial Number ${serialNumber} does not belong to the item ${item}.`
2023-04-25 07:07:29 +00:00
);
}
2023-05-04 10:45:12 +00:00
const status = snDoc.status ?? 'Inactive';
const schemaName = row.parentSchemaName;
const isReturn = !!row.parentdoc?.returnAgainst;
const isSubmitted = !!row.parentdoc?.submitted;
if (
schemaName === 'PurchaseReceipt' &&
status !== 'Inactive' &&
!isSubmitted &&
!isReturn
) {
2023-05-04 10:45:12 +00:00
throw new ValidationError(
t`Serial Number ${serialNumber} is not Inactive`
);
2023-04-25 07:07:29 +00:00
}
if (
schemaName === 'Shipment' &&
status !== 'Active' &&
!isSubmitted &&
!isReturn
) {
2023-05-04 10:45:12 +00:00
throw new ValidationError(
t`Serial Number ${serialNumber} is not Active.`
2023-04-25 07:07:29 +00:00
);
}
}
}
2023-05-04 10:45:12 +00:00
export function getSerialNumbers(serialNumber: string): string[] {
if (!serialNumber) {
return [];
2023-04-25 07:07:29 +00:00
}
2023-05-04 10:45:12 +00:00
return serialNumber
.split('\n')
.map((s) => s.trim())
.filter(Boolean);
2023-05-04 10:45:12 +00:00
}
export function getSerialNumberFromDoc(doc: StockTransfer | StockMovement) {
if (!doc.items?.length) {
return [];
2023-04-25 07:07:29 +00:00
}
2023-05-04 10:45:12 +00:00
return doc.items
.map((item) =>
getSerialNumbers(item.serialNumber ?? '').map((serialNumber) => ({
serialNumber,
item,
}))
)
2023-05-04 10:45:12 +00:00
.flat()
.filter(Boolean);
}
export async function createSerialNumbers(doc: Transfer) {
const items = doc.items ?? [];
const serialNumberCreateList = items
.map((item) => {
const serialNumbers = getSerialNumbers(item.serialNumber ?? '');
return serialNumbers.map((serialNumber) => ({
item: item.item ?? '',
serialNumber,
isIncoming: isSerialNumberIncoming(item),
}));
})
.flat()
.filter(({ item, isIncoming }) => isIncoming && item);
for (const { item, serialNumber } of serialNumberCreateList) {
if (await doc.fyo.db.exists(ModelNameEnum.SerialNumber, serialNumber)) {
continue;
}
const snDoc = doc.fyo.doc.getNewDoc(ModelNameEnum.SerialNumber, {
name: serialNumber,
item,
});
const status: SerialNumberStatus = 'Active';
await snDoc.set('status', status);
await snDoc.sync();
}
}
function isSerialNumberIncoming(item: TransferItem) {
if (item.parentdoc?.schemaName === ModelNameEnum.Shipment) {
return false;
}
if (item.parentdoc?.schemaName === ModelNameEnum.PurchaseReceipt) {
return true;
}
return !!item.toLocation && !item.fromLocation;
}
export async function canValidateSerialNumber(
item: StockTransferItem | StockMovementItem,
serialNumber: string
) {
if (!isSerialNumberIncoming(item)) {
return true;
}
return await item.fyo.db.exists(ModelNameEnum.SerialNumber, serialNumber);
}
2023-05-04 10:45:12 +00:00
export async function updateSerialNumbers(
doc: StockTransfer | StockMovement,
isCancel: boolean,
isReturn = false
2023-05-04 10:45:12 +00:00
) {
for (const row of doc.items ?? []) {
if (!row.serialNumber) {
continue;
}
const status = getSerialNumberStatus(doc, row, isCancel, isReturn);
2023-05-04 10:45:12 +00:00
await updateSerialNumberStatus(status, row.serialNumber, doc.fyo);
2023-04-25 07:07:29 +00:00
}
2023-05-04 10:45:12 +00:00
}
async function updateSerialNumberStatus(
status: SerialNumberStatus,
serialNumber: string,
fyo: Fyo
) {
for (const name of getSerialNumbers(serialNumber)) {
const doc = await fyo.doc.getDoc(ModelNameEnum.SerialNumber, name);
await doc.setAndSync('status', status);
2023-04-25 07:07:29 +00:00
}
2023-05-04 10:45:12 +00:00
}
2023-04-25 07:07:29 +00:00
2023-05-04 10:45:12 +00:00
function getSerialNumberStatus(
doc: StockTransfer | StockMovement,
item: StockTransferItem | StockMovementItem,
isCancel: boolean,
isReturn: boolean
2023-05-04 10:45:12 +00:00
): SerialNumberStatus {
if (doc.schemaName === ModelNameEnum.Shipment) {
if (isReturn) {
return isCancel ? 'Delivered' : 'Active';
}
2023-05-04 10:45:12 +00:00
return isCancel ? 'Active' : 'Delivered';
2023-04-25 07:07:29 +00:00
}
2023-05-04 10:45:12 +00:00
if (doc.schemaName === ModelNameEnum.PurchaseReceipt) {
if (isReturn) {
return isCancel ? 'Active' : 'Delivered';
}
2023-05-04 10:45:12 +00:00
return isCancel ? 'Inactive' : 'Active';
}
2023-04-25 07:07:29 +00:00
2023-05-04 10:45:12 +00:00
return getSerialNumberStatusForStockMovement(
doc as StockMovement,
item,
isCancel
2023-05-04 10:45:12 +00:00
);
}
2023-04-25 07:07:29 +00:00
2023-05-04 10:45:12 +00:00
function getSerialNumberStatusForStockMovement(
doc: StockMovement,
item: StockTransferItem | StockMovementItem,
isCancel: boolean
2023-05-04 10:45:12 +00:00
): SerialNumberStatus {
if (doc.movementType === 'MaterialIssue') {
return isCancel ? 'Active' : 'Delivered';
2023-04-25 07:07:29 +00:00
}
2023-05-04 10:45:12 +00:00
if (doc.movementType === 'MaterialReceipt') {
return isCancel ? 'Inactive' : 'Active';
}
2023-04-25 07:07:29 +00:00
2023-05-04 10:45:12 +00:00
if (doc.movementType === 'MaterialTransfer') {
return 'Active';
}
// MovementType is Manufacture
if (item.fromLocation) {
return isCancel ? 'Active' : 'Delivered';
2023-05-04 10:45:12 +00:00
}
return isCancel ? 'Inactive' : 'Active';
}