Prototyped the barcode idea; Added a basic production system.
This commit is contained in:
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;
|
||||
Reference in New Issue
Block a user