Added a reports page with a set of links to download CSV reports.
This commit is contained in:
@@ -15,3 +15,4 @@ notices-for-facebook-graph-api-2
|
|||||||
1.4.1-add-shell-server-package
|
1.4.1-add-shell-server-package
|
||||||
1.4.3-split-account-service-packages
|
1.4.3-split-account-service-packages
|
||||||
1.5-add-dynamic-import-package
|
1.5-add-dynamic-import-package
|
||||||
|
1.7-split-underscore-from-meteor-base
|
||||||
|
|||||||
@@ -4,25 +4,25 @@
|
|||||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||||
# but you can also edit it by hand.
|
# but you can also edit it by hand.
|
||||||
|
|
||||||
meteor-base@1.2.0 # Packages every Meteor app needs to have
|
meteor-base@1.4.0 # Packages every Meteor app needs to have
|
||||||
mobile-experience@1.0.5 # Packages for a great mobile UX
|
mobile-experience@1.0.5 # Packages for a great mobile UX
|
||||||
mongo@1.3.1 # The database Meteor supports right now
|
mongo@1.6.0 # The database Meteor supports right now
|
||||||
blaze-html-templates@1.0.4 # Compile .html files into Meteor Blaze views
|
blaze-html-templates@1.0.4 # Compile .html files into Meteor Blaze views
|
||||||
reactive-var@1.0.11 # Reactive variable for tracker
|
reactive-var@1.0.11 # Reactive variable for tracker
|
||||||
reactive-dict@1.2.0 # ???
|
reactive-dict@1.2.1 # ???
|
||||||
tracker@1.1.3 # Meteor's client-side reactive programming library
|
tracker@1.2.0 # Meteor's client-side reactive programming library
|
||||||
tomwasd:history-polyfill # Adds IE 8/9 support for HTML5 history.
|
tomwasd:history-polyfill # Adds IE 8/9 support for HTML5 history.
|
||||||
email@1.2.3 # Adds the Meteor/Email package for sending lost password emails
|
email@1.2.3 # Adds the Meteor/Email package for sending lost password emails
|
||||||
|
|
||||||
standard-minifier-css@1.3.5 # CSS minifier run for production mode
|
standard-minifier-css@1.5.2 # CSS minifier run for production mode
|
||||||
standard-minifier-js@2.2.0 # JS minifier run for production mode
|
standard-minifier-js@2.4.0 # JS minifier run for production mode
|
||||||
es5-shim@4.6.15 # ECMAScript 5 compatibility for older browsers.
|
es5-shim@4.8.0 # ECMAScript 5 compatibility for older browsers.
|
||||||
poorvavyas:es6-shim
|
poorvavyas:es6-shim
|
||||||
ecmascript@0.9.0 # Enable ECMAScript2015+ syntax in app code
|
ecmascript@0.12.3 # Enable ECMAScript2015+ syntax in app code
|
||||||
|
|
||||||
#accounts-ui
|
#accounts-ui
|
||||||
#accounts-base
|
#accounts-base
|
||||||
accounts-password@1.5.0
|
accounts-password@1.5.1
|
||||||
useraccounts:core
|
useraccounts:core
|
||||||
useraccounts:unstyled
|
useraccounts:unstyled
|
||||||
useraccounts:flow-routing # Configures email flows. Used for AccountsTemplates class.
|
useraccounts:flow-routing # Configures email flows. Used for AccountsTemplates class.
|
||||||
@@ -33,12 +33,12 @@ arillo:flow-router-helpers # Provides various template helpers such as {{pathFo
|
|||||||
#tomwasd:flow-router-seo
|
#tomwasd:flow-router-seo
|
||||||
kadira:blaze-layout
|
kadira:blaze-layout
|
||||||
|
|
||||||
shell-server@0.3.0 # ???
|
shell-server@0.4.0 # ???
|
||||||
meteortoys:allthings
|
meteortoys:allthings
|
||||||
stylus@2.513.13
|
stylus@2.513.13
|
||||||
session@1.1.7
|
session@1.2.0
|
||||||
##browser-policy # Adds support for specifying browser level security rules related to content and what's allowed to laod on the page.
|
##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.5 # Allows for checking the structure and types of arguments passed to Meteor methods and publications.
|
check@1.3.1 # 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.
|
#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
|
aldeed:simple-schema@1.5.3
|
||||||
@@ -61,6 +61,8 @@ juliancwirko:s-alert # Client error/alert handling
|
|||||||
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.
|
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:logger
|
||||||
ostrio:loggermongo
|
ostrio:loggermongo
|
||||||
dynamic-import@0.2.0
|
dynamic-import@0.5.0
|
||||||
markdown@1.0.12
|
markdown@1.0.12
|
||||||
wcrisman:jquery-custom-scrollbar
|
wcrisman:jquery-custom-scrollbar
|
||||||
|
underscore
|
||||||
|
meteorhacks:aggregate # Allows databaseName.aggragate(pipeline) calls the exact same way you would on the command line in the mongo tool.
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
METEOR@1.6.0.1
|
METEOR@1.8.0.1
|
||||||
|
|||||||
123
.meteor/versions
123
.meteor/versions
@@ -1,63 +1,65 @@
|
|||||||
accounts-base@1.4.0
|
accounts-base@1.4.3
|
||||||
accounts-password@1.5.0
|
accounts-password@1.5.1
|
||||||
alanning:roles@1.2.16
|
alanning:roles@1.2.16
|
||||||
aldeed:collection2@2.10.0
|
aldeed:collection2@2.10.0
|
||||||
aldeed:collection2-core@1.2.0
|
aldeed:collection2-core@1.2.0
|
||||||
aldeed:schema-deny@1.1.0
|
aldeed:schema-deny@1.1.0
|
||||||
aldeed:schema-index@1.1.1
|
aldeed:schema-index@1.1.1
|
||||||
aldeed:simple-schema@1.5.3
|
aldeed:simple-schema@1.5.4
|
||||||
aldeed:template-extension@4.1.0
|
aldeed:template-extension@4.1.0
|
||||||
allow-deny@1.1.0
|
allow-deny@1.1.0
|
||||||
arillo:flow-router-helpers@0.5.2
|
arillo:flow-router-helpers@0.5.2
|
||||||
autoupdate@1.3.12
|
autoupdate@1.5.0
|
||||||
babel-compiler@6.24.7
|
babel-compiler@7.2.4
|
||||||
babel-runtime@1.1.1
|
babel-runtime@1.3.0
|
||||||
base64@1.0.10
|
base64@1.0.11
|
||||||
binary-heap@1.0.10
|
binary-heap@1.0.11
|
||||||
blaze@2.3.2
|
blaze@2.3.3
|
||||||
blaze-html-templates@1.1.2
|
blaze-html-templates@1.1.2
|
||||||
blaze-tools@1.0.10
|
blaze-tools@1.0.10
|
||||||
boilerplate-generator@1.3.1
|
boilerplate-generator@1.6.0
|
||||||
caching-compiler@1.1.9
|
caching-compiler@1.2.1
|
||||||
caching-html-compiler@1.1.2
|
caching-html-compiler@1.1.3
|
||||||
callback-hook@1.0.10
|
callback-hook@1.1.0
|
||||||
check@1.2.5
|
check@1.3.1
|
||||||
coffeescript@1.0.17
|
coffeescript@1.0.17
|
||||||
ddp@1.4.0
|
ddp@1.4.0
|
||||||
ddp-client@2.2.0
|
ddp-client@2.3.3
|
||||||
ddp-common@1.3.0
|
ddp-common@1.4.0
|
||||||
ddp-rate-limiter@1.0.7
|
ddp-rate-limiter@1.0.7
|
||||||
ddp-server@2.1.1
|
ddp-server@2.2.0
|
||||||
deps@1.0.12
|
deps@1.0.12
|
||||||
diff-sequence@1.0.7
|
diff-sequence@1.1.1
|
||||||
dynamic-import@0.2.1
|
dynamic-import@0.5.1
|
||||||
ecmascript@0.9.0
|
ecmascript@0.12.4
|
||||||
ecmascript-runtime@0.5.0
|
ecmascript-runtime@0.7.0
|
||||||
ecmascript-runtime-client@0.5.0
|
ecmascript-runtime-client@0.8.0
|
||||||
ecmascript-runtime-server@0.5.0
|
ecmascript-runtime-server@0.7.1
|
||||||
ejson@1.1.0
|
ejson@1.1.0
|
||||||
email@1.2.3
|
email@1.2.3
|
||||||
es5-shim@4.6.15
|
es5-shim@4.8.0
|
||||||
|
fetch@0.1.0
|
||||||
fortawesome:fontawesome@4.7.0
|
fortawesome:fontawesome@4.7.0
|
||||||
geojson-utils@1.0.10
|
geojson-utils@1.0.10
|
||||||
hot-code-push@1.0.4
|
hot-code-push@1.0.4
|
||||||
html-tools@1.0.11
|
html-tools@1.0.11
|
||||||
htmljs@1.0.11
|
htmljs@1.0.11
|
||||||
http@1.3.0
|
http@1.4.2
|
||||||
id-map@1.0.9
|
id-map@1.1.0
|
||||||
|
inter-process-messaging@0.1.0
|
||||||
jcbernack:reactive-aggregate@0.7.0
|
jcbernack:reactive-aggregate@0.7.0
|
||||||
jquery@1.11.10
|
jquery@1.11.11
|
||||||
juliancwirko:s-alert@3.2.0
|
juliancwirko:s-alert@3.2.0
|
||||||
kadira:blaze-layout@2.3.0
|
kadira:blaze-layout@2.3.0
|
||||||
kadira:flow-router@2.12.1
|
kadira:flow-router@2.12.1
|
||||||
launch-screen@1.1.1
|
launch-screen@1.1.1
|
||||||
livedata@1.0.18
|
livedata@1.0.18
|
||||||
localstorage@1.2.0
|
localstorage@1.2.0
|
||||||
logging@1.1.19
|
logging@1.1.20
|
||||||
markdown@1.0.12
|
markdown@1.0.12
|
||||||
mdg:validation-error@0.2.0
|
mdg:validation-error@0.2.0
|
||||||
meteor@1.8.2
|
meteor@1.9.2
|
||||||
meteor-base@1.2.0
|
meteor-base@1.4.0
|
||||||
meteorhacks:aggregate@1.3.0
|
meteorhacks:aggregate@1.3.0
|
||||||
meteorhacks:collection-utils@1.2.0
|
meteorhacks:collection-utils@1.2.0
|
||||||
meteortoys:allthings@4.0.0
|
meteortoys:allthings@4.0.0
|
||||||
@@ -76,62 +78,65 @@ meteortoys:status@4.0.0
|
|||||||
meteortoys:sub@4.0.0
|
meteortoys:sub@4.0.0
|
||||||
meteortoys:throttle@4.0.0
|
meteortoys:throttle@4.0.0
|
||||||
meteortoys:toggle@4.0.0
|
meteortoys:toggle@4.0.0
|
||||||
meteortoys:toykit@4.0.1
|
meteortoys:toykit@4.0.2
|
||||||
minifier-css@1.2.16
|
minifier-css@1.4.1
|
||||||
minifier-js@2.2.2
|
minifier-js@2.4.0
|
||||||
minimongo@1.4.3
|
minimongo@1.4.5
|
||||||
mizzao:bootboxjs@4.4.0
|
mizzao:bootboxjs@4.4.0
|
||||||
mobile-experience@1.0.5
|
mobile-experience@1.0.5
|
||||||
mobile-status-bar@1.0.14
|
mobile-status-bar@1.0.14
|
||||||
modules@0.11.1
|
modern-browsers@0.1.3
|
||||||
modules-runtime@0.9.1
|
modules@0.13.0
|
||||||
momentjs:moment@2.20.0
|
modules-runtime@0.10.3
|
||||||
mongo@1.3.1
|
momentjs:moment@2.23.0
|
||||||
|
mongo@1.6.0
|
||||||
|
mongo-decimal@0.1.0
|
||||||
mongo-dev-server@1.1.0
|
mongo-dev-server@1.1.0
|
||||||
mongo-id@1.0.6
|
mongo-id@1.0.7
|
||||||
mongo-livedata@1.0.12
|
mongo-livedata@1.0.12
|
||||||
msavin:jetsetter@4.0.0
|
msavin:jetsetter@4.0.0
|
||||||
msavin:mongol@4.0.1
|
msavin:mongol@4.0.1
|
||||||
npm-bcrypt@0.9.3
|
npm-bcrypt@0.9.3
|
||||||
npm-mongo@2.2.33
|
npm-mongo@3.1.1
|
||||||
observe-sequence@1.0.16
|
observe-sequence@1.0.16
|
||||||
ordered-dict@1.0.9
|
ordered-dict@1.1.0
|
||||||
ostrio:logger@2.0.5
|
ostrio:logger@2.0.7
|
||||||
ostrio:loggermongo@2.0.3
|
ostrio:loggermongo@2.0.4
|
||||||
poorvavyas:es6-shim@0.21.1
|
poorvavyas:es6-shim@0.21.1
|
||||||
promise@0.10.0
|
promise@0.11.2
|
||||||
raix:eventemitter@0.1.3
|
raix:eventemitter@0.1.3
|
||||||
random@1.0.10
|
random@1.1.0
|
||||||
rate-limit@1.0.8
|
rate-limit@1.0.9
|
||||||
reactive-dict@1.2.0
|
reactive-dict@1.2.1
|
||||||
reactive-var@1.0.11
|
reactive-var@1.0.11
|
||||||
reload@1.1.11
|
reload@1.2.0
|
||||||
retry@1.0.9
|
retry@1.1.0
|
||||||
routepolicy@1.0.12
|
routepolicy@1.1.0
|
||||||
service-configuration@1.0.11
|
service-configuration@1.0.11
|
||||||
session@1.1.7
|
session@1.2.0
|
||||||
sha@1.0.9
|
sha@1.0.9
|
||||||
shell-server@0.3.1
|
shell-server@0.4.0
|
||||||
|
socket-stream-client@0.2.2
|
||||||
softwarerero:accounts-t9n@1.3.11
|
softwarerero:accounts-t9n@1.3.11
|
||||||
spacebars@1.0.15
|
spacebars@1.0.15
|
||||||
spacebars-compiler@1.1.3
|
spacebars-compiler@1.1.3
|
||||||
srp@1.0.10
|
srp@1.0.12
|
||||||
standard-minifier-css@1.3.5
|
standard-minifier-css@1.5.2
|
||||||
standard-minifier-js@2.2.3
|
standard-minifier-js@2.4.0
|
||||||
stylus@2.513.13
|
stylus@2.513.14
|
||||||
templating@1.3.2
|
templating@1.3.2
|
||||||
templating-compiler@1.3.3
|
templating-compiler@1.3.3
|
||||||
templating-runtime@1.3.2
|
templating-runtime@1.3.2
|
||||||
templating-tools@1.1.2
|
templating-tools@1.1.2
|
||||||
tomwasd:history-polyfill@0.0.1
|
tomwasd:history-polyfill@0.0.1
|
||||||
tracker@1.1.3
|
tracker@1.2.0
|
||||||
ui@1.0.13
|
ui@1.0.13
|
||||||
underscore@1.0.10
|
underscore@1.0.10
|
||||||
url@1.1.0
|
url@1.2.0
|
||||||
useraccounts:core@1.14.2
|
useraccounts:core@1.14.2
|
||||||
useraccounts:flow-routing@1.14.2
|
useraccounts:flow-routing@1.14.2
|
||||||
useraccounts:unstyled@1.14.2
|
useraccounts:unstyled@1.14.2
|
||||||
wcrisman:jquery-custom-scrollbar@3.0.0
|
wcrisman:jquery-custom-scrollbar@3.0.0
|
||||||
webapp@1.4.0
|
webapp@1.7.2
|
||||||
webapp-hashing@1.0.9
|
webapp-hashing@1.0.9
|
||||||
zimme:active-route@2.3.2
|
zimme:active-route@2.3.2
|
||||||
|
|||||||
@@ -113,6 +113,10 @@ NOTE: Use MongoBooster on a windows development machine to connect to the dev da
|
|||||||
4. Look at the debug output by viewing the html files stored in `/tmp`. Use Samba to view them remotely.
|
4. Look at the debug output by viewing the html files stored in `/tmp`. Use Samba to view them remotely.
|
||||||
5. Look at the Nginx logs (should be the same as the stuff in /tmp).
|
5. Look at the Nginx logs (should be the same as the stuff in /tmp).
|
||||||
|
|
||||||
|
# Updating a Meteor Deployment *with* a NodeJS and Meteor version change.
|
||||||
|
1. Update the meteor app as normal (copy it to the /var/www/xxx directory as a build bundle, then run the script to unpack it).
|
||||||
|
2. Run `sudo n` to get the current version of NodeJS being used. Use `sudo n x.x.x` to download and change to the new version of NodeJS.
|
||||||
|
3. Edit the app's nginx file in /etc/nginx/sites-available/ to reference the new NodeJS install location. For example my current install location is specified as `server {... passenger_nodejs /usr/local/n/versions/node/8.9.3/bin/node ...}`}
|
||||||
|
|
||||||
|
|
||||||
#Running Server Side Code
|
#Running Server Side Code
|
||||||
|
|||||||
@@ -186,4 +186,5 @@ body
|
|||||||
@import "../imports/ui/Production.import.styl"
|
@import "../imports/ui/Production.import.styl"
|
||||||
@import "../imports/ui/Workers.import.styl"
|
@import "../imports/ui/Workers.import.styl"
|
||||||
@import "../imports/ui/Graphs.import.styl"
|
@import "../imports/ui/Graphs.import.styl"
|
||||||
|
@import "../imports/ui/Reports.import.styl"
|
||||||
@import "../imports/ui/TestList.import.styl"
|
@import "../imports/ui/TestList.import.styl"
|
||||||
265
imports/api/Reports.js
Normal file
265
imports/api/Reports.js
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
|
||||||
|
if(Meteor.isServer) {
|
||||||
|
WebApp.connectHandlers.use("/reports/AnnualTotals", (req, res, next) => {
|
||||||
|
try {
|
||||||
|
let result = Meteor.collections.Sales.aggregate([{$group: {_id: {$substr: ['$date', 0, 4]}, total: {$sum: {$multiply: ["$price", "$amount"]}}}}]);
|
||||||
|
|
||||||
|
result.toArray().then(function(result) {
|
||||||
|
res.writeHead(200, {'Content-Type': 'text/csv'});
|
||||||
|
|
||||||
|
res.write("Year,Sales Total\n");
|
||||||
|
|
||||||
|
for(let i = 0; i < result.length; i++) {
|
||||||
|
res.write(result[i]._id);
|
||||||
|
res.write(",");
|
||||||
|
res.write("" + result[i].total);
|
||||||
|
res.write("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
} catch(err) {
|
||||||
|
console.log(err);
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
WebApp.connectHandlers.use("/reports/MonthlyTotals", (req, res, next) => {
|
||||||
|
try {
|
||||||
|
let result = Meteor.collections.Sales.aggregate([{$group: {_id: {$substr: ['$date', 0, 6]}, total: {$sum: {$multiply: ["$price", "$amount"]}}}}]);
|
||||||
|
|
||||||
|
result.toArray().then(function(result) {
|
||||||
|
res.writeHead(200, {'Content-Type': 'text/csv'});
|
||||||
|
|
||||||
|
res.write("Date,Sales Total\n");
|
||||||
|
|
||||||
|
result.sort(function(a, b) {
|
||||||
|
return parseInt(a._id) - parseInt(b._id);
|
||||||
|
});
|
||||||
|
|
||||||
|
for(let i = 0; i < result.length; i++) {
|
||||||
|
res.write(result[i]._id.substr(4, 2) + "/" + result[i]._id.substr(0, 4));
|
||||||
|
res.write(",");
|
||||||
|
res.write("" + result[i].total);
|
||||||
|
res.write("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
} catch(err) {
|
||||||
|
console.log(err);
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
WebApp.connectHandlers.use("/reports/TagTotals", (req, res, next) => {
|
||||||
|
try {
|
||||||
|
//Aggregate all the sales by product id & year, then later create a map between products and tags to create tag totals.
|
||||||
|
let result = Meteor.collections.Sales.aggregate([{
|
||||||
|
$group: {
|
||||||
|
_id: {$concat: ["$productId", "-", {$substr: ['$date', 0, 4]}]},
|
||||||
|
productId: {$first: "$productId"},
|
||||||
|
year: {$first: {$substr: ['$date', 0, 4]}},
|
||||||
|
total: {$sum: {$multiply: ["$price", "$amount"]}}
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
|
||||||
|
result.toArray().then(function(result) {
|
||||||
|
let productSalesTotalsMapByYear = {};
|
||||||
|
|
||||||
|
//Create a map of maps: year -> product id -> sales totals.
|
||||||
|
for(let next of result) {
|
||||||
|
let productSalesTotalsMap = productSalesTotalsMapByYear[next.year];
|
||||||
|
|
||||||
|
//Create the map if necessary.
|
||||||
|
if(productSalesTotalsMap === undefined) productSalesTotalsMap = productSalesTotalsMapByYear[next.year] = {};
|
||||||
|
|
||||||
|
productSalesTotalsMap[next.productId] = next.total;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Now create a mapping between the tag id's and tag names for later use.
|
||||||
|
let tagIdToTagNameMap = {};
|
||||||
|
let tags = Meteor.collections.ProductTags.find({}, {fields: {_id: 1, name: 1}}).fetch();
|
||||||
|
|
||||||
|
for(let tag of tags) {
|
||||||
|
tagIdToTagNameMap[tag._id] = tag.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Now create a map between tag names -> [product ids] so that we can build a table below.
|
||||||
|
let tagProductIdsMap = {};
|
||||||
|
let products = Meteor.collections.Products.find({}, {fields: {_id: 1, tags: 1}}).fetch();
|
||||||
|
|
||||||
|
for(let product of products) {
|
||||||
|
for(let tagId of product.tags) {
|
||||||
|
let tagName = tagIdToTagNameMap[tagId];
|
||||||
|
let productIds = tagProductIdsMap[tagName];
|
||||||
|
|
||||||
|
//Initialize the array if undefined, and add to the mapping.
|
||||||
|
if(productIds === undefined) productIds = tagProductIdsMap[tagName] = [];
|
||||||
|
|
||||||
|
productIds.push(product._id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Collect the years in ascending oder.
|
||||||
|
let years = Object.keys(productSalesTotalsMapByYear).sort(function(a, b) {return parseInt(a) - parseInt(b);});
|
||||||
|
|
||||||
|
//Write the response table.
|
||||||
|
res.writeHead(200, {'Content-Type': 'text/csv'});
|
||||||
|
|
||||||
|
//Start with the table headers: Tag, Year1, Year2, ..
|
||||||
|
res.write("Tag");
|
||||||
|
|
||||||
|
//Iterate over the years and add them to the headers.
|
||||||
|
for(let year of years) {
|
||||||
|
res.write("," + year);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.write('\n');
|
||||||
|
|
||||||
|
//Now write the tag name, and the annual totals for each tag as a row.
|
||||||
|
for(let tagName in tagProductIdsMap) {
|
||||||
|
let productIds = tagProductIdsMap[tagName];
|
||||||
|
|
||||||
|
//console.log(result[i].name + "::" + result[i].total);
|
||||||
|
res.write(tagName);
|
||||||
|
|
||||||
|
//Iterate over each year to add its total as a column.
|
||||||
|
for(let year of years) {
|
||||||
|
let productSalesTotalsMap = productSalesTotalsMapByYear[year];
|
||||||
|
let annualTotal = 0;
|
||||||
|
|
||||||
|
//Iterate over all the product id's for the tag and sum the annual totals for each product that is tagged.
|
||||||
|
for(productId of productIds) {
|
||||||
|
let productAnnualTotal = productSalesTotalsMap[productId];
|
||||||
|
|
||||||
|
if(productAnnualTotal) annualTotal += productAnnualTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.write("," + annualTotal);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.write('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
|
||||||
|
|
||||||
|
//let tagSalesTotals = [];
|
||||||
|
//let tagIdToNameMap = [];
|
||||||
|
|
||||||
|
////Find all products and collect sales totals by tag id.
|
||||||
|
//let products = Meteor.collections.Products.find({}, {fields: {_id: 1, tags: 1}}).fetch();
|
||||||
|
//
|
||||||
|
//for(let product of products) {
|
||||||
|
// for(let tag of product.tags) {
|
||||||
|
// let tagSalesTotal = tagSalesTotals[tag];
|
||||||
|
// let productSalesTotal = productSalesTotalsMap[product._id];
|
||||||
|
//
|
||||||
|
// if(tagSalesTotal === undefined) tagSalesTotal = 0;
|
||||||
|
// if(productSalesTotal === undefined) productSalesTotal = 0;
|
||||||
|
//
|
||||||
|
// tagSalesTotals[tag] = tagSalesTotal + productSalesTotal;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
////Find all tags and convert the tag id's to tag names.
|
||||||
|
//let tags = Meteor.collections.ProductTags.find({}, {fields: {_id: 1, name: 1}}).fetch();
|
||||||
|
//
|
||||||
|
//for(let tag of tags) {
|
||||||
|
// tagIdToNameMap[tag._id] = tag.name;
|
||||||
|
//}
|
||||||
|
|
||||||
|
////Write the response table.
|
||||||
|
//res.writeHead(200, {'Content-Type': 'text/csv'});
|
||||||
|
//
|
||||||
|
//res.write("Tag,Sales Total\n");
|
||||||
|
//
|
||||||
|
////result.sort(function(a, b) {
|
||||||
|
//// return a.name > b.name ? 1 : -1;
|
||||||
|
////});
|
||||||
|
//
|
||||||
|
//for(let tagId in tagSalesTotals) {
|
||||||
|
// let tagTotal = tagSalesTotals[tagId];
|
||||||
|
// let tagName = tagIdToNameMap[tagId];
|
||||||
|
//
|
||||||
|
// //console.log(result[i].name + "::" + result[i].total);
|
||||||
|
// res.write(tagName + ',' + tagTotal + '\n');
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//res.end();
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Get all tags
|
||||||
|
//Get all products for each tag
|
||||||
|
//Total all sales for each product
|
||||||
|
//let tags = Meteor.collections.ProductTags.find({},{fields: {_id: 1, name: 1}}).fetch();
|
||||||
|
//let result = [];
|
||||||
|
//
|
||||||
|
//for(let tag of tags) {
|
||||||
|
// let tagTotal = 0;
|
||||||
|
// let products = Meteor.collections.Products.find({tags: tag._id}, {fields: {_id: 1}}).fetch();
|
||||||
|
//
|
||||||
|
// for(let product of products) {
|
||||||
|
// let sales = Meteor.collections.Sales.find({productId: product._id}, {fields: {amount: 1, price: 1, _id: 0}}).fetch();
|
||||||
|
//
|
||||||
|
// for(let sale of sales) {
|
||||||
|
// tagTotal += sale.amount * sale.price;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// result.push({name: tag.name, total: tagTotal});
|
||||||
|
//}
|
||||||
|
|
||||||
|
//res.writeHead(200, {'Content-Type': 'text/csv'});
|
||||||
|
//
|
||||||
|
//res.write("Tag,Sales Total\n");
|
||||||
|
//
|
||||||
|
//result.sort(function(a, b) {
|
||||||
|
// return a.name > b.name ? 1 : -1;
|
||||||
|
//});
|
||||||
|
//
|
||||||
|
//for(let i = 0; i < result.length; i++) {
|
||||||
|
// console.log(result[i].name + "::" + result[i].total);
|
||||||
|
// res.write(result[i].name);
|
||||||
|
// res.write(",");
|
||||||
|
// res.write("" + result[i].total);
|
||||||
|
// res.write("\n");
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//res.end();
|
||||||
|
} catch(err) {
|
||||||
|
console.log(err);
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
WebApp.connectHandlers.use("/reports/WeekYearTable", (req, res, next) => {
|
||||||
|
try {
|
||||||
|
let result = Meteor.collections.Sales.aggregate([{$group: {_id: {$substr: ['$date', 0, 6]}, total: {$sum: {$multiply: ["$price", "$amount"]}}}}]);
|
||||||
|
|
||||||
|
result.toArray().then(function(result) {
|
||||||
|
res.writeHead(200, {'Content-Type': 'text/csv'});
|
||||||
|
|
||||||
|
res.write("Date,Sales Total\n");
|
||||||
|
|
||||||
|
result.sort(function(a, b) {
|
||||||
|
return parseInt(a._id) - parseInt(b._id);
|
||||||
|
});
|
||||||
|
|
||||||
|
for(let i = 0; i < result.length; i++) {
|
||||||
|
res.write(result[i]._id.substr(4, 2) + "/" + result[i]._id.substr(0, 4));
|
||||||
|
res.write(",");
|
||||||
|
res.write("" + result[i].total);
|
||||||
|
res.write("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
} catch(err) {
|
||||||
|
console.log(err);
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -349,7 +349,7 @@ if(Meteor.isServer) {
|
|||||||
|
|
||||||
let dateString = date.toString();
|
let dateString = date.toString();
|
||||||
let timestamp = new Date(dateString.substring(0, 4) + "-" + dateString.substring(4, 6) + "-" + dateString.substring(6, 8) + "T00:00:00Z");
|
let timestamp = new Date(dateString.substring(0, 4) + "-" + dateString.substring(4, 6) + "-" + dateString.substring(6, 8) + "T00:00:00Z");
|
||||||
let weekOfYear = sale.timestamp.getWeek().toString();
|
let weekOfYear = timestamp.getWeek().toString();
|
||||||
|
|
||||||
if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) {
|
if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) {
|
||||||
Sales.update(id, {$set: {date, venueId, price, amount, timestamp, weekOfYear}}, function(err, id) {
|
Sales.update(id, {$set: {date, venueId, price, amount, timestamp, weekOfYear}}, function(err, id) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import Logs from "./Logs.js";
|
|||||||
import Users from "./User.js";
|
import Users from "./User.js";
|
||||||
import UserRoles from "./Roles.js";
|
import UserRoles from "./Roles.js";
|
||||||
import Workers from "./Worker.js";
|
import Workers from "./Worker.js";
|
||||||
|
import './Reports.js';
|
||||||
|
|
||||||
//Save the collections in the Meteor.collections property for easy access without name conflicts.
|
//Save the collections in the Meteor.collections property for easy access without name conflicts.
|
||||||
Meteor.collections = {Measures, Venues, Products, ProductTags, Sales, SalesSheets, Logs, Users, UserRoles, Workers};
|
Meteor.collections = {Measures, Venues, Products, ProductTags, Sales, SalesSheets, Logs, Users, UserRoles, Workers};
|
||||||
|
|||||||
@@ -102,6 +102,13 @@ pri.route('/graphs', {
|
|||||||
BlazeLayout.render('Body', {content: 'Graphs'});
|
BlazeLayout.render('Body', {content: 'Graphs'});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
pri.route('/reports', {
|
||||||
|
name: 'Reports',
|
||||||
|
action: function(params, queryParams) {
|
||||||
|
require("/imports/ui/Reports.js");
|
||||||
|
BlazeLayout.render('Body', {content: 'Reports'});
|
||||||
|
}
|
||||||
|
});
|
||||||
pri.route('/graphTest', {
|
pri.route('/graphTest', {
|
||||||
name: 'GraphTest',
|
name: 'GraphTest',
|
||||||
action: function(params, queryParams) {
|
action: function(params, queryParams) {
|
||||||
|
|||||||
7
imports/ui/Reports.html
Normal file
7
imports/ui/Reports.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<template name="Reports">
|
||||||
|
<div id="reports">
|
||||||
|
<a download="AnnualTotals.csv" href="/reports/AnnualTotals">Annual Totals (csv)</a><br/>
|
||||||
|
<a download="MonthlyTotals.csv" href="/reports/MonthlyTotals">Monthly Totals (csv)</a><br/>
|
||||||
|
<a download="TagTotals.csv" href="/reports/TagTotals">Tag Totals (csv)</a><br/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
8
imports/ui/Reports.import.styl
vendored
Normal file
8
imports/ui/Reports.import.styl
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#reports
|
||||||
|
display: table
|
||||||
|
content-box: border-box
|
||||||
|
padding: 10px 20px
|
||||||
|
height: 100%
|
||||||
|
width: 100%
|
||||||
|
text-align: left
|
||||||
|
margin-left:20px
|
||||||
54
imports/ui/Reports.js
Normal file
54
imports/ui/Reports.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
|
||||||
|
import './Reports.html';
|
||||||
|
|
||||||
|
let PREFIX = "reports.";
|
||||||
|
|
||||||
|
//let SalesTotals = new Meteor.Collection("salesTotals");
|
||||||
|
//
|
||||||
|
//Meteor.subscribe("venues");
|
||||||
|
//Meteor.subscribe("productTags");
|
||||||
|
|
||||||
|
let salesTotalsSubscription;
|
||||||
|
|
||||||
|
|
||||||
|
Template.Reports.onCreated(function() {
|
||||||
|
//let template = Template.instance();
|
||||||
|
|
||||||
|
//Tracker.autorun(function() {
|
||||||
|
// salesTotalsSubscription = template.subscribe("salesTotals", Session.get(PREFIX + "time"), Session.get(PREFIX + "options"));
|
||||||
|
//});
|
||||||
|
});
|
||||||
|
Template.Reports.onRendered(function() {
|
||||||
|
//Template.instance()
|
||||||
|
//Tracker.autorun(function() {
|
||||||
|
// if(salesTotalsSubscription.ready()) {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//});
|
||||||
|
});
|
||||||
|
//Template.Reports.helpers({
|
||||||
|
// sales: function() {
|
||||||
|
// let sort = [];
|
||||||
|
//
|
||||||
|
// sort.push(['year', 'asc']);// year = 1;
|
||||||
|
// if(Session.get(PREFIX + "time") === 'weekly') sort.push(['week', 'asc']); // .week = 1;
|
||||||
|
// if(Session.get(PREFIX + "time") === 'monthly') sort.push(['month', 'asc']); // .month = 1;
|
||||||
|
// if(Session.get(PREFIX + "options") === 'markets') sort.push(['venue', 'asc']); // .month = 1;
|
||||||
|
//
|
||||||
|
// return SalesTotals.find({}, {sort: sort});
|
||||||
|
// },
|
||||||
|
// showTime: function(time) {
|
||||||
|
// return Session.get(PREFIX + "time") === time;
|
||||||
|
// },
|
||||||
|
// showOption: function(option) {
|
||||||
|
// return Session.get(PREFIX + "options") === option;
|
||||||
|
// },
|
||||||
|
// formatTotal: function(total) {
|
||||||
|
// return "$" + total.toFixed(2);
|
||||||
|
// }
|
||||||
|
//});
|
||||||
|
//Template.Reports.events({
|
||||||
|
// 'click annualNumbers': function(event, template) {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//});
|
||||||
@@ -51,6 +51,13 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li class="{{isActiveRoute 'Reports'}}">
|
||||||
|
<a href="{{pathFor 'Reports'}}">
|
||||||
|
Reports
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
© Petit Teton LLC 2017
|
© Petit Teton LLC 2017
|
||||||
|
|||||||
1986
package-lock.json
generated
1986
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@
|
|||||||
"build": "npm install --product && meteor build --architecture os.linux.x86_64 --server-only ../"
|
"build": "npm install --product && meteor build --architecture os.linux.x86_64 --server-only ../"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"1.0": "^1.0.2",
|
"@babel/runtime": "^7.2.0",
|
||||||
"babel-runtime": "^6.18.0",
|
"babel-runtime": "^6.18.0",
|
||||||
"csv-parse": "latest",
|
"csv-parse": "latest",
|
||||||
"d3": "^4.4.2",
|
"d3": "^4.4.2",
|
||||||
|
|||||||
Reference in New Issue
Block a user