Redesigned the querying for the sale duplicates screen to use aggregation; Finished the styling of the sale duplicate screen; Tested the functionality of sale duplicates; Added a way to show hidden (ignored) duplicates.
This commit is contained in:
@@ -3,30 +3,41 @@ import './Sales.html';
|
||||
import '/imports/util/selectize/selectize.js';
|
||||
import swal from 'sweetalert2';
|
||||
|
||||
/**
|
||||
* Notes:
|
||||
* The Sale 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(sale.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.
|
||||
*/
|
||||
|
||||
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);
|
||||
|
||||
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};
|
||||
}
|
||||
|
||||
//if(Template.Sales.salesSubscription) Template.Sales.salesSubscription.stop();
|
||||
Template.Sales.salesSubscription = Meteor.subscribe("sales", query, sort, QUERY_LIMIT, Session.get(PREFIX + 'skipCount'));
|
||||
Session.set(PREFIX + 'saleCount', Meteor.call('getSalesCount', Session.get(PREFIX + 'searchQuery')));
|
||||
});
|
||||
});
|
||||
Template.Sales.onDestroyed(function() {
|
||||
if(Template.Sales.salesSubscription) {
|
||||
Template.Sales.salesSubscription.stop();
|
||||
}
|
||||
});
|
||||
Template.Sales.helpers({
|
||||
displayNewSale: function() {
|
||||
@@ -77,6 +88,9 @@ Template.Sales.events({
|
||||
|
||||
Session.set(PREFIX + "showOnlyComments", !$button.hasClass('on'));
|
||||
$button.toggleClass('on');
|
||||
},
|
||||
'click .showDuplicates': function(event, template) {
|
||||
FlowRouter.go('SaleDuplicates');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -93,10 +107,10 @@ Template.Sale.helpers({
|
||||
return Meteor.collections.Products.findOne({_id: id}, {fields: {name: 1}}).name;
|
||||
},
|
||||
formatDateAndWeek: function(date) {
|
||||
return moment(date).format("MM/DD/YYYY (w)");
|
||||
return moment.utc(date.toString(), "YYYYMMDD").utc().format("MM/DD/YYYY (w)");
|
||||
},
|
||||
formatDate: function(date) {
|
||||
return moment(date).format("MM/DD/YYYY");
|
||||
formatDateTime: function(date) {
|
||||
return moment.utc(date).format("MM/DD/YYYY");
|
||||
},
|
||||
formatPrice: function(price) {
|
||||
return price.toLocaleString("en-US", {style: 'currency', currency: 'USD', minimumFractionDigits: 2});
|
||||
@@ -162,10 +176,8 @@ Template.Sale.events({
|
||||
});
|
||||
|
||||
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.selectedDate = new ReactiveVar(moment(this.data.date.toString(), "YYYYMMDD").toDate());
|
||||
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);
|
||||
@@ -207,7 +219,7 @@ Template.SaleEditor.events({
|
||||
|
||||
//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))
|
||||
if(priceData.effectiveDate && date && moment.utc(priceData.effectiveDate.toString(), "YYYYMMDD").isAfter(date))
|
||||
price = priceData.previousPrice;
|
||||
else
|
||||
price = priceData.price
|
||||
@@ -231,7 +243,7 @@ Template.SaleEditor.events({
|
||||
template.$('form[name="editSaleForm"]').data('bs.validator').validate(function(isValid) {
|
||||
if(isValid) {
|
||||
let id = template.data._id;
|
||||
let date = template.selectedDate.get();
|
||||
let date = ~~(moment(template.selectedDate.get()).format("YYYYMMDD")); // Note: The ~~ is a bitwise not that is a fast method of converting a string to a number.
|
||||
let venue = template.selectedVenue.get();
|
||||
let price = template.price.get();
|
||||
let amount = template.amount.get();
|
||||
@@ -245,36 +257,6 @@ Template.SaleEditor.events({
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//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');
|
||||
// }
|
||||
// });
|
||||
//}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -289,11 +271,13 @@ 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();
|
||||
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();
|
||||
|
||||
@@ -313,11 +297,70 @@ Template.SaleSearch.events({
|
||||
}
|
||||
|
||||
Session.set(PREFIX + 'searchQuery', searchQuery);
|
||||
Session.set(PREFIX + 'searchFields', searchFields)
|
||||
Session.set(PREFIX + 'searchFields', searchFields);
|
||||
Session.set(PREFIX + 'skipCount', 0); //Reset the paging of the results.
|
||||
}, 500)
|
||||
});
|
||||
|
||||
Template.DateRangeSearch.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.DateRangeSearch.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.DateRangeSearch.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.
|
||||
};
|
||||
|
||||
Template.InsertSale.onCreated(function() {
|
||||
this.selectedDate = new ReactiveVar();
|
||||
this.selectedProduct = new ReactiveVar();
|
||||
@@ -351,7 +394,7 @@ Template.InsertSale.events({
|
||||
let insertSaleMeasures = template.$(".insertSaleMeasure");
|
||||
|
||||
let sale = {
|
||||
date: moment(template.find("[name='date']").value, "YYYY-MM-DD").toDate(),
|
||||
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,
|
||||
venueId: template.selectedVenue.get()._id
|
||||
};
|
||||
@@ -381,6 +424,7 @@ Template.InsertSale.events({
|
||||
if(error) sAlert.error("Failed to insert the sale!\n" + error);
|
||||
else {
|
||||
sAlert.success("Sale Created");
|
||||
nextMeasure.find(".amount").val(0);
|
||||
|
||||
//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++) {
|
||||
|
||||
Reference in New Issue
Block a user