@harbortouch/skytab-analytics-report-utils 0.8.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -51,6 +51,7 @@ __export(index_exports, {
51
51
  TICKET_LIVE_DEFAULT_VISIBLE_COLUMNS: () => TICKET_LIVE_DEFAULT_VISIBLE_COLUMNS,
52
52
  TICKET_SUMMARY_AVAILABLE_COLUMNS: () => TICKET_SUMMARY_AVAILABLE_COLUMNS,
53
53
  TICKET_SUMMARY_DEFAULT_VISIBLE_COLUMNS: () => TICKET_SUMMARY_DEFAULT_VISIBLE_COLUMNS,
54
+ calculateFieldTotal: () => calculateFieldTotal,
54
55
  calculateReportTotals: () => calculateReportTotals,
55
56
  dailySalesConfig: () => dailySalesConfig,
56
57
  dailySalesDiscountsConfig: () => dailySalesDiscountsConfig,
@@ -98,18 +99,18 @@ var FIELDS = {
98
99
  type: "percent",
99
100
  columnKey: "salesVarLwPct",
100
101
  footerCalculation: {
101
- type: "percentChange",
102
- numeratorField: "salesAmountNet",
103
- denominatorField: "salesAmountNetLastWeek"
102
+ type: "variancePercent",
103
+ numeratorField: "salesVarLW",
104
+ denominatorField: "salesAmountNet"
104
105
  }
105
106
  },
106
107
  salesVarLYPct: {
107
108
  type: "percent",
108
109
  columnKey: "salesVarLyPct",
109
110
  footerCalculation: {
110
- type: "percentChange",
111
- numeratorField: "salesAmountNet",
112
- denominatorField: "salesAmountNetLastYear"
111
+ type: "variancePercent",
112
+ numeratorField: "salesVarLY",
113
+ denominatorField: "salesAmountNet"
113
114
  }
114
115
  },
115
116
  salesAmountNetLastWeek: { type: "money" },
@@ -127,18 +128,18 @@ var FIELDS = {
127
128
  type: "percent",
128
129
  columnKey: "guestsVarLwPct",
129
130
  footerCalculation: {
130
- type: "percentChange",
131
- numeratorField: "guestsCount",
132
- denominatorField: "guestsCountLastWeek"
131
+ type: "variancePercent",
132
+ numeratorField: "guestsVarLW",
133
+ denominatorField: "guestsCount"
133
134
  }
134
135
  },
135
136
  guestsVarLYPct: {
136
137
  type: "percent",
137
138
  columnKey: "guestsVarLyPct",
138
139
  footerCalculation: {
139
- type: "percentChange",
140
- numeratorField: "guestsCount",
141
- denominatorField: "guestsCountLastYear"
140
+ type: "variancePercent",
141
+ numeratorField: "guestsVarLY",
142
+ denominatorField: "guestsCount"
142
143
  }
143
144
  },
144
145
  guestsCountLastWeek: { type: "number" },
@@ -156,18 +157,22 @@ var FIELDS = {
156
157
  type: "money",
157
158
  columnKey: "salesPerGuestLw",
158
159
  footerCalculation: {
159
- type: "average",
160
- numeratorField: "salesAmountNetLastWeek",
161
- denominatorField: "guestsCountLastWeek"
160
+ type: "quotientOfDifferences",
161
+ numeratorField: "salesAmountNet",
162
+ numeratorAdjustField: "salesVarLW",
163
+ denominatorField: "guestsCount",
164
+ denominatorAdjustField: "guestsVarLW"
162
165
  }
163
166
  },
164
167
  salesPerGuestLY: {
165
168
  type: "money",
166
169
  columnKey: "salesPerGuestLy",
167
170
  footerCalculation: {
168
- type: "average",
169
- numeratorField: "salesAmountNetLastYear",
170
- denominatorField: "guestsCountLastYear"
171
+ type: "quotientOfDifferences",
172
+ numeratorField: "salesAmountNet",
173
+ numeratorAdjustField: "salesVarLY",
174
+ denominatorField: "guestsCount",
175
+ denominatorAdjustField: "guestsVarLY"
171
176
  }
172
177
  }
173
178
  };
@@ -509,7 +514,16 @@ var FIELDS6 = {
509
514
  orderTypeName: { type: "string", columnKey: "orderType", filteringAvailable: false },
510
515
  taxName: { type: "string", columnKey: "taxName", filteringAvailable: false },
511
516
  taxValue: { type: "money", columnKey: "taxableSales" },
512
- taxRate: { type: "number", columnKey: "taxRate" },
517
+ taxRate: {
518
+ type: "conditional",
519
+ columnKey: "taxRate",
520
+ conditionalConfiguration: {
521
+ conditionalField: "taxIsFlat",
522
+ conditionalFormat: (field) => {
523
+ return field ? "money" : "percent";
524
+ }
525
+ }
526
+ },
513
527
  recordsCount: { type: "number", columnKey: "quantity" },
514
528
  taxableSales: { type: "money", columnKey: "taxableSales" },
515
529
  totalCollected: { type: "money", columnKey: "totalCollected" },
@@ -1515,65 +1529,54 @@ function isNumericType(type) {
1515
1529
  }
1516
1530
 
1517
1531
  // src/totals.ts
1518
- function calculateReportTotals(data, fieldConfig, opts) {
1519
- const rows = data;
1520
- const { labelField, label } = opts;
1521
- const fieldsToSum = /* @__PURE__ */ new Set();
1522
- const derivedFields = [];
1523
- for (const [field, config] of Object.entries(fieldConfig)) {
1524
- const { type, columnKey, footerCalculation: calc } = config;
1525
- if (!type || type === "string" || type === "date" || type === "time" || type === "boolean") {
1526
- continue;
1527
- }
1528
- if (calc?.type === "none") {
1529
- } else if (!calc || calc.type === "sum") {
1530
- if (columnKey || calc?.type === "sum") {
1531
- fieldsToSum.add(field);
1532
- }
1533
- } else {
1534
- derivedFields.push({ field, calc });
1535
- if (calc.numeratorField) {
1536
- fieldsToSum.add(calc.numeratorField);
1537
- }
1538
- if (calc.denominatorField) {
1539
- fieldsToSum.add(calc.denominatorField);
1540
- }
1541
- }
1532
+ var sumField = (rows, field) => rows.reduce((acc, row) => acc + (row[field] ?? 0), 0);
1533
+ var calculateFieldTotal = (rows, field, config) => {
1534
+ const { type } = config;
1535
+ if (!type || type === "string" || type === "date" || type === "time" || type === "boolean" || type === "conditional") {
1536
+ return null;
1542
1537
  }
1543
- const sums = {};
1544
- for (const field of fieldsToSum) {
1545
- sums[field] = rows.reduce((acc, row) => acc + (row[field] ?? 0), 0);
1538
+ const calc = config.footerCalculation;
1539
+ if (calc?.type === "none") {
1540
+ return 0;
1546
1541
  }
1547
- const derived = {};
1548
- for (const { field, calc } of derivedFields) {
1549
- const num = sums[calc.numeratorField ?? ""] ?? 0;
1550
- const den = sums[calc.denominatorField ?? ""] ?? 0;
1551
- if (calc.type === "percentChange") {
1552
- derived[field] = den ? (num - den) / den : 0;
1553
- } else if (calc.type === "average") {
1554
- derived[field] = den > 0 ? num / den : 0;
1555
- }
1542
+ if (!calc || calc.type === "sum") {
1543
+ return config.columnKey || calc?.type === "sum" ? sumField(rows, field) : 0;
1544
+ }
1545
+ const { numeratorField, denominatorField } = calc;
1546
+ if (!numeratorField || !denominatorField) {
1547
+ return 0;
1548
+ }
1549
+ const num = sumField(rows, numeratorField);
1550
+ const den = sumField(rows, denominatorField);
1551
+ if (calc.type === "percentChange") {
1552
+ return den ? (num - den) / den : 0;
1553
+ }
1554
+ if (calc.type === "variancePercent") {
1555
+ const prior = den - num;
1556
+ return prior ? num / prior : 0;
1557
+ }
1558
+ if (calc.type === "average") {
1559
+ return den > 0 ? num / den : 0;
1560
+ }
1561
+ if (calc.type === "quotientOfDifferences") {
1562
+ const { numeratorAdjustField, denominatorAdjustField } = calc;
1563
+ const numAdj = numeratorAdjustField ? sumField(rows, numeratorAdjustField) : 0;
1564
+ const denAdj = denominatorAdjustField ? sumField(rows, denominatorAdjustField) : 0;
1565
+ const effectiveDen = den - denAdj;
1566
+ return effectiveDen !== 0 ? (num - numAdj) / effectiveDen : 0;
1556
1567
  }
1568
+ return 0;
1569
+ };
1570
+ var calculateReportTotals = (data, fieldConfig, opts) => {
1571
+ const { labelField, label } = opts;
1557
1572
  const result = {};
1558
1573
  for (const [field, config] of Object.entries(fieldConfig)) {
1559
- const { type } = config;
1560
- if (!type || type === "string" || type === "date" || type === "time") {
1561
- result[field] = "";
1562
- } else if (type === "boolean") {
1563
- result[field] = false;
1564
- } else {
1565
- result[field] = 0;
1566
- }
1574
+ const total = calculateFieldTotal(data, field, config);
1575
+ result[field] = total ?? "";
1567
1576
  }
1568
1577
  result[labelField] = label;
1569
- for (const [field, value] of Object.entries(sums)) {
1570
- result[field] = value;
1571
- }
1572
- for (const [field, value] of Object.entries(derived)) {
1573
- result[field] = value;
1574
- }
1575
1578
  return result;
1576
- }
1579
+ };
1577
1580
 
1578
1581
  // src/reports/dailySalesRefundsVoids.ts
1579
1582
  var FIRST_COLUMN_WIDTH8 = 240;
@@ -1720,9 +1723,18 @@ var getReportFormattingLocaleOptions = (locations) => {
1720
1723
  ).values()
1721
1724
  );
1722
1725
  };
1723
- var formatFieldValue = (value, type, localeOptions) => {
1726
+ var DEFAULT_FIELD_CONFIG = { type: "string" };
1727
+ var formatFieldValue = (value, type, localeOptions, fullRecord, fieldConfig = DEFAULT_FIELD_CONFIG) => {
1724
1728
  const { currency, locale } = localeOptions;
1725
1729
  switch (type) {
1730
+ case "conditional": {
1731
+ if (!fullRecord) {
1732
+ return formatString(value);
1733
+ }
1734
+ const conditionalConfig = fieldConfig.conditionalConfiguration;
1735
+ const configType = conditionalConfig.conditionalFormat(fullRecord[conditionalConfig.conditionalField]);
1736
+ return formatFieldValue(value, configType, localeOptions, fullRecord, fieldConfig);
1737
+ }
1726
1738
  case "string":
1727
1739
  return formatString(value);
1728
1740
  case "money":
@@ -1777,6 +1789,7 @@ var formatFieldValue = (value, type, localeOptions) => {
1777
1789
  TICKET_LIVE_DEFAULT_VISIBLE_COLUMNS,
1778
1790
  TICKET_SUMMARY_AVAILABLE_COLUMNS,
1779
1791
  TICKET_SUMMARY_DEFAULT_VISIBLE_COLUMNS,
1792
+ calculateFieldTotal,
1780
1793
  calculateReportTotals,
1781
1794
  dailySalesConfig,
1782
1795
  dailySalesDiscountsConfig,
package/dist/index.d.cts CHANGED
@@ -1179,22 +1179,34 @@ declare function getColumnMetadata(key: ReportColumnKey): {
1179
1179
  };
1180
1180
  declare function getColumnExportHeaderLabel(colTitle: string, groupTitle: string | null): string;
1181
1181
 
1182
- type FieldType = 'string' | 'money' | 'percent' | 'number' | 'fixedNumber' | 'date' | 'time' | 'boolean';
1182
+ type FieldType = 'string' | 'money' | 'percent' | 'number' | 'fixedNumber' | 'date' | 'time' | 'boolean' | 'conditional';
1183
1183
  type ColumnAlignment = 'left' | 'right';
1184
- type FooterCalculationType = 'sum' | 'percentChange' | 'average' | 'none';
1184
+ type FooterCalculationType = 'sum' | 'percentChange' | 'variancePercent' | 'average' | 'quotientOfDifferences' | 'none';
1185
1185
  interface FooterCalculation<TField = string> {
1186
1186
  type: FooterCalculationType;
1187
1187
  numeratorField?: TField;
1188
+ numeratorAdjustField?: string;
1188
1189
  denominatorField?: TField;
1190
+ denominatorAdjustField?: string;
1189
1191
  }
1190
- interface ColumnPresentationConfig<TField = string> {
1192
+ interface ConditionalConfiguration<TField = string> {
1193
+ conditionalField: TField;
1194
+ conditionalFormat: (value: unknown) => Exclude<FieldType, 'conditional'>;
1195
+ }
1196
+ interface BaseColumnPresentationConfig<TField = string> {
1191
1197
  columnKey?: ReportColumnKey;
1192
- type: FieldType;
1193
1198
  size?: number;
1194
1199
  enableSorting?: boolean;
1195
1200
  filteringAvailable?: boolean;
1196
1201
  footerCalculation?: FooterCalculation<TField>;
1197
1202
  }
1203
+ type ColumnPresentationConfig<TField = string> = (BaseColumnPresentationConfig<TField> & {
1204
+ type: Exclude<FieldType, 'conditional'>;
1205
+ conditionalConfiguration?: never;
1206
+ }) | (BaseColumnPresentationConfig<TField> & {
1207
+ type: 'conditional';
1208
+ conditionalConfiguration: ConditionalConfiguration<TField>;
1209
+ });
1198
1210
  interface ColumnGroupConfig {
1199
1211
  id: string;
1200
1212
  headerTranslationKey: string;
@@ -1232,7 +1244,8 @@ interface CalculateReportTotalsOptions {
1232
1244
  labelField: string;
1233
1245
  label: string;
1234
1246
  }
1235
- declare function calculateReportTotals<T>(data: T[], fieldConfig: Record<string, ColumnPresentationConfig>, opts: CalculateReportTotalsOptions): T;
1247
+ declare const calculateFieldTotal: <T>(rows: T[], field: keyof T, config: ColumnPresentationConfig) => number | null;
1248
+ declare const calculateReportTotals: <T>(data: T[], fieldConfig: Record<string, ColumnPresentationConfig>, opts: CalculateReportTotalsOptions) => T;
1236
1249
 
1237
1250
  type SalesSummaryField = 'groupById' | 'groupByName' | 'salesAmountNet' | 'salesVarLW' | 'salesVarLY' | 'salesVarLWPct' | 'salesVarLYPct' | 'salesAmountNetLastWeek' | 'salesAmountNetLastYear' | 'salesAmountGross' | 'salesAmountGrossLastWeek' | 'salesAmountGrossLastYear' | 'ticketsCount' | 'ticketsCountLastWeek' | 'ticketsCountLastYear' | 'guestsCount' | 'guestsVarLW' | 'guestsVarLY' | 'guestsVarLWPct' | 'guestsVarLYPct' | 'guestsCountLastWeek' | 'guestsCountLastYear' | 'salesPerGuestTY' | 'salesPerGuestLW' | 'salesPerGuestLY';
1238
1251
  declare const SALES_SUMMARY_AVAILABLE_COLUMNS: SalesSummaryField[];
@@ -1342,6 +1355,6 @@ declare const getReportFormattingLocaleOptions: (locations: {
1342
1355
  countryCode: string;
1343
1356
  language: string;
1344
1357
  }[]) => LocaleOption[];
1345
- declare const formatFieldValue: (value: unknown, type: FieldType, localeOptions: LocaleOption) => string;
1358
+ declare const formatFieldValue: (value: unknown, type: FieldType, localeOptions: LocaleOption, fullRecord: Record<string, unknown> | null | undefined, fieldConfig?: ColumnPresentationConfig) => string;
1346
1359
 
1347
- export { COLUMN_REGISTRY, type CalculateReportTotalsOptions, type ColumnAlignment, type ColumnGroupConfig, type ColumnMetadata, type ColumnPresentationConfig, DAILY_SALES_AVAILABLE_COLUMNS, DAILY_SALES_BY_GROUP_AVAILABLE_COLUMNS, DAILY_SALES_DEFAULT_VISIBLE_COLUMNS, DAILY_SALES_DISCOUNTS_AVAILABLE_COLUMNS, DAILY_SALES_DISCOUNTS_DEFAULT_VISIBLE_COLUMNS, DAILY_SALES_PAYMENTS_AVAILABLE_COLUMNS, DAILY_SALES_PAYMENTS_DEFAULT_VISIBLE_COLUMNS, DAILY_SALES_REFUNDS_VOIDS_AVAILABLE_COLUMNS, DAILY_SALES_REFUNDS_VOIDS_DEFAULT_VISIBLE_COLUMNS, DAILY_SALES_TAXES_AVAILABLE_COLUMNS, DAILY_SALES_TAXES_DEFAULT_VISIBLE_COLUMNS, DAILY_SALES_TRENDS_AVAILABLE_COLUMNS, EMPLOYEE_TIMECARD_AVAILABLE_COLUMNS, EMPLOYEE_TIMECARD_DEFAULT_VISIBLE_COLUMNS, type FieldType, type FooterCalculation, type FooterCalculationType, type FormatDateOptions, type FormatMoneyOptions, type FormatNumberOptions, type FormatTimeOptions, ITEM_TAX_AVAILABLE_COLUMNS, ITEM_TAX_DEFAULT_VISIBLE_COLUMNS, type LocaleOption, MODIFIER_MIX_AVAILABLE_COLUMNS, MODIFIER_MIX_DEFAULT_VISIBLE_COLUMNS, PRODUCT_MIX_AVAILABLE_COLUMNS, PRODUCT_MIX_CHART_FIELDS, PRODUCT_MIX_DEFAULT_VISIBLE_COLUMNS, Report, type ReportColumnKey, type ReportConfig, type ReportType, SALES_BY_ITEM_DETAIL_AVAILABLE_COLUMNS, SALES_BY_ITEM_DETAIL_DEFAULT_VISIBLE_COLUMNS, SALES_SUMMARY_AVAILABLE_COLUMNS, SALES_SUMMARY_DEFAULT_VISIBLE_COLUMNS, TICKET_LIVE_AVAILABLE_COLUMNS, TICKET_LIVE_DEFAULT_VISIBLE_COLUMNS, TICKET_SUMMARY_AVAILABLE_COLUMNS, TICKET_SUMMARY_DEFAULT_VISIBLE_COLUMNS, type TimeFormat, calculateReportTotals, dailySalesConfig, dailySalesDiscountsConfig, dailySalesPaymentsConfig, dailySalesRefundsVoidsConfig, dailySalesTaxesConfig, dailySalesTrendsConfig, employeeTimecardConfig, formatDate, formatFieldValue, formatFixedNumber, formatInteger, formatMoney, formatMoneyWithoutSymbol, formatPercent, formatString, formatTime, getColumnAlignment, getColumnExportHeaderLabel, getColumnMetadata, getDateToFormat, getNumberToFormat, getReportConfig, getReportFormattingLocaleOptions, isNumericType, itemTaxConfig, modifierMixConfig, productMixConfig, salesByItemDetailConfig, salesSummaryConfig, ticketLiveConfig, ticketSummaryConfig };
1360
+ export { COLUMN_REGISTRY, type CalculateReportTotalsOptions, type ColumnAlignment, type ColumnGroupConfig, type ColumnMetadata, type ColumnPresentationConfig, DAILY_SALES_AVAILABLE_COLUMNS, DAILY_SALES_BY_GROUP_AVAILABLE_COLUMNS, DAILY_SALES_DEFAULT_VISIBLE_COLUMNS, DAILY_SALES_DISCOUNTS_AVAILABLE_COLUMNS, DAILY_SALES_DISCOUNTS_DEFAULT_VISIBLE_COLUMNS, DAILY_SALES_PAYMENTS_AVAILABLE_COLUMNS, DAILY_SALES_PAYMENTS_DEFAULT_VISIBLE_COLUMNS, DAILY_SALES_REFUNDS_VOIDS_AVAILABLE_COLUMNS, DAILY_SALES_REFUNDS_VOIDS_DEFAULT_VISIBLE_COLUMNS, DAILY_SALES_TAXES_AVAILABLE_COLUMNS, DAILY_SALES_TAXES_DEFAULT_VISIBLE_COLUMNS, DAILY_SALES_TRENDS_AVAILABLE_COLUMNS, EMPLOYEE_TIMECARD_AVAILABLE_COLUMNS, EMPLOYEE_TIMECARD_DEFAULT_VISIBLE_COLUMNS, type FieldType, type FooterCalculation, type FooterCalculationType, type FormatDateOptions, type FormatMoneyOptions, type FormatNumberOptions, type FormatTimeOptions, ITEM_TAX_AVAILABLE_COLUMNS, ITEM_TAX_DEFAULT_VISIBLE_COLUMNS, type LocaleOption, MODIFIER_MIX_AVAILABLE_COLUMNS, MODIFIER_MIX_DEFAULT_VISIBLE_COLUMNS, PRODUCT_MIX_AVAILABLE_COLUMNS, PRODUCT_MIX_CHART_FIELDS, PRODUCT_MIX_DEFAULT_VISIBLE_COLUMNS, Report, type ReportColumnKey, type ReportConfig, type ReportType, SALES_BY_ITEM_DETAIL_AVAILABLE_COLUMNS, SALES_BY_ITEM_DETAIL_DEFAULT_VISIBLE_COLUMNS, SALES_SUMMARY_AVAILABLE_COLUMNS, SALES_SUMMARY_DEFAULT_VISIBLE_COLUMNS, TICKET_LIVE_AVAILABLE_COLUMNS, TICKET_LIVE_DEFAULT_VISIBLE_COLUMNS, TICKET_SUMMARY_AVAILABLE_COLUMNS, TICKET_SUMMARY_DEFAULT_VISIBLE_COLUMNS, type TimeFormat, calculateFieldTotal, calculateReportTotals, dailySalesConfig, dailySalesDiscountsConfig, dailySalesPaymentsConfig, dailySalesRefundsVoidsConfig, dailySalesTaxesConfig, dailySalesTrendsConfig, employeeTimecardConfig, formatDate, formatFieldValue, formatFixedNumber, formatInteger, formatMoney, formatMoneyWithoutSymbol, formatPercent, formatString, formatTime, getColumnAlignment, getColumnExportHeaderLabel, getColumnMetadata, getDateToFormat, getNumberToFormat, getReportConfig, getReportFormattingLocaleOptions, isNumericType, itemTaxConfig, modifierMixConfig, productMixConfig, salesByItemDetailConfig, salesSummaryConfig, ticketLiveConfig, ticketSummaryConfig };
package/dist/index.d.ts CHANGED
@@ -1179,22 +1179,34 @@ declare function getColumnMetadata(key: ReportColumnKey): {
1179
1179
  };
1180
1180
  declare function getColumnExportHeaderLabel(colTitle: string, groupTitle: string | null): string;
1181
1181
 
1182
- type FieldType = 'string' | 'money' | 'percent' | 'number' | 'fixedNumber' | 'date' | 'time' | 'boolean';
1182
+ type FieldType = 'string' | 'money' | 'percent' | 'number' | 'fixedNumber' | 'date' | 'time' | 'boolean' | 'conditional';
1183
1183
  type ColumnAlignment = 'left' | 'right';
1184
- type FooterCalculationType = 'sum' | 'percentChange' | 'average' | 'none';
1184
+ type FooterCalculationType = 'sum' | 'percentChange' | 'variancePercent' | 'average' | 'quotientOfDifferences' | 'none';
1185
1185
  interface FooterCalculation<TField = string> {
1186
1186
  type: FooterCalculationType;
1187
1187
  numeratorField?: TField;
1188
+ numeratorAdjustField?: string;
1188
1189
  denominatorField?: TField;
1190
+ denominatorAdjustField?: string;
1189
1191
  }
1190
- interface ColumnPresentationConfig<TField = string> {
1192
+ interface ConditionalConfiguration<TField = string> {
1193
+ conditionalField: TField;
1194
+ conditionalFormat: (value: unknown) => Exclude<FieldType, 'conditional'>;
1195
+ }
1196
+ interface BaseColumnPresentationConfig<TField = string> {
1191
1197
  columnKey?: ReportColumnKey;
1192
- type: FieldType;
1193
1198
  size?: number;
1194
1199
  enableSorting?: boolean;
1195
1200
  filteringAvailable?: boolean;
1196
1201
  footerCalculation?: FooterCalculation<TField>;
1197
1202
  }
1203
+ type ColumnPresentationConfig<TField = string> = (BaseColumnPresentationConfig<TField> & {
1204
+ type: Exclude<FieldType, 'conditional'>;
1205
+ conditionalConfiguration?: never;
1206
+ }) | (BaseColumnPresentationConfig<TField> & {
1207
+ type: 'conditional';
1208
+ conditionalConfiguration: ConditionalConfiguration<TField>;
1209
+ });
1198
1210
  interface ColumnGroupConfig {
1199
1211
  id: string;
1200
1212
  headerTranslationKey: string;
@@ -1232,7 +1244,8 @@ interface CalculateReportTotalsOptions {
1232
1244
  labelField: string;
1233
1245
  label: string;
1234
1246
  }
1235
- declare function calculateReportTotals<T>(data: T[], fieldConfig: Record<string, ColumnPresentationConfig>, opts: CalculateReportTotalsOptions): T;
1247
+ declare const calculateFieldTotal: <T>(rows: T[], field: keyof T, config: ColumnPresentationConfig) => number | null;
1248
+ declare const calculateReportTotals: <T>(data: T[], fieldConfig: Record<string, ColumnPresentationConfig>, opts: CalculateReportTotalsOptions) => T;
1236
1249
 
1237
1250
  type SalesSummaryField = 'groupById' | 'groupByName' | 'salesAmountNet' | 'salesVarLW' | 'salesVarLY' | 'salesVarLWPct' | 'salesVarLYPct' | 'salesAmountNetLastWeek' | 'salesAmountNetLastYear' | 'salesAmountGross' | 'salesAmountGrossLastWeek' | 'salesAmountGrossLastYear' | 'ticketsCount' | 'ticketsCountLastWeek' | 'ticketsCountLastYear' | 'guestsCount' | 'guestsVarLW' | 'guestsVarLY' | 'guestsVarLWPct' | 'guestsVarLYPct' | 'guestsCountLastWeek' | 'guestsCountLastYear' | 'salesPerGuestTY' | 'salesPerGuestLW' | 'salesPerGuestLY';
1238
1251
  declare const SALES_SUMMARY_AVAILABLE_COLUMNS: SalesSummaryField[];
@@ -1342,6 +1355,6 @@ declare const getReportFormattingLocaleOptions: (locations: {
1342
1355
  countryCode: string;
1343
1356
  language: string;
1344
1357
  }[]) => LocaleOption[];
1345
- declare const formatFieldValue: (value: unknown, type: FieldType, localeOptions: LocaleOption) => string;
1358
+ declare const formatFieldValue: (value: unknown, type: FieldType, localeOptions: LocaleOption, fullRecord: Record<string, unknown> | null | undefined, fieldConfig?: ColumnPresentationConfig) => string;
1346
1359
 
1347
- export { COLUMN_REGISTRY, type CalculateReportTotalsOptions, type ColumnAlignment, type ColumnGroupConfig, type ColumnMetadata, type ColumnPresentationConfig, DAILY_SALES_AVAILABLE_COLUMNS, DAILY_SALES_BY_GROUP_AVAILABLE_COLUMNS, DAILY_SALES_DEFAULT_VISIBLE_COLUMNS, DAILY_SALES_DISCOUNTS_AVAILABLE_COLUMNS, DAILY_SALES_DISCOUNTS_DEFAULT_VISIBLE_COLUMNS, DAILY_SALES_PAYMENTS_AVAILABLE_COLUMNS, DAILY_SALES_PAYMENTS_DEFAULT_VISIBLE_COLUMNS, DAILY_SALES_REFUNDS_VOIDS_AVAILABLE_COLUMNS, DAILY_SALES_REFUNDS_VOIDS_DEFAULT_VISIBLE_COLUMNS, DAILY_SALES_TAXES_AVAILABLE_COLUMNS, DAILY_SALES_TAXES_DEFAULT_VISIBLE_COLUMNS, DAILY_SALES_TRENDS_AVAILABLE_COLUMNS, EMPLOYEE_TIMECARD_AVAILABLE_COLUMNS, EMPLOYEE_TIMECARD_DEFAULT_VISIBLE_COLUMNS, type FieldType, type FooterCalculation, type FooterCalculationType, type FormatDateOptions, type FormatMoneyOptions, type FormatNumberOptions, type FormatTimeOptions, ITEM_TAX_AVAILABLE_COLUMNS, ITEM_TAX_DEFAULT_VISIBLE_COLUMNS, type LocaleOption, MODIFIER_MIX_AVAILABLE_COLUMNS, MODIFIER_MIX_DEFAULT_VISIBLE_COLUMNS, PRODUCT_MIX_AVAILABLE_COLUMNS, PRODUCT_MIX_CHART_FIELDS, PRODUCT_MIX_DEFAULT_VISIBLE_COLUMNS, Report, type ReportColumnKey, type ReportConfig, type ReportType, SALES_BY_ITEM_DETAIL_AVAILABLE_COLUMNS, SALES_BY_ITEM_DETAIL_DEFAULT_VISIBLE_COLUMNS, SALES_SUMMARY_AVAILABLE_COLUMNS, SALES_SUMMARY_DEFAULT_VISIBLE_COLUMNS, TICKET_LIVE_AVAILABLE_COLUMNS, TICKET_LIVE_DEFAULT_VISIBLE_COLUMNS, TICKET_SUMMARY_AVAILABLE_COLUMNS, TICKET_SUMMARY_DEFAULT_VISIBLE_COLUMNS, type TimeFormat, calculateReportTotals, dailySalesConfig, dailySalesDiscountsConfig, dailySalesPaymentsConfig, dailySalesRefundsVoidsConfig, dailySalesTaxesConfig, dailySalesTrendsConfig, employeeTimecardConfig, formatDate, formatFieldValue, formatFixedNumber, formatInteger, formatMoney, formatMoneyWithoutSymbol, formatPercent, formatString, formatTime, getColumnAlignment, getColumnExportHeaderLabel, getColumnMetadata, getDateToFormat, getNumberToFormat, getReportConfig, getReportFormattingLocaleOptions, isNumericType, itemTaxConfig, modifierMixConfig, productMixConfig, salesByItemDetailConfig, salesSummaryConfig, ticketLiveConfig, ticketSummaryConfig };
1360
+ export { COLUMN_REGISTRY, type CalculateReportTotalsOptions, type ColumnAlignment, type ColumnGroupConfig, type ColumnMetadata, type ColumnPresentationConfig, DAILY_SALES_AVAILABLE_COLUMNS, DAILY_SALES_BY_GROUP_AVAILABLE_COLUMNS, DAILY_SALES_DEFAULT_VISIBLE_COLUMNS, DAILY_SALES_DISCOUNTS_AVAILABLE_COLUMNS, DAILY_SALES_DISCOUNTS_DEFAULT_VISIBLE_COLUMNS, DAILY_SALES_PAYMENTS_AVAILABLE_COLUMNS, DAILY_SALES_PAYMENTS_DEFAULT_VISIBLE_COLUMNS, DAILY_SALES_REFUNDS_VOIDS_AVAILABLE_COLUMNS, DAILY_SALES_REFUNDS_VOIDS_DEFAULT_VISIBLE_COLUMNS, DAILY_SALES_TAXES_AVAILABLE_COLUMNS, DAILY_SALES_TAXES_DEFAULT_VISIBLE_COLUMNS, DAILY_SALES_TRENDS_AVAILABLE_COLUMNS, EMPLOYEE_TIMECARD_AVAILABLE_COLUMNS, EMPLOYEE_TIMECARD_DEFAULT_VISIBLE_COLUMNS, type FieldType, type FooterCalculation, type FooterCalculationType, type FormatDateOptions, type FormatMoneyOptions, type FormatNumberOptions, type FormatTimeOptions, ITEM_TAX_AVAILABLE_COLUMNS, ITEM_TAX_DEFAULT_VISIBLE_COLUMNS, type LocaleOption, MODIFIER_MIX_AVAILABLE_COLUMNS, MODIFIER_MIX_DEFAULT_VISIBLE_COLUMNS, PRODUCT_MIX_AVAILABLE_COLUMNS, PRODUCT_MIX_CHART_FIELDS, PRODUCT_MIX_DEFAULT_VISIBLE_COLUMNS, Report, type ReportColumnKey, type ReportConfig, type ReportType, SALES_BY_ITEM_DETAIL_AVAILABLE_COLUMNS, SALES_BY_ITEM_DETAIL_DEFAULT_VISIBLE_COLUMNS, SALES_SUMMARY_AVAILABLE_COLUMNS, SALES_SUMMARY_DEFAULT_VISIBLE_COLUMNS, TICKET_LIVE_AVAILABLE_COLUMNS, TICKET_LIVE_DEFAULT_VISIBLE_COLUMNS, TICKET_SUMMARY_AVAILABLE_COLUMNS, TICKET_SUMMARY_DEFAULT_VISIBLE_COLUMNS, type TimeFormat, calculateFieldTotal, calculateReportTotals, dailySalesConfig, dailySalesDiscountsConfig, dailySalesPaymentsConfig, dailySalesRefundsVoidsConfig, dailySalesTaxesConfig, dailySalesTrendsConfig, employeeTimecardConfig, formatDate, formatFieldValue, formatFixedNumber, formatInteger, formatMoney, formatMoneyWithoutSymbol, formatPercent, formatString, formatTime, getColumnAlignment, getColumnExportHeaderLabel, getColumnMetadata, getDateToFormat, getNumberToFormat, getReportConfig, getReportFormattingLocaleOptions, isNumericType, itemTaxConfig, modifierMixConfig, productMixConfig, salesByItemDetailConfig, salesSummaryConfig, ticketLiveConfig, ticketSummaryConfig };
package/dist/index.js CHANGED
@@ -10,18 +10,18 @@ var FIELDS = {
10
10
  type: "percent",
11
11
  columnKey: "salesVarLwPct",
12
12
  footerCalculation: {
13
- type: "percentChange",
14
- numeratorField: "salesAmountNet",
15
- denominatorField: "salesAmountNetLastWeek"
13
+ type: "variancePercent",
14
+ numeratorField: "salesVarLW",
15
+ denominatorField: "salesAmountNet"
16
16
  }
17
17
  },
18
18
  salesVarLYPct: {
19
19
  type: "percent",
20
20
  columnKey: "salesVarLyPct",
21
21
  footerCalculation: {
22
- type: "percentChange",
23
- numeratorField: "salesAmountNet",
24
- denominatorField: "salesAmountNetLastYear"
22
+ type: "variancePercent",
23
+ numeratorField: "salesVarLY",
24
+ denominatorField: "salesAmountNet"
25
25
  }
26
26
  },
27
27
  salesAmountNetLastWeek: { type: "money" },
@@ -39,18 +39,18 @@ var FIELDS = {
39
39
  type: "percent",
40
40
  columnKey: "guestsVarLwPct",
41
41
  footerCalculation: {
42
- type: "percentChange",
43
- numeratorField: "guestsCount",
44
- denominatorField: "guestsCountLastWeek"
42
+ type: "variancePercent",
43
+ numeratorField: "guestsVarLW",
44
+ denominatorField: "guestsCount"
45
45
  }
46
46
  },
47
47
  guestsVarLYPct: {
48
48
  type: "percent",
49
49
  columnKey: "guestsVarLyPct",
50
50
  footerCalculation: {
51
- type: "percentChange",
52
- numeratorField: "guestsCount",
53
- denominatorField: "guestsCountLastYear"
51
+ type: "variancePercent",
52
+ numeratorField: "guestsVarLY",
53
+ denominatorField: "guestsCount"
54
54
  }
55
55
  },
56
56
  guestsCountLastWeek: { type: "number" },
@@ -68,18 +68,22 @@ var FIELDS = {
68
68
  type: "money",
69
69
  columnKey: "salesPerGuestLw",
70
70
  footerCalculation: {
71
- type: "average",
72
- numeratorField: "salesAmountNetLastWeek",
73
- denominatorField: "guestsCountLastWeek"
71
+ type: "quotientOfDifferences",
72
+ numeratorField: "salesAmountNet",
73
+ numeratorAdjustField: "salesVarLW",
74
+ denominatorField: "guestsCount",
75
+ denominatorAdjustField: "guestsVarLW"
74
76
  }
75
77
  },
76
78
  salesPerGuestLY: {
77
79
  type: "money",
78
80
  columnKey: "salesPerGuestLy",
79
81
  footerCalculation: {
80
- type: "average",
81
- numeratorField: "salesAmountNetLastYear",
82
- denominatorField: "guestsCountLastYear"
82
+ type: "quotientOfDifferences",
83
+ numeratorField: "salesAmountNet",
84
+ numeratorAdjustField: "salesVarLY",
85
+ denominatorField: "guestsCount",
86
+ denominatorAdjustField: "guestsVarLY"
83
87
  }
84
88
  }
85
89
  };
@@ -421,7 +425,16 @@ var FIELDS6 = {
421
425
  orderTypeName: { type: "string", columnKey: "orderType", filteringAvailable: false },
422
426
  taxName: { type: "string", columnKey: "taxName", filteringAvailable: false },
423
427
  taxValue: { type: "money", columnKey: "taxableSales" },
424
- taxRate: { type: "number", columnKey: "taxRate" },
428
+ taxRate: {
429
+ type: "conditional",
430
+ columnKey: "taxRate",
431
+ conditionalConfiguration: {
432
+ conditionalField: "taxIsFlat",
433
+ conditionalFormat: (field) => {
434
+ return field ? "money" : "percent";
435
+ }
436
+ }
437
+ },
425
438
  recordsCount: { type: "number", columnKey: "quantity" },
426
439
  taxableSales: { type: "money", columnKey: "taxableSales" },
427
440
  totalCollected: { type: "money", columnKey: "totalCollected" },
@@ -1427,65 +1440,54 @@ function isNumericType(type) {
1427
1440
  }
1428
1441
 
1429
1442
  // src/totals.ts
1430
- function calculateReportTotals(data, fieldConfig, opts) {
1431
- const rows = data;
1432
- const { labelField, label } = opts;
1433
- const fieldsToSum = /* @__PURE__ */ new Set();
1434
- const derivedFields = [];
1435
- for (const [field, config] of Object.entries(fieldConfig)) {
1436
- const { type, columnKey, footerCalculation: calc } = config;
1437
- if (!type || type === "string" || type === "date" || type === "time" || type === "boolean") {
1438
- continue;
1439
- }
1440
- if (calc?.type === "none") {
1441
- } else if (!calc || calc.type === "sum") {
1442
- if (columnKey || calc?.type === "sum") {
1443
- fieldsToSum.add(field);
1444
- }
1445
- } else {
1446
- derivedFields.push({ field, calc });
1447
- if (calc.numeratorField) {
1448
- fieldsToSum.add(calc.numeratorField);
1449
- }
1450
- if (calc.denominatorField) {
1451
- fieldsToSum.add(calc.denominatorField);
1452
- }
1453
- }
1443
+ var sumField = (rows, field) => rows.reduce((acc, row) => acc + (row[field] ?? 0), 0);
1444
+ var calculateFieldTotal = (rows, field, config) => {
1445
+ const { type } = config;
1446
+ if (!type || type === "string" || type === "date" || type === "time" || type === "boolean" || type === "conditional") {
1447
+ return null;
1454
1448
  }
1455
- const sums = {};
1456
- for (const field of fieldsToSum) {
1457
- sums[field] = rows.reduce((acc, row) => acc + (row[field] ?? 0), 0);
1449
+ const calc = config.footerCalculation;
1450
+ if (calc?.type === "none") {
1451
+ return 0;
1458
1452
  }
1459
- const derived = {};
1460
- for (const { field, calc } of derivedFields) {
1461
- const num = sums[calc.numeratorField ?? ""] ?? 0;
1462
- const den = sums[calc.denominatorField ?? ""] ?? 0;
1463
- if (calc.type === "percentChange") {
1464
- derived[field] = den ? (num - den) / den : 0;
1465
- } else if (calc.type === "average") {
1466
- derived[field] = den > 0 ? num / den : 0;
1467
- }
1453
+ if (!calc || calc.type === "sum") {
1454
+ return config.columnKey || calc?.type === "sum" ? sumField(rows, field) : 0;
1455
+ }
1456
+ const { numeratorField, denominatorField } = calc;
1457
+ if (!numeratorField || !denominatorField) {
1458
+ return 0;
1459
+ }
1460
+ const num = sumField(rows, numeratorField);
1461
+ const den = sumField(rows, denominatorField);
1462
+ if (calc.type === "percentChange") {
1463
+ return den ? (num - den) / den : 0;
1464
+ }
1465
+ if (calc.type === "variancePercent") {
1466
+ const prior = den - num;
1467
+ return prior ? num / prior : 0;
1468
+ }
1469
+ if (calc.type === "average") {
1470
+ return den > 0 ? num / den : 0;
1471
+ }
1472
+ if (calc.type === "quotientOfDifferences") {
1473
+ const { numeratorAdjustField, denominatorAdjustField } = calc;
1474
+ const numAdj = numeratorAdjustField ? sumField(rows, numeratorAdjustField) : 0;
1475
+ const denAdj = denominatorAdjustField ? sumField(rows, denominatorAdjustField) : 0;
1476
+ const effectiveDen = den - denAdj;
1477
+ return effectiveDen !== 0 ? (num - numAdj) / effectiveDen : 0;
1468
1478
  }
1479
+ return 0;
1480
+ };
1481
+ var calculateReportTotals = (data, fieldConfig, opts) => {
1482
+ const { labelField, label } = opts;
1469
1483
  const result = {};
1470
1484
  for (const [field, config] of Object.entries(fieldConfig)) {
1471
- const { type } = config;
1472
- if (!type || type === "string" || type === "date" || type === "time") {
1473
- result[field] = "";
1474
- } else if (type === "boolean") {
1475
- result[field] = false;
1476
- } else {
1477
- result[field] = 0;
1478
- }
1485
+ const total = calculateFieldTotal(data, field, config);
1486
+ result[field] = total ?? "";
1479
1487
  }
1480
1488
  result[labelField] = label;
1481
- for (const [field, value] of Object.entries(sums)) {
1482
- result[field] = value;
1483
- }
1484
- for (const [field, value] of Object.entries(derived)) {
1485
- result[field] = value;
1486
- }
1487
1489
  return result;
1488
- }
1490
+ };
1489
1491
 
1490
1492
  // src/reports/dailySalesRefundsVoids.ts
1491
1493
  var FIRST_COLUMN_WIDTH8 = 240;
@@ -1632,9 +1634,18 @@ var getReportFormattingLocaleOptions = (locations) => {
1632
1634
  ).values()
1633
1635
  );
1634
1636
  };
1635
- var formatFieldValue = (value, type, localeOptions) => {
1637
+ var DEFAULT_FIELD_CONFIG = { type: "string" };
1638
+ var formatFieldValue = (value, type, localeOptions, fullRecord, fieldConfig = DEFAULT_FIELD_CONFIG) => {
1636
1639
  const { currency, locale } = localeOptions;
1637
1640
  switch (type) {
1641
+ case "conditional": {
1642
+ if (!fullRecord) {
1643
+ return formatString(value);
1644
+ }
1645
+ const conditionalConfig = fieldConfig.conditionalConfiguration;
1646
+ const configType = conditionalConfig.conditionalFormat(fullRecord[conditionalConfig.conditionalField]);
1647
+ return formatFieldValue(value, configType, localeOptions, fullRecord, fieldConfig);
1648
+ }
1638
1649
  case "string":
1639
1650
  return formatString(value);
1640
1651
  case "money":
@@ -1688,6 +1699,7 @@ export {
1688
1699
  TICKET_LIVE_DEFAULT_VISIBLE_COLUMNS,
1689
1700
  TICKET_SUMMARY_AVAILABLE_COLUMNS,
1690
1701
  TICKET_SUMMARY_DEFAULT_VISIBLE_COLUMNS,
1702
+ calculateFieldTotal,
1691
1703
  calculateReportTotals,
1692
1704
  dailySalesConfig,
1693
1705
  dailySalesDiscountsConfig,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harbortouch/skytab-analytics-report-utils",
3
- "version": "0.8.2",
3
+ "version": "0.10.0",
4
4
  "description": "Centralized report column presentation configuration for SkyTab Analytics",
5
5
  "engines": {
6
6
  "node": ">=22.6.0",
@@ -22,7 +22,7 @@
22
22
  "type": "module",
23
23
  "scripts": {
24
24
  "build": "tsup",
25
- "dev": "tsup --watch",
25
+ "dev": "tsup --watch --no-clean",
26
26
  "lint:js": "eslint src",
27
27
  "lint:js:fix": "eslint src --fix",
28
28
  "lint:types": "tsc --noEmit",