import {Mongo} from "meteor/mongo"; import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; import 'meteor/aldeed:collection2/static' import SimpleSchema from 'meteor/aldeed:simple-schema'; import Products from "./Product"; import Measures from "./Measure"; let Barcodes = new Mongo.Collection('Barcodes'); if(Meteor.isServer) { //Set MongoDB indexes (or remove them) here. try { Barcodes.rawCollection().createIndex({barcodeId: -1}, {unique: true}) Barcodes.rawCollection().createIndex({productAndMeasureId: -1}, {unique: true}) } catch(e) {console.log("Caught exception while setting indexes in MongoDB"); console.error(e)} } // A simple mapping between a concatenation of the product & measure ID and a unique sequential number for the barcode. This allows us to have a small number to keep our barcodes simple, while maintaining the more traditional MongoDB ID's for the Product and Measure. const BarcodesSchema = new SimpleSchema({ barcodeId: { type: Number, label: "Barcode ID", optional: false, //index: 1, //unique: true }, productAndMeasureId: { //Just the two ids jammed together with a single space between them. type: String, label: "Product And Measure ID", optional: false, //index: 1, //unique: true } }); if(Meteor.isServer) { //Meteor.publish('barcodes', function() { // return Barcodes.find({}); //}); Meteor.methods({ getBarcodeId: async function(productId, measureId) { check(productId, String); check(measureId, String); let hasProduct = await Meteor.collections.Products.findOneAsync({_id: productId}, {fields: {}}); let hasMeasure = await Meteor.collections.Measures.findOneAsync({_id: measureId}, {fields: {}}); if(hasProduct && hasMeasure) { let existing = await Barcodes.findOneAsync({productAndMeasureId: productId + ' ' + measureId}); if(existing) { return existing.barcodeId; } else { let c = 0; //Try a thousand times before failing. Should never fail, should also not ever need to try a thousand times (unless we somehow automate label generation to the point where a 1000 processes at once are requesting labels that have never been generated before - highly unlikely). while(c++ < 1000) { //Lookup the most likely next barcode id from the db, then attempt to insert with it. If it fails due to duplication, then increment and repeat. //let cursor = Products.find({}, {barcodeId: 1}).sort({barcodeId: -1}).limit(1); //Since currently products are never removed, we shouldn't need to detect sequence gaps and fill them in (odds are we will never use more than 10k numbers anyway). //let barcodeId = cursor.hasNext() ? cursor.next().barcodeId + 1 : 1; let product = await Products.findOneAsync({barcodeId: {$exists: true}}, {sort: {barcodeId: -1}}) let barcodeId = product ? product.barcodeId : 1; await Barcodes.insertAsync({productAndMeasureId: productId + ' ' + measureId, barcodeId}, function(err, id) { if(err) console.log(err); else return barcodeId; }); } //If we are still here, then there was a massive failure (c exceeded 1000). console.log("We failed to generate a new barcode ID 1000 times, so we are giving up. This should never happen."); throw new Meteor.Error(403, "Unable to generate a barcode ID."); } } else { //Cannot find either the product or the measure in the db. Cannot give an id. console.log("Unable to generate a barcode ID because we could not find the product " + productId + " OR we could not find the measure " + measureId); throw new Meteor.Error(403, "Unable to find the product or the measure. Both must exist in order to generate a barcode ID."); } }, }); } //Allows the client to do DB interaction without calling server side methods, while still retaining control over whether the user can make changes. Barcodes.allow({ insert: function() {return false;}, update: function() {return false;}, remove: function() {return false;} }); export default Barcodes;