2
0
mirror of https://github.com/frappe/books.git synced 2024-11-09 23:30:56 +00:00

test: batchNumber transactions

This commit is contained in:
akshayitzme 2023-02-21 11:37:55 +05:30
parent 09591703e4
commit 306372db1c
4 changed files with 236 additions and 13 deletions

View File

@ -132,13 +132,14 @@ export class StockManager {
}
const date = details.date.toISOString();
const formattedDate = this.fyo.format(details.date, 'Datetime');
const isItemHasBatchNumber = await this.fyo.getValue(
'Item',
details.item,
'hasBatchNumber'
);
if (isItemHasBatchNumber) {
if (isItemHasBatchNumber && !this.isCancelled) {
if (!details.batchNumber) {
throw new ValidationError(
t`Please enter Batch Number for ${details.item}`
@ -154,12 +155,11 @@ export class StockManager {
details.batchNumber
)) ?? 0;
if (details.quantity < itemsInBatch) return;
if (details.quantity <= itemsInBatch) return;
const formattedDate = this.fyo.format(details.date, 'Datetime');
throw new ValidationError(
[
t`Insufficient Quantity in Batch ${details.batchNumber}`,
t`Insufficient Quantity`,
t`Additional quantity (${
details.quantity - itemsInBatch
}) is required in batch ${
@ -180,8 +180,6 @@ export class StockManager {
undefined
)) ?? 0;
const formattedDate = this.fyo.format(details.date, 'Datetime');
if (this.isCancelled) {
quantityBefore += details.quantity;
}

View File

@ -1,4 +1,5 @@
import { Fyo } from 'fyo';
import { BatchNumber } from 'models/baseModels/BatchNumber/BatchNumber';
import { ModelNameEnum } from 'models/types';
import { StockMovement } from '../StockMovement';
import { StockTransfer } from '../StockTransfer';
@ -26,6 +27,7 @@ type Transfer = {
item: string;
from?: string;
to?: string;
batchNumber?: string;
quantity: number;
rate: number;
};
@ -34,8 +36,23 @@ interface TransferTwo extends Omit<Transfer, 'from' | 'to'> {
location: string;
}
export function getItem(name: string, rate: number) {
return { name, rate, trackItem: true };
export function getItem(name: string, rate: number, hasBatchNumber?: boolean) {
return { name, rate, trackItem: true, hasBatchNumber };
}
export async function getBatchNumber(
schemaName: ModelNameEnum.BatchNumber,
batchNumber: string,
expiryDate: Date,
manufactureDate: Date,
fyo: Fyo
): Promise<BatchNumber> {
const doc = fyo.doc.getNewDoc(schemaName, {
batchNumber,
expiryDate,
manufactureDate,
}) as BatchNumber;
return doc;
}
export async function getStockTransfer(
@ -62,11 +79,11 @@ export async function getStockMovement(
movementType,
date,
}) as StockMovement;
for (const {
item,
from: fromLocation,
to: toLocation,
batchNumber: batchNumber,
quantity,
rate,
} of transfers) {
@ -74,6 +91,7 @@ export async function getStockMovement(
item,
fromLocation,
toLocation,
batchNumber,
rate,
quantity,
});

View File

@ -1,6 +1,6 @@
import {
assertDoesNotThrow,
assertThrows
assertThrows,
} from 'backend/database/tests/helpers';
import { ModelNameEnum } from 'models/types';
import { default as tape, default as test } from 'tape';
@ -17,10 +17,12 @@ const itemMap = {
Pen: {
name: 'Pen',
rate: 700,
hasBatchNumber: true,
},
Ink: {
name: 'Ink',
rate: 50,
hasBatchNumber: false,
},
};
@ -29,14 +31,22 @@ const locationMap = {
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
*/
test('create dummy items & locations', async (t) => {
// Create Items
for (const { name, rate } of Object.values(itemMap)) {
const item = getItem(name, rate);
for (const { name, rate, hasBatchNumber } of Object.values(itemMap)) {
const item = getItem(name, rate, hasBatchNumber);
await fyo.doc.getNewDoc(ModelNameEnum.Item, item).sync();
t.ok(await fyo.db.exists(ModelNameEnum.Item, name), `${name} exists`);
}
@ -48,6 +58,25 @@ 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
*/
@ -90,6 +119,55 @@ test('create stock movement, material receipt', async (t) => {
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,
undefined,
undefined,
undefined,
batchNumber
),
quantity
);
});
test('create stock movement, material transfer', async (t) => {
const { rate } = itemMap.Ink;
const quantity = 2;
@ -136,6 +214,60 @@ test('create stock movement, material transfer', async (t) => {
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.Ink.name), quantity);
});
test('create stock movement, material issue', async (t) => {
const { rate } = itemMap.Ink;
const quantity = 2;
@ -169,6 +301,50 @@ test('create stock movement, material issue', async (t) => {
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
*/
@ -214,6 +390,7 @@ async function runEntries(
item: string;
to?: string;
from?: string;
batchNumber?: string;
quantity: number;
rate: number;
}[];
@ -235,7 +412,7 @@ async function runEntries(
}
test('create stock movements, invalid entries, in sequence', async (t) => {
const { name: item, rate } = itemMap.Ink;
const { name: item, rate } = itemMap.Pen;
const quantity = 10;
await runEntries(
item,
@ -249,6 +426,7 @@ test('create stock movements, invalid entries, in sequence', async (t) => {
{
item,
to: locationMap.LocationOne,
batchNumber: batchNumberMap.batchNumberOne.name,
quantity,
rate,
},
@ -264,6 +442,7 @@ test('create stock movements, invalid entries, in sequence', async (t) => {
item,
from: locationMap.LocationOne,
to: locationMap.LocationTwo,
batchNumber: batchNumberMap.batchNumberOne.name,
quantity: quantity + 1,
rate,
},
@ -278,6 +457,7 @@ test('create stock movements, invalid entries, in sequence', async (t) => {
{
item,
from: locationMap.LocationOne,
batchNumber: batchNumberMap.batchNumberOne.name,
quantity: quantity + 1,
rate,
},
@ -292,6 +472,7 @@ test('create stock movements, invalid entries, in sequence', async (t) => {
{
item,
from: locationMap.LocationOne,
batchNumber: batchNumberMap.batchNumberOne.name,
to: locationMap.LocationTwo,
quantity,
rate,
@ -307,6 +488,7 @@ test('create stock movements, invalid entries, in sequence', async (t) => {
{
item,
from: locationMap.LocationTwo,
batchNumber: batchNumberMap.batchNumberOne.name,
quantity,
rate,
},
@ -371,4 +553,28 @@ 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);

View File

@ -4,6 +4,7 @@ export enum ModelNameEnum {
AccountingLedgerEntry = 'AccountingLedgerEntry',
AccountingSettings = 'AccountingSettings',
Address = 'Address',
BatchNumber= 'BatchNumber',
Color = 'Color',
CompanySettings = 'CompanySettings',
Currency = 'Currency',