import './Sales.html'; import '/imports/util/selectize/selectize.js'; import swal from 'sweetalert2'; let QUERY_LIMIT = 20; let PREFIX = "Sales."; Meteor.subscribe("products"); Session.set(PREFIX + "sortOption", "date"); Session.set(PREFIX + "showOnlyComments", false); 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"); let query = _.clone(Session.get(PREFIX + 'searchQuery')); if(showOnlyComments) { if(!query) query = {}; query.comment = {$exists: true}; } Meteor.subscribe("sales", query, sort, QUERY_LIMIT, Session.get(PREFIX + 'skipCount')); Session.set(PREFIX + 'saleCount', Meteor.call('getSalesCount', Session.get(PREFIX + 'searchQuery'))); }); Template.Sales.onCreated(function() { Session.set(PREFIX + "displayNewSale", false); }); Template.Sales.helpers({ displayNewSale: function() { return Session.get(PREFIX + "displayNewSale"); }, sales: function() { let sortOption = Session.get(PREFIX + "sortOption"); return Meteor.collections.Sales.find({}, {sort: (sortOption == 'createdAt' ? {createdAt: -1} : {date: -1, createdAt: -1})}); }, disablePrev: function() { return (Session.get(PREFIX + 'skipCount') || 0) == 0; }, disableNext: function() { return Session.get(PREFIX + 'saleCount') - (Session.get(PREFIX + 'skipCount') || 0) - QUERY_LIMIT <= 0; }, editing: function() { let editedSale = Session.get(PREFIX + "editedSale"); return editedSale == this._id; } }); Template.Sales.events({ 'click .newSaleButton': function(event, template) { if(template.$('.newSaleButton').hasClass('active')) { Session.set(PREFIX + 'displayNewSale', false); } else { Session.set(PREFIX + 'displayNewSale', true); Session.set(PREFIX + "editedSale", undefined); //Clear the edited sale so that only one editor is open at a time. } template.$('.newSaleButton').toggleClass('active'); }, 'click .prevButton': function(event, template) { if(!$(event.target).hasClass('disabled')) Session.set(PREFIX + 'skipCount', Math.max(0, (Session.get(PREFIX + 'skipCount') || 0) - QUERY_LIMIT)); }, 'click .nextButton': function(event, template) { if(!$(event.target).hasClass('disabled')) Session.set(PREFIX + 'skipCount', (Session.get(PREFIX + 'skipCount') || 0) + QUERY_LIMIT); }, 'change select[name="sortSelect"]': function(event, template) { Session.get(PREFIX + 'skipCount', 0); Session.set(PREFIX + "sortOption", $(event.target).val()); }, 'click .showOnlyComments': function(event, template) { let $button = $(event.target); Session.set(PREFIX + "showOnlyComments", !$button.hasClass('on')); $button.toggleClass('on'); } }); Template.Sale.onCreated(function() { }); Template.Sale.helpers({ measureName: function(id) { return Meteor.collections.Measures.findOne({_id: id}, {fields: {name: 1}}).name; }, venueName: function(id) { return Meteor.collections.Venues.findOne({_id: id}, {fields: {name: 1}}).name; }, productName: function(id) { return Meteor.collections.Products.findOne({_id: id}, {fields: {name: 1}}).name; }, formatDateAndWeek: function(date) { return moment(date).format("MM/DD/YYYY (w)"); }, formatDate: function(date) { return moment(date).format("MM/DD/YYYY"); }, formatPrice: function(price) { return price.toLocaleString("en-US", {style: 'currency', currency: 'USD', minimumFractionDigits: 2}); }, formatTotalPrice: function(price, amount) { return (price * amount).toLocaleString("en-US", {style: 'currency', currency: 'USD', minimumFractionDigits: 2}); }, showTotalPrice: function(amount) { return amount > 1; }, commentClass: function() { return this.comment ? "hasComment" : ""; } }); Template.Sale.events({ "click .actionEdit": function(event, template) { Session.set(PREFIX + "editedSale", this._id); Session.set(PREFIX + 'displayNewSale', false); //Ensure the new sale editor is closed. template.$('.newSaleButton').removeClass('active'); }, "click .saleRemove": function(event, template) { let _this = this; swal({ title: "Are you sure?", text: "This will permanently remove the sale.", type: "question", showCancelButton: true, confirmButtonColor: "#DD6B55", confirmButtonText: "Yes" }).then( function(isConfirm) { if(isConfirm) { // Meteor.collections.Sales.remove(_this._id); Meteor.call('deleteSale', _this._id); } }, function(dismiss) { } ); }, "click .editComment": function(event, template) { let _this = this; swal({ title: "Sale Comment", text: "Change the comment, or clear it to remove the comment.", input: "textarea", showCancelButton: true, closeOnConfirm: true, closeOnCancel: true, animation: "slide-from-top", inputPlaceholder: "Write a comment...", allowEscapeKey: true, inputValue: _this.comment ? _this.comment : "" }).then( function(text) { Meteor.call('editSaleComment', _this._id, text); }, function(dismiss) {} ); } }); Template.SaleEditor.onCreated(function() { let _this = this; this.product = Meteor.collections.Products.findOne({_id: this.data.productId}); this.selectedDate = new ReactiveVar(this.data.date); this.selectedVenue = new ReactiveVar(Meteor.collections.Venues.findOne({_id: this.data.venueId})); this.price = new ReactiveVar(this.data.price); this.amount = new ReactiveVar(this.data.amount); }); Template.SaleEditor.onRendered(function() { this.$('form[name="editSaleForm"]').validator(); this.$('[name="venue"]').buildCombo({cursor: Meteor.collections.Venues.find({}), selection: this.selectedVenue, comparator: function(a, b) {return a._id == b._id;}, textAttr: 'name', listClass: 'comboList'}); this.$('input[name="date"]').val(moment(this.selectedDate.get()).format("YYYY-MM-DD")); }); Template.SaleEditor.helpers({ measureName: function(id) { let measure = Meteor.collections.Measures.findOne({_id: id}, {fields: {name: 1}}); return measure ? measure.name : "???"; }, productName: function() { let product = Template.instance().product; return product ? product.name : "???"; }, price: function() { return Template.instance().price.get(); }, amount: function() { return Template.instance().amount.get(); }, total: function() { let template = Template.instance(); return (template.price.get() * template.amount.get()).toFixed(2); } }); Template.SaleEditor.events({ 'click .setDefaultPrice': function(event, template) { let date = template.selectedDate.get(); let prices = template.product.prices; let priceData; let price = 0; if(prices) priceData = prices[template.data.measureId]; //If this product has pricing data for the given measure, then either use the price, or the previousPrice (if there is one and the effectiveDate is after the sale date). if(priceData) { if(priceData.effectiveDate && date && moment(priceData.effectiveDate).isAfter(date)) price = priceData.previousPrice; else price = priceData.price } template.price.set(price); }, 'change input[name="date"]': function(event, template) { template.selectedDate.set(moment(event.target.value, "YYYY-MM-DD").toDate()); }, 'change .price': function(event, template) { template.price.set(parseFloat($(event.target).val())); }, 'change .amount': function(event, template) { template.amount.set(parseFloat($(event.target).val())); }, "click .editorCancel": function(event, template) { Session.set(PREFIX + "editedSale", undefined); }, "click .editorApply": function(event, template) { template.$('form[name="editSaleForm"]').data('bs.validator').validate(function(isValid) { if(isValid) { let id = template.data._id; let date = template.selectedDate.get(); let venue = template.selectedVenue.get(); let price = template.price.get(); let amount = template.amount.get(); Meteor.call("updateSale", id, date, venue._id, price, amount, function(error, result) { if(error) sAlert.error(error); else { sAlert.success("Sale updated."); Session.set(PREFIX + "editedSale", undefined); } }); } }); //let name = template.$("input[name='name']").val().trim(); //let tags = template.$(".productTagsEditor").select2('data'); //let aliases = template.$(".productAliasesEditor").select2('data'); //let measures = template.$(".productMeasuresEditor").select2('data'); // //tags = tags.map((n)=>n.id); //aliases = aliases.map((n)=>n.id); //measures = measures.map((n)=>n.id); // //if(Session.get(PREFIX + 'displayNewProduct')) { // Meteor.call("createProduct", name, tags, aliases, measures, function(error, result) { // if(error) sAlert.error(error); // else { // sAlert.success("Product created."); // Session.set(PREFIX + 'displayNewProduct', false); // template.parentTemplate().$('.newProductButton').removeClass('active'); // } // }); //} //else { // Meteor.call("updateProduct", this._id, name, tags, aliases, measures, function(error, result) { // if(error) sAlert.error(error); // else { // sAlert.success("Product updated."); // Session.set(PREFIX + "editedProduct", undefined); // template.parentTemplate().$('.newProductButton').removeClass('active'); // } // }); //} } }); Template.SaleSearch.helpers({ searchValue: function() { let searchFields = Session.get(PREFIX + 'searchFields'); return (searchFields && searchFields[this.columnName]) ? searchFields[this.columnName] : ''; } }); Template.SaleSearch.events({ "keyup .searchInput": _.throttle(function(event, template) { let searchQuery = Session.get(PREFIX + 'searchQuery') || {}; let searchFields = Session.get(PREFIX + 'searchFields') || {}; let searchValue = template.$('.searchInput').val(); if(searchValue) { if(this.number) searchValue = parseFloat(searchValue); 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. }, 500) }); Template.InsertSale.onCreated(function() { this.selectedDate = new ReactiveVar(); this.selectedProduct = new ReactiveVar(); this.selectedVenue = new ReactiveVar(); }); Template.InsertSale.onRendered(function() { this.$('.insertSaleForm').validator(); //TODO: Make the query for products reactive, by putting it inside an autorun block. //TODO: Fix the combo's change event firing. It does not fire a change event when selecting an item for the first time. It's $(input).val() call also returns the name of the thing selected instead of the selected object. // Note: The combo will automatically update our selection reactive variable. No need to capture change events. 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="venue"]').buildCombo({cursor: Meteor.collections.Venues.find({}), selection: this.selectedVenue, textAttr: 'name', listClass: 'comboList'}); }); Template.InsertSale.events({ //'change input[name="product"]': function(event, template) { // let selectedId = template.$('input[name="product"]').val(); // let selected = Meteor.collections.Products.findOne(selectedId); // template.selectedProduct.set(selected); //}, 'change input[name="date"]': function(event, template) { template.selectedDate.set(moment(event.target.value, "YYYY-MM-DD").toDate()); }, 'click input[type="submit"]': function(event, template) { event.preventDefault(); template.$('.insertSaleForm').data('bs.validator').validate(function(isValid) { if(isValid) { let sales = []; let insertSaleMeasures = template.$(".insertSaleMeasure"); let sale = { date: moment(template.find("[name='date']").value, "YYYY-MM-DD").toDate(), productId: template.selectedProduct.get()._id, venueId: template.selectedVenue.get()._id }; //Iterate over the measures for the sale (based on the product chosen) and collection amounts and prices. for(let next = 0; next < insertSaleMeasures.length; next++) { let nextMeasure = $(insertSaleMeasures[next]); let measureId = nextMeasure.find(".measureId").val(); let price = parseFloat(nextMeasure.find(".price").val()); let amount = parseFloat(nextMeasure.find(".amount").val()); if(amount > 0) { let nextSale = _.clone(sale); nextSale.measureId = measureId; nextSale.price = price; nextSale.amount = amount; sales.push(nextSale); } } //Iterate over the product measures that have a quantity greater than zero and add them as a sale. for(let index = 0; index < sales.length; index++) { let next = sales[index]; //console.log("Inserting: " + JSON.stringify(next)); Meteor.call('insertSale', next, function(error) { if(error) sAlert.error("Failed to insert the sale!\n" + error); else { sAlert.success("Sale Created"); //Clear the measure quantity fields so the user can enter another sale without the quantities already set. for(let next = 0; next < insertSaleMeasures.length; next++) { let nextMeasure = $(insertSaleMeasures[next]); nextMeasure.find(".amount").val(0); } } }); } } }); } }); Template.InsertSale.helpers({ productMeasures: function() { let product = Template.instance().selectedProduct.get(); let result = product ? product.measures : []; for(let i = 0; i < result.length; i++) { result[i] = Meteor.collections.Measures.findOne(result[i]); } // if(product) console.log("Found " + result.length + " measures for the product " + product.name); // else console.log("No product!"); return result; }, venues: function() { return Meteor.collections.Venues.find({}); } }); Template.InsertSaleMeasure.onCreated(function() { let _this = this; this.price = new ReactiveVar(0); this.amount = new ReactiveVar(0); Tracker.autorun(function() { let date = _this.parentTemplate().selectedDate.get(); let prices = _this.parentTemplate().selectedProduct.get().prices; let priceData; let price = 0; if(prices) priceData = prices[_this.data._id]; //If this product has pricing data for the given measure, then either use the price, or the previousPrice (if there is one and the effectiveDate is after the sale date). if(priceData) { if(priceData.effectiveDate && date && moment(priceData.effectiveDate).isAfter(date)) price = priceData.previousPrice; else price = priceData.price } _this.price.set(price); }); }); Template.InsertSaleMeasure.events({ 'change .price': function(event, template) { template.price.set(parseFloat($(event.target).val())); }, 'change .amount': function(event, template) { template.amount.set(parseFloat($(event.target).val())); }, 'focus input[name="amount"],input[name="price"]': 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.InsertSaleMeasure.helpers({ price: function() { return Template.instance().price.get().toFixed(2); }, total: function() { let template = Template.instance(); return (template.price.get() * template.amount.get()).toFixed(2); }, amount: function() { return Template.instance().amount.get(); } });