Compare commits

..

No commits in common. "3bd1549c85ec43b8dbbc3710ecb388eac8e1d757" and "bce40ce30d5798723f613b8cf98aadb6d818c7bb" have entirely different histories.

5 changed files with 51 additions and 75 deletions

View File

@ -15,6 +15,11 @@ const stockTransferLineSchema = new Schema(
required: true,
},
quantity: { type: Number, required: true },
toStockLocation: {
type: Schema.Types.ObjectId,
ref: 'stockLocation',
required: true,
},
toStockType: {
type: String,
required: false,
@ -32,27 +37,18 @@ const stockTransferLineSchema = new Schema(
const stockTransferSchema = new Schema(
{
_reference: { type: String, default: () => generateId()() },
name: { type: String, required: true },
state: {
type: { type: String, required: true, default: 'draft' },
progress: { type: Number, required: false },
},
postedAt: { type: Date, required: false },
fromLocation: {
type: Schema.Types.ObjectId,
ref: 'stockLocation',
required: true,
},
toLocation: {
type: Schema.Types.ObjectId,
ref: 'stockLocation',
required: true,
},
lines: { type: [stockTransferLineSchema], default: [] },
},
{ timestamps: true }
);
stockTransferSchema.index({ 'state.type': 'text' });
stockTransferSchema.index({ name: 'text' });
stockTransferSchema.statics.stats = async function () {
const [draft, posted] = await Promise.all([

View File

@ -13,8 +13,8 @@ const toId = (value) => {
export function normalizeAuditLevelLine(line) {
const allItems = Boolean(line?.allItems);
const allSkus = allItems ? true : Boolean(line?.allSkus);
const item = allItems ? null : (line?.item?._id ?? line?.item ?? null);
const itemSku = allItems || allSkus ? null : (line?.itemSku?._id ?? line?.itemSku ?? null);
const item = allItems ? null : line?.item?._id ?? line?.item ?? null;
const itemSku = allItems || allSkus ? null : line?.itemSku?._id ?? line?.itemSku ?? null;
return {
_id: line?._id,
@ -62,7 +62,7 @@ const stockAuditLevelSchema = new Schema(
{
_reference: { type: String, default: () => generateId()() },
name: { type: String, required: true },
tags: [{ type: String, required: true }],
tags: [{ type: String }],
auditLines: { type: [stockAuditLevelLineSchema], default: [] },
},
{ timestamps: true }

View File

@ -9,11 +9,12 @@ const listAllowedFilters = [
'state',
'state.type',
'postedAt',
'name',
'createdAt',
'updatedAt',
'_reference',
];
const listAllowedSorters = ['createdAt', 'postedAt', 'state', 'updatedAt'];
const listAllowedSorters = ['name', 'createdAt', 'postedAt', 'state', 'updatedAt'];
const propertiesAllowedFilters = ['state.type'];
import {
listStockTransfersRouteHandler,

View File

@ -30,14 +30,9 @@ const normalizeLineInput = (l) => ({
fromStockType: l.fromStockType,
fromStock: l.fromStock?._id ?? l.fromStock,
quantity: Number(l.quantity),
toStockLocation: l.toStockLocation?._id ?? l.toStockLocation,
});
const toId = (value) => {
if (value == null) return null;
if (typeof value === 'object' && value._id) return String(value._id);
return String(value);
};
async function createStockEvent(newData, user) {
const result = await newObject({
model: stockEventModel,
@ -96,36 +91,20 @@ async function createStock(model, newData, user) {
return result;
}
async function executePostedLine(transfer, line, user) {
const fromLocId = transfer.fromLocation;
const toLocId = transfer.toLocation;
const [fromLoc, toLoc] = await Promise.all([
stockLocationModel.findById(fromLocId).lean(),
stockLocationModel.findById(toLocId).lean(),
]);
if (!fromLoc) {
throw new Error(`Unknown from location: ${fromLocId}`);
}
if (!toLoc) {
throw new Error(`Unknown to location: ${toLocId}`);
async function executePostedLine(transferId, line, user) {
const toLocId = line.toStockLocation;
const loc = await stockLocationModel.findById(toLocId).lean();
if (!loc) {
throw new Error(`Unknown stock location: ${toLocId}`);
}
if (!(line.quantity > 0)) {
throw new Error('Line quantity must be positive');
}
const assertStockAtFromLocation = (stock) => {
if (toId(stock?.stockLocation) !== toId(fromLocId)) {
throw new Error('From stock must be at the transfer from location');
}
};
if (line.fromStockType === 'filamentStock') {
const src = await filamentStockModel.findById(line.fromStock);
if (!src) throw new Error('From filament stock not found');
assertStockAtFromLocation(src);
const netAvail = src.currentWeight?.net ?? 0;
if (line.quantity > netAvail) {
throw new Error('Filament transfer quantity exceeds available net weight');
@ -150,7 +129,7 @@ async function executePostedLine(transfer, line, user) {
);
await createStockEventsForLine({
transferId: transfer._id,
transferId,
fromId: src._id,
fromType: 'filamentStock',
toId: dest._id,
@ -166,7 +145,6 @@ async function executePostedLine(transfer, line, user) {
if (line.fromStockType === 'partStock') {
const src = await partStockModel.findById(line.fromStock);
if (!src) throw new Error('From part stock not found');
assertStockAtFromLocation(src);
const currentQuantity = src.currentQuantity;
if (line.quantity > currentQuantity) {
throw new Error('Part transfer quantity exceeds current quantity');
@ -186,7 +164,7 @@ async function executePostedLine(transfer, line, user) {
);
await createStockEventsForLine({
transferId: transfer._id,
transferId,
fromId: src._id,
fromType: 'partStock',
toId: dest._id,
@ -202,7 +180,6 @@ async function executePostedLine(transfer, line, user) {
if (line.fromStockType === 'productStock') {
const src = await productStockModel.findById(line.fromStock);
if (!src) throw new Error('From product stock not found');
assertStockAtFromLocation(src);
if (line.quantity > src.currentQuantity) {
throw new Error('Product transfer quantity exceeds current quantity');
}
@ -222,7 +199,7 @@ async function executePostedLine(transfer, line, user) {
);
await createStockEventsForLine({
transferId: transfer._id,
transferId,
fromId: src._id,
fromType: 'productStock',
toId: dest._id,
@ -238,13 +215,6 @@ async function executePostedLine(transfer, line, user) {
throw new Error(`Unsupported from stock type: ${line.fromStockType}`);
}
const stockTransferPopulate = [
{ path: 'fromLocation' },
{ path: 'toLocation' },
{ path: 'lines.fromStock' },
{ path: 'lines.toStock' },
];
export const listStockTransfersRouteHandler = async (
req,
res,
@ -265,7 +235,11 @@ export const listStockTransfersRouteHandler = async (
search,
sort,
order,
populate: stockTransferPopulate,
populate: [
{ path: 'lines.fromStock' },
{ path: 'lines.toStockLocation' },
{ path: 'lines.toStock' },
],
});
if (result?.error) {
@ -289,7 +263,11 @@ export const listStockTransfersByPropertiesRouteHandler = async (
model: stockTransferModel,
properties,
filter,
populate: stockTransferPopulate,
populate: [
{ path: 'lines.fromStock' },
{ path: 'lines.toStockLocation' },
{ path: 'lines.toStock' },
],
masterFilter,
});
@ -331,7 +309,11 @@ export const getStockTransferRouteHandler = async (req, res) => {
const result = await getObject({
model: stockTransferModel,
id,
populate: stockTransferPopulate,
populate: [
{ path: 'lines.fromStock' },
{ path: 'lines.toStockLocation' },
{ path: 'lines.toStock' },
],
});
if (result?.error) {
logger.warn(`Stock transfer not found with supplied id.`);
@ -361,11 +343,8 @@ export const editStockTransferRouteHandler = async (req, res) => {
const updateData = {
lines: (req.body.lines || []).map((l) => normalizeLineInput(l)),
};
if (req.body.fromLocation !== undefined) {
updateData.fromLocation = req.body.fromLocation?._id ?? req.body.fromLocation;
}
if (req.body.toLocation !== undefined) {
updateData.toLocation = req.body.toLocation?._id ?? req.body.toLocation;
if (req.body.name !== undefined) {
updateData.name = req.body.name;
}
const result = await editObject({
@ -373,7 +352,11 @@ export const editStockTransferRouteHandler = async (req, res) => {
id,
updateData,
user: req.user,
populate: stockTransferPopulate,
populate: [
{ path: 'lines.fromStock' },
{ path: 'lines.toStockLocation' },
{ path: 'lines.toStock' },
],
});
if (result.error) {
@ -413,9 +396,8 @@ export const editMultipleStockTransfersRouteHandler = async (req, res) => {
export const newStockTransferRouteHandler = async (req, res) => {
const newData = {
name: req.body.name,
state: req.body.state ?? { type: 'draft' },
fromLocation: req.body.fromLocation?._id ?? req.body.fromLocation,
toLocation: req.body.toLocation?._id ?? req.body.toLocation,
lines: (req.body.lines || []).map((l) => normalizeLineInput(l)),
};
const result = await newObject({
@ -489,19 +471,12 @@ export const postStockTransferRouteHandler = async (req, res) => {
return res.status(400).send({ error: 'Stock transfer has no lines.', code: 400 });
}
if (!doc.fromLocation || !doc.toLocation) {
return res.status(400).send({
error: 'Stock transfer must have from and to locations.',
code: 400,
});
}
const updatedLines = [];
try {
for (const line of doc.lines) {
const plain = line.toObject();
const { toStockType, toStock } = await executePostedLine(doc, plain, req.user);
const { toStockType, toStock } = await executePostedLine(doc._id, plain, req.user);
updatedLines.push({
...plain,
toStockType,
@ -527,7 +502,11 @@ export const postStockTransferRouteHandler = async (req, res) => {
const posted = await getObject({
model: stockTransferModel,
id,
populate: stockTransferPopulate,
populate: [
{ path: 'lines.fromStock' },
{ path: 'lines.toStockLocation' },
{ path: 'lines.toStock' },
],
});
if (posted?.error) {

View File

@ -35,7 +35,7 @@ export const EXPORT_FILTER_BY_TYPE = {
shipment: ['order._id', 'orderType', 'courierService._id'],
stockEvent: ['parent._id', 'parentType', 'owner._id', 'ownerType'],
stockLocation: ['name', 'address'],
stockTransfer: ['state.type', 'postedAt'],
stockTransfer: ['name', 'state.type', 'postedAt'],
stockAudit: ['auditLevel._id', 'stockLocation._id', 'state.type', 'postedAt'],
stockAuditLevel: ['name', 'tags'],
documentJob: ['documentTemplate', 'documentPrinter', 'object._id', 'objectType'],