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 };