Tom Butcher e98828bfc7
Some checks failed
farmcontrol/farmcontrol-api/pipeline/head There was a failure building this commit
Update configuration and enhance marketplace integration with new policies
This commit modifies the log level in the configuration file from "trace" to "debug" for improved logging clarity. It also introduces new routes for fulfillment, return, and payment policies in the index file, enhancing the marketplace integration capabilities. Additionally, the initialization process is updated to start the marketplace worker, ensuring that the application can handle marketplace operations effectively. Various schemas are updated to include new fields and relationships for policies, improving the overall functionality and flexibility of the marketplace management system.
2026-08-29 00:47:55 +01:00

514 lines
13 KiB
JavaScript

import config from '../../config.js';
import { shipmentModel } from '../../database/schemas/inventory/shipment.schema.js';
import log4js from 'log4js';
import mongoose from 'mongoose';
import {
deleteObject,
listObjects,
getObject,
editObject,
editObjects,
newObject,
listObjectsByProperties,
getModelStats,
getModelHistory,
checkStates,
searchObjects,
getPropertyValues,
getObjectNeighbors,
} from '../../database/database.js';
const logger = log4js.getLogger('Shipments');
logger.level = config.server.logLevel;
import { orderItemModel } from '../../database/schemas/inventory/orderitem.schema.js';
import { salesOrderModel } from '../../database/schemas/sales/salesorder.schema.js';
import { marketplaceModel } from '../../database/schemas/sales/marketplace.schema.js';
import * as marketplaceIntegration from '../../integrations/marketplace.js';
export const listShipmentsRouteHandler = async (
req,
res,
page = 1,
limit = 25,
property = '',
filter = {},
search = '',
sort = '',
order = 'ascend'
) => {
const result = await listObjects({
model: shipmentModel,
page,
limit,
property,
filter,
search,
sort,
order,
populate: ['order', 'courierService', 'taxRate'],
});
if (result?.error) {
logger.error('Error listing shipments.');
res.status(result.code).send(result);
return;
}
logger.debug(`List of shipments (Page ${page}, Limit ${limit}). Count: ${result.length}`);
res.send(result);
};
export const listShipmentsByPropertiesRouteHandler = async (
req,
res,
properties = '',
filter = {},
masterFilter = {}
) => {
const result = await listObjectsByProperties({
model: shipmentModel,
properties,
filter,
populate: ['courierService'],
masterFilter,
});
if (result?.error) {
logger.error('Error listing shipments.');
res.status(result.code).send(result);
return;
}
logger.debug(`List of shipments. Count: ${result.length}`);
res.send(result);
};
export const getShipmentPropertyValuesRouteHandler = async (req, res, property) => {
const result = await getPropertyValues({
model: shipmentModel,
property,
});
res.send(result);
};
export const searchShipmentsRouteHandler = async (req, res, search) => {
const result = await searchObjects({
model: shipmentModel,
search,
});
res.send(result);
};
export const getShipmentRouteHandler = async (req, res) => {
const id = req.params.id;
const result = await getObject({
model: shipmentModel,
id,
populate: ['order', 'courierService', 'taxRate'],
});
if (result?.error) {
logger.warn(`Shipment not found with supplied id.`);
return res.status(result.code).send(result);
}
logger.debug(`Retreived shipment with ID: ${id}`);
res.send(result);
};
export const editShipmentRouteHandler = async (req, res) => {
// Get ID from params
const id = new mongoose.Types.ObjectId(req.params.id);
logger.trace(`Shipment with ID: ${id}`);
const updateData = {
updatedAt: new Date(),
orderType: req.body.orderType,
order: req.body.order,
courierService: req.body.courierService,
trackingNumber: req.body.trackingNumber,
amount: req.body.amount,
amountWithTax: req.body.amountWithTax,
taxRate: req.body.taxRate,
};
// Create audit log before updating
const result = await editObject({
model: shipmentModel,
id,
updateData,
user: req.user,
populate: ['order', 'courierService', 'taxRate'],
});
if (result.error) {
logger.error('Error editing shipment:', result.error);
res.status(result).send(result);
return;
}
logger.debug(`Edited shipment with ID: ${id}`);
res.send(result);
};
export const editMultipleShipmentsRouteHandler = async (req, res) => {
const updates = req.body.map((update) => ({
_id: update._id,
orderType: update.orderType,
order: update.order,
courierService: update.courierService,
trackingNumber: update.trackingNumber,
amount: update.amount,
amountWithTax: update.amountWithTax,
taxRate: update.taxRate,
}));
if (!Array.isArray(updates)) {
return res.status(400).send({ error: 'Body must be an array of updates.', code: 400 });
}
const result = await editObjects({
model: shipmentModel,
updates,
user: req.user,
});
if (result.error) {
logger.error('Error editing shipments:', result.error);
res.status(result.code || 500).send(result);
return;
}
logger.debug(`Edited ${updates.length} shipments`);
res.send(result);
};
export const newShipmentRouteHandler = async (req, res) => {
const newData = {
updatedAt: new Date(),
orderType: req.body.orderType,
order: req.body.order,
courierService: req.body.courierService,
trackingNumber: req.body.trackingNumber,
amount: req.body.amount,
amountWithTax: req.body.amountWithTax,
taxRate: req.body.taxRate,
shippedAt: req.body.shippedAt,
expectedAt: req.body.expectedAt,
deliveredAt: req.body.deliveredAt,
state: { type: 'draft' },
};
const result = await newObject({
model: shipmentModel,
newData,
user: req.user,
});
if (result.error) {
logger.error('No shipment created:', result.error);
return res.status(result.code).send(result);
}
logger.debug(`New shipment with ID: ${result._id}`);
res.send(result);
};
export const deleteShipmentRouteHandler = async (req, res) => {
// Get ID from params
const id = new mongoose.Types.ObjectId(req.params.id);
logger.trace(`Shipment with ID: ${id}`);
const result = await deleteObject({
model: shipmentModel,
id,
user: req.user,
});
if (result.error) {
logger.error('No shipment deleted:', result.error);
return res.status(result.code).send(result);
}
logger.debug(`Deleted shipment with ID: ${result._id}`);
res.send(result);
};
export const getShipmentStatsRouteHandler = async (req, res) => {
const result = await getModelStats({ model: shipmentModel });
if (result?.error) {
logger.error('Error fetching shipment stats:', result.error);
return res.status(result.code).send(result);
}
logger.trace('Shipment stats:', result);
res.send(result);
};
export const getShipmentHistoryRouteHandler = async (req, res) => {
const from = req.query.from;
const to = req.query.to;
const result = await getModelHistory({ model: shipmentModel, from, to });
if (result?.error) {
logger.error('Error fetching shipment history:', result.error);
return res.status(result.code).send(result);
}
logger.trace('Shipment history:', result);
res.send(result);
};
export const shipShipmentRouteHandler = async (req, res) => {
const id = new mongoose.Types.ObjectId(req.params.id);
logger.trace(`Shipment with ID: ${id}`);
const checkStatesResult = await checkStates({ model: shipmentModel, id, states: ['planned'] });
if (checkStatesResult.error) {
logger.error('Error checking shipment states:', checkStatesResult.error);
res.status(checkStatesResult.code).send(checkStatesResult);
return;
}
if (checkStatesResult === false) {
logger.error('Shipment is not in planned state.');
res.status(400).send({ error: 'Shipment is not in planned state.', code: 400 });
return;
}
const orderItemsResult = await listObjects({
model: orderItemModel,
filter: { shipment: id },
pagination: false,
});
if (orderItemsResult.error) {
logger.error('Error listing order items:', orderItemsResult.error);
res.status(orderItemsResult.code).send(orderItemsResult);
return;
}
for (const orderItem of orderItemsResult) {
if (orderItem.state.type != 'ordered') {
logger.error('Order item is not in ordered state.');
res.status(400).send({ error: 'Order item is not in ordered state.', code: 400 });
return;
}
}
for (const orderItem of orderItemsResult) {
await editObject({
model: orderItemModel,
id: orderItem._id,
user: req.user,
updateData: {
state: { type: 'shipped' },
receivedAt: new Date(),
},
});
}
const updateData = {
state: { type: 'shipped' },
shippedAt: new Date(),
};
const result = await editObject({ model: shipmentModel, id, updateData, user: req.user });
if (result.error) {
logger.error('Error shipping shipment:', result.error);
res.status(result.code).send(result);
return;
}
logger.debug(`Shipped shipment with ID: ${id}`);
res.send(result);
const orderId = result.order?._id || result.order;
if (result.orderType === 'salesOrder' && orderId) {
salesOrderModel
.findById(orderId)
.lean()
.then(async (salesOrder) => {
if (!salesOrder?.marketplace) return;
const marketplace = await marketplaceModel.findById(salesOrder.marketplace);
if (!marketplace) return;
const shipment = await shipmentModel.findById(id).populate('courierService').lean();
marketplaceIntegration.pushMarketplaceShipmentFulfillment(marketplace, req.user, shipment);
})
.catch((err) => {
logger.warn(`Failed to push marketplace fulfillment for shipment ${id}: ${err.message}`);
});
}
return;
};
export const receiveShipmentRouteHandler = async (req, res) => {
const id = new mongoose.Types.ObjectId(req.params.id);
logger.trace(`Shipment with ID: ${id}`);
const checkStatesResult = await checkStates({ model: shipmentModel, id, states: ['shipped'] });
if (checkStatesResult.error) {
logger.error('Error checking shipment states:', checkStatesResult.error);
res.status(checkStatesResult.code).send(checkStatesResult);
return;
}
if (checkStatesResult === false) {
logger.error('Shipment is not in shipped state.');
res.status(400).send({ error: 'Shipment is not in shipped state.', code: 400 });
return;
}
const orderItemsResult = await listObjects({
model: orderItemModel,
filter: { shipment: id },
pagination: false,
});
if (orderItemsResult.error) {
logger.error('Error listing order items:', orderItemsResult.error);
res.status(orderItemsResult.code).send(orderItemsResult);
return;
}
for (const orderItem of orderItemsResult) {
if (orderItem.state.type != 'shipped') {
logger.error('Order item is not in shipped state.');
res.status(400).send({ error: 'Order item is not in shipped state.', code: 400 });
return;
}
}
for (const orderItem of orderItemsResult) {
await editObject({
model: orderItemModel,
id: orderItem._id,
updateData: {
state: { type: 'received' },
receivedAt: new Date(),
},
user: req.user,
});
}
const result = await editObject({
model: shipmentModel,
id,
updateData: {
state: { type: 'delivered' },
deliveredAt: new Date(),
},
user: req.user,
});
if (result.error) {
logger.error('Error receiving shipment:', result.error);
res.status(result.code).send(result);
return;
}
logger.debug(`Received shipment with ID: ${id}`);
res.send(result);
};
export const cancelShipmentRouteHandler = async (req, res) => {
const id = new mongoose.Types.ObjectId(req.params.id);
logger.trace(`Shipment with ID: ${id}`);
const checkStatesResult = await checkStates({
model: shipmentModel,
id,
states: ['planned', 'shipped'],
});
if (checkStatesResult.error) {
logger.error('Error checking shipment states:', checkStatesResult.error);
res.status(checkStatesResult.code).send(checkStatesResult);
return;
}
if (checkStatesResult === false) {
logger.error('Shipment is not in a cancellable state.');
res.status(400).send({
error: 'Shipment is not in a cancellable state (must be planned or shipped).',
code: 400,
});
return;
}
const orderItemsResult = await listObjects({
model: orderItemModel,
filter: { shipment: id },
pagination: false,
});
if (orderItemsResult.error) {
logger.error('Error listing order items:', orderItemsResult.error);
res.status(orderItemsResult.code).send(orderItemsResult);
return;
}
// Cancel related order items if they are in cancellable states
for (const orderItem of orderItemsResult) {
if (orderItem.state.type === 'draft' || orderItem.state.type === 'ordered') {
await editObject({
model: orderItemModel,
id: orderItem._id,
updateData: {
state: { type: 'cancelled' },
},
user: req.user,
});
}
}
const updateData = {
updatedAt: new Date(),
state: { type: 'cancelled' },
cancelledAt: new Date(),
};
const result = await editObject({
model: shipmentModel,
id,
updateData,
user: req.user,
});
if (result.error) {
logger.error('Error cancelling shipment:', result.error);
res.status(result.code).send(result);
return;
}
logger.debug(`Cancelled shipment with ID: ${id}`);
res.send(result);
};
export const getShipmentNeighborsRouteHandler = async (
req,
res,
property = '',
filter = {},
search = '',
sort = '',
order = 'ascend',
id
) => {
if (!id) {
return res.status(400).send({ error: 'Missing id parameter', code: 400 });
}
const result = await getObjectNeighbors({
model: shipmentModel,
id,
filter,
search,
sort,
order,
});
if (result?.error) {
logger.error('Error fetching shipment neighbors.');
return res.status(result.code).send(result);
}
logger.debug(`Retrieved shipment neighbors for ID: ${id}`);
res.send(result);
};