2022-11-03 10:53:34 +00:00
|
|
|
import {
|
|
|
|
assertDoesNotThrow,
|
2023-02-21 06:07:55 +00:00
|
|
|
assertThrows,
|
2022-11-03 10:53:34 +00:00
|
|
|
} from 'backend/database/tests/helpers';
|
2022-11-01 08:17:55 +00:00
|
|
|
import { ModelNameEnum } from 'models/types';
|
2022-11-03 10:53:34 +00:00
|
|
|
import { default as tape, default as test } from 'tape';
|
2022-11-01 08:17:55 +00:00
|
|
|
import { closeTestFyo, getTestFyo, setupTestFyo } from 'tests/helpers';
|
2022-11-02 14:56:37 +00:00
|
|
|
import { StockMovement } from '../StockMovement';
|
2023-05-04 10:45:12 +00:00
|
|
|
import { MovementTypeEnum } from '../types';
|
2022-11-02 14:56:37 +00:00
|
|
|
import { getItem, getSLEs, getStockMovement } from './helpers';
|
2022-11-01 08:17:55 +00:00
|
|
|
|
|
|
|
const fyo = getTestFyo();
|
|
|
|
|
|
|
|
setupTestFyo(fyo, __filename);
|
|
|
|
|
2022-11-02 14:56:37 +00:00
|
|
|
const itemMap = {
|
|
|
|
Pen: {
|
|
|
|
name: 'Pen',
|
|
|
|
rate: 700,
|
|
|
|
},
|
|
|
|
Ink: {
|
|
|
|
name: 'Ink',
|
|
|
|
rate: 50,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
const locationMap = {
|
|
|
|
LocationOne: 'LocationOne',
|
|
|
|
LocationTwo: 'LocationTwo',
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Section 1: Test Creation of Items and Locations
|
|
|
|
*/
|
|
|
|
|
|
|
|
test('create dummy items & locations', async (t) => {
|
|
|
|
// Create Items
|
2023-02-28 05:55:48 +00:00
|
|
|
for (const { name, rate } of Object.values(itemMap)) {
|
|
|
|
const item = getItem(name, rate);
|
2022-11-02 14:56:37 +00:00
|
|
|
await fyo.doc.getNewDoc(ModelNameEnum.Item, item).sync();
|
|
|
|
t.ok(await fyo.db.exists(ModelNameEnum.Item, name), `${name} exists`);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create Locations
|
|
|
|
for (const name of Object.values(locationMap)) {
|
|
|
|
await fyo.doc.getNewDoc(ModelNameEnum.Location, { name }).sync();
|
|
|
|
t.ok(await fyo.db.exists(ModelNameEnum.Location, name), `${name} exists`);
|
2022-11-01 08:17:55 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-11-02 14:56:37 +00:00
|
|
|
/**
|
|
|
|
* Section 2: Test Creation of Stock Movements
|
|
|
|
*/
|
|
|
|
|
|
|
|
test('create stock movement, material receipt', async (t) => {
|
|
|
|
const { rate } = itemMap.Ink;
|
|
|
|
const quantity = 2;
|
|
|
|
const amount = rate * quantity;
|
|
|
|
const stockMovement = await getStockMovement(
|
2023-05-04 10:45:12 +00:00
|
|
|
MovementTypeEnum.MaterialReceipt,
|
2022-11-03 10:53:34 +00:00
|
|
|
new Date('2022-11-03T09:57:04.528'),
|
2022-11-02 14:56:37 +00:00
|
|
|
[
|
|
|
|
{
|
|
|
|
item: itemMap.Ink.name,
|
|
|
|
to: locationMap.LocationOne,
|
|
|
|
quantity,
|
|
|
|
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), 1);
|
|
|
|
t.equal(sle.item, itemMap.Ink.name);
|
|
|
|
t.equal(parseFloat(sle.rate), rate);
|
|
|
|
t.equal(sle.quantity, quantity);
|
|
|
|
t.equal(sle.location, locationMap.LocationOne);
|
2022-11-03 10:53:34 +00:00
|
|
|
t.equal(await fyo.db.getStockQuantity(itemMap.Ink.name), quantity);
|
2022-11-02 14:56:37 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
test('create stock movement, material transfer', async (t) => {
|
|
|
|
const { rate } = itemMap.Ink;
|
|
|
|
const quantity = 2;
|
|
|
|
|
|
|
|
const stockMovement = await getStockMovement(
|
2023-05-04 10:45:12 +00:00
|
|
|
MovementTypeEnum.MaterialTransfer,
|
2022-11-03 10:53:34 +00:00
|
|
|
new Date('2022-11-03T09:58:04.528'),
|
2022-11-02 14:56:37 +00:00
|
|
|
[
|
|
|
|
{
|
|
|
|
item: itemMap.Ink.name,
|
|
|
|
from: locationMap.LocationOne,
|
|
|
|
to: locationMap.LocationTwo,
|
|
|
|
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.Ink.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');
|
|
|
|
}
|
2022-11-01 08:17:55 +00:00
|
|
|
}
|
2022-11-03 10:53:34 +00:00
|
|
|
|
|
|
|
t.equal(
|
|
|
|
await fyo.db.getStockQuantity(itemMap.Ink.name, locationMap.LocationOne),
|
|
|
|
0
|
|
|
|
);
|
|
|
|
t.equal(await fyo.db.getStockQuantity(itemMap.Ink.name), quantity);
|
2022-11-01 08:17:55 +00:00
|
|
|
});
|
|
|
|
|
2022-11-02 14:56:37 +00:00
|
|
|
test('create stock movement, material issue', async (t) => {
|
|
|
|
const { rate } = itemMap.Ink;
|
|
|
|
const quantity = 2;
|
|
|
|
|
|
|
|
const stockMovement = await getStockMovement(
|
2023-05-04 10:45:12 +00:00
|
|
|
MovementTypeEnum.MaterialIssue,
|
2022-11-03 10:53:34 +00:00
|
|
|
new Date('2022-11-03T09:59:04.528'),
|
2022-11-02 14:56:37 +00:00
|
|
|
[
|
|
|
|
{
|
|
|
|
item: itemMap.Ink.name,
|
|
|
|
from: locationMap.LocationTwo,
|
|
|
|
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.Ink.name);
|
|
|
|
t.equal(parseFloat(sle.rate), rate);
|
|
|
|
t.equal(sle.quantity, -quantity);
|
|
|
|
t.equal(sle.location, locationMap.LocationTwo);
|
2022-11-03 10:53:34 +00:00
|
|
|
t.equal(await fyo.db.getStockQuantity(itemMap.Ink.name), 0);
|
2022-11-02 14:56:37 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Section 3: Test Cancellation of Stock Movements
|
|
|
|
*/
|
|
|
|
|
|
|
|
test('cancel stock movement', async (t) => {
|
|
|
|
const names = (await fyo.db.getAllRaw(ModelNameEnum.StockMovement)) as {
|
|
|
|
name: string;
|
|
|
|
}[];
|
|
|
|
|
|
|
|
for (const { name } of names) {
|
|
|
|
const slesBefore = await getSLEs(name, ModelNameEnum.StockMovement, fyo);
|
|
|
|
const doc = (await fyo.doc.getDoc(
|
|
|
|
ModelNameEnum.StockMovement,
|
|
|
|
name
|
|
|
|
)) as StockMovement;
|
|
|
|
|
2023-05-04 10:45:12 +00:00
|
|
|
if (doc.movementType === MovementTypeEnum.MaterialTransfer) {
|
2022-11-02 14:56:37 +00:00
|
|
|
t.equal(slesBefore.length, (doc.items?.length ?? 0) * 2);
|
|
|
|
} else {
|
|
|
|
t.equal(slesBefore.length, doc.items?.length ?? 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
await doc.cancel();
|
|
|
|
const slesAfter = await getSLEs(name, ModelNameEnum.StockMovement, fyo);
|
|
|
|
t.equal(slesAfter.length, 0);
|
|
|
|
}
|
2022-11-03 10:53:34 +00:00
|
|
|
|
|
|
|
t.equal(await fyo.db.getStockQuantity(itemMap.Ink.name), null);
|
2022-11-02 14:56:37 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Section 4: Test Invalid entries
|
|
|
|
*/
|
|
|
|
|
2022-11-03 10:53:34 +00:00
|
|
|
async function runEntries(
|
|
|
|
item: string,
|
|
|
|
entries: {
|
2023-05-04 10:45:12 +00:00
|
|
|
type: MovementTypeEnum;
|
2022-11-03 10:53:34 +00:00
|
|
|
date: Date;
|
|
|
|
valid: boolean;
|
|
|
|
postQuantity: number;
|
|
|
|
items: {
|
|
|
|
item: string;
|
|
|
|
to?: string;
|
|
|
|
from?: string;
|
|
|
|
quantity: number;
|
|
|
|
rate: number;
|
|
|
|
}[];
|
|
|
|
}[],
|
|
|
|
t: tape.Test
|
|
|
|
) {
|
|
|
|
for (const { type, date, items, valid, postQuantity } of entries) {
|
|
|
|
const stockMovement = await getStockMovement(type, date, items, fyo);
|
|
|
|
await stockMovement.sync();
|
|
|
|
|
|
|
|
if (valid) {
|
|
|
|
await assertDoesNotThrow(async () => await stockMovement.submit());
|
|
|
|
} else {
|
|
|
|
await assertThrows(async () => await stockMovement.submit());
|
|
|
|
}
|
|
|
|
|
|
|
|
t.equal(await fyo.db.getStockQuantity(item), postQuantity);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
test('create stock movements, invalid entries, in sequence', async (t) => {
|
2023-02-21 06:07:55 +00:00
|
|
|
const { name: item, rate } = itemMap.Pen;
|
2022-11-03 10:53:34 +00:00
|
|
|
const quantity = 10;
|
|
|
|
await runEntries(
|
|
|
|
item,
|
|
|
|
[
|
|
|
|
{
|
2023-05-04 10:45:12 +00:00
|
|
|
type: MovementTypeEnum.MaterialReceipt,
|
2022-11-03 10:53:34 +00:00
|
|
|
date: new Date('2022-11-03T09:58:04.528'),
|
|
|
|
valid: true,
|
|
|
|
postQuantity: quantity,
|
|
|
|
items: [
|
|
|
|
{
|
|
|
|
item,
|
|
|
|
to: locationMap.LocationOne,
|
|
|
|
quantity,
|
|
|
|
rate,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
{
|
2023-05-04 10:45:12 +00:00
|
|
|
type: MovementTypeEnum.MaterialTransfer,
|
2022-11-03 10:53:34 +00:00
|
|
|
date: new Date('2022-11-03T09:58:05.528'),
|
|
|
|
valid: false,
|
|
|
|
postQuantity: quantity,
|
|
|
|
items: [
|
|
|
|
{
|
|
|
|
item,
|
|
|
|
from: locationMap.LocationOne,
|
|
|
|
to: locationMap.LocationTwo,
|
|
|
|
quantity: quantity + 1,
|
|
|
|
rate,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
{
|
2023-05-04 10:45:12 +00:00
|
|
|
type: MovementTypeEnum.MaterialIssue,
|
2022-11-03 10:53:34 +00:00
|
|
|
date: new Date('2022-11-03T09:58:06.528'),
|
|
|
|
valid: false,
|
|
|
|
postQuantity: quantity,
|
|
|
|
items: [
|
|
|
|
{
|
|
|
|
item,
|
|
|
|
from: locationMap.LocationOne,
|
|
|
|
quantity: quantity + 1,
|
|
|
|
rate,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
{
|
2023-05-04 10:45:12 +00:00
|
|
|
type: MovementTypeEnum.MaterialTransfer,
|
2022-11-03 10:53:34 +00:00
|
|
|
date: new Date('2022-11-03T09:58:07.528'),
|
|
|
|
valid: true,
|
|
|
|
postQuantity: quantity,
|
|
|
|
items: [
|
|
|
|
{
|
|
|
|
item,
|
|
|
|
from: locationMap.LocationOne,
|
|
|
|
to: locationMap.LocationTwo,
|
|
|
|
quantity,
|
|
|
|
rate,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
{
|
2023-05-04 10:45:12 +00:00
|
|
|
type: MovementTypeEnum.MaterialIssue,
|
2022-11-03 10:53:34 +00:00
|
|
|
date: new Date('2022-11-03T09:58:08.528'),
|
|
|
|
valid: true,
|
|
|
|
postQuantity: 0,
|
|
|
|
items: [
|
|
|
|
{
|
|
|
|
item,
|
|
|
|
from: locationMap.LocationTwo,
|
|
|
|
quantity,
|
|
|
|
rate,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
t
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
test('create stock movements, invalid entries, out of sequence', async (t) => {
|
|
|
|
const { name: item, rate } = itemMap.Ink;
|
|
|
|
const quantity = 10;
|
|
|
|
await runEntries(
|
|
|
|
item,
|
|
|
|
[
|
|
|
|
{
|
2023-05-04 10:45:12 +00:00
|
|
|
type: MovementTypeEnum.MaterialReceipt,
|
2022-11-03 10:53:34 +00:00
|
|
|
date: new Date('2022-11-15'),
|
|
|
|
valid: true,
|
|
|
|
postQuantity: quantity,
|
|
|
|
items: [
|
|
|
|
{
|
|
|
|
item,
|
|
|
|
to: locationMap.LocationOne,
|
|
|
|
quantity,
|
|
|
|
rate,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
{
|
2023-05-04 10:45:12 +00:00
|
|
|
type: MovementTypeEnum.MaterialIssue,
|
2022-11-03 10:53:34 +00:00
|
|
|
date: new Date('2022-11-17'),
|
|
|
|
valid: true,
|
|
|
|
postQuantity: quantity - 5,
|
|
|
|
items: [
|
|
|
|
{
|
|
|
|
item,
|
|
|
|
from: locationMap.LocationOne,
|
|
|
|
quantity: quantity - 5,
|
|
|
|
rate,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
{
|
2023-05-04 10:45:12 +00:00
|
|
|
type: MovementTypeEnum.MaterialTransfer,
|
2022-11-03 10:53:34 +00:00
|
|
|
date: new Date('2022-11-16'),
|
|
|
|
valid: false,
|
|
|
|
postQuantity: quantity - 5,
|
|
|
|
items: [
|
|
|
|
{
|
|
|
|
item,
|
|
|
|
from: locationMap.LocationOne,
|
|
|
|
to: locationMap.LocationTwo,
|
|
|
|
quantity,
|
|
|
|
rate,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
t
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2022-11-01 08:17:55 +00:00
|
|
|
closeTestFyo(fyo, __filename);
|