466 lines
17 KiB
JavaScript
466 lines
17 KiB
JavaScript
|
|
import './Production.html';
|
|
|
|
let QUERY_LIMIT = 100;
|
|
let QUERY_LIMIT_INCREMENT = 100;
|
|
let PREFIX = "Production.";
|
|
|
|
Tracker.autorun(function() {
|
|
//Meteor.subscribe("batches");
|
|
Meteor.subscribe("workers");
|
|
Meteor.subscribe("products");
|
|
Meteor.subscribe("measures");
|
|
});
|
|
|
|
Template.Production.onCreated(function() {
|
|
Session.set(PREFIX + "displayNew", false); //Whether the new dialog is inlined in the table and visible.
|
|
|
|
Session.set(PREFIX + "sortOption", 'date'); //Allows us to sort the results of the batch query by batch attribute.
|
|
Session.set(PREFIX + 'skipCount', 0); //Allows us to page through the results of the batch query. Currently not used.
|
|
Session.set(PREFIX + 'batchCount', 0); //A count of all batches in the system (that fit our current query). Useful for paging and dynamic loading.
|
|
Session.set(PREFIX + "queryLimit", QUERY_LIMIT);
|
|
Session.set(PREFIX + "showDeleted", false);
|
|
Session.set(PREFIX + "editedId", undefined);
|
|
|
|
Tracker.autorun(function() {
|
|
let sortOption = Session.get(PREFIX + "sortOption");
|
|
let sort = sortOption == 'createdAt' ? {createdAt: -1} : {date: -1, createdAt: -1};
|
|
//let showOnlyComments = Session.get(PREFIX + "showOnlyComments"); //Not needed here. Shows how to limit the query to only records with certain features.
|
|
let query = _.clone(Session.get(PREFIX + 'searchQuery'));
|
|
|
|
//if(showOnlyComments) {
|
|
// if(!query) query = {};
|
|
// query.comment = {$exists: true};
|
|
//}
|
|
|
|
if(!Session.get(PREFIX + "showDeleted")) {
|
|
if(!query) query = {};
|
|
query.deletedAt = {$exists: false};
|
|
}
|
|
|
|
Template.Production.batchesSubscription = Meteor.subscribe("batches", query, sort, QUERY_LIMIT, Session.get(PREFIX + 'skipCount'));
|
|
Session.set(PREFIX + 'batchCount', Meteor.call('getBatchCount', Session.get(PREFIX + 'searchQuery')));
|
|
});
|
|
});
|
|
Template.Production.onDestroyed(function() {
|
|
if(Template.Production.batchSubscription) {
|
|
Template.Production.batchSubscription.stop();
|
|
}
|
|
});
|
|
Template.Production.onRendered(function() {
|
|
$(".tableContainer").mCustomScrollbar({
|
|
scrollButtons: {enable:true},
|
|
theme: "light-thick",
|
|
scrollbarPosition: "outside",
|
|
scrollEasing: "linear"
|
|
});
|
|
});
|
|
Template.Production.helpers({
|
|
displayNew: function() {
|
|
return Session.get(PREFIX + "displayNew");
|
|
},
|
|
batches: function() {
|
|
let sortOption = Session.get(PREFIX + "sortOption");
|
|
|
|
return Meteor.collections.Batches.find({}, {sort: (sortOption == 'createdAt' ? {createdAt: -1} : {date: -1, createdAt: -1})});
|
|
},
|
|
disableLoadMore: function() {
|
|
return Session.get(PREFIX + 'batchCount') - (Session.get(PREFIX + 'skipCount') || 0) - Session.get(PREFIX + "queryLimit") <= 0;
|
|
},
|
|
showDeletedSelected: function() {
|
|
return (Session.get(PREFIX + "showDeleted")) ? "selected" : "";
|
|
}
|
|
});
|
|
Template.Production.events({
|
|
'click .loadMoreLink': function(event, template) {
|
|
event.preventDefault();
|
|
Session.set(PREFIX + 'queryLimit', Session.get(PREFIX + "queryLimit") + QUERY_LIMIT_INCREMENT);
|
|
},
|
|
'click .newButton': function(event, template) {
|
|
if(template.$('.newButton').hasClass('active')) {
|
|
Session.set(PREFIX + 'displayNew', false);
|
|
}
|
|
else {
|
|
Session.set(PREFIX + 'displayNew', true);
|
|
Session.set(PREFIX + "editedId", undefined); //Clear the edited product so that only one editor is open at a time.
|
|
}
|
|
template.$('.newButton').toggleClass('active');
|
|
},
|
|
'click .showDeletedButton': function(event, template) {
|
|
Session.set(PREFIX + "showDeleted", !Session.get(PREFIX + "showDeleted")); //Toggle the display of deleted production.
|
|
}
|
|
});
|
|
|
|
|
|
Template.Batch.onCreated(function() {
|
|
});
|
|
Template.Batch.onRendered(function() {
|
|
});
|
|
Template.Batch.helpers({
|
|
hasLabelsClass: function() {
|
|
return this.hasLabels === true ? "hasLabels" : "noLabels";
|
|
},
|
|
name: function() {
|
|
let product = Meteor.collections.Products.findOne({_id: this.productId});
|
|
let measure = Meteor.collections.Measures.findOne({_id: this.measureId});
|
|
return product.name + " (" + measure.name + ")";
|
|
},
|
|
date: function() {
|
|
return moment(this.date, "YYYYMMDD").format("MM/DD/YYYY");
|
|
},
|
|
cook: function() {
|
|
let worker = Meteor.collections.Workers.findOne({_id: this.cookId});
|
|
return worker.name;
|
|
},
|
|
canner: function() {
|
|
let worker = Meteor.collections.Workers.findOne({_id: this.cannerId});
|
|
return worker.name;
|
|
},
|
|
editing: function() {
|
|
let editedId = Session.get(PREFIX + "editedId");
|
|
|
|
return editedId && editedId.toString() === this._id.toString();
|
|
},
|
|
getRowClass: function() {
|
|
return this.deletedAt ? "deleted" : "";
|
|
},
|
|
isDeleted: function() {
|
|
return this.deletedAt;
|
|
},
|
|
jdate: function() {
|
|
return moment(this.date, "YYYYMMDD").format("YYDDDD");
|
|
}
|
|
});
|
|
Template.Batch.events({
|
|
"click .hasLabels": function(event, template) {
|
|
Meteor.call('setBatchHasLabels', this._id, !(this.hasLabels === true), function(error, result) {
|
|
if(error) sAlert.error(error);
|
|
});
|
|
},
|
|
"click .actionEdit": function(event, template) {
|
|
Session.set(PREFIX + "editedId", this._id);
|
|
Session.set(PREFIX + 'displayNew', false); //Ensure the new editor is closed.
|
|
template.parentTemplate().$('.newButton').removeClass('active');
|
|
},
|
|
"click .actionDelete": function(event, template) {
|
|
Meteor.call('deleteBatch', this._id, function(error, result) {
|
|
if(error) sAlert.error(error);
|
|
else sAlert.success("Production Batch Deleted");
|
|
});
|
|
},
|
|
'click .actionUndelete': function(event, template) {
|
|
Meteor.call('undeleteBatch', this._id, function(error, result) {
|
|
if(error) sAlert.error(error);
|
|
else sAlert.success("Production Batch No Longer Deleted");
|
|
});
|
|
}
|
|
});
|
|
|
|
|
|
Template.BatchEditor.onCreated(function() {
|
|
});
|
|
Template.BatchEditor.onRendered(function() {
|
|
//Not working????
|
|
//this.$('.editorForm').validator();
|
|
});
|
|
Template.BatchEditor.helpers({
|
|
name: function() {
|
|
let product = Meteor.collections.Products.findOne({_id: this.productId});
|
|
let measure = Meteor.collections.Measures.findOne({_id: this.measureId});
|
|
|
|
return (product ? product.name : "") + " (" + (measure ? measure.name : "") + ")";
|
|
},
|
|
date: function() {
|
|
return moment(this.date, "YYYYMMDD").format("MM/DD/YYYY");
|
|
},
|
|
cook: function() {
|
|
let worker = Meteor.collections.Workers.findOne({_id: this.cookId});
|
|
return worker.name;
|
|
},
|
|
canner: function() {
|
|
let worker = Meteor.collections.Workers.findOne({_id: this.cannerId});
|
|
return worker.name;
|
|
}
|
|
});
|
|
Template.BatchEditor.events({
|
|
'click .editorCancel': function(event, template) {
|
|
Session.set(PREFIX + "editedId", undefined);
|
|
},
|
|
'click .editorApply': function(event, template) {
|
|
//template.$('.editorForm').data('bs.validator').validate(function(isValid) {
|
|
// if(isValid) {
|
|
let id = template.data._id;
|
|
let amount = parseInt(template.$('input.amount').val());
|
|
let comment = template.$('#batchEditorComment').val();
|
|
|
|
console.log(id + " " + amount + " " + comment);
|
|
|
|
if(Number.isInteger(amount) && amount > 0) {
|
|
|
|
Meteor.call('updateBatch', id, amount, comment, function(error) {
|
|
if(error) sAlert.error("Failed to update the batch!\n" + error);
|
|
else {
|
|
sAlert.success("Production batch updated.");
|
|
Session.set(PREFIX + "editedId", undefined);
|
|
}
|
|
});
|
|
}
|
|
else {
|
|
sAlert.error("Amount must be a number greater than zero.");
|
|
}
|
|
// }
|
|
//});
|
|
}
|
|
});
|
|
|
|
|
|
Template.BatchNew.onCreated(function() {
|
|
this.selectedProduct = new ReactiveVar();
|
|
this.selectedCook = new ReactiveVar();
|
|
this.selectedCanner = new ReactiveVar();
|
|
});
|
|
Template.BatchNew.onRendered(function() {
|
|
this.$('.insertForm').validator();
|
|
this.$('[name="product"]').buildCombo({cursor: Meteor.collections.Products.find({$or: [{hidden: false}, {hidden: {$exists:false}}]}), selection: this.selectedProduct, textAttr: 'name', listClass: 'comboList', getClasses: function(data) {
|
|
return (data && data.deactivated) ? "deactivated" : "";
|
|
}});
|
|
this.$('[name="cook"]').buildCombo({cursor: Meteor.collections.Workers.find({}), selection: this.selectedCook, textAttr: 'name', listClass: 'comboList'});
|
|
this.$('[name="canner"]').buildCombo({cursor: Meteor.collections.Workers.find({}), selection: this.selectedCanner, textAttr: 'name', listClass: 'comboList'});
|
|
this.$('input[name="product"]').focus();
|
|
});
|
|
Template.BatchNew.helpers({
|
|
//products: function() {
|
|
// return Meteor.collections.Products.find({});
|
|
//},
|
|
//productSelected: function() {
|
|
// let product = this;
|
|
// let batch = Template.parentData();
|
|
//
|
|
// return batch.productId === product._id ? "selected" : "";
|
|
//},
|
|
productMeasures: function() {
|
|
//Show only the list allowed by the product
|
|
let product = Template.instance().selectedProduct.get();
|
|
|
|
if(product) {
|
|
let measures = Meteor.collections.Measures.find({}).fetch();
|
|
let measuresById = {};
|
|
let allowedMeasureIds = product.measures;
|
|
let allowedMeasures = [];
|
|
|
|
//Create a hashmap of measures by their id.
|
|
for(let measure of measures) measuresById[measure._id.toString()] = measure;
|
|
|
|
//Create a list of measures allowed for the product.
|
|
for(let measureId of allowedMeasureIds) {
|
|
allowedMeasures.push(measuresById[measureId.toString()]);
|
|
}
|
|
|
|
return allowedMeasures;
|
|
}
|
|
else return [];
|
|
},
|
|
today: () => {
|
|
return moment().format("YYYY-MM-DD");
|
|
}
|
|
});
|
|
Template.BatchNew.events({
|
|
'change input[name="date"]': function(event, template) {
|
|
template.selectedDate.set(moment(event.target.value, "YYYY-MM-DD").toDate());
|
|
},
|
|
'click .editorCancel': function(event, template) {
|
|
Session.set(PREFIX + "editedId", undefined);
|
|
},
|
|
'click .editorApply': function(event, template) {
|
|
event.preventDefault();
|
|
template.$('.insertForm').data('bs.validator').validate(function(isValid) {
|
|
if(isValid) {
|
|
let batches = [];
|
|
let insertMeasure = template.$(".insertMeasure");
|
|
|
|
let batch = {
|
|
date: ~~(moment(template.find("[name='date']").value, "YYYY-MM-DD").format("YYYYMMDD")), // Note: ~~ performs a bitwise not which is a fast method of converting a string to a number.
|
|
productId: template.selectedProduct.get()._id,
|
|
cookId: template.selectedCook.get()._id,
|
|
cannerId: template.selectedCanner.get()._id,
|
|
comment: template.$('.comment').val()
|
|
};
|
|
|
|
//Iterate over the measures for the batch (based on the product chosen) and amounts.
|
|
for(let next = 0; next < insertMeasure.length; next++) {
|
|
let nextMeasure = $(insertMeasure[next]);
|
|
let measureId = nextMeasure.find(".measureId").val();
|
|
let amount = parseFloat(nextMeasure.find(".amount").val());
|
|
|
|
if(amount > 0) {
|
|
let next = _.clone(batch);
|
|
|
|
next.measureId = measureId;
|
|
next.amount = amount;
|
|
batches.push(next);
|
|
}
|
|
}
|
|
|
|
Meteor.call('insertBatches', batches, function(error) {
|
|
if(error) sAlert.error("Failed to insert the batch!\n" + error);
|
|
else {
|
|
let productCombo = template.$("input[name='product']");
|
|
let measureFields = template.$(".amount");
|
|
|
|
sAlert.success("Production batches created.");
|
|
|
|
//Clear the measure quantity fields so the user can enter another sale without the quantities already set.
|
|
measureFields.val(0);
|
|
//Set the focus to the product field of the form.
|
|
//productCombo.focus();
|
|
//Clear the product since it is highly unlikely the same product will be added twice for the same date.
|
|
productCombo.val("");
|
|
Session.set(PREFIX + "displayNew", false);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
|
|
Template.InsertBatchMeasure.onCreated(function() {
|
|
let _this = this;
|
|
|
|
this.amount = new ReactiveVar(0);
|
|
});
|
|
Template.InsertBatchMeasure.onRendered(function() {
|
|
});
|
|
Template.InsertBatchMeasure.helpers({
|
|
amount: function() {
|
|
return Template.instance().amount.get();
|
|
}
|
|
});
|
|
Template.InsertBatchMeasure.events({
|
|
'change .amount': function(event, template) {
|
|
template.amount.set(parseFloat($(event.target).val()));
|
|
},
|
|
'focus input[name="amount"]': function(event, template) {
|
|
//See http://stackoverflow.com/questions/3150275/jquery-input-select-all-on-focus
|
|
//Handle selecting the text in the field on receipt of focus.
|
|
let $this = $(this)
|
|
.one('mouseup.mouseupSelect', function() {
|
|
$this.select();
|
|
return false;
|
|
})
|
|
.one('mousedown', function() {
|
|
// compensate for untriggered 'mouseup' caused by focus via tab
|
|
$this.off('mouseup.mouseupSelect');
|
|
})
|
|
.select();
|
|
}
|
|
});
|
|
|
|
|
|
Template.BatchSearch.onCreated(function() {
|
|
});
|
|
Template.BatchSearch.onRendered(function() {
|
|
});
|
|
Template.BatchSearch.helpers({
|
|
searchValue: function() {
|
|
let searchFields = Session.get(PREFIX + 'searchFields');
|
|
|
|
return (searchFields && searchFields[this.columnName]) ? searchFields[this.columnName] : '';
|
|
}
|
|
});
|
|
Template.BatchSearch.events({
|
|
"keyup .searchInput": _.throttle(function(event, template) {
|
|
let searchQuery = Session.get(PREFIX + 'searchQuery') || {};
|
|
let searchFields = Session.get(PREFIX + 'searchFields') || {};
|
|
let searchValue = template.$(event.target).val();
|
|
|
|
if(searchValue) {
|
|
if(this.number) searchValue = parseFloat(searchValue);
|
|
|
|
// A collection name will be provided if there is a related table of data that will contain the text provided and will map to an ID that is then searched for in the current table of data.
|
|
// For example we are displaying a table of Sales which has the ID of a Product. The Product table has a Name field and the search box searches for Product Names. The ID's of the Products found should be used to filter the Sales by Product ID.
|
|
if(this.collection) {
|
|
let ids = Meteor.collections[this.collection].find({[this.collectionQueryColumnName]: {$regex: searchValue, $options: 'i'}}, {fields: {[this.collectionResultColumnName]: 1}}).fetch();
|
|
|
|
//Convert the ids to an array of ids instead of an array of objects containing an id.
|
|
for(let i = 0; i < ids.length; i++) {ids[i] = ids[i]._id;}
|
|
searchQuery[this.columnName] = {$in: ids};
|
|
searchFields[this.columnName] = searchValue;
|
|
}
|
|
else {
|
|
searchFields[this.columnName] = searchQuery[this.columnName] = searchValue;
|
|
}
|
|
}
|
|
else {
|
|
//Remove columns from the search query whose values are empty so we don't bother the database with them.
|
|
delete searchQuery[this.columnName];
|
|
delete searchFields[this.columnName];
|
|
}
|
|
|
|
Session.set(PREFIX + 'searchQuery', searchQuery);
|
|
Session.set(PREFIX + 'searchFields', searchFields);
|
|
Session.set(PREFIX + 'skipCount', 0); //Reset the paging of the results.
|
|
Session.set(PREFIX + "queryLimit", QUERY_LIMIT); //Reset the query limit in case we loaded more
|
|
}, 500)
|
|
});
|
|
|
|
|
|
Template.BatchDateRangeSearch.helpers({
|
|
startDate: function() {
|
|
let searchFields = Session.get(PREFIX + 'searchFields');
|
|
let searchValue = (searchFields && searchFields[this.columnName]) ? searchFields[this.columnName] : {};
|
|
|
|
return searchValue.start ? moment(searchValue.start.toString(), "YYYYMMDD").format("MM/DD/YYYY") : "";
|
|
},
|
|
endDate: function() {
|
|
let searchFields = Session.get(PREFIX + 'searchFields');
|
|
let searchValue = (searchFields && searchFields[this.columnName]) ? searchFields[this.columnName] : {};
|
|
|
|
return searchValue.end ? moment(searchValue.end.toString(), "YYYYMMDD").format("MM/DD/YYYY") : "";
|
|
}
|
|
});
|
|
Template.BatchDateRangeSearch.events({
|
|
"change .searchDateStartInput": function(event, template) {Template.DateRangeSearch.dateChanged(true, event, template)},
|
|
"keyup .searchDateStartInput": _.throttle(function(event, template) {Template.DateRangeSearch.dateChanged(true, event, template)}, 500),
|
|
"change .searchDateEndInput": function(event, template) {Template.DateRangeSearch.dateChanged(false, event, template)},
|
|
"keyup .searchDateEndInput": _.throttle(function(event, template) {Template.DateRangeSearch.dateChanged(false, event, template)}, 500)
|
|
});
|
|
Template.BatchDateRangeSearch.dateChanged = function(isStart, event, template) {
|
|
let searchQuery = Session.get(PREFIX + 'searchQuery') || {};
|
|
let searchFields = Session.get(PREFIX + 'searchFields') || {};
|
|
let searchValue = template.$(event.target).val();
|
|
let columnName = template.data.columnName;
|
|
|
|
if(searchValue) {
|
|
let search = searchQuery[columnName];
|
|
|
|
// Create a search object and attach it to the searchFields and searchQuery objects if needed.
|
|
if(!search) {
|
|
search = {type: 'dateRange'};
|
|
searchFields[columnName] = searchQuery[columnName] = search;
|
|
}
|
|
|
|
// Use moment to parse date and convert it to YYYYMMDD for searching the database.
|
|
searchValue = ~~(moment(searchValue, searchValue.includes("-") ? "YYYY-MM-DD" : "MM/DD/YYYY").format("YYYYMMDD")); // Note: ~~ performs a bitwise not which is a fast method of converting a string to a number.
|
|
// Save the search ending date.
|
|
isStart ? search.start = searchValue : search.end = searchValue;
|
|
}
|
|
else {
|
|
if(searchQuery[columnName]) {
|
|
// Remove columns from the search query whose values are empty so we don't bother the database with them.
|
|
if(isStart) {
|
|
delete searchQuery[columnName].start;
|
|
delete searchFields[columnName].start;
|
|
}
|
|
else {
|
|
delete searchQuery[columnName].end;
|
|
delete searchFields[columnName].end;
|
|
}
|
|
}
|
|
}
|
|
|
|
Session.set(PREFIX + 'searchQuery', searchQuery);
|
|
Session.set(PREFIX + 'searchFields', searchFields);
|
|
Session.set(PREFIX + 'skipCount', 0); //Reset the paging of the results.
|
|
Session.set(PREFIX + "queryLimit", QUERY_LIMIT); //Reset the query limit in case we loaded more
|
|
}; |