Skip to content
This repository has been archived by the owner on Aug 4, 2020. It is now read-only.

Commit

Permalink
closes #56
Browse files Browse the repository at this point in the history
  • Loading branch information
seiyria committed Jan 6, 2017
1 parent 2982586 commit f77c464
Show file tree
Hide file tree
Showing 10 changed files with 363 additions and 237 deletions.
2 changes: 1 addition & 1 deletion migrations/logs.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ exports.up = (knex) => {
table.increments('id').primary();
table.integer('locationId').unsigned().references('location.id');
table.string('terminalId');
table.string('message');
table.string('message', 500);
table.string('foundAt'); // client or server
table.string('stack', 5000);
table.timestamps();
Expand Down
2 changes: 2 additions & 0 deletions src/client/models/promotion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as _ from 'lodash';

import { OrganizationalUnit } from './organizationalunit';
import { PromoItem } from './promoitem';
import { InvoicePromo } from './invoicepromo';

export type DiscountType =
'Dollar' | 'Percent';
Expand Down Expand Up @@ -31,6 +32,7 @@ export class Promotion {
organizationalunit?: OrganizationalUnit;

promoItems?: PromoItem[];
invoicePromos?: InvoicePromo[];

temporary?: boolean;

Expand Down
2 changes: 2 additions & 0 deletions src/client/models/reportconfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ export type ReportRoute =
'base/inventory/current'
| 'base/inventory/old'
| 'base/inventory/reorder'
| 'base/promotions/all'
| 'base/promotions/pos'
| 'base/sales/completed'
| 'base/sales/voided';

Expand Down
54 changes: 43 additions & 11 deletions src/client/pages/reporting/configurations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,39 @@ const stockItemColumns = [
{ name: 'SKU', key: 'sku' },
{ name: 'Category', key: 'organizationalunit.name', allowGroup: true },
{ name: 'Taxable', key: 'taxable' },
{ name: 'Cost', key: 'cost', always: true },
{ name: 'Quantity', key: 'quantity', always: true },
{ name: 'Cost', key: 'cost', isNumeric: true, always: true },
{ name: 'Quantity', key: 'quantity', isNumeric: true, always: true },
{ name: 'Last Sold', key: 'lastSoldAt' },
{ name: 'Reorder Alert', key: 'reorderThreshold' },
{ name: 'Reorder Up To', key: 'reorderUpToAmount' },
{ name: 'Reorder Alert', key: 'reorderThreshold', isNumeric: true },
{ name: 'Reorder Up To', key: 'reorderUpToAmount', isNumeric: true },
{ name: 'Vendor Name', key: 'vendors[0].name', allowGroup: true },
{ name: 'Vendor SKU', key: 'vendors[0].stockId' },
{ name: 'Vendor Cost', key: 'vendors[0].cost' }
{ name: 'Vendor Cost', key: 'vendors[0].cost', isNumeric: true }
];

const invoiceColumns = [
{ name: 'Purchase Time', key: 'purchaseTime' },
{ name: 'Purchase Method', key: 'purchaseMethod', allowGroup: true },
{ name: 'Purchase Price', key: 'purchasePrice' },
{ name: 'Purchase Price', key: 'purchasePrice', isNumeric: true },
{ name: 'Location', key: 'location.name', allowGroup: true },
{ name: 'Terminal', key: 'terminalId', allowGroup: true },
{ name: 'Tax Collected', key: 'taxCollected' },
{ name: 'Cash Given', key: 'cashGiven' },
{ name: 'Subtotal', key: 'subtotal' },
{ name: '# Items', key: 'stockitems.length' },
{ name: '# Promos', key: 'promotions.length' }
{ name: 'Tax Collected', key: 'taxCollected', isNumeric: true },
{ name: 'Cash Given', key: 'cashGiven', isNumeric: true },
{ name: 'Subtotal', key: 'subtotal', isNumeric: true },
{ name: '# Items', key: 'stockitems.length', isNumeric: true },
{ name: '# Promos', key: 'promotions.length', isNumeric: true }
];

const promoColumns = [
{ name: 'Name', key: 'name' },
{ name: 'Discount Value', key: 'discountValue', isNumeric: true },
{ name: 'Discount Type', key: 'discountType', allowGroup: true },
{ name: 'Promotion Type', key: 'itemReductionType', allowGroup: true },
{ name: 'Discount Group', key: 'discountGrouping', allowGroup: true },
{ name: 'Start Date', key: 'startDate' },
{ name: 'End Date', key: 'endDate' },
{ name: '# Items Required', key: 'numItemsRequired', isNumeric: true },
{ name: '# Uses', key: 'invoicePromos.length', isNumeric: true }
];

export const AllReportConfigurations: ReportConfiguration[] = [
Expand Down Expand Up @@ -60,6 +72,26 @@ export const AllReportConfigurations: ReportConfiguration[] = [
modifyData: (item) => item['Reorder Quantity'] = item['Reorder Up To'] - item.Quantity,
columnChecked: ['Name', 'Quantity', 'Reorder Alert', 'Reorder Up To', 'Vendor Name', 'Vendor SKU', 'Vendor Cost'] },

{ internalId: 7, name: 'Promotions (PoS)', reportRoute: 'base/promotions/pos', columns: promoColumns,
filters: { multiDateFilter: true, sortBy: true, groupBy: true },
datePeriod: 0, dateDenomination: 'Day',
options: [
{ name: 'Reverse Sort', short: 'reverseSort' },
{ name: 'Use Custom Dates', short: 'useCustomDatePicker' },
{ name: 'Show Totals', short: 'showTotals', checked: true }
],
columnChecked: ['Name', 'Discount Value', 'Discount Type', 'Start Date'] },

{ internalId: 8, name: 'Promotions (Used)', reportRoute: 'base/promotions/all', columns: promoColumns,
filters: { multiDateFilter: true, sortBy: true, groupBy: true },
datePeriod: 0, dateDenomination: 'Day',
options: [
{ name: 'Reverse Sort', short: 'reverseSort' },
{ name: 'Use Custom Dates', short: 'useCustomDatePicker' },
{ name: 'Show Totals', short: 'showTotals', checked: true }
],
columnChecked: ['Name', '# Uses'] },

{ internalId: 4, name: 'Sales (Completed)', reportRoute: 'base/sales/completed', columns: invoiceColumns,
filters: { multiDateFilter: true, sortBy: true, groupBy: true, groupByDate: true, locationFilter: true },
datePeriod: 0, dateDenomination: 'Day',
Expand Down
236 changes: 236 additions & 0 deletions src/client/pages/reporting/reportdatatransformer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@

import * as _ from 'lodash';
const dateFunctions = require('date-fns');

export const reportRunPeriod = (report): string => {
if(!report.startDate) { return ''; }
if(report.startDate && !report.endDate) { return new Date(report.startDate).toDateString(); }
return `${new Date(report.startDate).toDateString()} - ${new Date(report.endDate).toDateString()}`;
};

export const reportBody = (report, settings, data) => {

const usefulColumns: any[] = _(report.columns)
.filter(col => col.checked)
// .map('name')
.value();

if(report.modifyColumns) {
report.modifyColumns(usefulColumns);
}

const reportPeriod = reportRunPeriod(report);

const content = `
<table>
<caption class="main-title">${settings.businessName}</caption>
<caption class="sub-title">${report.name}</caption>
<caption class="info-title">Run on ${new Date()}</caption>
${reportPeriod ? `<caption class="info-title">Report Period: ${reportPeriod}</caption>` : ''}
<caption>&nbsp;</caption>
${_.map(data, ({ name, group }) => {
return `
<thead>
<tr><th colspan="${usefulColumns.length}">Grouping: ${name || 'Unknown'}</th></tr>
<tr>
${_.map(usefulColumns, (col) => {
return `<th>${col.name}</th>`;
}).join('')}
</tr>
</thead>
<tbody>
${_.map(group, (item: any) => {
return `
<tr class="data-row ${item.doBold ? 'bold' : ''}">
${_.map(usefulColumns, col => {
return `<td class="${col.isNumeric ? 'number-column' : ''}">
${_.isUndefined(item[col.name]) ? '' : item[col.name]}
</td>`; }).join('')
}
</tr>`;
}).join('')}
<tr class="table-separator"></tr>
</tbody>`;
}).join('')}
</table>
`;
return content;
};

export const reportStyles = () => {
return `
#print-button { position: absolute; top: 0; right: 0; }
#csv-button { position: absolute; top: 20px; right: 0; }
.main-title { font-size: 2rem; font-weight: bold; }
.sub-title { font-size: 1.5rem; }
.info-title { font-style: italic; }
table { border-collapse: collapse; }
.data-row:nth-child(2n) { background-color: #ccc; }
.table-separator { height: 20px; }
td, th { padding-left: 10px; padding-right: 10px; margin-left: 5px; margin-right: 10px; margin-top: 50px; }
td.number-column { text-align: right; }
.print-show { display: none; }
.bold { font-weight: bold; }
@page {
size: landscape;
}
@media print {
.print-hide { display: none; }
.print-show { display: block; }
}
`;
};

export const reportAggregate = (report, reportName, settings, data) => {
return `
<html>
<head>
<title>${reportName}</title>
<style>
${reportStyles()}
</style>
<body>
<button class="print-hide" id="print-button" onclick="window.print()">print</button>
<button class="print-hide" id="csv-button">csv</button>
${reportBody(report, settings, data)}
</body>
</head>
</html>
`;
};

export const transformData = (report, settings, data): any[] => {
const AGGREGATE_KEYS = [
{ key: 'Purchase Price', decimals: 2 },
{ key: 'Tax Collected', decimals: 2 },
{ key: 'Subtotal', decimals: 2 },
{ key: 'Cash Given', decimals: 2 },
{ key: '# Items', decimals: 0 },
{ key: '# Promos', decimals: 0 },
{ key: '# Uses', decimals: 0 },
{ key: 'Quantity', decimals: 0 },
{ key: 'Reorder Quantity',decimals: 0 }
];

let baseData = _.map(data, item => {
return _.reduce(report.columns, (prev, cur: any) => {
if(!cur.checked && !cur.always) { return prev; }
const value = _.get(item, cur.key, '');

prev[cur.name] = value;

if(_.isNull(value)) {
prev[cur.name] = '';
}

if(_.includes(['Purchase Time', 'Last Sold', 'Start Date', 'End Date'], cur.name)) {
if(!value) {
prev[cur.name] = 'Never';
} else {
prev[cur.name] = dateFunctions.format(new Date(value), 'YYYY-MM-DD hh:mm A');
}
}

if(cur.name === 'Purchase Method') {
prev[cur.name] = settings.invoiceMethodDisplay(value);
}

return prev;
}, {});
});

if(report.sortBy) {
baseData = _.sortBy(baseData, item => {
const value = item[report.sortBy];
if(!_.isNaN(+value)) { return +value; }
return (value || '').toLowerCase();
});

if(report.optionValues.reverseSort) {
baseData = baseData.reverse();
}
}

if(report.modifyData) {
_.each(baseData, item => {
report.modifyData(item);
});
}

let dataGroups: any = { All: baseData };

if(report.groupBy) {
dataGroups = _.groupBy(baseData, report.groupBy);
}

if(report.groupByDate) {
const DATE_KEY = 'Purchase Time';
const dateGroupBy = report.groupByDate;

_.each(dataGroups, (group, key) => {

dataGroups[key] = [];

const groupedByDate = _.groupBy(group, purchase => dateFunctions[`startOf${report.groupByDate}`](purchase[DATE_KEY]));

_.each(groupedByDate, (dateGroup: any[]) => {

const startDateValue = dateFunctions[`startOf${dateGroupBy}`](dateGroup[0][DATE_KEY]);
const endDateValue = dateFunctions[`endOf${dateGroupBy}`](dateGroup[0][DATE_KEY]);

_.each([startDateValue, endDateValue], date => {
dateFunctions.setSeconds(date, 0);
dateFunctions.setMinutes(date, 0);
});

const startDateFormatted = dateFunctions.format(startDateValue, 'YYYY-MM-DD hh:mm A');
const endDateFormatted = dateFunctions.format(endDateValue, 'YYYY-MM-DD hh:mm A');

const baseObj = {
'Purchase Time': `${startDateFormatted} - ${endDateFormatted}`,
'Purchase Method': 'Unknown'
};

_.each(AGGREGATE_KEYS, aggKey => baseObj[aggKey.key] = 0);

const aggregateObject = _.reduce(dateGroup, (prev, cur) => {
_.each(AGGREGATE_KEYS, aggKey => prev[aggKey.key] += (+cur[aggKey.key] || 0));
return prev;
}, baseObj);

_.each(AGGREGATE_KEYS, aggKey => aggregateObject[aggKey.key] = aggregateObject[aggKey.key].toFixed(aggKey.decimals));

dataGroups[key].push(aggregateObject);
});
});
}

if(report.optionValues.showTotals) {
_.each(dataGroups, (group) => {
const baseObj: any = { Name: 'Totals', doBold: true, 'Purchase Time': 'Totals' };

_.each(AGGREGATE_KEYS, ({ key, decimals }) => {
baseObj[key] = (_.reduce(group, (prev, cur: any) => prev + (+cur[key] || 0), 0)).toFixed(decimals);
});

if(_.some(group, (item: any) => item.Quantity)) {
baseObj.Cost = (_.reduce(group, (prev, cur: any) => prev + ((cur.Cost || 0) * +cur.Quantity), 0)).toFixed(2);
baseObj['Vendor Cost'] = (_.reduce(group, (prev, cur: any) => {
return prev + ((cur['Vendor Cost'] || 0) * (+cur['Reorder Quantity'] || +cur.Quantity));
}, 0)).toFixed(2);

}

group.push(baseObj);
});
}

return _.map(dataGroups, (group, name) => ({ name, group }));
};
2 changes: 1 addition & 1 deletion src/client/pages/reporting/reporting.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<ion-content>
<ion-grid full-height>
<ion-row full-height>
<ion-col width-25 full-height scroll-list-container>
<ion-col width-20 full-height scroll-list-container>
<ion-list scroll-list>
<ion-list-header no-border-top>Base Reports</ion-list-header>
<button ion-item *ngFor="let report of baseReports" (click)="selectNewReport(report)" [disabled]="runningReport">
Expand Down
Loading

0 comments on commit f77c464

Please sign in to comment.