2
0
mirror of https://github.com/frappe/books.git synced 2024-11-14 01:14:03 +00:00
books/models/inventory/StockMovement.ts

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

194 lines
4.7 KiB
TypeScript
Raw Normal View History

2023-05-04 10:45:12 +00:00
import { Fyo, t } from 'fyo';
import type { Doc } from 'fyo/model/doc';
import {
Action,
DefaultMap,
FiltersMap,
FormulaMap,
ListViewSettings,
} from 'fyo/model/types';
import { ValidationError } from 'fyo/utils/errors';
2023-05-04 10:45:12 +00:00
import { LedgerPosting } from 'models/Transactional/LedgerPosting';
import {
addItem,
getDocStatusListColumn,
getLedgerLinkAction,
} from 'models/helpers';
import { ModelNameEnum } from 'models/types';
import { Money } from 'pesa';
2023-05-04 10:45:12 +00:00
import { SerialNumber } from './SerialNumber';
import { StockMovementItem } from './StockMovementItem';
import { Transfer } from './Transfer';
2023-04-25 07:07:29 +00:00
import {
2023-05-04 10:45:12 +00:00
getSerialNumberFromDoc,
updateSerialNumbers,
2023-04-25 07:07:29 +00:00
validateBatch,
2023-05-04 10:45:12 +00:00
validateSerialNumber,
2023-04-25 07:07:29 +00:00
} from './helpers';
2023-05-04 10:45:12 +00:00
import { MovementType, MovementTypeEnum, SerialNumberStatus } from './types';
export class StockMovement extends Transfer {
name?: string;
date?: Date;
numberSeries?: string;
movementType?: MovementType;
items?: StockMovementItem[];
amount?: Money;
override get isTransactional(): boolean {
return false;
}
override async getPosting(): Promise<LedgerPosting | null> {
return null;
}
formulas: FormulaMap = {
amount: {
formula: () => {
return this.items?.reduce(
(acc, item) => acc.add(item.amount ?? 0),
this.fyo.pesa(0)
);
},
dependsOn: ['items'],
},
};
async validate() {
await super.validate();
2023-02-27 13:09:18 +00:00
this.validateManufacture();
2023-02-28 06:01:04 +00:00
await validateBatch(this);
2023-05-04 10:45:12 +00:00
await validateSerialNumber(this);
await validateSerialNumberStatus(this);
}
async afterSubmit(): Promise<void> {
await super.afterSubmit();
await updateSerialNumbers(this, false);
}
async afterCancel(): Promise<void> {
await super.afterCancel();
await updateSerialNumbers(this, true);
2023-02-27 13:09:18 +00:00
}
validateManufacture() {
2023-05-04 10:45:12 +00:00
if (this.movementType !== MovementTypeEnum.Manufacture) {
return;
}
const hasFrom = this.items?.findIndex((f) => f.fromLocation) !== -1;
const hasTo = this.items?.findIndex((f) => f.toLocation) !== -1;
if (!hasFrom) {
throw new ValidationError(this.fyo.t`Item with From location not found`);
}
if (!hasTo) {
throw new ValidationError(this.fyo.t`Item with To location not found`);
}
}
static filters: FiltersMap = {
numberSeries: () => ({ referenceType: ModelNameEnum.StockMovement }),
};
static defaults: DefaultMap = {
date: () => new Date(),
};
static getListViewSettings(fyo: Fyo): ListViewSettings {
const movementTypeMap = {
2023-05-04 10:45:12 +00:00
[MovementTypeEnum.MaterialIssue]: fyo.t`Material Issue`,
[MovementTypeEnum.MaterialReceipt]: fyo.t`Material Receipt`,
[MovementTypeEnum.MaterialTransfer]: fyo.t`Material Transfer`,
[MovementTypeEnum.Manufacture]: fyo.t`Manufacture`,
};
return {
columns: [
'name',
getDocStatusListColumn(),
'date',
{
label: fyo.t`Movement Type`,
fieldname: 'movementType',
fieldtype: 'Select',
display(value): string {
2023-05-04 10:45:12 +00:00
return movementTypeMap[value as MovementTypeEnum] ?? '';
},
},
],
};
}
_getTransferDetails() {
return (this.items ?? []).map((row) => ({
item: row.item!,
rate: row.rate!,
quantity: row.quantity!,
2023-02-28 06:01:04 +00:00
batch: row.batch!,
2023-05-04 10:45:12 +00:00
serialNumber: row.serialNumber!,
fromLocation: row.fromLocation,
toLocation: row.toLocation,
}));
2022-10-29 06:15:23 +00:00
}
static getActions(fyo: Fyo): Action[] {
return [getLedgerLinkAction(fyo, true)];
}
async addItem(name: string) {
return await addItem(name, this);
}
}
2023-05-04 10:45:12 +00:00
async function validateSerialNumberStatus(doc: StockMovement) {
for (const serialNumber of getSerialNumberFromDoc(doc)) {
const snDoc = await doc.fyo.doc.getDoc(
ModelNameEnum.SerialNumber,
serialNumber
);
if (!(snDoc instanceof SerialNumber)) {
continue;
}
const status = snDoc.status ?? 'Inactive';
if (doc.movementType === 'MaterialReceipt' && status !== 'Inactive') {
throw new ValidationError(
t`Active Serial Number ${serialNumber} cannot be used for Material Issue`
);
}
if (doc.movementType === 'MaterialIssue' && status !=='Active') {
validateMaterialIssueSerialNumber(serialNumber, status);
throw new ValidationError(
t`Inactive Serial Number ${serialNumber} cannot be used for Material Issue`
);
}
}
}
async function validateMaterialReceiptSerialNumber(
serialNumber: string,
status: string
) {
if (status === 'Inactive') {
return;
}
}
async function validateMaterialIssueSerialNumber(
serialNumber: string,
status: SerialNumberStatus
) {
if (status === 'Active') {
return;
}
throw new ValidationError(t`Serial Number ${serialNumber} is not Active.`);
}