Added stock location and stock transfer features, including new routes, database schemas, and service updates. Enhanced existing stock-related functionalities to support filtering and population of stock location data.
This commit is contained in:
parent
1b858d8814
commit
d3c662a9ec
@ -20,6 +20,11 @@ const filamentStockSchema = new Schema(
|
||||
gross: { type: Number, required: true },
|
||||
},
|
||||
filamentSku: { type: mongoose.Schema.Types.ObjectId, ref: 'filamentSku', required: true },
|
||||
stockLocation: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'stockLocation',
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
@ -12,6 +12,11 @@ const partStockSchema = new Schema(
|
||||
progress: { type: Number, required: false },
|
||||
},
|
||||
partSku: { type: mongoose.Schema.Types.ObjectId, ref: 'partSku', required: true },
|
||||
stockLocation: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'stockLocation',
|
||||
required: false,
|
||||
},
|
||||
currentQuantity: { type: Number, required: true },
|
||||
sourceType: { type: String, required: true },
|
||||
source: { type: Schema.Types.ObjectId, refPath: 'sourceType', required: true },
|
||||
|
||||
@ -19,6 +19,11 @@ const productStockSchema = new Schema(
|
||||
},
|
||||
postedAt: { type: Date, required: false },
|
||||
productSku: { type: mongoose.Schema.Types.ObjectId, ref: 'productSku', required: true },
|
||||
stockLocation: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'stockLocation',
|
||||
required: false,
|
||||
},
|
||||
currentQuantity: { type: Number, required: true },
|
||||
partStocks: [partStockUsageSchema],
|
||||
},
|
||||
|
||||
@ -25,7 +25,7 @@ const stockEventSchema = new Schema(
|
||||
ownerType: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: ['user', 'subJob', 'stockAudit'],
|
||||
enum: ['user', 'subJob', 'stockAudit', 'stockTransfer'],
|
||||
},
|
||||
timestamp: { type: Date, default: Date.now },
|
||||
},
|
||||
|
||||
29
src/database/schemas/inventory/stocklocation.schema.js
Normal file
29
src/database/schemas/inventory/stocklocation.schema.js
Normal file
@ -0,0 +1,29 @@
|
||||
import mongoose from 'mongoose';
|
||||
import { generateId } from '../../utils.js';
|
||||
const { Schema } = mongoose;
|
||||
|
||||
const stockLocationSchema = new Schema(
|
||||
{
|
||||
_reference: { type: String, default: () => generateId()() },
|
||||
name: { type: String, required: true },
|
||||
notes: { type: String, required: false },
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
stockLocationSchema.statics.stats = async function () {
|
||||
const total = await this.countDocuments({});
|
||||
return { total: { count: total } };
|
||||
};
|
||||
|
||||
stockLocationSchema.statics.history = async function () {
|
||||
return [];
|
||||
};
|
||||
|
||||
stockLocationSchema.virtual('id').get(function () {
|
||||
return this._id;
|
||||
});
|
||||
|
||||
stockLocationSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
export const stockLocationModel = mongoose.model('stockLocation', stockLocationSchema);
|
||||
71
src/database/schemas/inventory/stocktransfer.schema.js
Normal file
71
src/database/schemas/inventory/stocktransfer.schema.js
Normal file
@ -0,0 +1,71 @@
|
||||
import mongoose from 'mongoose';
|
||||
import { generateId } from '../../utils.js';
|
||||
const { Schema } = mongoose;
|
||||
|
||||
const stockTransferLineSchema = new Schema(
|
||||
{
|
||||
fromStockType: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: ['filamentStock', 'partStock', 'productStock'],
|
||||
},
|
||||
fromStock: {
|
||||
type: Schema.Types.ObjectId,
|
||||
refPath: 'fromStockType',
|
||||
required: true,
|
||||
},
|
||||
quantity: { type: Number, required: true },
|
||||
toStockLocation: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'stockLocation',
|
||||
required: true,
|
||||
},
|
||||
toStockType: {
|
||||
type: String,
|
||||
required: false,
|
||||
enum: ['filamentStock', 'partStock', 'productStock'],
|
||||
},
|
||||
toStock: {
|
||||
type: Schema.Types.ObjectId,
|
||||
refPath: 'toStockType',
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
{ _id: true }
|
||||
);
|
||||
|
||||
const stockTransferSchema = new Schema(
|
||||
{
|
||||
_reference: { type: String, default: () => generateId()() },
|
||||
state: {
|
||||
type: { type: String, required: true, default: 'draft' },
|
||||
progress: { type: Number, required: false },
|
||||
},
|
||||
postedAt: { type: Date, required: false },
|
||||
lines: { type: [stockTransferLineSchema], default: [] },
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
stockTransferSchema.statics.stats = async function () {
|
||||
const [draft, posted] = await Promise.all([
|
||||
this.countDocuments({ 'state.type': 'draft' }),
|
||||
this.countDocuments({ 'state.type': 'posted' }),
|
||||
]);
|
||||
return {
|
||||
draft: { count: draft },
|
||||
posted: { count: posted },
|
||||
};
|
||||
};
|
||||
|
||||
stockTransferSchema.statics.history = async function () {
|
||||
return [];
|
||||
};
|
||||
|
||||
stockTransferSchema.virtual('id').get(function () {
|
||||
return this._id;
|
||||
});
|
||||
|
||||
stockTransferSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
export const stockTransferModel = mongoose.model('stockTransfer', stockTransferSchema);
|
||||
@ -31,19 +31,10 @@ partSchema.virtual('id').get(function () {
|
||||
partSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
partSchema.statics.recalculate = async function (part, user) {
|
||||
const orderItemModel = mongoose.model('orderItem');
|
||||
const itemId = part._id;
|
||||
const draftOrderItems = await orderItemModel
|
||||
.find({
|
||||
'state.type': 'draft',
|
||||
itemType: 'part',
|
||||
item: itemId,
|
||||
})
|
||||
.populate('order')
|
||||
.lean();
|
||||
|
||||
for (const orderItem of draftOrderItems) {
|
||||
await orderItemModel.recalculate(orderItem, user);
|
||||
const partSkuModel = mongoose.model('partSku');
|
||||
const skus = await partSkuModel.find({ part: part._id }).select('_id').lean();
|
||||
for (const sku of skus) {
|
||||
await partSkuModel.recalculate(sku, user);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -17,6 +17,8 @@ import { stockEventModel } from './inventory/stockevent.schema.js';
|
||||
import { stockAuditModel } from './inventory/stockaudit.schema.js';
|
||||
import { partStockModel } from './inventory/partstock.schema.js';
|
||||
import { productStockModel } from './inventory/productstock.schema.js';
|
||||
import { stockLocationModel } from './inventory/stocklocation.schema.js';
|
||||
import { stockTransferModel } from './inventory/stocktransfer.schema.js';
|
||||
import { auditLogModel } from './management/auditlog.schema.js';
|
||||
import { userModel } from './management/user.schema.js';
|
||||
import { appPasswordModel } from './management/apppassword.schema.js';
|
||||
@ -157,6 +159,20 @@ export const models = {
|
||||
referenceField: '_reference',
|
||||
label: 'Product Stock',
|
||||
},
|
||||
SLN: {
|
||||
model: stockLocationModel,
|
||||
idField: '_id',
|
||||
type: 'stockLocation',
|
||||
referenceField: '_reference',
|
||||
label: 'Stock Location',
|
||||
},
|
||||
STT: {
|
||||
model: stockTransferModel,
|
||||
idField: '_id',
|
||||
type: 'stockTransfer',
|
||||
referenceField: '_reference',
|
||||
label: 'Stock Transfer',
|
||||
},
|
||||
ADL: {
|
||||
model: auditLogModel,
|
||||
idField: '_id',
|
||||
|
||||
@ -29,6 +29,8 @@ import {
|
||||
orderItemRoutes,
|
||||
shipmentRoutes,
|
||||
stockAuditRoutes,
|
||||
stockLocationRoutes,
|
||||
stockTransferRoutes,
|
||||
stockEventRoutes,
|
||||
auditLogRoutes,
|
||||
noteTypeRoutes,
|
||||
@ -152,6 +154,8 @@ app.use('/orderitems', orderItemRoutes);
|
||||
app.use('/shipments', shipmentRoutes);
|
||||
app.use('/stockevents', stockEventRoutes);
|
||||
app.use('/stockaudits', stockAuditRoutes);
|
||||
app.use('/stocklocations', stockLocationRoutes);
|
||||
app.use('/stocktransfers', stockTransferRoutes);
|
||||
app.use('/auditlogs', auditLogRoutes);
|
||||
app.use('/notetypes', noteTypeRoutes);
|
||||
app.use('/documentsizes', documentSizesRoutes);
|
||||
|
||||
@ -24,6 +24,8 @@ import orderItemRoutes from './inventory/orderitems.js';
|
||||
import shipmentRoutes from './inventory/shipments.js';
|
||||
import stockEventRoutes from './inventory/stockevents.js';
|
||||
import stockAuditRoutes from './inventory/stockaudits.js';
|
||||
import stockLocationRoutes from './inventory/stocklocations.js';
|
||||
import stockTransferRoutes from './inventory/stocktransfers.js';
|
||||
import auditLogRoutes from './management/auditlogs.js';
|
||||
import noteTypeRoutes from './management/notetypes.js';
|
||||
import documentSizesRoutes from './management/documentsizes.js';
|
||||
@ -75,6 +77,8 @@ export {
|
||||
shipmentRoutes,
|
||||
stockEventRoutes,
|
||||
stockAuditRoutes,
|
||||
stockLocationRoutes,
|
||||
stockTransferRoutes,
|
||||
auditLogRoutes,
|
||||
noteTypeRoutes,
|
||||
noteRoutes,
|
||||
|
||||
@ -18,7 +18,15 @@ import {
|
||||
// list of filament stocks
|
||||
router.get('/', isAuthenticated, (req, res) => {
|
||||
const { page, limit, property, search, sort, order } = req.query;
|
||||
const allowedFilters = ['filamentSku', 'state', 'startingWeight', 'currentWeight', 'filamentSku._id'];
|
||||
const allowedFilters = [
|
||||
'filamentSku',
|
||||
'state',
|
||||
'startingWeight',
|
||||
'currentWeight',
|
||||
'filamentSku._id',
|
||||
'stockLocation',
|
||||
'stockLocation._id',
|
||||
];
|
||||
const filter = getFilter(req.query, allowedFilters);
|
||||
listFilamentStocksRouteHandler(req, res, page, limit, property, filter, search, sort, order);
|
||||
});
|
||||
|
||||
@ -18,7 +18,15 @@ import {
|
||||
// list of part stocks
|
||||
router.get('/', isAuthenticated, (req, res) => {
|
||||
const { page, limit, property, search, sort, order } = req.query;
|
||||
const allowedFilters = ['partSku', 'state', 'startingQuantity', 'currentQuantity', 'partSku._id'];
|
||||
const allowedFilters = [
|
||||
'partSku',
|
||||
'state',
|
||||
'startingQuantity',
|
||||
'currentQuantity',
|
||||
'partSku._id',
|
||||
'stockLocation',
|
||||
'stockLocation._id',
|
||||
];
|
||||
const filter = getFilter(req.query, allowedFilters);
|
||||
listPartStocksRouteHandler(req, res, page, limit, property, filter, search, sort, order);
|
||||
});
|
||||
|
||||
@ -18,7 +18,14 @@ import {
|
||||
|
||||
router.get('/', isAuthenticated, (req, res) => {
|
||||
const { page, limit, property, search, sort, order } = req.query;
|
||||
const allowedFilters = ['productSku', 'state', 'currentQuantity', 'productSku._id'];
|
||||
const allowedFilters = [
|
||||
'productSku',
|
||||
'state',
|
||||
'currentQuantity',
|
||||
'productSku._id',
|
||||
'stockLocation',
|
||||
'stockLocation._id',
|
||||
];
|
||||
const filter = getFilter(req.query, allowedFilters);
|
||||
listProductStocksRouteHandler(req, res, page, limit, property, filter, search, sort, order);
|
||||
});
|
||||
|
||||
64
src/routes/inventory/stocklocations.js
Normal file
64
src/routes/inventory/stocklocations.js
Normal file
@ -0,0 +1,64 @@
|
||||
import express from 'express';
|
||||
import { isAuthenticated } from '../../keycloak.js';
|
||||
import { getFilter, convertPropertiesString } from '../../utils.js';
|
||||
|
||||
const router = express.Router();
|
||||
import {
|
||||
listStockLocationsRouteHandler,
|
||||
getStockLocationRouteHandler,
|
||||
editStockLocationRouteHandler,
|
||||
editMultipleStockLocationsRouteHandler,
|
||||
newStockLocationRouteHandler,
|
||||
deleteStockLocationRouteHandler,
|
||||
listStockLocationsByPropertiesRouteHandler,
|
||||
getStockLocationStatsRouteHandler,
|
||||
getStockLocationHistoryRouteHandler,
|
||||
} from '../../services/inventory/stocklocations.js';
|
||||
|
||||
router.get('/', isAuthenticated, (req, res) => {
|
||||
const { page, limit, property, search, sort, order } = req.query;
|
||||
const allowedFilters = ['name', 'notes'];
|
||||
const filter = getFilter(req.query, allowedFilters);
|
||||
listStockLocationsRouteHandler(req, res, page, limit, property, filter, search, sort, order);
|
||||
});
|
||||
|
||||
router.get('/properties', isAuthenticated, (req, res) => {
|
||||
let properties = convertPropertiesString(req.query.properties);
|
||||
const allowedFilters = ['name'];
|
||||
const filter = getFilter(req.query, allowedFilters, false);
|
||||
var masterFilter = {};
|
||||
if (req.query.masterFilter) {
|
||||
masterFilter = JSON.parse(req.query.masterFilter);
|
||||
}
|
||||
listStockLocationsByPropertiesRouteHandler(req, res, properties, filter, masterFilter);
|
||||
});
|
||||
|
||||
router.post('/', isAuthenticated, (req, res) => {
|
||||
newStockLocationRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.get('/stats', isAuthenticated, (req, res) => {
|
||||
getStockLocationStatsRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.get('/history', isAuthenticated, (req, res) => {
|
||||
getStockLocationHistoryRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.get('/:id', isAuthenticated, (req, res) => {
|
||||
getStockLocationRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.put('/', isAuthenticated, async (req, res) => {
|
||||
editMultipleStockLocationsRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.put('/:id', isAuthenticated, async (req, res) => {
|
||||
editStockLocationRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.delete('/:id', isAuthenticated, async (req, res) => {
|
||||
deleteStockLocationRouteHandler(req, res);
|
||||
});
|
||||
|
||||
export default router;
|
||||
69
src/routes/inventory/stocktransfers.js
Normal file
69
src/routes/inventory/stocktransfers.js
Normal file
@ -0,0 +1,69 @@
|
||||
import express from 'express';
|
||||
import { isAuthenticated } from '../../keycloak.js';
|
||||
import { getFilter, convertPropertiesString } from '../../utils.js';
|
||||
|
||||
const router = express.Router();
|
||||
import {
|
||||
listStockTransfersRouteHandler,
|
||||
getStockTransferRouteHandler,
|
||||
editStockTransferRouteHandler,
|
||||
editMultipleStockTransfersRouteHandler,
|
||||
newStockTransferRouteHandler,
|
||||
deleteStockTransferRouteHandler,
|
||||
postStockTransferRouteHandler,
|
||||
listStockTransfersByPropertiesRouteHandler,
|
||||
getStockTransferStatsRouteHandler,
|
||||
getStockTransferHistoryRouteHandler,
|
||||
} from '../../services/inventory/stocktransfers.js';
|
||||
|
||||
router.get('/', isAuthenticated, (req, res) => {
|
||||
const { page, limit, property, search, sort, order } = req.query;
|
||||
const allowedFilters = ['state', 'state.type', 'postedAt'];
|
||||
const filter = getFilter(req.query, allowedFilters);
|
||||
listStockTransfersRouteHandler(req, res, page, limit, property, filter, search, sort, order);
|
||||
});
|
||||
|
||||
router.get('/properties', isAuthenticated, (req, res) => {
|
||||
let properties = convertPropertiesString(req.query.properties);
|
||||
const allowedFilters = ['state.type'];
|
||||
const filter = getFilter(req.query, allowedFilters, false);
|
||||
var masterFilter = {};
|
||||
if (req.query.masterFilter) {
|
||||
masterFilter = JSON.parse(req.query.masterFilter);
|
||||
}
|
||||
listStockTransfersByPropertiesRouteHandler(req, res, properties, filter, masterFilter);
|
||||
});
|
||||
|
||||
router.post('/', isAuthenticated, (req, res) => {
|
||||
newStockTransferRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.get('/stats', isAuthenticated, (req, res) => {
|
||||
getStockTransferStatsRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.get('/history', isAuthenticated, (req, res) => {
|
||||
getStockTransferHistoryRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.get('/:id', isAuthenticated, (req, res) => {
|
||||
getStockTransferRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.put('/', isAuthenticated, async (req, res) => {
|
||||
editMultipleStockTransfersRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.put('/:id', isAuthenticated, async (req, res) => {
|
||||
editStockTransferRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.delete('/:id', isAuthenticated, async (req, res) => {
|
||||
deleteStockTransferRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.post('/:id/post', isAuthenticated, async (req, res) => {
|
||||
postStockTransferRouteHandler(req, res);
|
||||
});
|
||||
|
||||
export default router;
|
||||
@ -36,7 +36,7 @@ export const listFilamentStocksRouteHandler = async (
|
||||
search,
|
||||
sort,
|
||||
order,
|
||||
populate: [{ path: 'filamentSku' }],
|
||||
populate: [{ path: 'filamentSku' }, { path: 'stockLocation' }],
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
@ -60,7 +60,7 @@ export const listFilamentStocksByPropertiesRouteHandler = async (
|
||||
model: filamentStockModel,
|
||||
properties,
|
||||
filter,
|
||||
populate: ['filamentSku'],
|
||||
populate: ['filamentSku', 'stockLocation'],
|
||||
masterFilter,
|
||||
});
|
||||
|
||||
@ -79,7 +79,7 @@ export const getFilamentStockRouteHandler = async (req, res) => {
|
||||
const result = await getObject({
|
||||
model: filamentStockModel,
|
||||
id,
|
||||
populate: [{ path: 'filamentSku' }],
|
||||
populate: [{ path: 'filamentSku' }, { path: 'stockLocation' }],
|
||||
});
|
||||
if (result?.error) {
|
||||
logger.warn(`Filament Stock not found with supplied id.`);
|
||||
@ -95,8 +95,9 @@ export const editFilamentStockRouteHandler = async (req, res) => {
|
||||
|
||||
logger.trace(`Filament Stock with ID: ${id}`);
|
||||
|
||||
const updateData = {};
|
||||
// Create audit log before updating
|
||||
const updateData = {
|
||||
stockLocation: req.body.stockLocation,
|
||||
};
|
||||
const result = await editObject({
|
||||
model: filamentStockModel,
|
||||
id,
|
||||
@ -148,6 +149,7 @@ export const newFilamentStockRouteHandler = async (req, res) => {
|
||||
currentWeight: req.body.currentWeight,
|
||||
filamentSku: req.body.filamentSku,
|
||||
state: req.body.state,
|
||||
stockLocation: req.body.stockLocation,
|
||||
};
|
||||
const result = await newObject({
|
||||
model: filamentStockModel,
|
||||
|
||||
@ -36,7 +36,7 @@ export const listPartStocksRouteHandler = async (
|
||||
search,
|
||||
sort,
|
||||
order,
|
||||
populate: [{ path: 'partSku' }],
|
||||
populate: [{ path: 'partSku' }, { path: 'stockLocation' }, { path: 'source' }],
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
@ -60,7 +60,7 @@ export const listPartStocksByPropertiesRouteHandler = async (
|
||||
model: partStockModel,
|
||||
properties,
|
||||
filter,
|
||||
populate: ['partSku'],
|
||||
populate: ['partSku', 'stockLocation', 'source'],
|
||||
masterFilter,
|
||||
});
|
||||
|
||||
@ -79,7 +79,7 @@ export const getPartStockRouteHandler = async (req, res) => {
|
||||
const result = await getObject({
|
||||
model: partStockModel,
|
||||
id,
|
||||
populate: [{ path: 'partSku' }],
|
||||
populate: [{ path: 'partSku' }, { path: 'stockLocation' }, { path: 'source' }],
|
||||
});
|
||||
if (result?.error) {
|
||||
logger.warn(`Part Stock not found with supplied id.`);
|
||||
@ -96,7 +96,6 @@ export const editPartStockRouteHandler = async (req, res) => {
|
||||
logger.trace(`Part Stock with ID: ${id}`);
|
||||
|
||||
const updateData = {};
|
||||
// Create audit log before updating
|
||||
const result = await editObject({
|
||||
model: partStockModel,
|
||||
id,
|
||||
@ -148,6 +147,9 @@ export const newPartStockRouteHandler = async (req, res) => {
|
||||
currentQuantity: req.body.currentQuantity,
|
||||
partSku: req.body.partSku,
|
||||
state: req.body.state,
|
||||
sourceType: req.body.sourceType,
|
||||
source: req.body.source,
|
||||
stockLocation: req.body.stockLocation,
|
||||
};
|
||||
const result = await newObject({
|
||||
model: partStockModel,
|
||||
|
||||
@ -38,7 +38,11 @@ export const listProductStocksRouteHandler = async (
|
||||
search,
|
||||
sort,
|
||||
order,
|
||||
populate: [{ path: 'productSku' }, { path: 'partStocks.partStock' }],
|
||||
populate: [
|
||||
{ path: 'productSku' },
|
||||
{ path: 'partStocks.partStock' },
|
||||
{ path: 'stockLocation' },
|
||||
],
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
@ -62,7 +66,7 @@ export const listProductStocksByPropertiesRouteHandler = async (
|
||||
model: productStockModel,
|
||||
properties,
|
||||
filter,
|
||||
populate: ['productSku', 'partStocks.partStock'],
|
||||
populate: ['productSku', 'partStocks.partStock', 'stockLocation'],
|
||||
masterFilter,
|
||||
});
|
||||
|
||||
@ -81,7 +85,12 @@ export const getProductStockRouteHandler = async (req, res) => {
|
||||
const result = await getObject({
|
||||
model: productStockModel,
|
||||
id,
|
||||
populate: [{ path: 'partStocks.partSku' }, { path: 'partStocks.partStock' }, { path: 'productSku' }],
|
||||
populate: [
|
||||
{ path: 'partStocks.partSku' },
|
||||
{ path: 'partStocks.partStock' },
|
||||
{ path: 'productSku' },
|
||||
{ path: 'stockLocation' },
|
||||
],
|
||||
});
|
||||
if (result?.error) {
|
||||
logger.warn(`Product Stock not found with supplied id.`);
|
||||
@ -111,6 +120,7 @@ export const editProductStockRouteHandler = async (req, res) => {
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
stockLocation: req.body?.stockLocation,
|
||||
partStocks: req.body?.partStocks?.map((partStock) => ({
|
||||
quantity: partStock.quantity,
|
||||
partStock: partStock.partStock,
|
||||
@ -173,6 +183,7 @@ export const newProductStockRouteHandler = async (req, res) => {
|
||||
currentQuantity: req.body.currentQuantity,
|
||||
productSku: req.body.productSku,
|
||||
state: req.body.state ?? { type: 'draft' },
|
||||
stockLocation: req.body.stockLocation,
|
||||
partStocks: (productSku.parts || []).map((part) => ({
|
||||
partSku: part.partSku,
|
||||
quantity: part.quantity,
|
||||
|
||||
196
src/services/inventory/stocklocations.js
Normal file
196
src/services/inventory/stocklocations.js
Normal file
@ -0,0 +1,196 @@
|
||||
import config from '../../config.js';
|
||||
import { stockLocationModel } from '../../database/schemas/inventory/stocklocation.schema.js';
|
||||
import log4js from 'log4js';
|
||||
import mongoose from 'mongoose';
|
||||
import {
|
||||
deleteObject,
|
||||
listObjects,
|
||||
getObject,
|
||||
editObject,
|
||||
editObjects,
|
||||
newObject,
|
||||
listObjectsByProperties,
|
||||
getModelStats,
|
||||
getModelHistory,
|
||||
} from '../../database/database.js';
|
||||
|
||||
const logger = log4js.getLogger('Stock Locations');
|
||||
logger.level = config.server.logLevel;
|
||||
|
||||
export const listStockLocationsRouteHandler = async (
|
||||
req,
|
||||
res,
|
||||
page = 1,
|
||||
limit = 25,
|
||||
property = '',
|
||||
filter = {},
|
||||
search = '',
|
||||
sort = '',
|
||||
order = 'ascend'
|
||||
) => {
|
||||
const result = await listObjects({
|
||||
model: stockLocationModel,
|
||||
page,
|
||||
limit,
|
||||
property,
|
||||
filter,
|
||||
search,
|
||||
sort,
|
||||
order,
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
logger.error('Error listing stock locations.');
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`List of stock locations (Page ${page}, Limit ${limit}). Count: ${result.length}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const listStockLocationsByPropertiesRouteHandler = async (
|
||||
req,
|
||||
res,
|
||||
properties = '',
|
||||
filter = {},
|
||||
masterFilter = {}
|
||||
) => {
|
||||
const result = await listObjectsByProperties({
|
||||
model: stockLocationModel,
|
||||
properties,
|
||||
filter,
|
||||
masterFilter,
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
logger.error('Error listing stock locations.');
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`List of stock locations. Count: ${result.length}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const getStockLocationRouteHandler = async (req, res) => {
|
||||
const id = req.params.id;
|
||||
const result = await getObject({
|
||||
model: stockLocationModel,
|
||||
id,
|
||||
});
|
||||
if (result?.error) {
|
||||
logger.warn(`Stock location not found with supplied id.`);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
logger.debug(`Retrieved stock location with ID: ${id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const editStockLocationRouteHandler = async (req, res) => {
|
||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||
|
||||
const updateData = {
|
||||
name: req.body.name,
|
||||
notes: req.body.notes,
|
||||
};
|
||||
|
||||
const result = await editObject({
|
||||
model: stockLocationModel,
|
||||
id,
|
||||
updateData,
|
||||
user: req.user,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
logger.error('Error editing stock location:', result.error);
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`Edited stock location with ID: ${id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const editMultipleStockLocationsRouteHandler = async (req, res) => {
|
||||
const updates = req.body.map((update) => ({
|
||||
_id: update._id,
|
||||
}));
|
||||
|
||||
if (!Array.isArray(updates)) {
|
||||
return res.status(400).send({ error: 'Body must be an array of updates.', code: 400 });
|
||||
}
|
||||
|
||||
const result = await editObjects({
|
||||
model: stockLocationModel,
|
||||
updates,
|
||||
user: req.user,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
logger.error('Error editing stock locations:', result.error);
|
||||
res.status(result.code || 500).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`Edited ${updates.length} stock locations`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const newStockLocationRouteHandler = async (req, res) => {
|
||||
const newData = {
|
||||
name: req.body.name,
|
||||
notes: req.body.notes,
|
||||
};
|
||||
const result = await newObject({
|
||||
model: stockLocationModel,
|
||||
newData,
|
||||
user: req.user,
|
||||
});
|
||||
if (result.error) {
|
||||
logger.error('No stock location created:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
|
||||
logger.debug(`New stock location with ID: ${result._id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const deleteStockLocationRouteHandler = async (req, res) => {
|
||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||
|
||||
const result = await deleteObject({
|
||||
model: stockLocationModel,
|
||||
id,
|
||||
user: req.user,
|
||||
});
|
||||
if (result.error) {
|
||||
logger.error('No stock location deleted:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
|
||||
logger.debug(`Deleted stock location with ID: ${result._id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const getStockLocationStatsRouteHandler = async (req, res) => {
|
||||
const result = await getModelStats({ model: stockLocationModel });
|
||||
if (result?.error) {
|
||||
logger.error('Error fetching stock location stats:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
logger.trace('Stock location stats:', result);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const getStockLocationHistoryRouteHandler = async (req, res) => {
|
||||
const from = req.query.from;
|
||||
const to = req.query.to;
|
||||
const result = await getModelHistory({ model: stockLocationModel, from, to });
|
||||
if (result?.error) {
|
||||
logger.error('Error fetching stock location history:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
logger.trace('Stock location history:', result);
|
||||
res.send(result);
|
||||
};
|
||||
461
src/services/inventory/stocktransfers.js
Normal file
461
src/services/inventory/stocktransfers.js
Normal file
@ -0,0 +1,461 @@
|
||||
import config from '../../config.js';
|
||||
import { stockTransferModel } from '../../database/schemas/inventory/stocktransfer.schema.js';
|
||||
import { stockLocationModel } from '../../database/schemas/inventory/stocklocation.schema.js';
|
||||
import { filamentStockModel } from '../../database/schemas/inventory/filamentstock.schema.js';
|
||||
import { partStockModel } from '../../database/schemas/inventory/partstock.schema.js';
|
||||
import { productStockModel } from '../../database/schemas/inventory/productstock.schema.js';
|
||||
import { stockEventModel } from '../../database/schemas/inventory/stockevent.schema.js';
|
||||
import log4js from 'log4js';
|
||||
import mongoose from 'mongoose';
|
||||
import {
|
||||
deleteObject,
|
||||
listObjects,
|
||||
getObject,
|
||||
editObject,
|
||||
editObjects,
|
||||
newObject,
|
||||
listObjectsByProperties,
|
||||
getModelStats,
|
||||
getModelHistory,
|
||||
checkStates,
|
||||
} from '../../database/database.js';
|
||||
|
||||
const logger = log4js.getLogger('Stock Transfers');
|
||||
logger.level = config.server.logLevel;
|
||||
|
||||
const normalizeLineInput = (l) => ({
|
||||
fromStockType: l.fromStockType,
|
||||
fromStock: l.fromStock?._id ?? l.fromStock,
|
||||
quantity: Number(l.quantity),
|
||||
toStockLocation: l.toStockLocation?._id ?? l.toStockLocation,
|
||||
});
|
||||
|
||||
async function createStockEventsForLine({ transferId, fromId, fromType, toId, toType, qty, unit }) {
|
||||
const ts = new Date();
|
||||
await stockEventModel.insertMany([
|
||||
{
|
||||
value: -Math.abs(qty),
|
||||
unit,
|
||||
parent: fromId,
|
||||
parentType: fromType,
|
||||
owner: transferId,
|
||||
ownerType: 'stockTransfer',
|
||||
timestamp: ts,
|
||||
},
|
||||
{
|
||||
value: Math.abs(qty),
|
||||
unit,
|
||||
parent: toId,
|
||||
parentType: toType,
|
||||
owner: transferId,
|
||||
ownerType: 'stockTransfer',
|
||||
timestamp: ts,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
async function executePostedLine(transferId, line) {
|
||||
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');
|
||||
}
|
||||
|
||||
if (line.fromStockType === 'filamentStock') {
|
||||
const src = await filamentStockModel.findById(line.fromStock);
|
||||
if (!src) throw new Error('From filament stock not found');
|
||||
const netAvail = src.currentWeight?.net ?? 0;
|
||||
if (line.quantity > netAvail) {
|
||||
throw new Error('Filament transfer quantity exceeds available net weight');
|
||||
}
|
||||
const tareBefore = Math.max(0, (src.currentWeight?.gross ?? 0) - (src.currentWeight?.net ?? 0));
|
||||
const newNet = netAvail - line.quantity;
|
||||
const ratio = netAvail > 0 ? newNet / netAvail : 0;
|
||||
const newGross = (src.currentWeight?.gross ?? 0) * ratio;
|
||||
await filamentStockModel.findByIdAndUpdate(src._id, {
|
||||
$set: { 'currentWeight.net': newNet, 'currentWeight.gross': newGross },
|
||||
});
|
||||
|
||||
const destWeight = {
|
||||
net: line.quantity,
|
||||
gross: line.quantity + tareBefore,
|
||||
};
|
||||
const dest = await filamentStockModel.create({
|
||||
state: src.state,
|
||||
startingWeight: destWeight,
|
||||
currentWeight: destWeight,
|
||||
filamentSku: src.filamentSku,
|
||||
stockLocation: toLocId,
|
||||
});
|
||||
|
||||
await createStockEventsForLine({
|
||||
transferId,
|
||||
fromId: src._id,
|
||||
fromType: 'filamentStock',
|
||||
toId: dest._id,
|
||||
toType: 'filamentStock',
|
||||
qty: line.quantity,
|
||||
unit: 'g',
|
||||
});
|
||||
|
||||
return { toStockType: 'filamentStock', toStock: dest._id };
|
||||
}
|
||||
|
||||
if (line.fromStockType === 'partStock') {
|
||||
const src = await partStockModel.findById(line.fromStock);
|
||||
console.log(src);
|
||||
if (!src) throw new Error('From part stock not found');
|
||||
const currentQuantity = src.state.type === 'new' ? src.startingQuantity : src.currentQuantity;
|
||||
if (line.quantity > currentQuantity) {
|
||||
throw new Error('Part transfer quantity exceeds current quantity');
|
||||
}
|
||||
await partStockModel.findByIdAndUpdate(src._id, {
|
||||
$inc: { currentQuantity: -line.quantity },
|
||||
});
|
||||
|
||||
const dest = await partStockModel.create({
|
||||
partSku: src.partSku,
|
||||
currentQuantity: line.quantity,
|
||||
state: { type: 'new' },
|
||||
sourceType: 'stockTransfer',
|
||||
source: transferId,
|
||||
stockLocation: toLocId,
|
||||
});
|
||||
|
||||
await createStockEventsForLine({
|
||||
transferId,
|
||||
fromId: src._id,
|
||||
fromType: 'partStock',
|
||||
toId: dest._id,
|
||||
toType: 'partStock',
|
||||
qty: line.quantity,
|
||||
unit: 'each',
|
||||
});
|
||||
|
||||
return { toStockType: 'partStock', toStock: dest._id };
|
||||
}
|
||||
|
||||
if (line.fromStockType === 'productStock') {
|
||||
const src = await productStockModel.findById(line.fromStock);
|
||||
if (!src) throw new Error('From product stock not found');
|
||||
if (line.quantity > src.currentQuantity) {
|
||||
throw new Error('Product transfer quantity exceeds current quantity');
|
||||
}
|
||||
await productStockModel.findByIdAndUpdate(src._id, {
|
||||
$inc: { currentQuantity: -line.quantity },
|
||||
});
|
||||
|
||||
const dest = await productStockModel.create({
|
||||
productSku: src.productSku,
|
||||
currentQuantity: line.quantity,
|
||||
state: { type: 'posted' },
|
||||
postedAt: new Date(),
|
||||
partStocks: [],
|
||||
stockLocation: toLocId,
|
||||
});
|
||||
|
||||
await createStockEventsForLine({
|
||||
transferId,
|
||||
fromId: src._id,
|
||||
fromType: 'productStock',
|
||||
toId: dest._id,
|
||||
toType: 'productStock',
|
||||
qty: line.quantity,
|
||||
unit: 'each',
|
||||
});
|
||||
|
||||
return { toStockType: 'productStock', toStock: dest._id };
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported from stock type: ${line.fromStockType}`);
|
||||
}
|
||||
|
||||
export const listStockTransfersRouteHandler = async (
|
||||
req,
|
||||
res,
|
||||
page = 1,
|
||||
limit = 25,
|
||||
property = '',
|
||||
filter = {},
|
||||
search = '',
|
||||
sort = '',
|
||||
order = 'ascend'
|
||||
) => {
|
||||
const result = await listObjects({
|
||||
model: stockTransferModel,
|
||||
page,
|
||||
limit,
|
||||
property,
|
||||
filter,
|
||||
search,
|
||||
sort,
|
||||
order,
|
||||
populate: [
|
||||
{ path: 'lines.fromStock' },
|
||||
{ path: 'lines.toStockLocation' },
|
||||
{ path: 'lines.toStock' },
|
||||
],
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
logger.error('Error listing stock transfers.');
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`List of stock transfers (Page ${page}, Limit ${limit}). Count: ${result.length}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const listStockTransfersByPropertiesRouteHandler = async (
|
||||
req,
|
||||
res,
|
||||
properties = '',
|
||||
filter = {},
|
||||
masterFilter = {}
|
||||
) => {
|
||||
const result = await listObjectsByProperties({
|
||||
model: stockTransferModel,
|
||||
properties,
|
||||
filter,
|
||||
populate: [
|
||||
{ path: 'lines.fromStock' },
|
||||
{ path: 'lines.toStockLocation' },
|
||||
{ path: 'lines.toStock' },
|
||||
],
|
||||
masterFilter,
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
logger.error('Error listing stock transfers.');
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`List of stock transfers. Count: ${result.length}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const getStockTransferRouteHandler = async (req, res) => {
|
||||
const id = req.params.id;
|
||||
const result = await getObject({
|
||||
model: stockTransferModel,
|
||||
id,
|
||||
populate: [
|
||||
{ path: 'lines.fromStock' },
|
||||
{ path: 'lines.toStockLocation' },
|
||||
{ path: 'lines.toStock' },
|
||||
],
|
||||
});
|
||||
if (result?.error) {
|
||||
logger.warn(`Stock transfer not found with supplied id.`);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
logger.debug(`Retrieved stock transfer with ID: ${id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const editStockTransferRouteHandler = async (req, res) => {
|
||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||
|
||||
const checkStatesResult = await checkStates({ model: stockTransferModel, id, states: ['draft'] });
|
||||
|
||||
if (checkStatesResult.error) {
|
||||
logger.error('Error checking stock transfer state:', checkStatesResult.error);
|
||||
res.status(checkStatesResult.code).send(checkStatesResult);
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkStatesResult === false) {
|
||||
logger.error('Stock transfer is not in draft state.');
|
||||
res.status(400).send({ error: 'Stock transfer is not in draft state.', code: 400 });
|
||||
return;
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
lines: (req.body.lines || []).map((l) => normalizeLineInput(l)),
|
||||
};
|
||||
|
||||
const result = await editObject({
|
||||
model: stockTransferModel,
|
||||
id,
|
||||
updateData,
|
||||
user: req.user,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
logger.error('Error editing stock transfer:', result.error);
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`Edited stock transfer with ID: ${id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const editMultipleStockTransfersRouteHandler = async (req, res) => {
|
||||
const updates = req.body.map((update) => ({
|
||||
_id: update._id,
|
||||
}));
|
||||
|
||||
if (!Array.isArray(updates)) {
|
||||
return res.status(400).send({ error: 'Body must be an array of updates.', code: 400 });
|
||||
}
|
||||
|
||||
const result = await editObjects({
|
||||
model: stockTransferModel,
|
||||
updates,
|
||||
user: req.user,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
logger.error('Error editing stock transfers:', result.error);
|
||||
res.status(result.code || 500).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`Edited ${updates.length} stock transfers`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const newStockTransferRouteHandler = async (req, res) => {
|
||||
const newData = {
|
||||
state: req.body.state ?? { type: 'draft' },
|
||||
lines: (req.body.lines || []).map((l) => normalizeLineInput(l)),
|
||||
};
|
||||
const result = await newObject({
|
||||
model: stockTransferModel,
|
||||
newData,
|
||||
user: req.user,
|
||||
});
|
||||
if (result.error) {
|
||||
logger.error('No stock transfer created:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
|
||||
logger.debug(`New stock transfer with ID: ${result._id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const deleteStockTransferRouteHandler = async (req, res) => {
|
||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||
|
||||
const checkStatesResult = await checkStates({ model: stockTransferModel, id, states: ['draft'] });
|
||||
|
||||
if (checkStatesResult.error) {
|
||||
logger.error('Error checking stock transfer state:', checkStatesResult.error);
|
||||
res.status(checkStatesResult.code).send(checkStatesResult);
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkStatesResult === false) {
|
||||
logger.error('Stock transfer is not in draft state.');
|
||||
res.status(400).send({ error: 'Stock transfer is not in draft state.', code: 400 });
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await deleteObject({
|
||||
model: stockTransferModel,
|
||||
id,
|
||||
user: req.user,
|
||||
});
|
||||
if (result.error) {
|
||||
logger.error('No stock transfer deleted:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
|
||||
logger.debug(`Deleted stock transfer with ID: ${result._id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const postStockTransferRouteHandler = async (req, res) => {
|
||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||
|
||||
const checkStatesResult = await checkStates({ model: stockTransferModel, id, states: ['draft'] });
|
||||
|
||||
if (checkStatesResult.error) {
|
||||
logger.error('Error checking stock transfer state:', checkStatesResult.error);
|
||||
res.status(checkStatesResult.code).send(checkStatesResult);
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkStatesResult === false) {
|
||||
logger.error('Stock transfer is not in draft state.');
|
||||
res.status(400).send({ error: 'Stock transfer is not in draft state.', code: 400 });
|
||||
return;
|
||||
}
|
||||
|
||||
const doc = await stockTransferModel.findById(id);
|
||||
if (!doc) {
|
||||
return res.status(404).send({ error: 'Stock transfer not found.', code: 404 });
|
||||
}
|
||||
|
||||
if (!doc.lines?.length) {
|
||||
return res.status(400).send({ error: 'Stock transfer has no lines.', code: 400 });
|
||||
}
|
||||
|
||||
const updatedLines = [];
|
||||
|
||||
try {
|
||||
for (const line of doc.lines) {
|
||||
const plain = line.toObject();
|
||||
const { toStockType, toStock } = await executePostedLine(doc._id, plain);
|
||||
updatedLines.push({
|
||||
...plain,
|
||||
toStockType,
|
||||
toStock,
|
||||
});
|
||||
}
|
||||
|
||||
const posted = await stockTransferModel
|
||||
.findByIdAndUpdate(
|
||||
id,
|
||||
{
|
||||
$set: {
|
||||
state: { type: 'posted' },
|
||||
postedAt: new Date(),
|
||||
lines: updatedLines,
|
||||
},
|
||||
},
|
||||
{ new: true }
|
||||
)
|
||||
.populate([
|
||||
{ path: 'lines.fromStock' },
|
||||
{ path: 'lines.toStockLocation' },
|
||||
{ path: 'lines.toStock' },
|
||||
])
|
||||
.lean();
|
||||
|
||||
logger.debug(`Posted stock transfer with ID: ${id}`);
|
||||
res.send(posted);
|
||||
} catch (err) {
|
||||
logger.error('Error posting stock transfer:', err);
|
||||
res.status(400).send({ error: err.message || 'Failed to post stock transfer', code: 400 });
|
||||
}
|
||||
};
|
||||
|
||||
export const getStockTransferStatsRouteHandler = async (req, res) => {
|
||||
const result = await getModelStats({ model: stockTransferModel });
|
||||
if (result?.error) {
|
||||
logger.error('Error fetching stock transfer stats:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
logger.trace('Stock transfer stats:', result);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const getStockTransferHistoryRouteHandler = async (req, res) => {
|
||||
const from = req.query.from;
|
||||
const to = req.query.to;
|
||||
const result = await getModelHistory({ model: stockTransferModel, from, to });
|
||||
if (result?.error) {
|
||||
logger.error('Error fetching stock transfer history:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
logger.trace('Stock transfer history:', result);
|
||||
res.send(result);
|
||||
};
|
||||
@ -23,6 +23,8 @@ export const EXPORT_FILTER_BY_TYPE = {
|
||||
orderItem: ['order._id', 'orderType', 'item._id', 'itemType', 'sku._id', 'shipment._id'],
|
||||
shipment: ['order._id', 'orderType', 'courierService._id'],
|
||||
stockEvent: ['parent._id', 'parentType', 'owner._id', 'ownerType'],
|
||||
stockLocation: ['name'],
|
||||
stockTransfer: ['state.type', 'postedAt'],
|
||||
stockAudit: ['filamentStock._id', 'partStock._id'],
|
||||
documentJob: ['documentTemplate', 'documentPrinter', 'object._id', 'objectType'],
|
||||
documentTemplate: ['parent._id', 'documentSize._id'],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user