Source

managers/ReceiptManager.js

const {INFO_PATH, DB, DEFAULT_QUERY_OPTIONS, ANCHORING_DOMAIN} = require('../constants');
const Manager = require("../../pdm-dsu-toolkit/managers/Manager");
const Receipt = require('../model/Receipt');
const IndividualReceipt = require('../model/IndividualReceipt');
const {toPage, paginate} = require("../../pdm-dsu-toolkit/managers/Page");

/**
 * Stock 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 ReceiptManager
 * @extends Manager
 * @memberOf Managers
 */
class ReceiptManager extends Manager{
    constructor(participantManager, callback) {
        super(participantManager, DB.receipts, ['batchNumber', 'gtin', 'sellerId', 'serialNumber', 'manufName', 'status'],  (err, manager) => {
            if (err)
                return callback ? callback(err) : console.log(err);
            manager.registerMessageListener((message, cb) => {
                manager.processMessageRecord(message, (err) => {
                    manager.refreshController();
                    cb(err);
                });
            });
            if (callback)
                callback(undefined, manager);
        });

        this.saleService = new (require('../services').SaleService)(ANCHORING_DOMAIN);
    }

    /**
     *
     * @param key
     * @param item
     * @param {Receipt} record
     * @return {{}}
     * @private
     */
    _indexItem(key, item, record) {
        if (!record){
            record = item;
            item = key;
            key = this._genCompostKey(item);
        }
        return Object.assign(item, {
            value: record
           })
    }

    _genCompostKey(individualReceipt){
        return `${individualReceipt.gtin}-${individualReceipt.batchNumber}-${individualReceipt.serialNumber}-${Date.now()}`;
    }

    _getDSUInfo(keySSI, callback){
        const self = this;
        this.saleService.get(keySSI, (err, sale) => {
            if (err)
                return self._err(`Unable to read Sale DSU ${keySSI}`, err, callback);
            callback(undefined, new Receipt(sale));
        });
    }

    _processMessageRecord(message, callback) {
        let self = this;
        if (!message || !Array.isArray(message))
            return callback(`Message ${message} does not have  non-empty string with keySSI. Skipping record.`);
            
        const cb = function(err, ...results){
            if (err)
                return self.cancelBatch(err2 => {
                    callback(err);
                });
            callback(undefined, ...results);
        }

        const receipts = message;

        const lines = [];

        const receiptIterator = function(receiptsCopy, callback){
            const receiptSSI = receiptsCopy.shift();
            if (!receiptSSI)
                return callback(undefined, lines);
            self._getDSUInfo(receiptSSI, (err, receipt) => {
                if (err) {
                    console.log(`Could not read DSU from Receipt keySSI in record ${message}. Skipping record.`);
                    return callback(err);
                }

                const individualReceiptIterator = function(indReceiptCopy, accumulator, callback){
                    if (!callback){
                        callback = accumulator;
                        accumulator = [];
                    }
                    const indReceipt = indReceiptCopy.shift();
                    if (!indReceipt)
                        return callback(undefined, accumulator);

                    const compostKey = self._genCompostKey(indReceipt);
                    self.getRecord(compostKey, (err, rec) => {
                        if (!err){
                            console.log(rec);
                            return callback(`There is already an entry for this individual product ${compostKey}, and all sales are final!`);
                        }

                        self.insertRecord(compostKey, self._indexItem(compostKey, indReceipt, receiptSSI), (err) => {
                            if (err)
                                return self._err(`Could not insert new Individual Receipt ${compostKey} in the db`, err, callback);
                            accumulator.push(compostKey);
                            console.log(`New Individual Receipt added: ${compostKey}`);
                            individualReceiptIterator(indReceiptCopy, accumulator, callback);
                        });
                    });
                }

                individualReceiptIterator(receipt.productList.slice(), callback);
            });
        }

        const dbAction = function(receipts, callback){
            try {
                self.beginBatch();
            } catch (e){
                return self.batchSchedule(() => dbAction(receipts, callback));
                //return callback(e);
            }

            receiptIterator(receipts.slice(), (err, newIndividualReceipts) => {
                if (err)
                    return cb(`Could not register all receipts`);
                self.commitBatch((err) => {
                    if(err)
                        return cb(err);
                    console.log(`Receipts successfully registered: ${JSON.stringify(newIndividualReceipts)}`);
                    callback(undefined, newIndividualReceipts);
                });        
            });
        }

        dbAction(receipts, callback);
    };

    /**
     * Creates a {@link Sale} entry
     * @param {IndividualReceipt} receipt
     * @param {function(err?, keySSI?, string?)} callback where the string is the mount path relative to the main DSU
     */
    create(receipt, callback) {
        callback(`Receipts cannot be manufactured`);
    }

    /**
     * updates a product from the list
     * @param {string|number} [id] the table key
     * @param {IndividualReceipt} newReceipt
     * @param {function(err, Sale?)} callback
     * @override
     */
    update(id, newReceipt, callback){
        return callback(`All sales are final`);
    }

    /**
     * reads ssi for that gtin in the db. loads is and reads the info at '/info'
     * @param {string} id
     * @param {boolean} [readDSU] defaults to true. decides if the manager loads and reads from the dsu or not
     * @param {function(err, IndividualReceipt|KeySSI, Archive)} callback returns the Product if readDSU and the dsu, the keySSI otherwise
     * @override
     */
    getOne(id, readDSU,  callback) {
        if (!callback){
            callback = readDSU;
            readDSU = true;
        }
        let self = this;
        self.getRecord(id, (err, receipt) => {
            if (err)
                return self._err(`Could not load record with key ${id} on table ${self._getTableName()}`, err, callback);
            if (!readDSU)
                return callback(undefined, receipt.pk);
            callback(undefined, new IndividualReceipt(receipt));
        });
    }

    /**
     * Lists all registered items according to query options provided
     * @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, object[])} callback
     * @override
     */
    getAll(readDSU, options, callback){
        const defaultOptions = () => Object.assign({}, DEFAULT_QUERY_OPTIONS, {
            query: [
                "__timestamp > 0",
                'id like /.*/g'
            ],
            sort: "dsc"
        });

        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;
        self.query(options.query, options.sort, options.limit, (err, records) => {
            if (err)
                return self._err(`Could not perform query`, err, callback);
            if (!readDSU)
                return callback(undefined, records.map(r => r.pk));
            callback(undefined, records.map( r => new IndividualReceipt(r)));
        });
    }
}


/**
 * @param {ParticipantManager} participantManager
 * @param {function(err, Manager)} [callback] optional callback for when the assurance that the table has already been indexed is required.
 * @returns {ReceiptManager}
 * @memberOf Managers
 */
const getReceiptManager = function (participantManager, callback) {
    let manager;
    try {
        manager = participantManager.getManager(ReceiptManager);
        if (callback)
            return callback(undefined, manager);
    } catch (e){
        manager = new ReceiptManager(participantManager, callback);
    }

    return manager;
}

module.exports = getReceiptManager;