Refactor stock transfer schema and related services to improve location handling
Some checks failed
farmcontrol/farmcontrol-api/pipeline/head There was a failure building this commit
Some checks failed
farmcontrol/farmcontrol-api/pipeline/head There was a failure building this commit
This commit modifies the `stockTransfer` schema by removing the `toStockLocation` field and introducing `fromLocation` and `toLocation` fields, ensuring better clarity in stock transfer operations. The routes and services are updated to reflect these changes, including adjustments in the `executePostedLine` function to validate stock locations. Additionally, the allowed filters and sorters in the stock transfer routes are refined, enhancing the overall functionality and maintainability of the inventory management system.
This commit is contained in:
parent
6623bf9bfd
commit
3bd1549c85
@ -15,11 +15,6 @@ 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,
|
||||
@ -37,18 +32,27 @@ 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({ name: 'text' });
|
||||
stockTransferSchema.index({ 'state.type': 'text' });
|
||||
|
||||
stockTransferSchema.statics.stats = async function () {
|
||||
const [draft, posted] = await Promise.all([
|
||||
|
||||
@ -9,12 +9,11 @@ const listAllowedFilters = [
|
||||
'state',
|
||||
'state.type',
|
||||
'postedAt',
|
||||
'name',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'_reference',
|
||||
];
|
||||
const listAllowedSorters = ['name', 'createdAt', 'postedAt', 'state', 'updatedAt'];
|
||||
const listAllowedSorters = ['createdAt', 'postedAt', 'state', 'updatedAt'];
|
||||
const propertiesAllowedFilters = ['state.type'];
|
||||
import {
|
||||
listStockTransfersRouteHandler,
|
||||
|
||||
@ -30,9 +30,14 @@ 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,
|
||||
@ -91,20 +96,36 @@ async function createStock(model, newData, user) {
|
||||
return result;
|
||||
}
|
||||
|
||||
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}`);
|
||||
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}`);
|
||||
}
|
||||
|
||||
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');
|
||||
@ -129,7 +150,7 @@ async function executePostedLine(transferId, line, user) {
|
||||
);
|
||||
|
||||
await createStockEventsForLine({
|
||||
transferId,
|
||||
transferId: transfer._id,
|
||||
fromId: src._id,
|
||||
fromType: 'filamentStock',
|
||||
toId: dest._id,
|
||||
@ -145,6 +166,7 @@ async function executePostedLine(transferId, 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');
|
||||
@ -164,7 +186,7 @@ async function executePostedLine(transferId, line, user) {
|
||||
);
|
||||
|
||||
await createStockEventsForLine({
|
||||
transferId,
|
||||
transferId: transfer._id,
|
||||
fromId: src._id,
|
||||
fromType: 'partStock',
|
||||
toId: dest._id,
|
||||
@ -180,6 +202,7 @@ async function executePostedLine(transferId, 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');
|
||||
}
|
||||
@ -199,7 +222,7 @@ async function executePostedLine(transferId, line, user) {
|
||||
);
|
||||
|
||||
await createStockEventsForLine({
|
||||
transferId,
|
||||
transferId: transfer._id,
|
||||
fromId: src._id,
|
||||
fromType: 'productStock',
|
||||
toId: dest._id,
|
||||
@ -215,6 +238,13 @@ async function executePostedLine(transferId, 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,
|
||||
@ -235,11 +265,7 @@ export const listStockTransfersRouteHandler = async (
|
||||
search,
|
||||
sort,
|
||||
order,
|
||||
populate: [
|
||||
{ path: 'lines.fromStock' },
|
||||
{ path: 'lines.toStockLocation' },
|
||||
{ path: 'lines.toStock' },
|
||||
],
|
||||
populate: stockTransferPopulate,
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
@ -263,11 +289,7 @@ export const listStockTransfersByPropertiesRouteHandler = async (
|
||||
model: stockTransferModel,
|
||||
properties,
|
||||
filter,
|
||||
populate: [
|
||||
{ path: 'lines.fromStock' },
|
||||
{ path: 'lines.toStockLocation' },
|
||||
{ path: 'lines.toStock' },
|
||||
],
|
||||
populate: stockTransferPopulate,
|
||||
masterFilter,
|
||||
});
|
||||
|
||||
@ -309,11 +331,7 @@ export const getStockTransferRouteHandler = async (req, res) => {
|
||||
const result = await getObject({
|
||||
model: stockTransferModel,
|
||||
id,
|
||||
populate: [
|
||||
{ path: 'lines.fromStock' },
|
||||
{ path: 'lines.toStockLocation' },
|
||||
{ path: 'lines.toStock' },
|
||||
],
|
||||
populate: stockTransferPopulate,
|
||||
});
|
||||
if (result?.error) {
|
||||
logger.warn(`Stock transfer not found with supplied id.`);
|
||||
@ -343,8 +361,11 @@ export const editStockTransferRouteHandler = async (req, res) => {
|
||||
const updateData = {
|
||||
lines: (req.body.lines || []).map((l) => normalizeLineInput(l)),
|
||||
};
|
||||
if (req.body.name !== undefined) {
|
||||
updateData.name = req.body.name;
|
||||
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;
|
||||
}
|
||||
|
||||
const result = await editObject({
|
||||
@ -352,11 +373,7 @@ export const editStockTransferRouteHandler = async (req, res) => {
|
||||
id,
|
||||
updateData,
|
||||
user: req.user,
|
||||
populate: [
|
||||
{ path: 'lines.fromStock' },
|
||||
{ path: 'lines.toStockLocation' },
|
||||
{ path: 'lines.toStock' },
|
||||
],
|
||||
populate: stockTransferPopulate,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
@ -396,8 +413,9 @@ 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({
|
||||
@ -471,12 +489,19 @@ 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._id, plain, req.user);
|
||||
const { toStockType, toStock } = await executePostedLine(doc, plain, req.user);
|
||||
updatedLines.push({
|
||||
...plain,
|
||||
toStockType,
|
||||
@ -502,11 +527,7 @@ export const postStockTransferRouteHandler = async (req, res) => {
|
||||
const posted = await getObject({
|
||||
model: stockTransferModel,
|
||||
id,
|
||||
populate: [
|
||||
{ path: 'lines.fromStock' },
|
||||
{ path: 'lines.toStockLocation' },
|
||||
{ path: 'lines.toStock' },
|
||||
],
|
||||
populate: stockTransferPopulate,
|
||||
});
|
||||
|
||||
if (posted?.error) {
|
||||
|
||||
@ -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: ['name', 'state.type', 'postedAt'],
|
||||
stockTransfer: ['state.type', 'postedAt'],
|
||||
stockAudit: ['auditLevel._id', 'stockLocation._id', 'state.type', 'postedAt'],
|
||||
stockAuditLevel: ['name', 'tags'],
|
||||
documentJob: ['documentTemplate', 'documentPrinter', 'object._id', 'objectType'],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user