Added more reports.

This commit is contained in:
Wynne Crisman
2019-01-06 10:43:06 -08:00
parent 5ccb8e6a44
commit c1183a1470
2 changed files with 293 additions and 99 deletions

View File

@@ -142,119 +142,311 @@ if(Meteor.isServer) {
}
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) => {
WebApp.connectHandlers.use("/reports/ProductSalesByMeasure", (req, res, next) => {
const separator = ";";
try {
let result = Meteor.collections.Sales.aggregate([{$group: {_id: {$substr: ['$date', 0, 6]}, total: {$sum: {$multiply: ["$price", "$amount"]}}}}]);
//Aggregate all the sale counts by product id, measure id, & year, then later create a map between products and tags to create tag totals, and measures to measure names to label them.
let result = Meteor.collections.Sales.aggregate([{
$group: {
_id: {$concat: ["$productId", "-", {$substr: ['$date', 0, 4]}, "-", "$measureId"]},
productId: {$first: "$productId"},
measureId: {$first: "$measureId"},
year: {$first: {$substr: ['$date', 0, 4]}},
count: {$sum: "$amount"},
total: {$sum: {$multiply: ["$price", "$amount"]}}
}
}]);
result.toArray().then(function(result) {
res.writeHead(200, {'Content-Type': 'text/csv'});
let totalByYear = {};
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");
//Create a map of maps: year -> product id -> measure id -> sales count.
for(let next of result) {
let totalByProduct = totalByYear[next.year];
//Create the map if necessary.
if(totalByProduct === undefined) totalByProduct = totalByYear[next.year] = {};
//Get the map of totals by product for the year.
let totalByMeasure = totalByProduct[next.productId];
//Create the map if necessary.
if(totalByMeasure === undefined) totalByMeasure = totalByProduct[next.productId] = {};
totalByMeasure[next.measureId] = {count: next.count, total: next.total};
}
//Create a list of ordered measures. We could use a map, but then getting the ordering correct would be difficult.
let measures = Meteor.collections.Measures.find({}, {fields: {_id: 1, name: 1}, sort: {order: 1}}).fetch();
//Now create a mapping between the product id's and product names for later use.
let products = Meteor.collections.Products.find({}, {fields: {_id: 1, name: 1}, sort: {name: 1}}).fetch();
//Collect the years in ascending oder.
let years = Object.keys(totalByYear).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: Product, Year1 Count, Year1 Total, Year2 Count, ..
res.write("Product");
//Iterate over the years and add them to the headers.
for(let year of years) {
res.write(separator + year + " Count" + separator + year + " Total");
}
res.write('\n');
for(let product of products) {
let includeProduct = false;
let productMeasureIds = {};
//Collect the measureIds for the product in all years of data (it may only exist in some years).
for(let year of years) {
let productTotals = totalByYear[year][product._id];
if(productTotals) {
for(let measureId of Object.keys(productTotals)) {
productMeasureIds[measureId] = 1;
includeProduct = true;
}
}
}
//If we have any data at all for the product then we should include it in the table.
if(includeProduct) {
res.write(product.name);
//First include the summation of all measures for each year.
for(let year of years) {
let productTotals = totalByYear[year][product._id];
let annualCount = 0;
let annualTotal = 0;
//There may be no information for some years on some products.
if(productTotals) {
for(let totals of Object.values(productTotals)) {
annualCount += totals.count;
annualTotal += totals.total;
}
}
res.write(separator + annualCount + separator + annualTotal);
}
res.write("\n");
//Iterate over all measures in the system (they are ordered), and write out the rows for each one that has data for this product. Use zeros for years without data.
for(let measure of measures) {
//If this measure has any data for any year then include it in the table.
if(productMeasureIds[measure._id]) {
res.write(product.name + " " + measure.name);
for(let year of years) {
let productTotals = totalByYear[year][product._id];
if(productTotals) {
let totals = productTotals[measure._id];
if(totals) {
res.write(separator + totals.count + separator + totals.total);
}
else {
res.write(separator + "0" + separator + "0");
}
}
else {
res.write(separator + "0" + separator + "0");
}
}
res.write("\n");
}
}
}
}
res.end();
}).catch((err) => {
console.log(err);
res.end();
});
} catch(err) {
console.log(err);
res.end();
}
});
WebApp.connectHandlers.use("/reports/TagSalesByMeasure", (req, res, next) => {
const separator = ";";
try {
//Aggregate all the sale counts by product id, measure id, & year, then later create a map between products and tags to create tag totals, and measures to measure names to label them.
let result = Meteor.collections.Sales.aggregate([{
$group: {
_id: {$concat: ["$productId", "-", {$substr: ['$date', 0, 4]}, "-", "$measureId"]},
productId: {$first: "$productId"},
measureId: {$first: "$measureId"},
year: {$first: {$substr: ['$date', 0, 4]}},
count: {$sum: "$amount"},
total: {$sum: {$multiply: ["$price", "$amount"]}}
}
}]);
result.toArray().then(function(result) {
let totalByYear = {};
//Create a map of maps: year -> product id -> measure id -> sales count.
for(let next of result) {
let totalByProduct = totalByYear[next.year];
//Create the map if necessary.
if(totalByProduct === undefined) totalByProduct = totalByYear[next.year] = {};
//Get the map of totals by product for the year.
let totalByMeasure = totalByProduct[next.productId];
//Create the map if necessary.
if(totalByMeasure === undefined) totalByMeasure = totalByProduct[next.productId] = {};
totalByMeasure[next.measureId] = {count: next.count, total: next.total};
}
//Create a list of ordered measures. We could use a map, but then getting the ordering correct would be difficult.
let measures = Meteor.collections.Measures.find({}, {fields: {_id: 1, name: 1}, sort: {order: 1}}).fetch();
//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(totalByYear).sort(function(a, b) {return parseInt(a) - parseInt(b);});
//A map of tag names -> measure id -> 1. The measureId's are not in an array, but rather an object setup as a HashSet. So tagMeasureIdMap['tagname']['measureId'] === 1 if the measure is related to the tag via sales for any year.
let tagMeasureIdMap = {};
//For each tag, collect measure id's that have data in the relevant years.
for(let tagName of Object.keys(tagProductIdsMap)) {
let productIds = tagProductIdsMap[tagName];
let measureIdMap = {};
//Iterate over the products related to the tag, and the years we are querying to find all measures with sales data.
for(let productId of productIds) {
for(let year of years) {
let totalsByMeasureIdMap = totalByYear[year][productId];
if(totalsByMeasureIdMap) {
for(let measureId in totalsByMeasureIdMap) {
measureIdMap[measureId] = 1;
}
}
}
}
tagMeasureIdMap[tagName] = measureIdMap;
}
//Write the response table.
res.writeHead(200, {'Content-Type': 'text/csv'});
//Start with the table headers: Product, Year1 Count, Year1 Total, Year2 Count, ..
res.write("Tag");
//Iterate over the years and add them to the headers.
for(let year of years) {
res.write(separator + year + " Count" + separator + year + " Total");
}
res.write('\n');
//Iterate over each tag and add rows for summation and each measure with data associated with it.
for(let tagName of Object.keys(tagProductIdsMap)) {
let productIds = tagProductIdsMap[tagName];
if(productIds) {
//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 totalByProduct = totalByYear[year];
let annualCount = 0;
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 totalByMeasure = totalByProduct[productId];
if(totalByMeasure) {
for(let totals of Object.values(totalByMeasure)) {
annualCount += totals.count;
annualTotal += totals.total;
}
}
}
res.write(separator + annualCount + separator + annualTotal);
}
res.write('\n');
//Write a row for each measure associated with the tag's products in the sales data.
for(let measure of measures) {
//If there is data for the given measure for any year and this tag, then write a row for it.
if(tagMeasureIdMap[tagName][measure._id]) {
res.write(tagName + " - " + measure.name);
//Write the sum of sales counts and $ amounts for each year for the tag and measure.
for(let year of years) {
let annualCount = 0;
let annualTotal = 0;
for(productId of productIds) {
let totals = totalByYear[year][productId] ? totalByYear[year][productId][measure._id] : null;
if(totals) {
annualCount += totals.count;
annualTotal += totals.total;
}
}
res.write(separator + annualCount + separator + annualTotal);
}
res.write('\n');
}
}
}
}
res.end();
}).catch((err) => {
console.log(err);
res.end();
});
} catch(err) {

View File

@@ -2,6 +2,8 @@
<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/>
<a download="TagTotals.csv" href="/reports/TagTotals">Tag Totals (csv)</a> The total sales ($) are shown by year for each tag. Since multiple tags can be associated with multiple products (N to N relationship), there will likely be overlap in the numbers.<br/>
<a download="TagSalesByMeasure.csv" href="/reports/TagSalesByMeasure">Tag Sales By Measure (csv)</a> This is a table of tags & measures by year. The tags and the measures associated with them are along the vertical column, and the years are horizontal. Both item counts and sales totals ($) are shown for each year.<br/>
<a download="ProductSalesByMeasure.csv" href="/reports/ProductSalesByMeasure">Product Sales By Measure (csv)</a> This is a table of products & product measures by year, showing the sales in terms of unit counts and dollar values for each year, each product, and each product's measure.<br/>
</div>
</template>