Files
PetitTetonMeteor/imports/api/Batch.js

271 lines
8.9 KiB
JavaScript
Raw Normal View History

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} : {};
2020-01-16 09:31:12 -08:00
//console.log("dbQuery=" + JSON.stringify(dbQuery));
//console.log("Result Count: " + Batches.find(query).count());
return Batches.find(dbQuery, {limit: limit, sort, skip: skipCount});
});
Meteor.methods({
getBatchCount: function(query) {
//TODO: Validate the query?
2020-01-16 09:31:12 -08:00
return Batches.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.");
},
2020-01-16 09:31:12 -08:00
//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, amount, comment) {
check(id, String);
2020-01-16 09:31:12 -08:00
check(amount, Number);
check(comment, Match.OneOf(String, undefined));
//Trim and convert empty comment to undefined.
comment = comment ? comment.trim() : undefined;
comment = comment && comment.length > 0 ? comment : undefined;
2020-01-16 09:31:12 -08:00
//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])) {
2020-01-16 09:31:12 -08:00
Batches.update(id, {$set: {comment, amount}}, 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;