Compare commits
2 Commits
bce40ce30d
...
3bd1549c85
| Author | SHA1 | Date | |
|---|---|---|---|
| 3bd1549c85 | |||
| 6623bf9bfd |
@ -15,11 +15,6 @@ const stockTransferLineSchema = new Schema(
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
quantity: { type: Number, required: true },
|
quantity: { type: Number, required: true },
|
||||||
toStockLocation: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: 'stockLocation',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
toStockType: {
|
toStockType: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
@ -37,18 +32,27 @@ const stockTransferLineSchema = new Schema(
|
|||||||
const stockTransferSchema = new Schema(
|
const stockTransferSchema = new Schema(
|
||||||
{
|
{
|
||||||
_reference: { type: String, default: () => generateId()() },
|
_reference: { type: String, default: () => generateId()() },
|
||||||
name: { type: String, required: true },
|
|
||||||
state: {
|
state: {
|
||||||
type: { type: String, required: true, default: 'draft' },
|
type: { type: String, required: true, default: 'draft' },
|
||||||
progress: { type: Number, required: false },
|
progress: { type: Number, required: false },
|
||||||
},
|
},
|
||||||
postedAt: { type: Date, 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: [] },
|
lines: { type: [stockTransferLineSchema], default: [] },
|
||||||
},
|
},
|
||||||
{ timestamps: true }
|
{ timestamps: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
stockTransferSchema.index({ name: 'text' });
|
stockTransferSchema.index({ 'state.type': 'text' });
|
||||||
|
|
||||||
stockTransferSchema.statics.stats = async function () {
|
stockTransferSchema.statics.stats = async function () {
|
||||||
const [draft, posted] = await Promise.all([
|
const [draft, posted] = await Promise.all([
|
||||||
|
|||||||
@ -13,8 +13,8 @@ const toId = (value) => {
|
|||||||
export function normalizeAuditLevelLine(line) {
|
export function normalizeAuditLevelLine(line) {
|
||||||
const allItems = Boolean(line?.allItems);
|
const allItems = Boolean(line?.allItems);
|
||||||
const allSkus = allItems ? true : Boolean(line?.allSkus);
|
const allSkus = allItems ? true : Boolean(line?.allSkus);
|
||||||
const item = allItems ? null : line?.item?._id ?? line?.item ?? null;
|
const item = allItems ? null : (line?.item?._id ?? line?.item ?? null);
|
||||||
const itemSku = allItems || allSkus ? null : line?.itemSku?._id ?? line?.itemSku ?? null;
|
const itemSku = allItems || allSkus ? null : (line?.itemSku?._id ?? line?.itemSku ?? null);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_id: line?._id,
|
_id: line?._id,
|
||||||
@ -62,7 +62,7 @@ const stockAuditLevelSchema = new Schema(
|
|||||||
{
|
{
|
||||||
_reference: { type: String, default: () => generateId()() },
|
_reference: { type: String, default: () => generateId()() },
|
||||||
name: { type: String, required: true },
|
name: { type: String, required: true },
|
||||||
tags: [{ type: String }],
|
tags: [{ type: String, required: true }],
|
||||||
auditLines: { type: [stockAuditLevelLineSchema], default: [] },
|
auditLines: { type: [stockAuditLevelLineSchema], default: [] },
|
||||||
},
|
},
|
||||||
{ timestamps: true }
|
{ timestamps: true }
|
||||||
|
|||||||
@ -9,12 +9,11 @@ const listAllowedFilters = [
|
|||||||
'state',
|
'state',
|
||||||
'state.type',
|
'state.type',
|
||||||
'postedAt',
|
'postedAt',
|
||||||
'name',
|
|
||||||
'createdAt',
|
'createdAt',
|
||||||
'updatedAt',
|
'updatedAt',
|
||||||
'_reference',
|
'_reference',
|
||||||
];
|
];
|
||||||
const listAllowedSorters = ['name', 'createdAt', 'postedAt', 'state', 'updatedAt'];
|
const listAllowedSorters = ['createdAt', 'postedAt', 'state', 'updatedAt'];
|
||||||
const propertiesAllowedFilters = ['state.type'];
|
const propertiesAllowedFilters = ['state.type'];
|
||||||
import {
|
import {
|
||||||
listStockTransfersRouteHandler,
|
listStockTransfersRouteHandler,
|
||||||
|
|||||||
@ -30,9 +30,14 @@ const normalizeLineInput = (l) => ({
|
|||||||
fromStockType: l.fromStockType,
|
fromStockType: l.fromStockType,
|
||||||
fromStock: l.fromStock?._id ?? l.fromStock,
|
fromStock: l.fromStock?._id ?? l.fromStock,
|
||||||
quantity: Number(l.quantity),
|
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) {
|
async function createStockEvent(newData, user) {
|
||||||
const result = await newObject({
|
const result = await newObject({
|
||||||
model: stockEventModel,
|
model: stockEventModel,
|
||||||
@ -91,20 +96,36 @@ async function createStock(model, newData, user) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function executePostedLine(transferId, line, user) {
|
async function executePostedLine(transfer, line, user) {
|
||||||
const toLocId = line.toStockLocation;
|
const fromLocId = transfer.fromLocation;
|
||||||
const loc = await stockLocationModel.findById(toLocId).lean();
|
const toLocId = transfer.toLocation;
|
||||||
if (!loc) {
|
|
||||||
throw new Error(`Unknown stock location: ${toLocId}`);
|
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}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(line.quantity > 0)) {
|
if (!(line.quantity > 0)) {
|
||||||
throw new Error('Line quantity must be positive');
|
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') {
|
if (line.fromStockType === 'filamentStock') {
|
||||||
const src = await filamentStockModel.findById(line.fromStock);
|
const src = await filamentStockModel.findById(line.fromStock);
|
||||||
if (!src) throw new Error('From filament stock not found');
|
if (!src) throw new Error('From filament stock not found');
|
||||||
|
assertStockAtFromLocation(src);
|
||||||
const netAvail = src.currentWeight?.net ?? 0;
|
const netAvail = src.currentWeight?.net ?? 0;
|
||||||
if (line.quantity > netAvail) {
|
if (line.quantity > netAvail) {
|
||||||
throw new Error('Filament transfer quantity exceeds available net weight');
|
throw new Error('Filament transfer quantity exceeds available net weight');
|
||||||
@ -129,7 +150,7 @@ async function executePostedLine(transferId, line, user) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await createStockEventsForLine({
|
await createStockEventsForLine({
|
||||||
transferId,
|
transferId: transfer._id,
|
||||||
fromId: src._id,
|
fromId: src._id,
|
||||||
fromType: 'filamentStock',
|
fromType: 'filamentStock',
|
||||||
toId: dest._id,
|
toId: dest._id,
|
||||||
@ -145,6 +166,7 @@ async function executePostedLine(transferId, line, user) {
|
|||||||
if (line.fromStockType === 'partStock') {
|
if (line.fromStockType === 'partStock') {
|
||||||
const src = await partStockModel.findById(line.fromStock);
|
const src = await partStockModel.findById(line.fromStock);
|
||||||
if (!src) throw new Error('From part stock not found');
|
if (!src) throw new Error('From part stock not found');
|
||||||
|
assertStockAtFromLocation(src);
|
||||||
const currentQuantity = src.currentQuantity;
|
const currentQuantity = src.currentQuantity;
|
||||||
if (line.quantity > currentQuantity) {
|
if (line.quantity > currentQuantity) {
|
||||||
throw new Error('Part transfer quantity exceeds current quantity');
|
throw new Error('Part transfer quantity exceeds current quantity');
|
||||||
@ -164,7 +186,7 @@ async function executePostedLine(transferId, line, user) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await createStockEventsForLine({
|
await createStockEventsForLine({
|
||||||
transferId,
|
transferId: transfer._id,
|
||||||
fromId: src._id,
|
fromId: src._id,
|
||||||
fromType: 'partStock',
|
fromType: 'partStock',
|
||||||
toId: dest._id,
|
toId: dest._id,
|
||||||
@ -180,6 +202,7 @@ async function executePostedLine(transferId, line, user) {
|
|||||||
if (line.fromStockType === 'productStock') {
|
if (line.fromStockType === 'productStock') {
|
||||||
const src = await productStockModel.findById(line.fromStock);
|
const src = await productStockModel.findById(line.fromStock);
|
||||||
if (!src) throw new Error('From product stock not found');
|
if (!src) throw new Error('From product stock not found');
|
||||||
|
assertStockAtFromLocation(src);
|
||||||
if (line.quantity > src.currentQuantity) {
|
if (line.quantity > src.currentQuantity) {
|
||||||
throw new Error('Product transfer quantity exceeds current quantity');
|
throw new Error('Product transfer quantity exceeds current quantity');
|
||||||
}
|
}
|
||||||
@ -199,7 +222,7 @@ async function executePostedLine(transferId, line, user) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await createStockEventsForLine({
|
await createStockEventsForLine({
|
||||||
transferId,
|
transferId: transfer._id,
|
||||||
fromId: src._id,
|
fromId: src._id,
|
||||||
fromType: 'productStock',
|
fromType: 'productStock',
|
||||||
toId: dest._id,
|
toId: dest._id,
|
||||||
@ -215,6 +238,13 @@ async function executePostedLine(transferId, line, user) {
|
|||||||
throw new Error(`Unsupported from stock type: ${line.fromStockType}`);
|
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 (
|
export const listStockTransfersRouteHandler = async (
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
@ -235,11 +265,7 @@ export const listStockTransfersRouteHandler = async (
|
|||||||
search,
|
search,
|
||||||
sort,
|
sort,
|
||||||
order,
|
order,
|
||||||
populate: [
|
populate: stockTransferPopulate,
|
||||||
{ path: 'lines.fromStock' },
|
|
||||||
{ path: 'lines.toStockLocation' },
|
|
||||||
{ path: 'lines.toStock' },
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
@ -263,11 +289,7 @@ export const listStockTransfersByPropertiesRouteHandler = async (
|
|||||||
model: stockTransferModel,
|
model: stockTransferModel,
|
||||||
properties,
|
properties,
|
||||||
filter,
|
filter,
|
||||||
populate: [
|
populate: stockTransferPopulate,
|
||||||
{ path: 'lines.fromStock' },
|
|
||||||
{ path: 'lines.toStockLocation' },
|
|
||||||
{ path: 'lines.toStock' },
|
|
||||||
],
|
|
||||||
masterFilter,
|
masterFilter,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -309,11 +331,7 @@ export const getStockTransferRouteHandler = async (req, res) => {
|
|||||||
const result = await getObject({
|
const result = await getObject({
|
||||||
model: stockTransferModel,
|
model: stockTransferModel,
|
||||||
id,
|
id,
|
||||||
populate: [
|
populate: stockTransferPopulate,
|
||||||
{ path: 'lines.fromStock' },
|
|
||||||
{ path: 'lines.toStockLocation' },
|
|
||||||
{ path: 'lines.toStock' },
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
logger.warn(`Stock transfer not found with supplied id.`);
|
logger.warn(`Stock transfer not found with supplied id.`);
|
||||||
@ -343,8 +361,11 @@ export const editStockTransferRouteHandler = async (req, res) => {
|
|||||||
const updateData = {
|
const updateData = {
|
||||||
lines: (req.body.lines || []).map((l) => normalizeLineInput(l)),
|
lines: (req.body.lines || []).map((l) => normalizeLineInput(l)),
|
||||||
};
|
};
|
||||||
if (req.body.name !== undefined) {
|
if (req.body.fromLocation !== undefined) {
|
||||||
updateData.name = req.body.name;
|
updateData.fromLocation = req.body.fromLocation?._id ?? req.body.fromLocation;
|
||||||
|
}
|
||||||
|
if (req.body.toLocation !== undefined) {
|
||||||
|
updateData.toLocation = req.body.toLocation?._id ?? req.body.toLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await editObject({
|
const result = await editObject({
|
||||||
@ -352,11 +373,7 @@ export const editStockTransferRouteHandler = async (req, res) => {
|
|||||||
id,
|
id,
|
||||||
updateData,
|
updateData,
|
||||||
user: req.user,
|
user: req.user,
|
||||||
populate: [
|
populate: stockTransferPopulate,
|
||||||
{ path: 'lines.fromStock' },
|
|
||||||
{ path: 'lines.toStockLocation' },
|
|
||||||
{ path: 'lines.toStock' },
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
@ -396,8 +413,9 @@ export const editMultipleStockTransfersRouteHandler = async (req, res) => {
|
|||||||
|
|
||||||
export const newStockTransferRouteHandler = async (req, res) => {
|
export const newStockTransferRouteHandler = async (req, res) => {
|
||||||
const newData = {
|
const newData = {
|
||||||
name: req.body.name,
|
|
||||||
state: req.body.state ?? { type: 'draft' },
|
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)),
|
lines: (req.body.lines || []).map((l) => normalizeLineInput(l)),
|
||||||
};
|
};
|
||||||
const result = await newObject({
|
const result = await newObject({
|
||||||
@ -471,12 +489,19 @@ export const postStockTransferRouteHandler = async (req, res) => {
|
|||||||
return res.status(400).send({ error: 'Stock transfer has no lines.', code: 400 });
|
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 = [];
|
const updatedLines = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (const line of doc.lines) {
|
for (const line of doc.lines) {
|
||||||
const plain = line.toObject();
|
const plain = line.toObject();
|
||||||
const { toStockType, toStock } = await executePostedLine(doc._id, plain, req.user);
|
const { toStockType, toStock } = await executePostedLine(doc, plain, req.user);
|
||||||
updatedLines.push({
|
updatedLines.push({
|
||||||
...plain,
|
...plain,
|
||||||
toStockType,
|
toStockType,
|
||||||
@ -502,11 +527,7 @@ export const postStockTransferRouteHandler = async (req, res) => {
|
|||||||
const posted = await getObject({
|
const posted = await getObject({
|
||||||
model: stockTransferModel,
|
model: stockTransferModel,
|
||||||
id,
|
id,
|
||||||
populate: [
|
populate: stockTransferPopulate,
|
||||||
{ path: 'lines.fromStock' },
|
|
||||||
{ path: 'lines.toStockLocation' },
|
|
||||||
{ path: 'lines.toStock' },
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (posted?.error) {
|
if (posted?.error) {
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export const EXPORT_FILTER_BY_TYPE = {
|
|||||||
shipment: ['order._id', 'orderType', 'courierService._id'],
|
shipment: ['order._id', 'orderType', 'courierService._id'],
|
||||||
stockEvent: ['parent._id', 'parentType', 'owner._id', 'ownerType'],
|
stockEvent: ['parent._id', 'parentType', 'owner._id', 'ownerType'],
|
||||||
stockLocation: ['name', 'address'],
|
stockLocation: ['name', 'address'],
|
||||||
stockTransfer: ['name', 'state.type', 'postedAt'],
|
stockTransfer: ['state.type', 'postedAt'],
|
||||||
stockAudit: ['auditLevel._id', 'stockLocation._id', 'state.type', 'postedAt'],
|
stockAudit: ['auditLevel._id', 'stockLocation._id', 'state.type', 'postedAt'],
|
||||||
stockAuditLevel: ['name', 'tags'],
|
stockAuditLevel: ['name', 'tags'],
|
||||||
documentJob: ['documentTemplate', 'documentPrinter', 'object._id', 'objectType'],
|
documentJob: ['documentTemplate', 'documentPrinter', 'object._id', 'objectType'],
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user