const {ANCHORING_DOMAIN, DB} = require('../constants');
const Manager = require("../../pdm-dsu-toolkit/managers/Manager");
const {Batch, Notification} = require('../model');
const getStockManager = require('./StockManager');
const getNotificationManager = require('./NotificationManager');
const {toPage, paginate} = require("../../pdm-dsu-toolkit/managers/Page");
/**
* Batch Manager Class
*
* 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 BatchManager
* @extends Manager
* @memberOf Managers
*/
class BatchManager extends Manager{
constructor(participantManager, callback) {
super(participantManager, DB.batches, ['gtin', 'batchNumber', 'expiry'], callback);
this.stockManager = getStockManager(participantManager);
this.notificationManager = getNotificationManager(participantManager);
this.productService = new (require('../services/ProductService'))(ANCHORING_DOMAIN);
this.batchService = new (require('../services/BatchService'))(ANCHORING_DOMAIN);
this.participantManager = participantManager;
}
/**
* generates the db's key for the batch
* @param {string|number} gtin
* @param {string|number} batchNumber
* @return {string}
* @private
*/
_genCompostKey(gtin, batchNumber){
return `${gtin}-${batchNumber}`;
}
/**
* Must wrap the 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 {Batch} item
* @param {string|object} record
* @return {object} the indexed object to be stored in the db
* @protected
* @override
*/
_indexItem(key, item, record){
return {
gtin: key,
batchNumber: item.batchNumber,
expiry: item.expiry,
status: item.batchStatus.status,
value: record
}
};
/**
* Util function that loads a BatchDSU and reads its information
* @param {string|KeySSI} keySSI
* @param {function(err, Batch, Archive)} callback
* @protected
* @override
*/
_getDSUInfo(keySSI, callback){
return this.batchService.get(keySSI, callback);
}
/**
* Creates a {@link Batch} dsu
* @param {Product} product
* @param {Batch} batch
* @param {function(err, keySSI, string)} callback first keySSI if for the batch, the second for its' product dsu
* @override
*/
create(product, batch, callback) {
let self = this;
const gtin = product.gtin;
self.batchService.create(gtin, batch, (err, keySSI) => {
if (err)
return callback(err);
const record = keySSI.getIdentifier();
const dbKey = self._genCompostKey(gtin, batch.batchNumber);
const dbAction = function(dbKey, record, gtin, batch, product, callback){
const cb = function(err, ...results){
if (err)
return self.cancelBatch(err2 => {
callback(err);
});
callback(undefined, ...results);
}
try {
self.beginBatch();
} catch (e){
return self.batchSchedule(() => dbAction(dbKey, record, gtin, batch, product, callback));
//return callback(e);
}
self.insertRecord(dbKey, self._indexItem(gtin, batch, record), (err) => {
if(err){
console.log(`Could not inset record with gtin ${gtin} and batch ${batch.batchNumber} on table ${self.tableName}`);
return cb(err);
}
const path =`${self.tableName}/${dbKey}`;
console.log(`batch ${batch.batchNumber} created stored at '${path}'`);
self.batchAllow(self.stockManager);
self.stockManager.manage(product, batch, (err) => {
self.batchDisallow(self.stockManager);
if(err){
console.log(`Error Updating Stock for ${product.gtin} batch ${batch.batchNumber}: ${err.message}`);
return cb(err);
}
console.log(`Stock for ${product.gtin} batch ${batch.batchNumber} updated`);
self.commitBatch((err) => {
if(err)
return cb(err);
callback(undefined, keySSI, path);
});
});
});
}
dbAction(dbKey, record, gtin, batch, product, callback);
});
}
/**
* reads the specific Batch information from a given gtin (if exists and is registered to the mah)
*
* @param {string|number} gtin
* @param {string|number} batchNumber
* @param {boolean} [readDSU] defaults to true. decides if the manager loads and reads from the dsu or not
* @param {function(err, Batch|KeySSI, Archive)} callback returns the batch if readDSU, the keySSI otherwise
* @override
*/
getOne(gtin, batchNumber, readDSU, callback){
let key;
if (!callback){
if (typeof batchNumber === 'boolean'){
key = gtin;
callback = readDSU;
readDSU = batchNumber;
} else {
callback = readDSU;
readDSU = true;
key = this._genCompostKey(gtin, batchNumber)
}
} else {
key = this._genCompostKey(gtin, batchNumber);
}
super.getOne(key, readDSU, callback);
}
/**
* Removes a product from the list (does not delete/invalidate DSU, simply 'forgets' the reference)
* @param {string|number} gtin
* @param {string|number} batchNumber
* @param {function(err)} callback
* @override
*/
remove(gtin, batchNumber, callback) {
super.remove(this._genCompostKey(gtin, batchNumber), callback);
}
/**
*
* @param model
* @returns {Batch}
* @override
*/
fromModel(model){
return new Batch(super.fromModel(model));
}
/**
* updates a Batch from the list
* @param {string|number} gtin
* @param {Batch} newBatch
* @param {function(err, Batch?, Archive?)} callback
* @override
*/
update(gtin, newBatch, callback){
if (!callback)
return callback(`No gtin Provided...`);
const self = this;
const key = this._genCompostKey(gtin, newBatch.batchNumber);
self.getRecord(key, (err, record) => {
if (err)
return self._err(`Unable to retrieve record with key ${key} from table ${self._getTableName()}`, err, callback);
self.batchService.update(gtin, record.value, newBatch, (err, updatedBatch, batchDsu) => {
if (err)
return self._err(`Could not Update Batch DSU`, err, callback);
const cb = function(err, ...results){
if (err)
return self.cancelBatch((err2) => {
callback(err);
});
callback(undefined, ...results);
}
const dbOperation = function (gtin, updatedBatch, record, callback){
try {
self.beginBatch();
} catch(e) {
return self.batchSchedule(() => dbOperation(gtin, updatedBatch, record, callback));
}
self.updateRecord(key, self._indexItem(gtin, updatedBatch, record.value), (err) => {
if (err)
return cb(err);
// callback(undefined, updatedBatch, batchDsu);
self.stockManager.getOne(gtin, true, (err, stock) => {
if (err)
return cb(err); //TODO: if not in stock, it must be in transit. handle shipments.
const batch = stock.batches.find(b => b.batchNumber === updatedBatch.batchNumber);
if (!batch)
return cb(`could not find batch`) //TODO: if not in stock, it must be in transit. handle shipments.
batch.batchStatus = updatedBatch.batchStatus;
self.batchAllow(self.stockManager);
self.stockManager.update(gtin, stock, (err) => {
self.batchDisallow(self.stockManager);
if (err)
return cb(err);
self.commitBatch((err) => {
if (err)
return cb(err);
self.stockManager.refreshController();
const productManager = self.participantManager.getManager('ProductManager');
productManager.refreshController();
self.stockManager.getStockTraceability(gtin, {manufName: self.getIdentity().id, batch: batch.batchNumber}, (err, results) => {
if (err || !results){
console.log(`Could not calculate partners with batch to send`, err, results);
return callback(undefined, newBatch, batchDsu);
}
const {partnersStock} = results;
if (!partnersStock){
console.log(`No Notification required. No stock found outside the producer for gtin ${gtin}, batch ${batch.batchNumber}`);
return callback(undefined, newBatch, batchDsu);
}
const toBeNotified = Object.keys(partnersStock);
const batchNotification = new Notification({
subject: self.tableName,
body: {
gtin: gtin,
batch: {
batchNumber: batch.batchNumber,
expiry: batch.expiry,
batchStatus: batch.batchStatus
}
}
});
self.notificationManager.pushToAll(toBeNotified, batchNotification, (err) => {
if (err)
console.log(`Could not send notifications to partners`, err);
callback(undefined, updatedBatch);
});
});
});
});
});
});
}
dbOperation(gtin, updatedBatch, record, callback);
});
});
}
}
/**
* @param {BaseManager} participantManager
* @param {function(err, Manager)} [callback] optional callback for when the assurance that the table has already been indexed is required.
* @returns {BatchManager}
* @memberOf Managers
*/
const getBatchManager = function (participantManager, callback) {
let manager;
try {
manager = participantManager.getManager(BatchManager);
if (callback)
return callback(undefined, manager);
} catch (e){
manager = new BatchManager(participantManager, callback);
}
return manager;
}
module.exports = getBatchManager;
Source