const { DB, DEFAULT_QUERY_OPTIONS } = require('../constants');
const ShipmentManager = require("./ShipmentManager");
const getReceivedOrderManager = require("./ReceivedOrderManager");
const {Shipment, Order, OrderStatus, ShipmentStatus, Wholesaler, Batch} = require('../model');
const {toPage, paginate} = require("../../pdm-dsu-toolkit/managers/Page");
/**
* Issued Shipment Manager Class - concrete ShipmentManager for issuedShipments.
*
* Manager Classes in this context should do the bridge between the controllers
* and the services exposing only the necessary api to the controllers while encapsulating <strong>all</strong> business logic.
*
* All Manager Classes should be singletons.
*
* This complete separation of concerts is very beneficial for 2 reasons:
* <ul>
* <li>Allows for testing since there's no browser dependent code (i think) since the DSUStorage can be 'mocked'</li>
* <li>Allows for different controllers access different business logic when necessary (while benefiting from the singleton behaviour)</li>
* </ul>
*
* @param {ParticipantManager} participantManager
* @param {function(err, Manager)} [callback] optional callback for when the assurance that the table has already been indexed is required.
* @class IssuedShipmentManager
* @extends Manager
* @memberOf Managers
*/
class IssuedShipmentManager extends ShipmentManager {
constructor(participantManager, callback) {
super(participantManager, DB.issuedShipments, ['shipmentId', 'senderId', 'requesterId', 'status'], callback);
this.participantManager = participantManager;
this.stockManager = participantManager.stockManager;
}
/**
* Must wrap the DB entry in an object like:
* <pre>
* {
* index1: ...
* index2: ...
* value: item
* }
* </pre>
* so the DB can be queried by each of the indexes and still allow for lazy loading
* @param {string} [key]
* @param {Shipment} item
* @param {string|object} record
* @return {object} the indexed object to be stored in the db
* @protected
* @override
*/
_indexItem(key, item, record) {
if (!record){
record = item;
item = key;
key = undefined;
}
return {...super._indexItem(key, item, record), requesterId: item.requesterId};
}
/**
* Binds the {@link Shipment#shipmentId} to the shipment and fills in participant details;
* @param {Shipment} shipment
* @param {function(err, Shipment)} callback
* @private
*/
_bindParticipant(shipment, callback){
shipment.shipmentId = shipment.shipmentId || Date.now();
let self = this;
self.getIdentity((err, wholesaler) => {
if (err)
return self._err(`Could not retrieve identity`, err, callback);
wholesaler = new Wholesaler(wholesaler);
shipment.senderId = wholesaler.id;
shipment.shipFromAddress = wholesaler.originAddress || wholesaler.address;
shipment.shipmentLines = shipment.shipmentLines.map(sl => {
sl.senderId = wholesaler.id;
sl.status = shipment.status;
return sl;
})
callback(undefined, shipment);
});
}
/**
* Creates a {@link Shipment} dsu
* @param {string} orderId the id to the received order that generates the shipment
* @param {Shipment} shipment
* @param {function(err, KeySSI, dbPath)} callback where the dbPath follows a "tableName/shipmentId" template.
* @override
*/
create(orderId, shipment, callback) {
let self = this;
const cb = function(err, ...results){
if (err)
return self.cancelBatch(err2 => {
callback(err);
});
callback(undefined, ...results);
}
const createInner = function(callback){
const receivedOrderManager = getReceivedOrderManager(self.participantManager);
const receivedOrderKey = receivedOrderManager._genCompostKey(shipment.requesterId, orderId);
receivedOrderManager.getOne(receivedOrderKey, true, (err, order, orderDSU, orderSSI) => {
if(err)
return cb(`Could not retrieve received order ${orderId}`);
self.shipmentService.create(shipment, orderSSI, (err, keySSI, shipmentLinesSSIs) => {
if(err)
return cb(`Could not create product DSU for ${shipment}`);
const record = keySSI.getIdentifier();
console.log("Shipment SSI=" + record);
self.insertRecord(self._genCompostKey(shipment.requesterId, shipment.shipmentId), self._indexItem(shipment, record), (err) => {
if(err)
return cb(`Could not insert record with shipmentId ${shipment.shipmentId} on table ${self.tableName}`);
const path = `${self.tableName}/${shipment.shipmentId}`;
console.log(`Shipment ${shipment.shipmentId} created stored at DB '${path}'`);
const aKey = keySSI.getIdentifier();
self.sendMessagesAsync(shipment, shipmentLinesSSIs, aKey);
callback(undefined, keySSI, path);
});
});
});
}
const gtinIterator = function(gtins, batchesObj, callback){
const gtin = gtins.shift();
if (!gtin)
return callback();
if (!(gtin in batchesObj))
return callback(`gtins not found in batches`);
const batches = batchesObj[gtin];
//console.log("Going to self.stockManager.manageAll(", gtin, batches);
self.batchAllow(self.stockManager);
self.stockManager.manageAll(gtin, batches, (err, removed) => {
self.batchDisallow(self.stockManager);
if(err) {
console.log(err);
return cb(`Could not update Stock for orderId=${orderId} because of ${err}`);
}
if (self.stockManager.serialization && self.stockManager.aggregation)
shipment.shipmentLines.filter(sl => sl.gtin === gtin && Object.keys(removed).indexOf(sl.batch) !== -1).forEach(sl => {
sl.serialNumbers = removed[sl.batch];
});
else
shipment.shipmentLines = shipment.shipmentLines.map(sl => {
sl.serialNumbers = undefined;
return sl;
});
gtinIterator(gtins, batchesObj, callback);
})
}
const gtins = shipment.shipmentLines.map(sl => sl.gtin);
const batchesObj = shipment.shipmentLines.reduce((accum, sl) => {
accum[sl.gtin] = accum[sl.gtin] || [];
accum[sl.gtin].push(new Batch({
batchNumber: sl.batch,
quantity: (-1) * sl.quantity
}))
return accum;
}, {});
const dbAction = function (gtins, batchesObj, callback){
//console.log(`dbAction for orderId=${orderId}`, gtins, batchesObj, shipment);
try {
self.beginBatch();
} catch (e){
return self.batchSchedule(() => dbAction(gtins, batchesObj, callback));
//return callback(e);
}
gtinIterator(gtins, batchesObj, (err) => {
if(err)
return cb(`Could not retrieve info from stock for orderId=${orderId} because of ${err}`);
console.log(`Shipment updated after Stock confirmation for orderId=${orderId}`);
createInner((err, keySSI, path) => {
if(err)
return cb(`Could not create Shipment for orderId=${orderId} because of ${err}`);
self.commitBatch((err) => {
if(err)
return cb(err);
console.log(`Shipment ${shipment.shipmentId} created!`);
callback(undefined, keySSI, path);
});
})
});
}
dbAction(gtins, batchesObj, callback);
}
sendMessagesAsync(shipment, shipmentLinesSSIs, aKey){
if (!aKey){
aKey = shipmentLinesSSIs;
shipmentLinesSSIs = undefined;
}
const self = this;
self.sendMessage(shipment.requesterId, DB.receivedShipments, aKey, (err) =>
self._messageCallback(err ? `Could not sent message to ${shipment.shipmentId} with ${DB.receivedShipments}: ${err}` : err,
`Message sent to ${shipment.requesterId}, ${DB.receivedShipments}, ${aKey}`));
if (shipmentLinesSSIs)
self.sendShipmentLinesToMAH([...shipment.shipmentLines], [...shipmentLinesSSIs], (err) =>
self._messageCallback( err ? `Could not transmit shipmentLines to The manufacturers` : 'Lines Notice sent to Manufacturers'));
}
/**
*
* @param err
* @param message
* @protected
* @override
*/
_messageCallback(err, message) {
super._messageCallback(err, message);
}
/**
* Lists all issued orders.
* @param {boolean} [readDSU] defaults to true. decides if the manager loads and reads from the dsu's {@link INFO_PATH} or not
* @param {object} [options] query options. defaults to {@link DEFAULT_QUERY_OPTIONS}
* @param {function(err, Order[])} callback
*/
getAll(readDSU, options, callback) {
const defaultOptions = () => Object.assign({}, DEFAULT_QUERY_OPTIONS);
if (!callback) {
if (!options) {
callback = readDSU;
options = defaultOptions();
readDSU = true;
}
if (typeof readDSU === 'boolean') {
callback = options;
options = defaultOptions();
}
if (typeof readDSU === 'object') {
callback = options;
options = readDSU;
readDSU = true;
}
}
options = options || defaultOptions();
let self = this;
super.getAll(readDSU, options, (err, result) => {
if (err)
return self._err(`Could not parse IssuedShipments ${JSON.stringify(result)}`, err, callback);
console.log(`Parsed ${result.length} shipments`);
callback(undefined, result);
});
}
/**
* updates an Issued Shipment
*
* @param {string} [key] key is optional so child classes can override them
* @param {Shipment} shipment
* @param {function(err, Shipment?, Archive?)} callback
*/
update(key, shipment, callback){
if (!callback){
callback = shipment;
shipment = key;
key = this._genCompostKey(shipment.requesterId, shipment.shipmentId);
}
const self = this;
super.update(key, shipment, (err, updatedShipment, keySSI, orderId, linesSSIs) => {
if (err)
return self._err(`Could not update Shipment`, err, callback);
try {
self.sendMessagesAsync(updatedShipment, linesSSIs, keySSI);
} catch (e) {
console.log(e);
}
callback(undefined, updatedShipment, keySSI);
});
}
updateByOrder(shipmentId, order, callback){
const shipmentKey = this._genCompostKey(order.requesterId, shipmentId);
const self = this;
self.getOne(shipmentKey, false, (err, record) => {
if (err)
return self._err(`Could not get Shipment to update`, err, callback);
self._getDSUInfo(record, (err, shipment) => {
if (err)
return self._err(`Unable to read shipment DSU`, err, callback);
shipment.status = order.status
self.shipmentService.update(record, shipment, (err, updatedShipment, dsu, orderId, shipmentLinesSSIs) => {
if (err)
return self._err(`Could not update shipment dsu`, err, callback);
self.updateRecord(shipmentKey, self._indexItem(shipmentKey, updatedShipment, record), (err) => {
if (err)
return self._err(`Could not update shipment record`, err, callback);
self.sendShipmentLinesToMAH([...shipment.shipmentLines], [...shipmentLinesSSIs], (err) =>
self._messageCallback( err ? `Could not transmit shipmentLines to The manufacturers` : 'Lines Notice sent to Manufacturers'));
self.refreshController({
mode: 'issued',
shipment: updatedShipment
});
callback();
});
});
});
});
}
}
/**
* @param {ParticipantManager} participantManager
* @param {function(err, Manager)} [callback] optional callback for when the assurance that the table has already been indexed is required.
* @returns {IssuedShipmentManager}
* @memberOf Managers
*/
const getIssuedShipmentManager = function (participantManager, callback) {
let manager;
try {
manager = participantManager.getManager(IssuedShipmentManager);
if (callback)
return callback(undefined, manager);
} catch (e){
manager = new IssuedShipmentManager(participantManager, callback);
}
return manager;
}
module.exports = {
getIssuedShipmentManager,
IssuedShipmentManager
};
Source