Added more reports.
This commit is contained in:
@@ -142,119 +142,311 @@ if(Meteor.isServer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res.end();
|
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) => {
|
}).catch((err) => {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
res.end();
|
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) {
|
} catch(err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
res.end();
|
res.end();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
WebApp.connectHandlers.use("/reports/WeekYearTable", (req, res, next) => {
|
WebApp.connectHandlers.use("/reports/ProductSalesByMeasure", (req, res, next) => {
|
||||||
|
const separator = ";";
|
||||||
|
|
||||||
try {
|
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) {
|
result.toArray().then(function(result) {
|
||||||
res.writeHead(200, {'Content-Type': 'text/csv'});
|
let totalByYear = {};
|
||||||
|
|
||||||
res.write("Date,Sales Total\n");
|
//Create a map of maps: year -> product id -> measure id -> sales count.
|
||||||
|
for(let next of result) {
|
||||||
result.sort(function(a, b) {
|
let totalByProduct = totalByYear[next.year];
|
||||||
return parseInt(a._id) - parseInt(b._id);
|
//Create the map if necessary.
|
||||||
});
|
if(totalByProduct === undefined) totalByProduct = totalByYear[next.year] = {};
|
||||||
|
|
||||||
for(let i = 0; i < result.length; i++) {
|
//Get the map of totals by product for the year.
|
||||||
res.write(result[i]._id.substr(4, 2) + "/" + result[i]._id.substr(0, 4));
|
let totalByMeasure = totalByProduct[next.productId];
|
||||||
res.write(",");
|
//Create the map if necessary.
|
||||||
res.write("" + result[i].total);
|
if(totalByMeasure === undefined) totalByMeasure = totalByProduct[next.productId] = {};
|
||||||
res.write("\n");
|
|
||||||
|
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();
|
res.end();
|
||||||
});
|
});
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
<div id="reports">
|
<div id="reports">
|
||||||
<a download="AnnualTotals.csv" href="/reports/AnnualTotals">Annual Totals (csv)</a><br/>
|
<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="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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
Reference in New Issue
Block a user