Added a Sales Sheet page along with other changes.
This commit is contained in:
@@ -1,29 +1,65 @@
|
||||
|
||||
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() {
|
||||
Meteor.subscribe("sales", Session.get(PREFIX + 'searchQuery'), QUERY_LIMIT, Session.get(PREFIX + 'skipCount'));
|
||||
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() {
|
||||
return Meteor.collections.Sales.find({}, {sort: {date: -1, createdAt: -1}});
|
||||
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));
|
||||
@@ -31,6 +67,16 @@ Template.Sales.events({
|
||||
'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');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -46,9 +92,12 @@ Template.Sale.helpers({
|
||||
productName: function(id) {
|
||||
return Meteor.collections.Products.findOne({_id: id}, {fields: {name: 1}}).name;
|
||||
},
|
||||
formatDate: function(date) {
|
||||
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});
|
||||
},
|
||||
@@ -57,21 +106,175 @@ Template.Sale.helpers({
|
||||
},
|
||||
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;
|
||||
bootbox.confirm({
|
||||
message: "Delete the sale?",
|
||||
buttons: {confirm: {label: "Yes", className: 'btn-success'}, cancel: {label: "No", className: "btn-danger"}},
|
||||
callback: function(result) {
|
||||
if(result) {
|
||||
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');
|
||||
// }
|
||||
// });
|
||||
//}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -116,37 +319,27 @@ Template.SaleSearch.events({
|
||||
});
|
||||
|
||||
Template.InsertSale.onCreated(function() {
|
||||
// $('#insertSale').validator();
|
||||
// $('#insertSale').data('bs.validator');
|
||||
// this.products = new ReactiveVar([]);
|
||||
this.selectedDate = new ReactiveVar();
|
||||
this.selectedProduct = new ReactiveVar();
|
||||
this.selectedVenue = new ReactiveVar();
|
||||
});
|
||||
Template.InsertSale.onRendered(function() {
|
||||
this.$('.insertSaleForm').validator();
|
||||
// this.$('[name="product"]').
|
||||
// this.autorun(function() {
|
||||
// this.$('[name="product"]').buildCombo(Meteor.collections.Products.find({}).fetch(), {textAttr: 'name', listClass: 'comboList'});
|
||||
// });
|
||||
|
||||
//TODO: Highlight deactivated products in combo
|
||||
//TODO: Default the price for each size product based on the date.
|
||||
//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'});
|
||||
|
||||
// this.autorun(function(){
|
||||
// this.products.set(Meteor.collections.Products.find({}));
|
||||
// }.bind(this));
|
||||
});
|
||||
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="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());
|
||||
},
|
||||
@@ -155,17 +348,20 @@ Template.InsertSale.events({
|
||||
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
|
||||
};
|
||||
let insertSaleMeasures = template.$(".insertSaleMeasure");
|
||||
|
||||
//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()).toFixed(2);
|
||||
let amount = parseFloat(nextMeasure.find(".amount").val()).toFixed(2);
|
||||
let price = parseFloat(nextMeasure.find(".price").val());
|
||||
let amount = parseFloat(nextMeasure.find(".amount").val());
|
||||
|
||||
if(amount > 0) {
|
||||
let nextSale = _.clone(sale);
|
||||
@@ -177,12 +373,22 @@ Template.InsertSale.events({
|
||||
}
|
||||
}
|
||||
|
||||
//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");
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -190,9 +396,6 @@ Template.InsertSale.events({
|
||||
}
|
||||
});
|
||||
Template.InsertSale.helpers({
|
||||
products: function() {
|
||||
return [{label: "Hermies", value: 1}, {label: "Ralfe", value: 2}, {label: "Bob", value: 3}];
|
||||
},
|
||||
productMeasures: function() {
|
||||
let product = Template.instance().selectedProduct.get();
|
||||
let result = product ? product.measures : [];
|
||||
@@ -242,6 +445,20 @@ Template.InsertSaleMeasure.events({
|
||||
},
|
||||
'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({
|
||||
|
||||
Reference in New Issue
Block a user