Prototyped the barcode idea; Added a basic production system.
This commit is contained in:
81
imports/api/Barcode.js
Normal file
81
imports/api/Barcode.js
Normal file
@@ -0,0 +1,81 @@
|
||||
import {Mongo} from "meteor/mongo";
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import {SimpleSchema} from 'meteor/aldeed:simple-schema';
|
||||
|
||||
Barcodes = new Mongo.Collection('Barcodes');
|
||||
|
||||
// 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: function(productId, measureId) {
|
||||
check(productId, String);
|
||||
check(measureId, String);
|
||||
|
||||
let hasProduct = Meteor.collections.Products.findOne({_id: productId}, {fields: {}});
|
||||
let hasMeasure = Meteor.collections.Measures.findOne({_id: measureId}, {fields: {}});
|
||||
|
||||
if(hasProduct && hasMeasure) {
|
||||
let existing = Barcodes.findOne({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;
|
||||
|
||||
Barcodes.insert({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;
|
||||
265
imports/api/Batch.js
Normal file
265
imports/api/Batch.js
Normal file
@@ -0,0 +1,265 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
import { check } from 'meteor/check';
|
||||
import {SimpleSchema} from 'meteor/aldeed:simple-schema';
|
||||
|
||||
/**
|
||||
* Notes:
|
||||
* The Batch object has a date field which stores the date as a number in the format YYYYMMDD. Converting this number into a local date is done with moment(batch.date.toString(), "YYYYMMDD").toDate(), and converting it to a number from a date can be accomplished with ~~(moment(date).format("YYYYMMDD")), where the ~~ is a bitwise not and converts a string to a number quickly and reliably.
|
||||
* A Batch in this system refers to one or more instances of cooking or preparing a product on a given date. It does NOT refer to each instance of cooking the product on that date (what might be called a batch in a kitchen). This might be more effectively called a Run, but that is a confusing word to use in a software system, so I chose to reuse the word Batch since we will not be tracking kitchen batches, just kitchen runs.
|
||||
*/
|
||||
|
||||
let Batches = new Mongo.Collection('Batches');
|
||||
let BatchesSchema = new SimpleSchema({
|
||||
date: {
|
||||
type: Number, // A number in the format of YYYYMMDD to allow for searching using greater and less than, and to prevent timezones from messing everything up.
|
||||
label: "Date",
|
||||
optional: false,
|
||||
index: 1
|
||||
},
|
||||
timestamp: { //This is based off the date with zero for the time and set to GMT (Zulu time).
|
||||
type: Date,
|
||||
label: "Timestamp",
|
||||
optional: true
|
||||
},
|
||||
weekOfYear: {
|
||||
type: Number,
|
||||
label: "Week Of Year",
|
||||
optional: true
|
||||
},
|
||||
amount: {
|
||||
type: Number,
|
||||
label: "Amount",
|
||||
optional: false,
|
||||
decimal: true
|
||||
},
|
||||
measureId: {
|
||||
type: String,
|
||||
label: "Measure Id",
|
||||
trim: false,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
index: 1
|
||||
},
|
||||
productId: {
|
||||
type: String,
|
||||
label: "Product Id",
|
||||
trim: false,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
index: 1,
|
||||
optional: false
|
||||
},
|
||||
cookId: {
|
||||
type: String,
|
||||
label: "Cook Worker Id",
|
||||
trim: false,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
index: 1
|
||||
},
|
||||
cannerId: {
|
||||
type: String,
|
||||
label: "Canner Worker Id",
|
||||
trim: false,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
index: 1,
|
||||
optional: false
|
||||
},
|
||||
hasLabels: {
|
||||
type: Boolean,
|
||||
label: "Has Labels",
|
||||
optional: false,
|
||||
defaultValue: false
|
||||
},
|
||||
comment: {
|
||||
type: String,
|
||||
trim: false,
|
||||
optional: true
|
||||
},
|
||||
createdAt: {
|
||||
type: Date,
|
||||
label: "Created On",
|
||||
optional: false
|
||||
},
|
||||
deletedAt: {
|
||||
type: Date,
|
||||
label: "Deleted On",
|
||||
optional: true
|
||||
}
|
||||
});
|
||||
Batches.attachSchema(BatchesSchema);
|
||||
//Ensure that the product ID, measure ID, and date combination are unique.
|
||||
// Note: I took this out because while it provides for cleaner views, it is overly complicated and could be easily done with a cleanup routine after the fact, or by aggregating the data in the queries.
|
||||
// What makes this complicated is the notes, cook, and canner references which may not be the same.
|
||||
//Batches.createIndex({productId: 1, measureId: 1, date: 1}, {unique: true, name: "ProductMeasureDateIndex"});
|
||||
|
||||
if(Meteor.isServer) {
|
||||
Meteor.publish('batches', function(query, sort, limit = 100, skipCount) {
|
||||
let dbQuery = [];
|
||||
|
||||
if(query) {
|
||||
_.each(_.keys(query), function(key) {
|
||||
//if(_.isObject(query[key])) dbQuery.push({[key]: query[key]});
|
||||
if(_.isObject(query[key])) {
|
||||
if(query[key].type === 'dateRange') {
|
||||
if(query[key].start && query[key].end)
|
||||
dbQuery.push({[key]: {$gte: query[key].start, $lte: query[key].end}});
|
||||
else if(query[key].start)
|
||||
dbQuery.push({[key]: {$gte: query[key].start}});
|
||||
else if(query[key].end)
|
||||
dbQuery.push({[key]: {$lte: query[key].end}});
|
||||
// Do nothing if a start and/or end are not provided.
|
||||
}
|
||||
else {
|
||||
dbQuery.push({[key]: query[key]});
|
||||
}
|
||||
}
|
||||
else if(_.isNumber(query[key])) dbQuery.push({[key]: query[key]});
|
||||
else {
|
||||
let searchValue = query[key];
|
||||
let searches = searchValue && searchValue.length > 0 ? searchValue.split(/\s+/) : undefined;
|
||||
|
||||
for(let search of searches) {
|
||||
dbQuery.push({[key]: {$regex: '\\b' + search, $options: 'i'}});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(!_.isNumber(limit)) limit = 100;
|
||||
if(!_.isNumber(skipCount) || skipCount < 0) skipCount = 0;
|
||||
|
||||
dbQuery = dbQuery.length > 0 ? {$and: dbQuery} : {};
|
||||
return Meteor.collections.Batches.find(dbQuery, {limit: limit, sort, skip: skipCount});
|
||||
});
|
||||
|
||||
Meteor.methods({
|
||||
getBatchCount: function(query) {
|
||||
//TODO: Validate the query?
|
||||
return Sales.find(query).count();
|
||||
},
|
||||
insertBatches: function(batches) { //Insert one or more batches (if one, you can pass just the batch).
|
||||
if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) {
|
||||
//Force it to be an array if it isn't.
|
||||
if(!Array.isArray(batches)) batches = [batches];
|
||||
|
||||
//Validate them all.
|
||||
for(let batch of batches) {
|
||||
check(batch, {
|
||||
date: Number, // TODO: Check that the format is YYYYMMDD
|
||||
amount: Match.Where(function(x) {
|
||||
check(x, Number);
|
||||
return x > 0;
|
||||
}),
|
||||
measureId: String,
|
||||
productId: String,
|
||||
cookId: String,
|
||||
cannerId: String,
|
||||
comment: Match.Optional(String)
|
||||
});
|
||||
}
|
||||
|
||||
for(let batch of batches) {
|
||||
let dateString = batch.date.toString();
|
||||
|
||||
batch.createdAt = new Date();
|
||||
batch.timestamp = new Date(dateString.substring(0, 4) + "-" + dateString.substring(4, 6) + "-" + dateString.substring(6, 8) + "T00:00:00Z");
|
||||
batch.weekOfYear = batch.timestamp.getWeek().toString();
|
||||
|
||||
if(batch.hasLabels === undefined) batch.hasLabels = false;
|
||||
}
|
||||
|
||||
for(let batch of batches) {
|
||||
Batches.insert(batch, function(err, id) {
|
||||
if(err) console.log(err);
|
||||
}, {bypassCollection2: true});
|
||||
}
|
||||
}
|
||||
else throw new Meteor.Error(403, "Not authorized.");
|
||||
},
|
||||
deleteBatch: function(id) { //Does not actually delete the batch, but rather just marks it for deleting by applying a deletion date.
|
||||
check(id, String);
|
||||
|
||||
if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) {
|
||||
let deletedAt = new Date();
|
||||
|
||||
//Batches.remove(id);
|
||||
Batches.update(id, {$set: {deletedAt}}, function(err, id) {
|
||||
if(err) console.log(err);
|
||||
});
|
||||
}
|
||||
else throw new Meteor.Error(403, "Not authorized.");
|
||||
},
|
||||
undeleteBatch: function(id) { //Revokes the previous deletion.
|
||||
check(id, String);
|
||||
|
||||
if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) {
|
||||
Batches.update(id, {$unset: {deletedAt:""}}, function(err, id) {
|
||||
if(err) console.log(err);
|
||||
});
|
||||
}
|
||||
else throw new Meteor.Error(403, "Not authorized.");
|
||||
},
|
||||
editBatchComment: function(id, comment) {
|
||||
check(id, String);
|
||||
check(comment, String);
|
||||
//Trim and convert empty comment to undefined.
|
||||
comment = comment ? comment.trim() : undefined;
|
||||
comment = comment && comment.length > 0 ? comment : undefined;
|
||||
|
||||
if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) {
|
||||
console.log("Changed comment of " + id + " to: " + comment);
|
||||
|
||||
if(comment) {
|
||||
Batches.update(id, {$set: {comment}}, function(error, count) {
|
||||
if(error) throw new Meteor.Error(400, "Unexpected database error: " + error);
|
||||
});
|
||||
}
|
||||
else {
|
||||
Batches.update(id, {$unset: {comment: ""}}, function(error, count) {
|
||||
if(error) throw new Meteor.Error(400, "Unexpected database error: " + error);
|
||||
});
|
||||
}
|
||||
}
|
||||
else throw new Meteor.Error(403, "Not authorized.");
|
||||
},
|
||||
updateBatch: function(id, date, amount) {
|
||||
check(id, String);
|
||||
check(date, Number); // TODO: Check that the format is YYYYMMDD
|
||||
check(amount, Number);
|
||||
|
||||
let dateString = date.toString();
|
||||
let timestamp = new Date(dateString.substring(0, 4) + "-" + dateString.substring(4, 6) + "-" + dateString.substring(6, 8) + "T00:00:00Z");
|
||||
let weekOfYear = timestamp.getWeek().toString();
|
||||
|
||||
if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) {
|
||||
Batches.update(id, {$set: {date, amount, timestamp, weekOfYear}}, function(err, id) {
|
||||
if(err) console.log(err);
|
||||
}, {bypassCollection2: true});
|
||||
}
|
||||
else throw new Meteor.Error(403, "Not authorized.");
|
||||
},
|
||||
setBatchHasLabels: function(id, hasLabels) {
|
||||
//console.log(id);
|
||||
//console.log(hasLabels);
|
||||
//check(id, Meteor.validators.ObjectID);
|
||||
check(id, String);
|
||||
check(hasLabels, Boolean);
|
||||
|
||||
if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) {
|
||||
Batches.update(id, {$set: {hasLabels}}, function(err, id) {
|
||||
if(err) console.log(err);
|
||||
}, {bypassCollection2: true});
|
||||
}
|
||||
else throw new Meteor.Error(403, "Not authorized.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//Allows the client to do DB interaction without calling server side methods, while still retaining control over whether the user can make changes.
|
||||
Batches.allow({
|
||||
insert: function() {return false;},
|
||||
update: function() {return false;},
|
||||
remove: function() {return false;}
|
||||
});
|
||||
|
||||
export default Batches;
|
||||
130
imports/api/Label.js
Normal file
130
imports/api/Label.js
Normal file
@@ -0,0 +1,130 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
import { check } from 'meteor/check';
|
||||
import {SimpleSchema} from 'meteor/aldeed:simple-schema';
|
||||
|
||||
|
||||
if(Meteor.isServer) {
|
||||
const puppeteer = require('puppeteer');
|
||||
//let Future = Npm.require('fibers/future');
|
||||
//
|
||||
//async function printLabels(data, callback) {
|
||||
// let params = "";
|
||||
// let url = Meteor.absoluteUrl("/LabelPrint");
|
||||
//
|
||||
// params = Object.keys(data).map(function(k) {
|
||||
// return encodeURIComponent(k) + "=" + encodeURIComponent(data[k]);
|
||||
// }).join('&');
|
||||
//
|
||||
// url += "?" + params;
|
||||
//
|
||||
// const browser = await puppeteer.launch();
|
||||
// const page = await browser.newPage();
|
||||
// console.log("Going to: " + url);
|
||||
// await page.goto(url, {waitUntil: 'networkidle0'});
|
||||
// // By removing the `path` option, we will receive a `Buffer` from `page.pdf`.
|
||||
// const pdf = await page.pdf({ width: "6in", height: "4in"}); // format: "A4" //path: 'C:\\Users\\Grumpy\\label.pdf'
|
||||
//
|
||||
// await browser.close();
|
||||
// callback(null, pdf);
|
||||
//}
|
||||
|
||||
Meteor.methods({
|
||||
//printLabels: function(width, height, layout, title1, title2, ingredients, date) {
|
||||
// console.log("Loaded Label");
|
||||
// let future = new Future();
|
||||
//
|
||||
// let boundCallback = Meteor.bindEnvironment(function(err, res) {
|
||||
// if(err) {
|
||||
// future.throw(err);
|
||||
// }
|
||||
// else {
|
||||
// future.return(res);
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// printLabels({width, height, layout, title1, title2, ingredients, date}, boundCallback);
|
||||
//
|
||||
// return future.wait();
|
||||
//}
|
||||
|
||||
async printLabels(width, height, layout, title1, title2, ingredients, date) {
|
||||
let data = {width, height, layout, title1, title2, ingredients, date};
|
||||
let params = "";
|
||||
let url = Meteor.absoluteUrl("/PrintLabel");
|
||||
|
||||
//Switch to the static page - for some reason the Meteor page is not loading correctly (it just appears blank).
|
||||
//url = Meteor.absoluteUrl("/LabelPrint.html");
|
||||
|
||||
params = Object.keys(data).map(function(k) {
|
||||
return encodeURIComponent(k) + "=" + encodeURIComponent(data[k]);
|
||||
}).join('&');
|
||||
|
||||
url += "?" + params;
|
||||
|
||||
const browser = await puppeteer.launch();
|
||||
const page = await browser.newPage();
|
||||
//url = Meteor.absoluteUrl("/StaticTest.html");
|
||||
//url = Meteor.absoluteUrl("/StaticTest.html");
|
||||
console.log("Going to: " + url);
|
||||
await page.goto(url, {waitUntil: 'networkidle0'}); //, {waitUntil: 'networkidle0'}
|
||||
const pdf = await page.pdf({width: '3in', height: '2in'}); //path: 'C:\\Users\\Grumpy\\label.pdf',
|
||||
//const pdf = await page.pdf({format: 'A4'});
|
||||
//await page.pdf({path: 'C:\\Users\\Grumpy\\label.pdf', width: '6in', height: '4in'});
|
||||
await browser.close();
|
||||
|
||||
return new Uint8Array(pdf, 0, pdf.length);
|
||||
}
|
||||
});
|
||||
|
||||
//Returns a JSON containing a denormalized list of products {product_id, measure_id, product_name, measure_name, price, }
|
||||
WebApp.connectHandlers.use("/labels/GetBarCodeData", (req, res, next) => {
|
||||
try {
|
||||
let barcodes = Meteor.collections.Barcodes.find({}, {fields: {_id: 0, barcodeId: 1, productAndMeasureId: 1}});
|
||||
let measures = Meteor.collections.Measures.find({}, {fields: {_id: 1, name: 1}, sort: {order: 1}}).fetch();
|
||||
//Note: Price data looks like this: {XZ5Z3CM49NDrJNADA /* MeasureID */: {price: 10.5, effectiveDate: ISODate("2017-01-12T13:14:18.876-08:00"), previousPrice: 9}, ...}
|
||||
//Measures is an array of MeasureIDs valid for this product.
|
||||
let products = Meteor.collections.Products.find({}, {fields: {_id: 1, name: 1, measures: 1, prices: 1}, sort: {order: 1}}).fetch();
|
||||
//let measuresById = measures.reduce((map, measure) => (map[measure._id] = measure), {});
|
||||
let measuresById = {};
|
||||
let barcodesByProductAndMeasureIds = {};
|
||||
let result = {};
|
||||
let today = new Date();
|
||||
|
||||
for(measure of measures) measuresById[measure._id] = measure;
|
||||
for(barcode of barcodes) barcodesByProductAndMeasureIds[barcode.productAndMeasureId] = barcode.barcodeId;
|
||||
//console.log(measuresById);
|
||||
|
||||
//for(let measureId of Object.keys(measuresById)) {
|
||||
// console.log(measureId + ":" + measuresById[measureId].name);
|
||||
//}
|
||||
|
||||
for(let product of products) {
|
||||
for(let measureId of product.measures) {
|
||||
let measureName = measuresById[measureId] ? measuresById[measureId].name : undefined;
|
||||
let priceData = product.prices ? product.prices[measureId] : undefined;
|
||||
let price = (priceData ? (priceData.effectiveDate && moment(priceData.effectiveDate).isAfter(today) ? priceData.previousPrice : priceData.price) : 0); //Get the price based on the effective date - whether we should use the new price or the old.
|
||||
let barcodeId = barcodesByProductAndMeasureIds[productId + " " + measureId];
|
||||
|
||||
//Ignore any product/measure combinations that don't have barcodes.
|
||||
if(barcodeId) {
|
||||
result[barcodeId] = {productId: product._id, measureId: measureId, productName: product.name, measureName, price};
|
||||
}
|
||||
|
||||
//TODO: Pass the product & measure data separately from the barcodes also to handle the missing barcode scenario. When a user types in a product name and picks a measure, we can record in the sale the product ID and measure ID, and we can lookup any pricing data.
|
||||
|
||||
//Log any errors so we can figure out what is going on.
|
||||
if(measureName === undefined) {
|
||||
//Note: We will pass a price of zero if the price is unknown. This should be fine for now.
|
||||
console.log(product._id + " " + product.name + " references a measure (" + measureId + ") which is not in the measures array.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.end(JSON.stringify(result), "JSON");
|
||||
} catch(err) {
|
||||
console.log(err);
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -120,4 +120,11 @@ if(Meteor.isServer) {
|
||||
});
|
||||
}
|
||||
|
||||
//Allows the client to do DB interaction without calling server side methods, while still retaining control over whether the user can make changes.
|
||||
Measures.allow({
|
||||
insert: function() {return false;},
|
||||
update: function() {return false;},
|
||||
remove: function() {return false;}
|
||||
});
|
||||
|
||||
export default Measures;
|
||||
@@ -132,12 +132,12 @@ const ProductsSchema = new SimpleSchema({
|
||||
label: "Updated On",
|
||||
optional: true
|
||||
},
|
||||
deactivated: {
|
||||
deactivated: { //This is turned on first, if true it will hide the product in production views, but keep the product available in the sale views. It is intended to be turned on for products that are no longer produced, but for which we have remaining inventory.
|
||||
type: Boolean,
|
||||
label: "Deactivated",
|
||||
optional: true
|
||||
},
|
||||
hidden: {
|
||||
hidden: { //Deactivated must first be true. Hides the product everywhere in the system except in historical pages. The inventory should be all sold prior to hiding a product.
|
||||
type: Boolean,
|
||||
label: "Hidden",
|
||||
optional: true
|
||||
@@ -188,6 +188,10 @@ if(Meteor.isServer) {
|
||||
if(measures) check(measures, [String]);
|
||||
|
||||
if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) {
|
||||
//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;
|
||||
//
|
||||
Products.insert({name, tags, aliases, measures, createdAt: new Date()}, {bypassCollection2: true}, function(err, id) {
|
||||
if(err) console.log(err);
|
||||
});
|
||||
|
||||
@@ -90,4 +90,11 @@ if(Meteor.isServer) {
|
||||
});
|
||||
}
|
||||
|
||||
//Allows the client to do DB interaction without calling server side methods, while still retaining control over whether the user can make changes.
|
||||
ProductTags.allow({
|
||||
insert: function() {return false;},
|
||||
update: function() {return false;},
|
||||
remove: function() {return false;}
|
||||
});
|
||||
|
||||
export default ProductTags;
|
||||
@@ -157,4 +157,11 @@ if(Meteor.isServer) {
|
||||
});
|
||||
}
|
||||
|
||||
//Allows the client to do DB interaction without calling server side methods, while still retaining control over whether the user can make changes.
|
||||
SalesSheets.allow({
|
||||
insert: function() {return false;},
|
||||
update: function() {return false;},
|
||||
remove: function() {return false;}
|
||||
});
|
||||
|
||||
export default SalesSheets;
|
||||
@@ -126,4 +126,11 @@ if(Meteor.isServer) {
|
||||
});
|
||||
}
|
||||
|
||||
//Allows the client to do DB interaction without calling server side methods, while still retaining control over whether the user can make changes.
|
||||
Venues.allow({
|
||||
insert: function() {return false;},
|
||||
update: function() {return false;},
|
||||
remove: function() {return false;}
|
||||
});
|
||||
|
||||
export default Venues;
|
||||
@@ -3,7 +3,7 @@ import { Mongo } from 'meteor/mongo';
|
||||
import { check } from 'meteor/check';
|
||||
import {SimpleSchema} from 'meteor/aldeed:simple-schema';
|
||||
|
||||
Workers = new Mongo.Collection('Workers');
|
||||
let Workers = new Mongo.Collection('Workers');
|
||||
let WORKER_ACTIVITIES = ['sales', 'prep', 'canning', 'farming'];
|
||||
let workersSchema = new SimpleSchema({
|
||||
name: {
|
||||
@@ -60,7 +60,7 @@ let workersSchema = new SimpleSchema({
|
||||
workersSchema.constants = {activities: WORKER_ACTIVITIES};
|
||||
Workers.attachSchema(workersSchema);
|
||||
|
||||
if(Meteor.isServer) Meteor.publish('Workers', function() {
|
||||
if(Meteor.isServer) Meteor.publish('workers', function() {
|
||||
return Workers.find({});
|
||||
});
|
||||
|
||||
@@ -122,4 +122,11 @@ if(Meteor.isServer) {
|
||||
});
|
||||
}
|
||||
|
||||
//Allows the client to do DB interaction without calling server side methods, while still retaining control over whether the user can make changes.
|
||||
Workers.allow({
|
||||
insert: function() {return false;},
|
||||
update: function() {return false;},
|
||||
remove: function() {return false;}
|
||||
});
|
||||
|
||||
export default Workers;
|
||||
@@ -1,3 +1,4 @@
|
||||
import {Mongo} from 'meteor/mongo';
|
||||
import Measures from "./Measure.js";
|
||||
import Venues from "./Venue.js";
|
||||
import Products from "./Product.js";
|
||||
@@ -8,15 +9,18 @@ import Logs from "./Logs.js";
|
||||
import Users from "./User.js";
|
||||
import UserRoles from "./Roles.js";
|
||||
import Workers from "./Worker.js";
|
||||
import Barcodes from "./Barcode.js";
|
||||
import Batches from "./Batch.js";
|
||||
import './Reports.js';
|
||||
import './Label.js';
|
||||
|
||||
//Save the collections in the Meteor.collections property for easy access without name conflicts.
|
||||
Meteor.collections = {Measures, Venues, Products, ProductTags, Sales, SalesSheets, Logs, Users, UserRoles, Workers};
|
||||
Meteor.collections = {Measures, Venues, Products, ProductTags, Sales, SalesSheets, Logs, Users, UserRoles, Workers, Barcodes, Batches};
|
||||
|
||||
//If this is the server then setup the default admin user if none exist.
|
||||
if(Meteor.isServer) {
|
||||
//Change this to find admin users, create a default admin user if none exists.
|
||||
if(Users.find({}).count() == 0) {
|
||||
if(Users.find({}).count() === 0) {
|
||||
try {
|
||||
console.log("Creating a default admin user: admin/admin");
|
||||
|
||||
@@ -28,4 +32,14 @@ if(Meteor.isServer) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
Meteor.validators = {};
|
||||
Meteor.validators.ObjectID = Match.Where(function(id) {
|
||||
if(id instanceof Mongo.ObjectID) {
|
||||
id = id._str;
|
||||
}
|
||||
|
||||
check(id, String);
|
||||
return /[0-9a-fA-F]{24}/.test(id);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user