2017-05-09 13:51:26 -07:00
import './SalesSheetForm.html' ;
import swal from 'sweetalert2' ;
2025-07-02 11:18:09 -07:00
import { ReactiveDict } from 'meteor/reactive-dict'
2017-05-09 13:51:26 -07:00
let PREFIX = "SalesSheetForm." ;
//******************************************************************
//** The form for filling out a sheet.
//******************************************************************
Template . SalesSheetForm . onCreated ( function ( ) {
let template = this ;
this . selectedDate = new ReactiveVar ( ) ;
this . selectedVenue = new ReactiveVar ( ) ;
this . salesSheet = new ReactiveVar ( ) ;
//this.measures = new ReactiveDict();
this . oddProductIds = new ReactiveDict ( false ) ;
this . productTemplates = [ ] ;
//Tracker.autorun(function() {
// let measures = Meteor.collections.Measures.find({}).fetch();
//
// template.measures.clear();
// for(let measure of measures) {
// template.measures.set(measure._id, measure);
// }
//});
// Place the sales sheet in a reactive var and put the setting of the reactive var in an autorun.
// The autorun is needed apparently to ensure changes to the data force a change in the reactive var.
Tracker . autorun ( function ( ) {
2017-10-20 14:54:58 -07:00
let data = Blaze . getData ( template . view ) ;
//****
// Note: I have commented the code below out because I was unable to get Meteor to properly (according to their docs on Template.currentData() being reactive) call my code when the template's input data changes.
// Because of this I have instead used the parent template's session variable "selectedSheet" to detect and handle the template data changing.
// To avoid session variable conflicts, I have been using a session variable name prefix that is the template's name followed by a '.' as a convention.
//****
2017-05-09 13:51:26 -07:00
//Force this to be reactive on the current data.
2017-10-20 14:54:58 -07:00
//try {
// Template.currentData();
//} catch(err) {
// // Ignore it. This always has an error accessing the currentData as the template is destroyed.
//}
2017-05-09 13:51:26 -07:00
//For some reason the current data is not always set, and does not always equal the template.data. We will use the template.data to get the actual ID of the sales sheet for the query.
2017-10-20 14:54:58 -07:00
//template.salesSheet.set(Template.instance() ? Meteor.collections.SalesSheets.findOne(Template.currentData()) : null);
//template.salesSheet.set(Session.get("SalesSheets." + "selectedSheet"));
template . salesSheet . set ( Meteor . collections . SalesSheets . findOne ( data ) ) ;
2017-05-09 13:51:26 -07:00
} ) ;
Tracker . autorun ( function ( ) {
2017-10-20 14:54:58 -07:00
let products = template . salesSheet . get ( ) ? template . salesSheet . get ( ) . products : [ ] ;
2017-05-09 13:51:26 -07:00
let index = 1 ;
// Note: We will ignore orphaned data in the dictionary so we don't have to clear the dictionary, or identify the orphans. The orphans are just a few extra product id's mapped to booleans, and should be fairly rare anyway.
// While ignoring headers (resetting index upon header), collect all odd product id's into a set.
for ( let next of products ) {
if ( next . productId ) {
if ( index % 2 != 0 ) {
template . oddProductIds . set ( next . productId , true ) ;
}
else {
template . oddProductIds . delete ( next . productId ) ;
}
index ++ ;
}
else index = 1 ;
}
} ) ;
} ) ;
Template . SalesSheetForm . onRendered ( function ( ) {
this . $ ( '.sheetHeader' ) . validator ( ) ;
this . $ ( '[name="venue"]' ) . buildCombo ( { cursor : Meteor . collections . Venues . find ( { } ) , selection : this . selectedVenue , textAttr : 'name' , listClass : 'comboList' } ) ;
} ) ;
Template . SalesSheetForm . helpers ( {
isHeading : function ( product ) {
return ! product . productId ;
} ,
products : function ( ) {
if ( Template . instance ( ) . salesSheet . get ( ) )
return Template . instance ( ) . salesSheet . get ( ) . products ;
else
return [ ] ;
} ,
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 ] ) ;
}
return result ;
} ,
venues : function ( ) {
return Meteor . collections . Venues . find ( { } ) ;
}
} ) ;
Template . SalesSheetForm . events ( {
'change input[name="date"]' : function ( event , template ) {
template . selectedDate . set ( moment ( event . target . value , "YYYY-MM-DD" ) . toDate ( ) ) ;
} ,
'click .sheetControls .resetSheet' : function ( event , template ) {
for ( let next of template . productTemplates ) {
next . reset ( ) ;
}
} ,
'click .sheetControls .saveSheet' : function ( event , template ) {
event . preventDefault ( ) ;
template . $ ( '.sheetHeader' ) . data ( 'bs.validator' ) . validate ( function ( isValid ) {
if ( isValid ) {
2017-10-20 14:54:58 -07:00
let date = ~ ~ ( moment ( template . selectedDate . get ( ) ) . format ( "YYYYMMDD" ) ) ;
2017-05-09 13:51:26 -07:00
let venueId = template . selectedVenue . get ( ) . _id ;
// Track the inserts and errors, display output to the user and log when everything is done.
let insertMetadata = {
serverErrors : [ ] ,
insertCount : 0 ,
finishedCount : 0 ,
isDoneInserting : false ,
incrementInsertCount : function ( ) {
this . insertCount ++ ;
} ,
incrementFinishedCount : function ( ) {
this . finishedCount ++ ;
this . finished ( ) ;
} ,
doneInserting : function ( ) {
this . isDoneInserting = true ;
this . finished ( ) ;
} ,
finished : function ( ) {
if ( this . isDoneInserting && this . finishedCount == this . insertCount ) {
if ( this . serverErrors . length > 0 ) {
let log = "__________________________________________\n" ;
log += "Server Errors:\n\n" ;
for ( let e of this . serverErrors ) {
log += e + "\n" ;
}
log += "\n__________________________________________" ;
console . log ( log ) ;
sAlert . error ( "Failed to insert some or all of the sales! See the browser logs for details. Successful sales had their amounts set to zero." ) ;
}
else {
sAlert . success ( "All " + this . insertCount + " sales were saved." ) ;
}
}
}
} ;
// Iterate over the product templates.
for ( let productTemplate of template . productTemplates ) {
let productId = productTemplate . product . get ( ) . _id ;
// Iterate over each measure template in each product template.
for ( let measureTemplate of productTemplate . measureTemplates ) {
let measureId = measureTemplate . measure . get ( ) . _id ;
let amount = measureTemplate . amount . get ( ) ;
let price = measureTemplate . price . get ( ) ;
// If the amount and price are above zero then we should record a sale.
if ( amount > 0 && price > 0 ) {
let sale = { date , venueId , productId , measureId , amount , price } ;
insertMetadata . incrementInsertCount ( ) ;
// Record the sale.
Meteor . call ( "insertSale" , sale , function ( error ) {
if ( error ) {
insertMetadata . serverErrors . push ( error ) ;
}
else {
measureTemplate . amount . set ( 0 ) ;
measureTemplate . price . set ( measureTemplate . autoSetPrice ) ;
}
insertMetadata . incrementFinishedCount ( ) ;
} ) ;
}
}
}
insertMetadata . doneInserting ( ) ;
}
} ) ;
}
} ) ;
// ***** A header in the sales sheet. *****
Template . SalesSheetFormHeader . onCreated ( function ( ) {
//this.parentTemplate(1).productTemplates.push(this);
} ) ;
Template . SalesSheetFormHeader . onDestroyed ( function ( ) {
//this.parentTemplate(1).productTemplates.remove(this);
} ) ;
Template . SalesSheetFormHeader . events ( {
} ) ;
Template . SalesSheetFormHeader . helpers ( {
} ) ;
// ***** A product in the sales sheet. *****
// The data is the SalesSheet's Product metadata (not a database Product object). It has a name and productId among other things.
Template . SalesSheetFormProduct . onCreated ( function ( ) {
let parent = this . parentTemplate ( 1 ) ;
let template = this ;
parent . productTemplates . push ( this ) ;
this . parent = parent ;
this . measureTemplates = [ ] ;
this . measureTemplatesDependancy = new Tracker . Dependency ;
this . product = new ReactiveVar ( ) ; //The actual product for this sheet product.
this . total = new ReactiveVar ( 0 ) ;
this . reset = function ( ) {
for ( let measureTemplate of template . measureTemplates ) {
measureTemplate . reset ( ) ;
}
} ;
//Set the product data in a reactive variable so that changes to the product (such as pricing) are immediately reflected in this sheet. This ensures that the view updates when the reactive variable value changes.
//Set the product reactive variable value in an autorun block so that if the product is updated in the local database, we are notified. This ensures the reactive variable value updates when the database changes.
template . autorun ( function ( ) {
// Save a reference to the product.
template . product . set ( Meteor . collections . Products . findOne ( Template . currentData ( ) . productId ) ) ;
// Depend on the array of measure templates.
template . measureTemplatesDependancy . depend ( ) ;
for ( let measureTemplate of template . measureTemplates ) {
measureTemplate . resetPrice ( ) ;
}
} ) ;
//Auto calculate a total from the amounts and prices. This is in a separate autorun so that it only runs when prices or amounts change.
template . autorun ( function ( ) {
let total = 0 ;
// Depend on the array of measure templates.
template . measureTemplatesDependancy . depend ( ) ;
for ( let measureTemplate of template . measureTemplates ) { //Iterate over the child templates that display measure data (price & amount).
total += measureTemplate . amount . get ( ) * measureTemplate . price . get ( ) ;
}
template . total . set ( total ) ;
} ) ;
} ) ;
Template . SalesSheetFormProduct . onDestroyed ( function ( ) {
this . parentTemplate ( 1 ) . productTemplates . remove ( this ) ;
} ) ;
Template . SalesSheetFormProduct . events ( {
} ) ;
Template . SalesSheetFormProduct . helpers ( {
measures : function ( ) {
return this . measureIds ;
} ,
total : function ( ) {
let total = Template . instance ( ) . total . get ( ) ;
return ( total ? total : 0 ) . toFixed ( 2 ) ;
} ,
odd : function ( ) {
return Template . instance ( ) . parent . oddProductIds . get ( Template . currentData ( ) . productId ) ;
}
} ) ;
// ***** A measure in the product in the sales sheet. *****
// Passed a measureId as the data.
Template . SalesSheetFormProductMeasure . onCreated ( function ( ) {
let parent = this . parentTemplate ( 1 ) ;
let template = this ;
//Save this template as a child of the parent in a way the parent can used to keep us up to date.
parent . measureTemplates . push ( this ) ;
parent . measureTemplatesDependancy . changed ( ) ;
this . measure = new ReactiveVar ( ) ;
this . amount = new ReactiveVar ( 0 ) ;
this . price = new ReactiveVar ( 0 ) ;
this . autoSetPrice = 0 ;
this . autorun ( function ( ) {
template . measure . set ( Meteor . collections . Measures . findOne ( Template . currentData ( ) ) ) ;
} ) ;
this . resetPrice = function ( overrideUserData ) { // overrideUserData: Optional - used to crush any user defined pricing.
let date = parent . parent . selectedDate . get ( ) ;
let prices = parent . product . get ( ) . prices ;
// Change the prices based on the price data.
// Ensure there is price data for the product.
if ( prices ) {
let measureId = template . measure . get ( ) . _id ;
if ( prices [ measureId ] && prices [ measureId ] . price ) { //Ensure the product has a default price for this measureId.
let price ;
//Determine whether we should use the current or previous price.
if ( prices [ measureId ] . effectiveDate && date && moment ( prices [ measureId ] . effectiveDate ) . isAfter ( date ) )
price = prices [ measureId ] . previousPrice ;
else
price = prices [ measureId ] . price ;
//Change the auto set price for the product. This will be changed if either the prices for the product change, or if the date of the sale changes the price (current vs previous pricing in the product).
if ( overrideUserData || template . autoSetPrice != price ) {
// Change the displayed price in the measure if the previous auto set price is the same as the currently displayed price.
// This prevents the auto pricing from over writing a user defined price.
if ( overrideUserData || template . price . get ( ) == template . autoSetPrice ) {
template . price . set ( price ) ;
}
// Save the auto set price so we know in the future if it has really changed. For example changing the date won't necessarily change this value.
template . autoSetPrice = price ;
}
}
else if ( overrideUserData ) {
template . price . set ( 0 ) ;
}
}
else if ( overrideUserData ) {
template . price . set ( 0 ) ;
}
} ;
this . reset = function ( ) {
template . resetPrice ( true ) ;
template . amount . set ( 0 ) ;
} ;
} ) ;
Template . SalesSheetFormProductMeasure . onDestroyed ( function ( ) {
let parent = this . parentTemplate ( 1 ) ;
parent . measureTemplates . remove ( this ) ;
parent . measureTemplatesDependancy . changed ( ) ;
} ) ;
Template . SalesSheetFormProductMeasure . events ( {
'change input[name="price"]' : function ( event , template ) {
let $input = $ ( event . target ) ;
let value = parseFloat ( $input . val ( ) ) ;
if ( isNaN ( value ) ) {
value = 0 ;
}
//Save the price in the price map by measureId.
template . price . set ( value ) ;
} ,
'change input[name="amount"]' : function ( event , template ) {
let $input = $ ( event . target ) ;
let value = parseFloat ( $input . val ( ) ) ;
if ( isNaN ( value ) ) {
value = 0 ;
}
//Save the amount in the amount map by measureId.
template . amount . set ( value ) ;
} ,
'focus input[name="price"]' : function ( event , template ) {
$ ( event . target ) . select ( ) ;
} ,
'focus input[name="amount"]' : function ( event , template ) {
$ ( event . target ) . select ( ) ;
}
} ) ;
Template . SalesSheetFormProductMeasure . helpers ( {
measureName : function ( ) {
return Template . instance ( ) . measure . get ( ) . name ;
} ,
measurePrice : function ( ) {
return Template . instance ( ) . price . get ( ) . toFixed ( 2 ) ;
} ,
measureAmount : function ( ) {
return Template . instance ( ) . amount . get ( ) . toFixed ( 2 ) ;
}
} ) ;