From 210517a5c200a60bc4e799920855985c9a9acf5c Mon Sep 17 00:00:00 2001 From: Wynne Crisman Date: Fri, 26 May 2017 11:17:32 -0700 Subject: [PATCH] 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. --- .idea/PetitTetonMeteor.iml | 1 + .../meteor_packages_auto_import_browser.xml | 386 +- .../meteor_packages_auto_import_npm.xml | 14574 ++++++++-------- .../markdown-navigator/profiles_settings.xml | 2 +- .idea/workspace.xml | 916 +- .meteor/.finished-upgraders | 1 + .meteor/packages | 24 +- .meteor/release | 2 +- .meteor/versions | 72 +- client/main.styl | 2 + imports/api/Logs.js | 44 + imports/api/Product.js | 9 +- imports/api/Sale.js | 252 +- imports/api/index.js | 3 +- imports/startup/client/routes.js | 15 + imports/ui/Measures.html | 69 +- imports/ui/Measures.import.styl | 163 +- imports/ui/MiscManagement.html | 27 + imports/ui/MiscManagement.import.styl | 35 + imports/ui/MiscManagement.js | 76 + imports/ui/Pricing.html | 99 +- imports/ui/Pricing.import.styl | 62 +- imports/ui/Pricing.js | 12 +- imports/ui/ProductTags.html | 32 +- imports/ui/ProductTags.import.styl | 54 +- imports/ui/Products.html | 73 +- imports/ui/Products.import.styl | 171 +- imports/ui/SaleDuplicates.html | 61 + imports/ui/SaleDuplicates.import.styl | 149 + imports/ui/SaleDuplicates.js | 212 + imports/ui/Sales.html | 35 +- imports/ui/Sales.import.styl | 23 +- imports/ui/Sales.js | 164 +- imports/ui/Venues.html | 69 +- imports/ui/Venues.import.styl | 163 +- imports/ui/layouts/Body.html | 5 + package.json | 1 + server/cleanDates.js | 57 + server/exportMissing.js | 48 + server/handleDuplicateSales.js | 24 + server/importMissing.js | 5469 ++++++ server/server.js | 2 + 42 files changed, 15153 insertions(+), 8505 deletions(-) create mode 100644 imports/api/Logs.js create mode 100644 imports/ui/MiscManagement.html create mode 100644 imports/ui/MiscManagement.import.styl create mode 100644 imports/ui/MiscManagement.js create mode 100644 imports/ui/SaleDuplicates.html create mode 100644 imports/ui/SaleDuplicates.import.styl create mode 100644 imports/ui/SaleDuplicates.js create mode 100644 server/cleanDates.js create mode 100644 server/exportMissing.js create mode 100644 server/handleDuplicateSales.js create mode 100644 server/importMissing.js diff --git a/.idea/PetitTetonMeteor.iml b/.idea/PetitTetonMeteor.iml index e85550c..743d822 100644 --- a/.idea/PetitTetonMeteor.iml +++ b/.idea/PetitTetonMeteor.iml @@ -10,6 +10,7 @@ + \ No newline at end of file diff --git a/.idea/libraries/meteor_packages_auto_import_browser.xml b/.idea/libraries/meteor_packages_auto_import_browser.xml index ee521af..c887b4e 100644 --- a/.idea/libraries/meteor_packages_auto_import_browser.xml +++ b/.idea/libraries/meteor_packages_auto_import_browser.xml @@ -7,10 +7,17 @@ + + + + + + + @@ -31,73 +38,67 @@ - - - - - - - - - - - - - - - - - - + - + - + - + - - + + + + + + + + + + + + + @@ -110,42 +111,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -161,13 +186,14 @@ + + - @@ -175,21 +201,8 @@ - - - - - - - - - - - - - @@ -200,10 +213,7 @@ - - - @@ -215,18 +225,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -243,14 +290,8 @@ - - - - - - @@ -266,38 +307,30 @@ + + + + - - - - - - - - - - - + + + + - - - - - - - + + + - + - + + - - @@ -309,23 +342,15 @@ - - - - - - - + - - - + @@ -333,23 +358,12 @@ - - - - - - - - - - - @@ -400,7 +414,6 @@ - @@ -409,10 +422,17 @@ + + + + + + + @@ -433,73 +453,67 @@ - - - - - - - - - - - - - - - - - - + - + - + - + - - + + + + + + + + + + + + + @@ -512,42 +526,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -563,13 +601,14 @@ + + - @@ -577,21 +616,8 @@ - - - - - - - - - - - - - @@ -602,10 +628,7 @@ - - - @@ -617,18 +640,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -645,14 +705,8 @@ - - - - - - @@ -668,38 +722,30 @@ + + + + - - - - - - - - - - - + + + + - - - - - - - + + + - + - + + - - @@ -711,23 +757,15 @@ - - - - - - - + - - - + @@ -735,23 +773,12 @@ - - - - - - - - - - - @@ -802,7 +829,6 @@ - diff --git a/.idea/libraries/meteor_packages_auto_import_npm.xml b/.idea/libraries/meteor_packages_auto_import_npm.xml index ca9e561..745aaf2 100644 --- a/.idea/libraries/meteor_packages_auto_import_npm.xml +++ b/.idea/libraries/meteor_packages_auto_import_npm.xml @@ -2,3471 +2,3085 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - - - - - + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - + + - + - + - + - + - - + + - + - - - - + + + + - - - + + + - - - - - - - - - - - + + + + + + + + + + + - - - - - - + + + + + + - + - + - - + + - - - + + + - - - + + + - - - - - - - - - + + + + + + + + + - + - + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - - - - - - - - - - - - + + + + + + + + + + + + - + - - + + - - - + + + - - - - + + + + - - - - - - + + + + + + - - - + + + - - - - + + + + - - - - - - - - - - + + + + + + + + + + - + - - - - - - - - - - - - - + + + + + + + + + + + + + - + - + - + - - - - + + + + - - + + - - - - - - - + + + + + + + - - - - + + + + - - - - + + + + - + - - + + - - + + - - - - + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - + - - - + - - - - - - - + - - - - - - - - + - + - + - - - - - - - - - - + + - - + - + - - - + + - - - + + + + + - - - - + + - - + + - - - + + - - - - + + + + - - - - - + + + + + - - - - - - - - + + - - - - + - - - - - - - - - + + + + - - - - - + + + + + - + - + - + - + - + - - - - - - - - + + + + - - - - - - + + - - - - + - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - + + + + + - - - - - + + - - - - - - - + + - - - - - - - - - - + + - - - - - - - + + + + - - - - - - - - - - - - - - + - - - - + + + - + - - - - - - + + + + + + + - - - - - - - - - - + + + + + - - - - - + + - - - - - - - - - - + + - - - - - - - - - + - - - - - + - - - - - - + - + - - - - - - - - - - - + + - - - - - - - - - - + - - + - - - - - - - - + - - + - - - - - - - - - + + - - - - - - - - - - + + + - - - - - - - - - + + - - - - - + - - - - + - - - - - - - + - - + - - - - - - - - - - - - - + + - - - - - - - - - - - - - - + + - - - - - - - + - - - - - - - - - - - + + - - - - - - - - - - + - - + - - - - - - - - - - + - - - - - - + - - - - - - - - + + - - - - - - - + - - - - - + + - - - - - - - + + - - - - - - - - - - - - + + - - - - - - - - - - - + + - - - - - - - - - - - - - + + - - - - - - - + - - + - - - - - - - - - - - - + + + - - - - - - - - - - - - + + + - - - - - + - - - - - + - - - - - - - - + - + - - - - - - - - - - - - - + + - - - - - - - - - + - - + - - - - - - - - - - + + - - - - - - - + - - - - - - - - - - - - - - - - + + - + - - - - + - - - - - - - - - - - - - - - - - + + - - - - - - - - - + + + - - - + + + - - - - + - - - - + - - - - - - - - + - + - - - - - - - - - - - - + + - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - + + - + + - + - + - - - - - + + - + - - - - + + - - - - - - - - - - - + - - - + + - - - - + + + - - - - - + + + + + + - - - - - + - - - + + - + + - - - - - - - - - - - - - - - - - - - - - - + - - - - - - + - - - - + + + - - - + + + + + + - - - - - + + + + + + + - + - - - + + + - - - + + - - - + + + + - - - + + + + - - - - - - - - + + - - - + + - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - + + - - - - - + + + - - - - - - - - - - - - - + + - - - - - + + + - - - + - - - - - - + + + - - - - - + - - - - - - - - - - - - - - + - - - - - - - - - - - + + + - - - - + + + - - - + - - - - - - - - - + - - + - - - + - - - - - + + - - - - - - - - - - - - - - + - - - - - - + + + - + - - - - - - - - + - - - - + + + - - - - + + + + - - - - + + + + + - - - + + + + - - - + - - - + - - + - - - - + - - - + - - - - - - - - - - - - - + + - - - - - - + - - - - + - - - - - - - - - - - + + + - - - - - + - - - + + + + + + - + - + + + + + - - + + - + - - + - - + + - - - - - - + - + - - - + - - - - + + + - - + - - + - - - + + - - - - - + - - - - - + + - + - - - - - + + - - + - + - - + + + - + - - + - - - + - - + + - - - + + + - - + + + + + + + @@ -3479,6 +3093,7 @@ + @@ -3496,6 +3111,8 @@ + + @@ -3528,6 +3145,7 @@ + @@ -3535,14 +3153,18 @@ + + + + @@ -3556,20 +3178,28 @@ + + + + + + + + @@ -3580,19 +3210,24 @@ + + + + + @@ -3603,6 +3238,7 @@ + @@ -3615,7 +3251,9 @@ + + @@ -3631,30 +3269,38 @@ + + + + + + + + @@ -3667,10 +3313,13 @@ + + + @@ -3680,13 +3329,18 @@ + + + + + @@ -3694,29 +3348,43 @@ + + + + + + + + + + + + + + @@ -3725,6 +3393,7 @@ + @@ -3732,6 +3401,7 @@ + @@ -3740,8 +3410,11 @@ + + + @@ -3749,6 +3422,7 @@ + @@ -3776,13 +3450,18 @@ + + + + + @@ -3794,9 +3473,11 @@ + + @@ -3827,27 +3508,36 @@ + + + + + + + + + @@ -3856,8 +3546,10 @@ + + @@ -3873,32 +3565,49 @@ + + + + + + + + + + + + + + + + + @@ -3906,24 +3615,39 @@ + + + + + + + + + + + + + + + @@ -3932,12 +3656,16 @@ + + + + @@ -3980,43 +3708,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4026,33 +3785,54 @@ + + + + + + + + + + + + + + + + + + + + + @@ -4068,7 +3848,9 @@ + + @@ -4077,6 +3859,7 @@ + @@ -4087,6 +3870,7 @@ + @@ -4116,45 +3900,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -4166,11 +3974,14 @@ + + + @@ -4178,36 +3989,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4218,26 +4055,42 @@ + + + + + + + + + + + + + + + + @@ -4247,57 +4100,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4306,14 +4194,20 @@ + + + + + + @@ -4321,22 +4215,30 @@ + + + + + + + + @@ -4344,7 +4246,9 @@ + + @@ -4388,12 +4292,14 @@ + + @@ -4401,20 +4307,27 @@ + + + + + + + @@ -4424,7 +4337,9 @@ + + @@ -4436,6 +4351,7 @@ + @@ -4445,21 +4361,35 @@ + + + + + + + + + + + + + + @@ -4469,42 +4399,62 @@ + + + + + + + + + + + + + + + + + + + + @@ -4513,9 +4463,11 @@ + + @@ -4611,13 +4563,11 @@ - - @@ -4633,6 +4583,7 @@ + @@ -4647,39 +4598,58 @@ + + + + + + + + + + + + + + + + + + + @@ -4687,30 +4657,40 @@ + + + + + + + + + + @@ -4720,6 +4700,7 @@ + @@ -4730,6 +4711,7 @@ + @@ -4738,26 +4720,42 @@ + + + + + + + + + + + + + + + + @@ -4765,8 +4763,10 @@ + + @@ -4777,9 +4777,11 @@ + + @@ -4796,8 +4798,10 @@ + + @@ -4806,8 +4810,10 @@ + + @@ -4815,198 +4821,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -5020,140 +4958,55 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -5182,3857 +5035,3655 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - - - - - + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - + + - + - + - + - + - - + + - + - - - - + + + + - - - + + + - - - - - - - - - - - + + + + + + + + + + + - - - - - - + + + + + + - + - + - - + + - - - + + + - - - + + + - - - - - - - - - + + + + + + + + + - + - + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - - - - - - - - - - - - + + + + + + + + + + + + - + - - + + - - - + + + - - - - + + + + - - - - - - + + + + + + - - - + + + - - - - + + + + - - - - - - - - - - + + + + + + + + + + - + - - - - - - - - - - - - - + + + + + + + + + + + + + - + - + - + - - - - + + + + - - + + - - - - - - - + + + + + + + - - - - + + + + - - - - + + + + - + - - + + - - + + - - - - + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - + - - - + - - - - - - - + - - - - - - - - + - + - + - - - - - - - - - - + + - - + - + - - - + + - - - + + + + + - - - - + + - - + + - - - + + - - - - + + + + - - - - - + + + + + - - - - - - - - + + - - - - + - - - - - - - - - + + + + - - - - - + + + + + - + - + - + - + - + - - - - - - - - + + + + - - - - - - + + - - - - + - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - + + + + + - - - - - + + - - - - - - - + + - - - - - - - - - - + + - - - - - - - + + + + - - - - - - - - - - - - - - + - - - - + + + - + - - - - - - + + + + + + + - - - - - - - - - - + + + + + - - - - - + + - - - - - - - - - - + + - - - - - - - - - + - - - - - + - - - - - - + - + - - - - - - - - - - - + + - - - - - - - - - - + - - + - - - - - - - - + - - + - - - - - - - - - + + - - - - - - - - - - + + + - - - - - - - - - + + - - - - - + - - - - + - - - - - - - + - - + - - - - - - - - - - - - - + + - - - - - - - - - - - - - - + + - - - - - - - + - - - - - - - - - - - + + - - - - - - - - - - + - - + - - - - - - - - - - + - - - - - - + - - - - - - - - + + - - - - - - - + - - - - - + + - - - - - - - + + - - - - - - - - - - - - + + - - - - - - - - - - - + + - - - - - - - - - - - - - + + - - - - - - - + - - + - - - - - - - - - - - - + + + - - - - - - - - - - - - + + + - - - - - + - - - - - + - - - - - - - - + - + - - - - - - - - - - - - - + + - - - - - - - - - + - - + - - - - - - - - - - + + - - - - - - - + - - - - - - - - - - - - - - - - + + - + - - - - + - - - - - - - - - - - - - - - - - + + - - - - - - - - - + + + - - - + + + - - - - + - - - - + - - - - - - - - + - + - - - - - - - - - - - - + + - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - + + - + + - + - + - - - - - + + - + - - - - + + - - - - - - - - - - - + - - - + + - - - - + + + - - - - - + + + + + + - - - - - + - - - + + - + + - - - - - - - - - - - - - - - - - - - - - - + - - - - - - + - - - - + + + - - - + + + + + + - - - - - + + + + + + + - + - - - + + + - - - + + - - - + + + + - - - + + + + - - - - - - - - + + - - - + + - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - + + - - - - - + + + - - - - - - - - - - - - - + + - - - - - + + + - - - + - - - - - - + + + - - - - - + - - - - - - - - - - - - - - + - - - - - - - - - - - + + + - - - - + + + - - - + - - - - - - - - - + - - + - - - + - - - - - + + - - - - - - - - - - - - - - + - - - - - - + + + - + - - - - - - - - + - - - - + + + - - - - + + + + - - - - + + + + + - - - + + + + - - - + - - - + - - + - - - - + - - - + - - - - - - - - - - - - - + + - - - - - - + - - - - + - - - - - - - - - - - + + + - - - - - + - - - + + + + + + - + - + + + + + - - + + - + - - + - - + + - - - - - - + - + - - - + - - - - + + + - - + - - + - - - + + - - - - - + - - - - - + + - + - - - - - + + - - + - + - - + + + - + - - + - - - + - - + + - - - + + + - - + + + + + + + @@ -9045,6 +8696,7 @@ + @@ -9062,6 +8714,8 @@ + + @@ -9094,6 +8748,7 @@ + @@ -9101,14 +8756,18 @@ + + + + @@ -9122,20 +8781,28 @@ + + + + + + + + @@ -9146,19 +8813,24 @@ + + + + + @@ -9169,6 +8841,7 @@ + @@ -9181,7 +8854,9 @@ + + @@ -9197,30 +8872,38 @@ + + + + + + + + @@ -9233,10 +8916,13 @@ + + + @@ -9246,13 +8932,18 @@ + + + + + @@ -9260,29 +8951,43 @@ + + + + + + + + + + + + + + @@ -9291,6 +8996,7 @@ + @@ -9298,6 +9004,7 @@ + @@ -9306,8 +9013,11 @@ + + + @@ -9315,6 +9025,7 @@ + @@ -9342,13 +9053,18 @@ + + + + + @@ -9360,9 +9076,11 @@ + + @@ -9393,27 +9111,36 @@ + + + + + + + + + @@ -9422,8 +9149,10 @@ + + @@ -9439,32 +9168,49 @@ + + + + + + + + + + + + + + + + + @@ -9472,24 +9218,39 @@ + + + + + + + + + + + + + + + @@ -9498,12 +9259,16 @@ + + + + @@ -9546,43 +9311,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -9592,33 +9388,54 @@ + + + + + + + + + + + + + + + + + + + + + @@ -9634,7 +9451,9 @@ + + @@ -9643,6 +9462,7 @@ + @@ -9653,6 +9473,7 @@ + @@ -9682,45 +9503,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -9732,11 +9577,14 @@ + + + @@ -9744,36 +9592,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -9784,26 +9658,42 @@ + + + + + + + + + + + + + + + + @@ -9813,57 +9703,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -9872,14 +9797,20 @@ + + + + + + @@ -9887,22 +9818,30 @@ + + + + + + + + @@ -9910,7 +9849,9 @@ + + @@ -9954,12 +9895,14 @@ + + @@ -9967,20 +9910,27 @@ + + + + + + + @@ -9990,7 +9940,9 @@ + + @@ -10002,6 +9954,7 @@ + @@ -10011,21 +9964,35 @@ + + + + + + + + + + + + + + @@ -10035,42 +10002,62 @@ + + + + + + + + + + + + + + + + + + + + @@ -10079,9 +10066,11 @@ + + @@ -10177,13 +10166,11 @@ - - @@ -10199,6 +10186,7 @@ + @@ -10213,39 +10201,58 @@ + + + + + + + + + + + + + + + + + + + @@ -10253,30 +10260,40 @@ + + + + + + + + + + @@ -10286,6 +10303,7 @@ + @@ -10296,6 +10314,7 @@ + @@ -10304,26 +10323,42 @@ + + + + + + + + + + + + + + + + @@ -10331,8 +10366,10 @@ + + @@ -10343,9 +10380,11 @@ + + @@ -10362,8 +10401,10 @@ + + @@ -10372,8 +10413,10 @@ + + @@ -10381,198 +10424,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -10586,140 +10561,55 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -10748,389 +10638,573 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.idea/markdown-navigator/profiles_settings.xml b/.idea/markdown-navigator/profiles_settings.xml index 674a591..57927c5 100644 --- a/.idea/markdown-navigator/profiles_settings.xml +++ b/.idea/markdown-navigator/profiles_settings.xml @@ -1,3 +1,3 @@ - + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 2acada6..885440e 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,30 +2,37 @@ + + + - - + - - + + + + + + + + + + + - - - - - + @@ -48,105 +55,111 @@ - - + + - - - - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + @@ -163,24 +176,6 @@ - tabContent - SalesSheetFormProduct - ReactiveDict - Number - console.log - salesSheet - .name - _. - productsDepe - odd - text-shad - sheetHeader - salesSheetProducts - salesSheetEditorControls - salesSheetFormProducts - salesSheetFormMeasures - resetAmounts - resetPrices resetAmount oddProductIds currentData @@ -193,6 +188,24 @@ text- text-sha transition + find + moment + searchInput + this + PREFIX + Pork, Bacon + date + formatDate + .find( + sales[i] + YYYY + SaleSearch + searchQuery + toggleShowHidden + showHidden + aggre + SalesTotals + salesDuplicates firstRow @@ -200,7 +213,6 @@ insertSale subcategory Sale - sale Subcategory bags: selectize @@ -222,6 +234,7 @@ salesSheetFormElements measureTemplates autoSetPrice + sale @@ -230,44 +243,9 @@ @@ -302,10 +315,10 @@ - @@ -354,6 +367,7 @@ + @@ -398,28 +412,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -571,7 +493,6 @@ - @@ -581,27 +502,39 @@ - + - - - - - - - + - + + + + + + + + + + @@ -759,11 +692,15 @@ + + + + - @@ -775,20 +712,19 @@ - - + - + - + - - + + - + - + @@ -817,420 +753,382 @@ - - - - - - - + - - - - - - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + - - - + + - + - - - - - + + - + - - - + + - + - - - + + - - - + - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + - - + + + + + + + + + - + - - - + + - + - - - + + - + - - - + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - + - - + + - + - - + + - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.meteor/.finished-upgraders b/.meteor/.finished-upgraders index aa60704..ce276f3 100644 --- a/.meteor/.finished-upgraders +++ b/.meteor/.finished-upgraders @@ -13,3 +13,4 @@ notices-for-facebook-graph-api-2 1.3.0-split-minifiers-package 1.4.0-remove-old-dev-bundle-link 1.4.1-add-shell-server-package +1.4.3-split-account-service-packages diff --git a/.meteor/packages b/.meteor/packages index c2a08fb..632b802 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -6,24 +6,24 @@ meteor-base@1.0.4 # Packages every Meteor app needs to have mobile-experience@1.0.4 # Packages for a great mobile UX -mongo@1.1.14 # The database Meteor supports right now +mongo@1.1.17 # The database Meteor supports right now blaze-html-templates@1.0.4 # Compile .html files into Meteor Blaze views reactive-var@1.0.11 # Reactive variable for tracker reactive-dict@1.1.8 # ??? jquery@1.11.10 # Helpful client-side library -tracker@1.1.1 # Meteor's client-side reactive programming library +tracker@1.1.3 # Meteor's client-side reactive programming library tomwasd:history-polyfill # Adds IE 8/9 support for HTML5 history. -email@1.1.18 # Adds the Meteor/Email package for sending lost password emails +email@1.2.1 # Adds the Meteor/Email package for sending lost password emails -standard-minifier-css@1.3.2 # CSS minifier run for production mode -standard-minifier-js@1.2.1 # JS minifier run for production mode +standard-minifier-css@1.3.4 # CSS minifier run for production mode +standard-minifier-js@2.0.0 # JS minifier run for production mode es5-shim@4.6.15 # ECMAScript 5 compatibility for older browsers. poorvavyas:es6-shim -ecmascript@0.6.1 # Enable ECMAScript2015+ syntax in app code +ecmascript@0.7.3 # Enable ECMAScript2015+ syntax in app code #accounts-ui #accounts-base -accounts-password@1.3.3 +accounts-password@1.3.6 useraccounts:core useraccounts:bootstrap useraccounts:flow-routing # Configures email flows. Used for AccountsTemplates class. @@ -34,12 +34,12 @@ arillo:flow-router-helpers # Provides various template helpers such as {{pathFo #tomwasd:flow-router-seo kadira:blaze-layout -shell-server@0.2.1 # ??? +shell-server@0.2.3 # ??? meteortoys:allthings -stylus@2.513.8 +stylus@2.513.9 session@1.1.7 ##browser-policy # Adds support for specifying browser level security rules related to content and what's allowed to laod on the page. -check@1.2.4 # Allows for checking the structure and types of arguments passed to Meteor methods and publications. +check@1.2.5 # Allows for checking the structure and types of arguments passed to Meteor methods and publications. #audit-argument-checks # Used in combination with the Check package for checking the structure and types of arguments passed to Meteor methods and publications. Automatically alerts when a method or publication does not use a check() call. aldeed:simple-schema@1.5.3 @@ -59,4 +59,6 @@ momentjs:moment mizzao:bootboxjs # ??? aldeed:template-extension juliancwirko:s-alert # Client error/alert handling -jcbernack:reactive-aggregate +jcbernack:reactive-aggregate # Allows us to create a new client collection (from the server) with the contents being an aggregate of server data. Note that aggregation can only be done on the server currently as mini-mongo does not support it. +ostrio:logger +ostrio:loggermongo diff --git a/.meteor/release b/.meteor/release index cb1ecb2..fb6f3bc 100644 --- a/.meteor/release +++ b/.meteor/release @@ -1 +1 @@ -METEOR@1.4.2.7 +METEOR@1.4.4.2 diff --git a/.meteor/versions b/.meteor/versions index 7e2cad9..9a2af42 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -1,5 +1,5 @@ -accounts-base@1.2.14 -accounts-password@1.3.3 +accounts-base@1.2.17 +accounts-password@1.3.6 alanning:roles@1.2.15 aldeed:collection2@2.10.0 aldeed:collection2-core@1.2.0 @@ -9,31 +9,31 @@ aldeed:simple-schema@1.5.3 aldeed:template-extension@4.0.0 allow-deny@1.0.5 arillo:flow-router-helpers@0.5.2 -autoupdate@1.2.11 -babel-compiler@6.13.0 +autoupdate@1.3.12 +babel-compiler@6.18.2 babel-runtime@1.0.1 base64@1.0.10 binary-heap@1.0.10 -blaze@2.1.9 +blaze@2.3.2 blaze-html-templates@1.0.5 blaze-tools@1.0.10 boilerplate-generator@1.0.11 caching-compiler@1.1.9 caching-html-compiler@1.0.7 callback-hook@1.0.10 -check@1.2.4 +check@1.2.5 coffeescript@1.0.17 ddp@1.2.5 -ddp-client@1.2.9 +ddp-client@1.3.4 ddp-common@1.2.8 -ddp-rate-limiter@1.0.6 -ddp-server@1.2.10 +ddp-rate-limiter@1.0.7 +ddp-server@1.3.14 deps@1.0.12 diff-sequence@1.0.7 -ecmascript@0.6.1 +ecmascript@0.7.3 ecmascript-runtime@0.3.15 ejson@1.0.13 -email@1.1.18 +email@1.2.1 es5-shim@4.6.15 fastclick@1.0.13 fortawesome:fontawesome@4.7.0 @@ -41,19 +41,19 @@ geojson-utils@1.0.10 hot-code-push@1.0.4 html-tools@1.0.11 htmljs@1.0.11 -http@1.1.8 +http@1.2.12 id-map@1.0.9 jcbernack:reactive-aggregate@0.7.0 jquery@1.11.10 juliancwirko:s-alert@3.2.0 kadira:blaze-layout@2.3.0 kadira:flow-router@2.12.1 -launch-screen@1.0.12 +launch-screen@1.1.1 livedata@1.0.18 localstorage@1.0.12 -logging@1.1.16 +logging@1.1.17 mdg:validation-error@0.2.0 -meteor@1.6.0 +meteor@1.6.1 meteor-base@1.0.4 meteorhacks:aggregate@1.3.0 meteorhacks:collection-utils@1.2.0 @@ -72,29 +72,31 @@ meteortoys:status@3.0.0 meteortoys:sub@3.0.0 meteortoys:throttle@3.0.0 meteortoys:toykit@3.0.4 -minifier-css@1.2.15 -minifier-js@1.2.15 -minimongo@1.0.19 +minifier-css@1.2.16 +minifier-js@2.0.0 +minimongo@1.0.23 mizzao:bootboxjs@4.4.0 mobile-experience@1.0.4 -mobile-status-bar@1.0.13 -modules@0.7.7 -modules-runtime@0.7.7 -momentjs:moment@2.17.1 -mongo@1.1.14 +mobile-status-bar@1.0.14 +modules@0.8.2 +modules-runtime@0.7.10 +momentjs:moment@2.18.1 +mongo@1.1.17 mongo-id@1.0.6 mongo-livedata@1.0.12 msavin:jetsetter@2.0.0 msavin:mongol@2.0.1 npm-bcrypt@0.9.2 -npm-mongo@2.2.11_2 -observe-sequence@1.0.14 +npm-mongo@2.2.24 +observe-sequence@1.0.16 ordered-dict@1.0.9 +ostrio:logger@1.1.2 +ostrio:loggermongo@1.1.3 poorvavyas:es6-shim@0.21.1 promise@0.8.8 raix:eventemitter@0.1.3 random@1.0.10 -rate-limit@1.0.6 +rate-limit@1.0.8 reactive-dict@1.1.8 reactive-var@1.0.11 reload@1.1.11 @@ -103,26 +105,26 @@ routepolicy@1.0.12 service-configuration@1.0.11 session@1.1.7 sha@1.0.9 -shell-server@0.2.1 +shell-server@0.2.3 softwarerero:accounts-t9n@1.3.6 spacebars@1.0.13 -spacebars-compiler@1.0.13 +spacebars-compiler@1.1.1 srp@1.0.10 -standard-minifier-css@1.3.2 -standard-minifier-js@1.2.1 -stylus@2.513.8 +standard-minifier-css@1.3.4 +standard-minifier-js@2.0.0 +stylus@2.513.9 templating@1.2.15 templating-compiler@1.2.15 templating-runtime@1.2.15 -templating-tools@1.0.5 +templating-tools@1.1.1 tomwasd:history-polyfill@0.0.1 -tracker@1.1.1 +tracker@1.1.3 ui@1.0.12 underscore@1.0.10 -url@1.0.11 +url@1.1.0 useraccounts:bootstrap@1.14.2 useraccounts:core@1.14.2 useraccounts:flow-routing@1.14.2 -webapp@1.3.12 +webapp@1.3.15 webapp-hashing@1.0.9 zimme:active-route@2.3.2 diff --git a/client/main.styl b/client/main.styl index 7222f10..7ee401c 100644 --- a/client/main.styl +++ b/client/main.styl @@ -168,11 +168,13 @@ body @import "../imports/ui/layouts/Full.import.styl" @import "../imports/ui/UserManagement.import.styl" +@import "../imports/ui/MiscManagement.import.styl" @import "../imports/ui/Measures.import.styl" @import "../imports/ui/Venues.import.styl" @import "../imports/ui/Products.import.styl" @import "../imports/ui/ProductTags.import.styl" @import "../imports/ui/Sales.import.styl" +@import "../imports/ui/SaleDuplicates.import.styl" @import "../imports/ui/SalesSheets.import.styl" @import "../imports/ui/SalesSheetForm.import.styl" @import "../imports/ui/SalesSheetEditor.import.styl" diff --git a/imports/api/Logs.js b/imports/api/Logs.js new file mode 100644 index 0000000..3cb318d --- /dev/null +++ b/imports/api/Logs.js @@ -0,0 +1,44 @@ +import { Meteor } from 'meteor/meteor'; +import { Mongo } from 'meteor/mongo'; + +// The logging tool is primarily for managing administrative functions such that administrators can view the app logs and issue commands that might generate administrative logging. + +Meteor.log = new Logger(); +Logs = new Mongo.Collection('Logs'); + +let logMongo = new LoggerMongo(Meteor.log, { + collection: Logs +}); +logMongo.enable({ + enable: true, + client: false, /* Client calls are not executed on the client. */ + server: true /* Calls from the client will be executed on the server. */ +}); + +if(Meteor.isServer) { + Logs._ensureIndex({'date': 1}, {expireAfterSeconds: 86400}); + Meteor.publish('logs', function() { + return Logs.find({}, {limit: 10000}); + }); + Meteor.methods({ + clearLogs: function() { + return Logs.remove({}, function(err) { + if(err) Meteor.log.error(err); + }); + } + }); +} + +Logs.allow({ + insert: () => false, + update: () => false, + remove: () => false +}); + +Logs.deny({ + insert: () => true, + update: () => true, + remove: () => true +}); + +export default Logs; \ No newline at end of file diff --git a/imports/api/Product.js b/imports/api/Product.js index 6d86544..de0367a 100644 --- a/imports/api/Product.js +++ b/imports/api/Product.js @@ -3,6 +3,13 @@ import { Mongo } from 'meteor/mongo'; import { check } from 'meteor/check'; import {SimpleSchema} from 'meteor/aldeed:simple-schema'; +/** + * Notes: + * The Product object has a prices field which is an object whose fields names are Measure ID's. Each field value (for each Measure ID) is an object that has a 'price', 'effectiveDate', and 'previousPrice'. + * The effectiveDate field 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. + * Because the structure of the Product object is so complicated, the normal checking that is done by the framework cannot be used. + */ + Products = new Mongo.Collection('Products'); const ProductsSchema = new SimpleSchema({ @@ -237,7 +244,7 @@ if(Meteor.isServer) { check(measureId, String); check(price, Number); if(setPrevious) check(setPrevious, Boolean); - if(effectiveDate) check(effectiveDate, Date); + if(effectiveDate) check(effectiveDate, Number); // TODO: Check that the format is YYYYMMDD if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) { let products = Products.find({_id: {$in: productIds}}, {fields: {prices: 1}}).fetch(); diff --git a/imports/api/Sale.js b/imports/api/Sale.js index 8da59ca..1d5a288 100644 --- a/imports/api/Sale.js +++ b/imports/api/Sale.js @@ -3,10 +3,15 @@ import { Mongo } from 'meteor/mongo'; import { check } from 'meteor/check'; import {SimpleSchema} from 'meteor/aldeed:simple-schema'; +/** + * 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. + */ + Sales = new Mongo.Collection('Sales'); let SalesSchema = new SimpleSchema({ date: { - type: Date, + type: Number, // A number in the format of YYYYMMDD to allow for searching using greater and less than, and to prevent timezones from messing everything up. label: "Date", optional: false, index: 1 @@ -58,6 +63,19 @@ let SalesSchema = new SimpleSchema({ trim: false, optional: true }, + ignoreDuplicates: { + type: Boolean, + optional: true + }, + isDuplicateOf: { + type: String, + trim: false, + optional: true + }, + duplicateCount: { + type: Number, + optional: true + }, createdAt: { type: Date, label: "Created On", @@ -71,14 +89,22 @@ if(Meteor.isServer) { let dbQuery = []; if(query) { - // _.each(_.keys(query), function(key) { - // if(_.isObject(query[key])) dbQuery[key] = query[key]; - // else if(_.isNumber(query[key])) dbQuery[key] = query[key]; - // else dbQuery[key] = {$regex: RegExp.escape(query[key]), $options: 'i'}; - // }); - _.each(_.keys(query), function(key) { - if(_.isObject(query[key])) dbQuery.push({[key]: query[key]}); + //if(_.isObject(query[key])) dbQuery.push({[key]: query[key]}); + if(_.isObject(query[key])) { + if(query[key].type === 'dateRange') { + if(query[key].start && query[key].end) + dbQuery.push({[key]: {$gte: query[key].start, $lte: query[key].end}}); + else if(query[key].start) + dbQuery.push({[key]: {$gte: query[key].start}}); + else if(query[key].end) + dbQuery.push({[key]: {$lte: query[key].end}}); + // Do nothing if a start and/or end are not provided. + } + else { + dbQuery.push({[key]: query[key]}); + } + } else if(_.isNumber(query[key])) dbQuery.push({[key]: query[key]}); else { let searchValue = query[key]; @@ -97,6 +123,65 @@ if(Meteor.isServer) { dbQuery = dbQuery.length > 0 ? {$and: dbQuery} : {}; return Meteor.collections.Sales.find(dbQuery, {limit: limit, sort, skip: skipCount}); }); + Meteor.publish('duplicateSales', function(query, includeIgnoredDuplicates) { + // Start with the duplicate count needing to be greater than zero, and with duplicates marked as ignored not included. + let dbQuery = [{duplicateCount: {$gt: 0}}]; + + // If we should include ignored duplicates than add it to the query as a requirement. + if(!includeIgnoredDuplicates) { + dbQuery.push({$or: [{ignoreDuplicates: {$exists: false}}, {ignoreDuplicates: false}]}); + } + + //if(query) { + // // Add each query requirement sent by the client. + // _.each(_.keys(query), function(key) { + // //if(_.isObject(query[key])) dbQuery.push({[key]: query[key]}); + // if(_.isObject(query[key])) { + // if(query[key].type === 'dateRange') { + // if(query[key].start && query[key].end) + // dbQuery.push({[key]: {$gte: query[key].start, $lte: query[key].end}}); + // else if(query[key].start) + // dbQuery.push({[key]: {$gte: query[key].start}}); + // else if(query[key].end) + // dbQuery.push({[key]: {$lte: query[key].end}}); + // // Do nothing if a start and/or end are not provided. + // } + // else { + // dbQuery.push({[key]: query[key]}); + // } + // } + // else if(_.isNumber(query[key])) dbQuery.push({[key]: query[key]}); + // else { + // let searchValue = query[key]; + // let searches = searchValue && searchValue.length > 0 ? searchValue.split(/\s+/) : undefined; + // + // for(let search of searches) { + // dbQuery.push({[key]: {$regex: '\\b' + search, $options: 'i'}}); + // } + // } + // }); + //} + + // Wrap the array of requirements with an $and, or remove the single requirement from the array (if there is only a single requirement). + if(dbQuery.length === 1) dbQuery = dbQuery[0]; + else dbQuery = {$and: dbQuery}; + + // Find all Sale objects marked as having at least one duplicate. + //return Meteor.collections.Sales.find(dbQuery); + + let pipeline = [ + {$match: dbQuery}, + {$lookup: {from: "Products", localField: "productId", foreignField: "_id", as: "product"}}, + {$lookup: {from: "Measures", localField: "measureId", foreignField: "_id", as: "measure"}}, + {$lookup: {from: "Venues", localField: "venueId", foreignField: "_id", as: "venue"}}, + {$unwind: "$product"}, + {$unwind: "$measure"}, + {$unwind: "$venue"}, + {$project: {_id: 1, date: 1, amount: 1, price: 1, venueId: 1, productId: 1, measureId: 1, duplicateCount: 1, ignoreDuplicates: 1, 'productName': '$product.name', 'measureName': '$measure.name', 'venueName': '$venue.name'}} + ]; + + ReactiveAggregate(this, Sales, pipeline, {clientCollection: 'duplicateSales'}); + }); // time: expects either undefined, 'weekly', or 'monthly' // options: expects either undefined, 'markets', or 'types' Meteor.publish('salesTotals', function(time, options) { @@ -185,7 +270,7 @@ if(Meteor.isServer) { }, insertSale: function(sale) { check(sale, { - date: Date, + date: Number, // TODO: Check that the format is YYYYMMDD amount: Match.Where(function(x) { check(x, Number); return x > 0; @@ -199,7 +284,7 @@ if(Meteor.isServer) { venueId: String, comment: Match.Optional(String) }); - //TODO: Check the structure of sale. Use: check(sale, {name: String, ...}); + sale.createdAt = new Date(); if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) { @@ -242,7 +327,7 @@ if(Meteor.isServer) { }, updateSale: function(id, date, venueId, price, amount) { check(id, String); - check(date, Date); + check(date, Number); // TODO: Check that the format is YYYYMMDD check(venueId, String); check(price, Number); check(amount, Number); @@ -252,7 +337,152 @@ if(Meteor.isServer) { if(err) console.log(err); }, {bypassCollection2: true}); } + else throw new Meteor.Error(403, "Not authorized."); + }, + countSales: function() { + if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) { + return Sales.find({}).count(); + } + else throw new Meteor.Error(403, "Not authorized."); + }, + removeDuplicateSales: function(id, justOne) { // Expects the id of the sale that has duplicates and an optional boolean flag (justOne) indicating whether just one duplicate should be removed, or all of them (default). + if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) { + // Remove either one or all of the duplicates of the Sale with the given ID. + if(justOne) { + let sale = Sales.findOne({isDuplicateOf: id}); + + if(sale) { + Sales.remove({_id: sale._id}); + } + } + else { + Sales.remove({isDuplicateOf: id}); + } + } + }, + ignoreDuplicateSales: function(id) { // Expects the id of the sale that has duplicates. Will mark this sale and all duplicates to be ignored in future duplicate checks. + if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) { + // Mark to ignore duplicates for this Sale (id) and all its duplicates, and clear any duplicate counts and references. + //Sales.update({$or: [{_id: id}, {isDuplicateOf: id}]}, {$set: {ignoreDuplicates: true}, $unset: {isDuplicateOf: "", duplicateCount: ""}}); + + // Mark to ignore duplicates for this Sale (id). We will leave the duplicate count and references so that the duplicates will show in a query if we want to revisit those marked as ignored. + Sales.update({$or: [{_id: id}, {isDuplicateOf: id}]}, {$set: {ignoreDuplicates: true}}); + } + }, + markDuplicateSales: function() { + if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) { + let sales = Sales.find({}, {sort: {date: 1, venueId: 1, productId: 1, price: 1, amount: 1, measureId: 1, createdAt: 1}}).fetch(); + + // Iterate over all the sales looking for sales that have duplicates. + // Since the sales are sorted by sale date, venueId, productId, price, amount, and measureId which all must be identical to be considered a possible duplicate sale, we only have to check subsequent sales until a non-duplicate is found. + for(let i = 0; i < sales.length;) { + let sale = sales[i]; + + // If this is marked as a duplicate of another sale, but we got to this point in the loop then the sale it is a duplicate of must have been removed or marked to ignore duplicates. + if(sale.isDuplicateOf) { + delete sale.isDuplicateOf; + Sales.update(sale._id, {$unset: {isDuplicateOf: ""}}, function(err, id) { + if(err) console.log(err); + }, {bypassCollection2: true}); + } + + //Skip this one if it is marked to ignore duplicates. + //if(sale.ignoreDuplicates) { + // i++; + //} + //else { + let keepChecking = true; + let duplicateCount = 0; + + // Keep checking subsequent sales until a non-duplicate is found. Ignore anything marked to ignore duplicates. Count the number of duplicates and also mark duplicates to reference the Sale we are currently checking. + while(keepChecking) { + let checkSale = sales[++i]; // Increment the index to the next Sale object. + + // Since it is possible to exceed the length of the array, we will check for an undefined next sale and set the flag to stop checking if one is found. + if(checkSale && sale.productId === checkSale.productId && sale.venueId === checkSale.venueId && sale.price === checkSale.price && sale.amount === checkSale.amount && sale.measureId === checkSale.measureId) { + // Mark the next sale as a duplicate of the currently examined sale. + checkSale.isDuplicateOf = sale._id; + Sales.update(checkSale._id, {$set: {isDuplicateOf: checkSale.isDuplicateOf}}, function(err, id) { + if(err) console.log(err); + }, {bypassCollection2: true}); + duplicateCount++; + } + else { + // Stop checking. + keepChecking = false; + } + } + + // Make sure the currently checked sale has a proper duplicate count before moving on in the search. + if(duplicateCount > 0) { + if(sale.duplicateCount !== duplicateCount) { + // Update the sale's duplicate count. + sale.duplicateCount = duplicateCount; + Sales.update(sale._id, {$set: {duplicateCount: sale.duplicateCount}}, function(err, id) { + if(err) console.log(err); + }, {bypassCollection2: true}); + } + } + else if(sale.duplicateCount) { + // Remove the duplicate count if it is set. + delete sale.duplicateCount; + Sales.update(sale._id, {$unset: {duplicateCount: ""}}, function(err, id) { + if(err) console.log(err); + }, {bypassCollection2: true}); + } + //} + } + } + else throw new Meteor.Error(403, "Not authorized."); } + /* + countDuplicateSales: function() { + if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_MANAGE])) { + let sales = Sales.find({}, {sort: {date: 1, venueId: 1, productId: 1, price: 1, amount: 1, measureId: 1}}).fetch(); + let salesByDate = {}; + let lastDate = undefined; + let lastDateCollection; + let duplicates = []; + + //Create a map of Sale arrays by sale date. + for(let i = 0; i < sales.length; i++) { + let date = sales[i].date; + + if(date) { + if(date === lastDate) { + lastDateCollection.push(sales[i]); + } + else { + lastDate = date; + salesByDate[date] = lastDateCollection = [sales[i]]; + } + } + else { + Meteor.log.error("Found a sale without a date!!!"); + } + } + + for(let date in salesByDate) { + let sales = salesByDate[date]; + + for(let i = 0; i < sales.length - 1; i++) { + if(sales[i].productId === sales[i+1].productId && sales[i].venueId === sales[i+1].venueId && sales[i].price === sales[i+1].price && sales[i].amount === sales[i+1].amount && sales[i].measureId === sales[i+1].amount) { + duplicates.push([sales[i], sales[i+1]]); + } + } + } + + return duplicates; + } + else throw new Meteor.Error(403, "Not authorized."); + }, + deleteDuplicateSales: function() { + if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_MANAGE])) { + + } + else throw new Meteor.Error(403, "Not authorized."); + } + */ }); } diff --git a/imports/api/index.js b/imports/api/index.js index ac79af3..0821f1e 100644 --- a/imports/api/index.js +++ b/imports/api/index.js @@ -4,11 +4,12 @@ import Products from "./Product.js"; import ProductTags from "./ProductTag.js"; import Sales from "./Sale.js"; import SalesSheets from "./SalesSheet.js"; +import Logs from "./Logs.js"; import Users from "./User.js"; import UserRoles from "./Roles.js"; //Save the collections in the Meteor.collections property for easy access without name conflicts. -Meteor.collections = {Measures, Venues, Products, ProductTags, Sales, SalesSheets, Users, UserRoles}; +Meteor.collections = {Measures, Venues, Products, ProductTags, Sales, SalesSheets, Logs, Users, UserRoles}; //If this is the server then setup the default admin user if none exist. if(Meteor.isServer) { diff --git a/imports/startup/client/routes.js b/imports/startup/client/routes.js index a4faffa..b16eefe 100644 --- a/imports/startup/client/routes.js +++ b/imports/startup/client/routes.js @@ -36,6 +36,13 @@ pri.route('/userManagement', { BlazeLayout.render('Body', {content: 'UserManagement'}); } }); +pri.route('/miscManagement', { + name: 'MiscManagement', + action: function(params, queryParams) { + require("/imports/ui/MiscManagement.js"); + BlazeLayout.render('Body', {content: 'MiscManagement'}); + } +}); pri.route('/sales', { name: 'Sales', action: function(params, queryParams) { @@ -44,6 +51,14 @@ pri.route('/sales', { BlazeLayout.render('Body', {content: 'Sales'}); } }); +pri.route('/saleDuplicates', { + name: 'SaleDuplicates', + action: function(params, queryParams) { + require("/imports/ui/SaleDuplicates.js"); + + BlazeLayout.render('Body', {content: 'SaleDuplicates'}); + } +}); pri.route('/salesSheets', { name: 'SalesSheets', action: function(params, queryParams) { diff --git a/imports/ui/Measures.html b/imports/ui/Measures.html index 50dcf35..86beb8f 100644 --- a/imports/ui/Measures.html +++ b/imports/ui/Measures.html @@ -1,37 +1,44 @@ diff --git a/imports/ui/Measures.import.styl b/imports/ui/Measures.import.styl index bdfd12d..a7d03a9 100644 --- a/imports/ui/Measures.import.styl +++ b/imports/ui/Measures.import.styl @@ -1,6 +1,9 @@ #measures - margin: 20px 20px + display: table + content-box: border-box + padding: 10px 20px height: 100% + width: 100% text-align: left .tableControls @@ -18,77 +21,89 @@ top: -4px display: inline-block - .tableContainer - width: 100% - margin-bottom: 20px - border: 0 - font-size: 12.5px - - table - table-layout: fixed + .listRow + display: table-row + .listCell + display: table-cell + position: relative + height: 100% width: 100% - .measureSearch - margin: 3px 0 2px 1px - .measureEditorTd - background: #deeac0 - input[name="name"], input[name="postfix"] - width: 100% - .editorDiv - margin: 4px 0 - label - font-family: "Arial Black", "Arial Bold", Gadget, sans-serif - font-size: .9em - padding-bottom: 4px - select2 - font-size: .4em - > thead - > tr - > th.name - width: auto - > th.postfix - width: auto - > th.actions - width: 90px - text-align: center - .newMeasureButton - margin-top: 4px - padding: 0px 12px - .fa-plus-circle - display: inline-block - .fa-times-circle - display: none - .newMeasureButton.active - background-color: #fb557b - color: black - .fa-times-circle - display: inline-block - .fa-plus-circle - display: none - > tbody - > tr - .actionRemove - color: #F77 - .actionEdit - color: #44F - .editorApply - color: green - .editorCancel - color: red - > tr.deactivated - background-color: #fac0d1 - .actionActivate - color: #158b18 - .actionHide - color: #6a0707 - .actionEdit - color: #0101e4 - > tr.deactivated:hover - background-color: #ffcadb - > tr.hidden - background-color: #e995ff - .actionEdit - color: #0101e4 - .actionShow - color: #027905 - > tr.hidden:hover - background-color: #ffb5ff \ No newline at end of file + .tableContainer + position: absolute + top: 0 + bottom: 0 + left: 0 + right: 0 + width: auto + height: auto + border: 0 + font-size: 12.5px + overflow-y: auto + table + table-layout: fixed + width: 100% + .measureSearch + margin: 3px 0 2px 1px + .measureEditorTd + background: #deeac0 + input[name="name"], input[name="postfix"] + width: 100% + .editorDiv + margin: 4px 0 + label + font-family: "Arial Black", "Arial Bold", Gadget, sans-serif + font-size: .9em + padding-bottom: 4px + select2 + font-size: .4em + > thead + > tr + > th.name + width: auto + > th.postfix + width: auto + > th.actions + width: 90px + text-align: center + .newMeasureButton + margin-top: 4px + padding: 0px 12px + .fa-plus-circle + display: inline-block + .fa-times-circle + display: none + .newMeasureButton.active + background-color: #fb557b + color: black + .fa-times-circle + display: inline-block + .fa-plus-circle + display: none + > tbody + > tr + .actionRemove + color: #F77 + .actionEdit + color: #44F + .editorApply + color: green + .editorCancel + color: red + > tr.deactivated + background-color: #fac0d1 + .actionActivate + color: #158b18 + .actionHide + color: #6a0707 + .actionEdit + color: #0101e4 + > tr.deactivated:hover + background-color: #ffcadb + > tr.hidden + background-color: #e995ff + .actionEdit + color: #0101e4 + .actionShow + color: #027905 + > tr.hidden:hover + background-color: #ffb5ff \ No newline at end of file diff --git a/imports/ui/MiscManagement.html b/imports/ui/MiscManagement.html new file mode 100644 index 0000000..4e29438 --- /dev/null +++ b/imports/ui/MiscManagement.html @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/imports/ui/MiscManagement.import.styl b/imports/ui/MiscManagement.import.styl new file mode 100644 index 0000000..b168de7 --- /dev/null +++ b/imports/ui/MiscManagement.import.styl @@ -0,0 +1,35 @@ +#miscManagement + display: table + content-box: border-box + padding: 10px 20px + height: 100% + width: 100% + text-align: left + + .controls + text-align: right + margin-right: 20px + + .pageContentRow + display: table-row + .pageContentCell + display: table-cell + position: relative + height: 100% + width: 100% + .pageContentContainer + position: absolute + top: 0 + bottom: 0 + left: 0 + right: 0 + width: auto + height: auto + border: 0 + font-size: 12.5px + overflow-y: auto + .logs + list-style-type: none + height: 100% + :hover + background: #CCC \ No newline at end of file diff --git a/imports/ui/MiscManagement.js b/imports/ui/MiscManagement.js new file mode 100644 index 0000000..e32b03e --- /dev/null +++ b/imports/ui/MiscManagement.js @@ -0,0 +1,76 @@ + +import './MiscManagement.html'; +import '/imports/util/selectize/selectize.js' + +let PREFIX = "MiscManagement"; + +Meteor.subscribe("logs"); +Meteor.subscribe("products"); +Meteor.subscribe("venues"); +Meteor.subscribe("measures"); + +Template.MiscManagement.helpers({ + logs: function() { + return Meteor.collections.Logs.find({}, {sort: {date: 1}}); + }, + logCount: function() { + return Meteor.collections.Logs.find({}).count(); + } +}); +Template.MiscManagement.events({ + "click .cleanDates": function(event, template) { + Meteor.call("cleanDates"); + }, + "click .importMissingSalesData": function(event, template) { + console.log("Calling importMissingSales"); + Meteor.call("importMissingSales"); + }, + "click .clearLogs": function(event, template) { + Meteor.call("clearLogs"); + }, + "click .countDuplicateSales": function(event, template) { + Meteor.log.info("Starting to count duplicates..."); + + let products = Meteor.collections.Products.find({}).fetch(); + let venues = Meteor.collections.Venues.find({}).fetch(); + let measures = Meteor.collections.Measures.find({}).fetch(); + let productNameMap = {}; + let venueNameMap = {}; + let measureNameMap = {}; + + for(let i = 0; i < products.length; i++) { + productNameMap[products[i]._id] = products[i].name; + } + for(let i = 0; i < venues.length; i++) { + venueNameMap[venues[i]._id] = venues[i].name; + } + for(let i = 0; i < measures.length; i++) { + measureNameMap[measures[i]._id] = measures[i].name; + } + + Meteor.call("countSales", function(err, result) { + if(err) Meteor.log.error(err); + else { + let salesCount = result; + + Meteor.call("countDuplicateSales", function(err, result) { + if(err) Meteor.log.error(err); + else { + Meteor.log.info("Duplicate Sales Counted: " + result.length + " out of " + salesCount + " total sales."); + + for(let i = 0; i < result.length; i++) { + let sale = result[i][0]; + Meteor.log.info("\tdate: " + sale.date + " product: " + productNameMap[sale.productId] + " venue: " + venueNameMap[sale.venueId] + " measure" + measureNameMap[sale.measureId] + " price: " + sale.price.toFixed(2) + " amount: " + sale.amount + " id: " + sale._id); + sale = result[i][1]; + Meteor.log.info("\tdate: " + sale.date + " product: " + productNameMap[sale.productId] + " venue: " + venueNameMap[sale.venueId] + " measure" + measureNameMap[sale.measureId] + " price: " + sale.price.toFixed(2) + " amount: " + sale.amount + " id: " + sale._id); + Meteor.log.info(" -- "); + } + } + }); + } + }); + }, + "click .deleteDuplicateSales": function(event, template) { + Meteor.call("deleteDuplicateSales"); + } +}); diff --git a/imports/ui/Pricing.html b/imports/ui/Pricing.html index f5c5741..f71822a 100644 --- a/imports/ui/Pricing.html +++ b/imports/ui/Pricing.html @@ -1,54 +1,61 @@ diff --git a/imports/ui/Pricing.import.styl b/imports/ui/Pricing.import.styl index c3d0b0a..d5e8573 100644 --- a/imports/ui/Pricing.import.styl +++ b/imports/ui/Pricing.import.styl @@ -1,6 +1,9 @@ #pricing - margin: 20px 20px + display: table + content-box: border-box + padding: 10px 20px height: 100% + width: 100% text-align: left .controls @@ -60,26 +63,39 @@ .resetButton margin-left: 20px - .tableContainer - width: 100% - margin-bottom: 20px - border: 0 - font-size: 12.5px - table - table-layout: fixed + .listRow + display: table-row + .listCell + display: table-cell + position: relative + height: 100% width: 100% - > thead - > tr - > th.name - width: auto - > th.current - width: 200px - > th.previous - width: 200px - > th.changeDate - width: 200px - > tbody - > tr.deactivated - background-color: #fac0d1 - > tr.deactivated:hover - background-color: #ffcadb \ No newline at end of file + .tableContainer + position: absolute + top: 0 + bottom: 0 + left: 0 + right: 0 + width: auto + height: auto + border: 0 + font-size: 12.5px + overflow-y: auto + table + table-layout: fixed + width: 100% + > thead + > tr + > th.name + width: auto + > th.current + width: 200px + > th.previous + width: 200px + > th.changeDate + width: 200px + > tbody + > tr.deactivated + background-color: #fac0d1 + > tr.deactivated:hover + background-color: #ffcadb \ No newline at end of file diff --git a/imports/ui/Pricing.js b/imports/ui/Pricing.js index 0bce1bb..b15eff0 100644 --- a/imports/ui/Pricing.js +++ b/imports/ui/Pricing.js @@ -1,6 +1,13 @@ import './Pricing.html'; +/** + * Notes: + * The Product object has a prices field which is an object whose fields names are Measure ID's. Each field value (for each Measure ID) is an object that has a 'price', 'effectiveDate', and 'previousPrice'. + * The effectiveDate field 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. + * Because the structure of the Product object is so complicated, the normal checking that is done by the framework cannot be used. + */ + let QUERY_LIMIT = 20; let PREFIX = "Pricing."; @@ -70,7 +77,7 @@ Template.Pricing.events({ Meteor.call("clearProductPrice", productIds, measureId) } else { - date = moment(date ? date : new Date().toDateInputValue(), "YYYY-MM-DD").toDate(); + date = ~~(moment(date ? date : new Date().toDateInputValue(), "YYYY-MM-DD").format("YYYYMMDD")); // The ~~ is a bitwise not which converts the string into a number in the format of YYYYMMDD for storage in the database; to avoid timezone issues. setPrevious = setPrevious == true || setPrevious == 'on' || setPrevious == "true" || setPrevious == "yes"; if(setPrevious == true && !date) { @@ -117,9 +124,8 @@ Template.PricingForProduct.helpers({ }, priceChangeDate: function() { let measureId = Session.get(PREFIX + "selectedMeasure"); - let date = this.prices && measureId && this.prices[measureId] && this.prices[measureId].effectiveDate ? this.prices[measureId].effectiveDate : undefined; - return date ? moment(date).format("MM/DD/YYYY (w)") : "-"; + return this.prices && measureId && this.prices[measureId] && this.prices[measureId].effectiveDate ? moment(this.prices[measureId].effectiveDate.toString(), "YYYYMMDD").format("MM/DD/YYYY (w)") : "-"; }, rowClass: function() { return this.deactivated ? "deactivated" : ""; diff --git a/imports/ui/ProductTags.html b/imports/ui/ProductTags.html index 83bb191..ecbbf39 100644 --- a/imports/ui/ProductTags.html +++ b/imports/ui/ProductTags.html @@ -26,20 +26,24 @@ -
- - - - - - - - - {{#each products}} - {{> ProductTag_Product}} - {{/each}} - -
Name {{>ProductTag_ProductSearch columnName='name'}}Tags {{>ProductTag_ProductSearch columnName='tags' collectionQueryColumnName='name' collection='ProductTags' collectionResultColumnName='_id'}}
+
+
+
+ + + + + + + + + {{#each products}} + {{> ProductTag_Product}} + {{/each}} + +
Name {{>ProductTag_ProductSearch columnName='name'}}Tags {{>ProductTag_ProductSearch columnName='tags' collectionQueryColumnName='name' collection='ProductTags' collectionResultColumnName='_id'}}
+
+
{{else}} {{/if}} diff --git a/imports/ui/ProductTags.import.styl b/imports/ui/ProductTags.import.styl index 52362ca..b368761 100644 --- a/imports/ui/ProductTags.import.styl +++ b/imports/ui/ProductTags.import.styl @@ -1,6 +1,9 @@ #productTags - margin: 20px 20px + display: table + content-box: border-box + padding: 10px 20px height: 100% + width: 100% text-align: left .tagInfo @@ -107,25 +110,38 @@ display: table-cell width: 240px vertical-align: bottom; - .tableContainer - width: 100% - margin-bottom: 20px - border: 0 - font-size: 12.5px - table - table-layout: fixed + .listRow + display: table-row + .listCell + display: table-cell + position: relative + height: 100% width: 100% - > thead - > tr - > th.name - width: auto - > th.tags - width: auto - > tbody - > tr.deactivated - background-color: #fac0d1 - > tr.deactivated:hover - background-color: #ffcadb + .tableContainer + position: absolute + top: 0 + bottom: 0 + left: 0 + right: 0 + width: auto + height: auto + border: 0 + font-size: 12.5px + overflow-y: auto + table + table-layout: fixed + width: 100% + > thead + > tr + > th.name + width: auto + > th.tags + width: auto + > tbody + > tr.deactivated + background-color: #fac0d1 + > tr.deactivated:hover + background-color: #ffcadb td.roles .role padding: 4px 4px diff --git a/imports/ui/Products.html b/imports/ui/Products.html index 1df8d5a..859f854 100644 --- a/imports/ui/Products.html +++ b/imports/ui/Products.html @@ -1,39 +1,46 @@ diff --git a/imports/ui/Products.import.styl b/imports/ui/Products.import.styl index 08ee3f1..fe1fe1b 100644 --- a/imports/ui/Products.import.styl +++ b/imports/ui/Products.import.styl @@ -1,6 +1,9 @@ #products - margin: 20px 20px + display: table + content-box: border-box + padding: 10px 20px height: 100% + width: 100% text-align: left .tableControls @@ -18,81 +21,93 @@ top: -4px display: inline-block - .tableContainer - width: 100% - margin-bottom: 20px - border: 0 - font-size: 12.5px - - table - table-layout: fixed + .listRow + display: table-row + .listCell + display: table-cell + position: relative + height: 100% width: 100% - .productSearch - margin: 3px 0 2px 1px - .productEditorTd - background: #deeac0 - input[name="name"], .productTagsEditor, .productAliasesEditor, .productMeasuresEditor - width: 100% - .editorDiv - margin: 4px 0 - label - font-family: "Arial Black", "Arial Bold", Gadget, sans-serif - font-size: .9em - padding-bottom: 4px - select2 - font-size: .4em - > thead - > tr - > th.name - width: auto - > th.tags - width: 220px - > th.aliases - width: 220px - > th.measures - width: 220px - > th.actions - width: 90px - text-align: center - .newProductButton - margin-top: 4px - padding: 0px 12px - .fa-plus-circle - display: inline-block - .fa-times-circle - display: none - .newProductButton.active - background-color: #fb557b - color: black - .fa-times-circle - display: inline-block - .fa-plus-circle - display: none - > tbody - > tr - .actionRemove - color: #F77 - .actionEdit - color: #44F - .editorApply - color: green - .editorCancel - color: red - > tr.deactivated - background-color: #fac0d1 - .actionActivate - color: #158b18 - .actionHide - color: #6a0707 - .actionEdit - color: #0101e4 - > tr.deactivated:hover - background-color: #ffcadb - > tr.hidden - background-color: #e995ff - .actionEdit - color: #0101e4 - .actionShow - color: #027905 - > tr.hidden:hover - background-color: #ffb5ff \ No newline at end of file + .tableContainer + position: absolute + top: 0 + bottom: 0 + left: 0 + right: 0 + width: auto + height: auto + border: 0 + font-size: 12.5px + overflow-y: auto + table + table-layout: fixed + width: 100% + .productSearch + margin: 3px 0 2px 1px + .productEditorTd + background: #deeac0 + input[name="name"], .productTagsEditor, .productAliasesEditor, .productMeasuresEditor + width: 100% + .editorDiv + margin: 4px 0 + label + font-family: "Arial Black", "Arial Bold", Gadget, sans-serif + font-size: .9em + padding-bottom: 4px + select2 + font-size: .4em + > thead + > tr + > th.name + width: auto + > th.tags + width: 220px + > th.aliases + width: 220px + > th.measures + width: 220px + > th.actions + width: 90px + text-align: center + .newProductButton + margin-top: 4px + padding: 0px 12px + .fa-plus-circle + display: inline-block + .fa-times-circle + display: none + .newProductButton:active + background-color: #fb557b + color: black + .fa-times-circle + display: inline-block + .fa-plus-circle + display: none + > tbody + > tr + .actionRemove + color: #F77 + .actionEdit + color: #44F + .editorApply + color: green + .editorCancel + color: red + > tr.deactivated + background-color: #fac0d1 + .actionActivate + color: #158b18 + .actionHide + color: #6a0707 + .actionEdit + color: #0101e4 + > tr.deactivated:hover + background-color: #ffcadb + > tr.hidden + background-color: #e995ff + .actionEdit + color: #0101e4 + .actionShow + color: #027905 + > tr.hidden:hover + background-color: #ffb5ff \ No newline at end of file diff --git a/imports/ui/SaleDuplicates.html b/imports/ui/SaleDuplicates.html new file mode 100644 index 0000000..8f2a363 --- /dev/null +++ b/imports/ui/SaleDuplicates.html @@ -0,0 +1,61 @@ + + + + + \ No newline at end of file diff --git a/imports/ui/SaleDuplicates.import.styl b/imports/ui/SaleDuplicates.import.styl new file mode 100644 index 0000000..0cffebc --- /dev/null +++ b/imports/ui/SaleDuplicates.import.styl @@ -0,0 +1,149 @@ +#saleDuplicates + display: table + content-box: border-box + padding: 10px 20px + height: 100% + width: 100% + text-align: left + .controls + text-align: left + display: table + width: 100% + .pageControls + padding: 4px 8px + margin: 4px 8px + display: table-cell + width: 240px + .tableControls + text-align: right + padding: 4px 8px + margin: 4px 12px 4px 8px + display: table-cell + .toggleShowHidden + margin: 0 40px 0 0 + position: relative + top: -4px + display: inline-block + .listRow + display: table-row + .listCell + display: table-cell + position: relative + height: 100% + width: 100% + .tableContainer + position: absolute + top: 0 + bottom: 0 + left: 0 + right: 0 + width: auto + height: auto + //width: 100% + //margin-bottom: 20px + border: 0 + font-size: 12.5px + overflow-y: auto + //height: 100% + label + font-size: 10px + font-weight: 800 + table + table-layout: fixed + min-width: 100% + .saleRemove + color: red + margin-left: 8px + .saleEdit + color: darkblue + margin-right: 8px + .editorApply + color: green + .editorCancel + color: red + thead + > tr + > th.amount + width: 90px + > th.product + width: auto + min-width: 140px + > th.price + width: 140px + > th.measure + width: 100px + > th.saleDate + width: 140px + > th.createdDate + width: 100px + > th.venue + width: 160px + > th.actions + width: 90px + tbody + > tr + > td.actions + .ignoreDuplicatesButton + padding: 0 2px + color: green + .ignoreDuplicatesButton:hover + color: #00bb00 + .ignoreDuplicatesButton:active + color: black + .ignoreDuplicatesButton.hidden + visibility: hidden + .removeAllDuplicatesButton, .removeOneDuplicateButton + padding: 0 2px + color: #a00000 + .removeAllDuplicatesButton:hover, .removeOneDuplicateButton:hover + color: red + .removeAllDuplicatesButton:active, .removeOneDuplicateButton:active + color: black + > tr.hidden:nth-child(odd) + background-color: #f4f0ab + > tr.hidden:nth-child(even) + background-color: #fff6c0 + > tr.hidden:hover + background-color: #ded + + .editComment + color: grey + .hasComment + color: black + .actionEdit + margin-right: 6px + color: #44F + .saleEditor + .heading + font-size: 2em + font-family: verdana, arial, helvetica, sans-serif + text-transform: uppercase + font-weight: 800 + margin: 6px 0 14px 0 + .priceContainer + display: table + width: 100% + .price + display: table-cell + padding-right: 10px + .priceButtons + display: table-cell + width: 1.5em + .setDefaultPrice + font-size: 1.5em + padding: 6px 8px + margin-left: 8px + border-radius: 8px + .setDefaultPrice:hover + text-shadow: 0px 0px 6px #00b900 + .setDefaultPrice:active + text-shadow: 0px 0px 6px grey + .insertSaleForm + .form-group, label + text-align: left + .formGroupHeading + font-size: 1.6em + font-family: "Arial Black", "Arial Bold", Gadget, sans-serif + font-style: normal + font-variant: normal + font-weight: 500 \ No newline at end of file diff --git a/imports/ui/SaleDuplicates.js b/imports/ui/SaleDuplicates.js new file mode 100644 index 0000000..ebc4125 --- /dev/null +++ b/imports/ui/SaleDuplicates.js @@ -0,0 +1,212 @@ + +import './SaleDuplicates.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 PREFIX = "SaleDuplicates."; +let DuplicateSales = new Meteor.Collection("duplicateSales"); +let duplicateSalesSubscription; + +Template.SaleDuplicates.onCreated(function() { + let template = Template.instance(); + + //Tracker.autorun(function() { + // let query = _.clone(Session.get(PREFIX + 'searchQuery')); + // + // duplicateSalesSubscription = template.subscribe("duplicateSales", query, Session.get(PREFIX + "showHidden")); + //}); + + Tracker.autorun(function() { + duplicateSalesSubscription = template.subscribe("duplicateSales", null, Session.get(PREFIX + "showHidden")); + }); +}); +Template.SaleDuplicates.onDestroyed(function() { + if(duplicateSalesSubscription) { + duplicateSalesSubscription.stop(); + } +}); +Template.SaleDuplicates.helpers({ + sales: function() { + let dbQuery = []; + let query = _.clone(Session.get(PREFIX + 'searchQuery')); + + if(query) { + // Add each query requirement sent by the client. + _.each(_.keys(query), function(key) { + //if(_.isObject(query[key])) dbQuery.push({[key]: query[key]}); + if(_.isObject(query[key])) { + if(query[key].type === 'dateRange') { + if(query[key].start && query[key].end) + dbQuery.push({[key]: {$gte: query[key].start, $lte: query[key].end}}); + else if(query[key].start) + dbQuery.push({[key]: {$gte: query[key].start}}); + else if(query[key].end) + dbQuery.push({[key]: {$lte: query[key].end}}); + // Do nothing if a start and/or end are not provided. + } + else { + dbQuery.push({[key]: query[key]}); + } + } + else if(_.isNumber(query[key])) dbQuery.push({[key]: query[key]}); + else { + let searchValue = query[key]; + let searches = searchValue && searchValue.length > 0 ? searchValue.split(/\s+/) : undefined; + + for(let search of searches) { + dbQuery.push({[key]: {$regex: '\\b' + search, $options: 'i'}}); + } + } + }); + } + + if(dbQuery.length > 1) dbQuery = {$and: dbQuery}; + else if(dbQuery.length == 1) dbQuery = dbQuery[0]; + else dbQuery = {}; + + return DuplicateSales.find(dbQuery, {sort: {date: -1, productName: 1}}); + }, + showHidden: function() { + return Session.get(PREFIX + "showHidden") ? "checked": ""; + } +}); +Template.SaleDuplicates.events({ + 'click .duplicateScan': function(event, template) { + Meteor.call("markDuplicateSales", function(err, result) { + Meteor.log.error(err); + }); + }, + 'change input[name="showHidden"]': function(event, template) { + //console.log("changed " + $(event.target).prop('checked')); + Session.set(PREFIX + "showHidden", $(event.target).prop('checked')); + } +}); + +Template.SaleDuplicate.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.utc(date.toString(), "YYYYMMDD").utc().format("MM/DD/YYYY (w)"); + }, + 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}); + }, + formatTotalPrice: function(price, amount) { + return (price * amount).toLocaleString("en-US", {style: 'currency', currency: 'USD', minimumFractionDigits: 2}); + }, + showTotalPrice: function(amount) { + return amount > 1; + }, + duplicateClasses: function() { + return this.ignoreDuplicates ? "hidden" : ""; + } +}); +Template.SaleDuplicate.events({ + "click .ignoreDuplicatesButton": function(event, template) { + Meteor.call('ignoreDuplicateSales', this._id, function(err, result) { + if(err) sAlert.error(err); + //else sAlert.success("Duplicates Ignored"); + }); + }, + "click .removeAllDuplicatesButton": function(event, template) { + let _this = this; + swal({ + title: "Are you sure?", + text: "This will permanently remove ALL duplicate sales.", + type: "question", + showCancelButton: true, + confirmButtonColor: "#DD6B55", + confirmButtonText: "Yes" + }).then( + function(isConfirm) { + if(isConfirm) { + Meteor.call('removeDuplicateSales', _this._id, function(err, result) { + if(err) sAlert.error(err); + //else sAlert.success("Duplicates Removed"); + }); + } + }, + function(dismiss) { + } + ); + }, + "click .removeOneDuplicateButton": function(event, template) { + let _this = this; + swal({ + title: "Are you sure?", + text: "This will permanently remove ONE duplicate sale.", + type: "question", + showCancelButton: true, + confirmButtonColor: "#DD6B55", + confirmButtonText: "Yes" + }).then( + function(isConfirm) { + if(isConfirm) { + Meteor.call('removeDuplicateSales', _this._id, true, function(err, result) { + if(err) sAlert.error(err); + //else sAlert.success("Duplicates Removed"); + }); + } + }, + function(dismiss) { + } + ); + } +}); + +Template.SaleDuplicateSearch.helpers({ + searchValue: function() { + let searchFields = Session.get(PREFIX + 'searchFields'); + + return (searchFields && searchFields[this.columnName]) ? searchFields[this.columnName] : ''; + } +}); +Template.SaleDuplicateSearch.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. + }, 500) +}); \ No newline at end of file diff --git a/imports/ui/Sales.html b/imports/ui/Sales.html index 023c09e..cbb1012 100644 --- a/imports/ui/Sales.html +++ b/imports/ui/Sales.html @@ -1,18 +1,23 @@ + +