@zeniai/client-epic-state 5.2.80 → 5.2.82

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.
@@ -1,3 +1,4 @@
1
+ import { isCustomerTransaction, } from '../../../entity/transaction/stateTypes/customerTransaction';
1
2
  import { isVendorTransaction } from '../../../entity/transaction/stateTypes/vendorTransaction';
2
3
  import { isEntityMatching, mergeTransactionAndEntityRecommendationsWithCOT, toLineEntity, } from '../../transactionDetail/transactionDetailLocalDataHelper';
3
4
  import { isAccountUncategorized } from '../../transactionDetail/transactionDetailLocalDataHelper';
@@ -208,7 +209,10 @@ const buildLineItemById = (transaction, recommendations) => {
208
209
  logo: accountLine.vendor.logo,
209
210
  }
210
211
  : undefined;
211
- const customer = accountLine.customer;
212
+ const customer = accountLine.customer ??
213
+ (isCustomerTransaction(transaction) === true
214
+ ? transaction.customer
215
+ : undefined);
212
216
  record.customer =
213
217
  customer != null && Boolean(customer.id) && Boolean(customer.name)
214
218
  ? {
@@ -59,11 +59,59 @@ export const localDataToSaveInvoicingSubscriptionPayload = (localData, shippingA
59
59
  };
60
60
  };
61
61
  const toEstimateDate = (value) => value != null && value !== '' ? date(value) : undefined;
62
+ const isItemLevelCoupon = (discount) => discount.entity_type === 'item_level_coupon';
63
+ const isCoupon = (discount) => discount.entity_type === 'document_level_coupon' ||
64
+ discount.entity_type === 'item_level_coupon';
65
+ /**
66
+ * Name the coupon behind a line's discount. Exact when the API links a coupon
67
+ * row to its line (`line_item_id`); otherwise only when a single item-level
68
+ * coupon applies, since then every discounted line got that one.
69
+ */
70
+ const lineDiscountLabel = (lineItem, discounts) => {
71
+ if (lineItem.discount_amount <= 0) {
72
+ return undefined;
73
+ }
74
+ const itemCoupons = discounts.filter(isItemLevelCoupon);
75
+ const linked = lineItem.id != null
76
+ ? itemCoupons.filter((coupon) => coupon.line_item_id === lineItem.id)
77
+ : [];
78
+ if (linked.length > 0) {
79
+ return [...new Set(linked.map((coupon) => coupon.description))].join(', ');
80
+ }
81
+ const names = [...new Set(itemCoupons.map((coupon) => coupon.description))];
82
+ return names.length === 1 ? names[0] : undefined;
83
+ };
84
+ /**
85
+ * One row per coupon. An item-level coupon comes back once per line it
86
+ * discounted, so its rows are summed; listing each would read as the same
87
+ * discount taken several times.
88
+ */
89
+ const mergeCouponRows = (discounts) => {
90
+ const rowsByCoupon = new Map();
91
+ discounts.filter(isCoupon).forEach((coupon) => {
92
+ // The API sends `entity_id: ""` when it has none; key those by name so
93
+ // two unrelated coupons are not merged into one row.
94
+ const couponKey = coupon.entity_id != null && coupon.entity_id !== ''
95
+ ? coupon.entity_id
96
+ : coupon.description;
97
+ const key = `${coupon.entity_type}:${couponKey}`;
98
+ const row = rowsByCoupon.get(key);
99
+ rowsByCoupon.set(key, {
100
+ amount: Math.round(((row?.amount ?? 0) + coupon.amount) * 100) / 100,
101
+ description: coupon.description,
102
+ });
103
+ });
104
+ return [...rowsByCoupon.values()];
105
+ };
62
106
  const mapInvoiceEstimate = (payload, money) => payload == null
63
107
  ? undefined
64
108
  : {
65
109
  amountDue: money(payload.amount_due),
66
110
  couponDiscountTotal: money(payload.coupon_discount_total),
111
+ coupons: mergeCouponRows(payload.discounts ?? []).map((coupon) => ({
112
+ amount: money(coupon.amount),
113
+ description: coupon.description,
114
+ })),
67
115
  creditsApplied: money(payload.credits_applied),
68
116
  dueDate: toEstimateDate(payload.due_date),
69
117
  grossSubTotal: money(payload.gross_sub_total),
@@ -74,6 +122,8 @@ const mapInvoiceEstimate = (payload, money) => payload == null
74
122
  dateTo: toEstimateDate(lineItem.date_to),
75
123
  description: lineItem.description,
76
124
  discountAmount: money(lineItem.discount_amount),
125
+ discountLabel: lineDiscountLabel(lineItem, payload.discounts ?? []),
126
+ grossAmount: money(lineItem.gross_amount ?? lineItem.amount),
77
127
  quantity: lineItem.quantity,
78
128
  taxAmount: lineItem.tax_amount != null
79
129
  ? money(lineItem.tax_amount)
package/lib/index.d.ts CHANGED
@@ -1128,7 +1128,7 @@ export { getInvoicingCustomerFormView } from './view/invoicing/editInvoicingCust
1128
1128
  export type { InvoicingCustomerFormView } from './view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector';
1129
1129
  export { clearInvoicingSubscriptionEstimate, fetchInvoicingSubscriptionEstimate, fetchInvoicingSubscriptionFormPage, initializeInvoicingSubscriptionAddress, resetEditInvoicingSubscriptionDetailView, submitInvoicingSubscriptionForm, updateInvoicingSubscriptionFormDraft, } from './view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer';
1130
1130
  export type { SubmitInvoicingSubscriptionFormPayload, UpdateInvoicingSubscriptionFormDraftPayload, } from './view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer';
1131
- export type { InvoicingSubscriptionEstimateLineItem, InvoicingSubscriptionFormLocalData, InvoicingSubscriptionInvoiceEstimate, } from './view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewState';
1131
+ export type { InvoicingSubscriptionEstimateCoupon, InvoicingSubscriptionEstimateLineItem, InvoicingSubscriptionFormLocalData, InvoicingSubscriptionInvoiceEstimate, } from './view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewState';
1132
1132
  export { INVOICING_SUBSCRIPTION_SHIPPING_ADDRESS_TYPE, entityToInvoicingSubscriptionFormLocalData, initialInvoicingSubscriptionFormLocalData, invoicingSubscriptionTodayIso, toInvoicingSubscriptionQuantity, } from './view/invoicing/editInvoicingSubscriptionDetailView/invoicingSubscriptionFormConfig';
1133
1133
  export { getEditInvoicingSubscriptionDetailViewState, getInvoicingSubscriptionFormView, } from './view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector';
1134
1134
  export type { EditInvoicingSubscriptionDetailView, InvoicingSubscriptionEstimateKind, InvoicingSubscriptionEstimateView, InvoicingSubscriptionFormView, InvoicingSubscriptionOption, } from './view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector';
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.hasTransactionLocalDataChanges = exports.isAnyItemWithUncategorizedExpenseAccount = exports.getSnackbarMessageForTransactionReview = exports.mergeTabSpecificLineItems = exports.getLineItemsByTransactionIdsFromLocalData = exports.getLineItemsByTransactionsIdsFromResponse = exports.checkIfAllLineItemsAreCategoryClassFilled = exports.setEntityRecommendationForLineIdInLocalData = exports.toSetAllLineItemsToCategoryClass = exports.toTransactionDetailLocalData = exports.shouldAutoSelectAndAdd = exports.MAX_SELECTION_LIMIT = exports.filterAutoTabLineItems = exports.removeTransactionFromTabView = exports.getPendingReviewLineIdsFromTransaction = void 0;
4
+ const customerTransaction_1 = require("../../../entity/transaction/stateTypes/customerTransaction");
4
5
  const vendorTransaction_1 = require("../../../entity/transaction/stateTypes/vendorTransaction");
5
6
  const transactionDetailLocalDataHelper_1 = require("../../transactionDetail/transactionDetailLocalDataHelper");
6
7
  const transactionDetailLocalDataHelper_2 = require("../../transactionDetail/transactionDetailLocalDataHelper");
@@ -216,7 +217,10 @@ const buildLineItemById = (transaction, recommendations) => {
216
217
  logo: accountLine.vendor.logo,
217
218
  }
218
219
  : undefined;
219
- const customer = accountLine.customer;
220
+ const customer = accountLine.customer ??
221
+ ((0, customerTransaction_1.isCustomerTransaction)(transaction) === true
222
+ ? transaction.customer
223
+ : undefined);
220
224
  record.customer =
221
225
  customer != null && Boolean(customer.id) && Boolean(customer.name)
222
226
  ? {
@@ -82,8 +82,17 @@ interface InvoicingSubscriptionEstimateLineItemPayload {
82
82
  unit_amount: number;
83
83
  date_from?: string;
84
84
  date_to?: string;
85
+ gross_amount?: number;
86
+ id?: string;
85
87
  tax_amount?: number | null;
86
88
  }
89
+ interface InvoicingSubscriptionEstimateDiscountPayload {
90
+ amount: number;
91
+ description: string;
92
+ entity_type: string;
93
+ entity_id?: string;
94
+ line_item_id?: string | null;
95
+ }
87
96
  interface InvoicingSubscriptionInvoiceEstimatePayload {
88
97
  amount_due: number;
89
98
  coupon_discount_total: number;
@@ -94,6 +103,7 @@ interface InvoicingSubscriptionInvoiceEstimatePayload {
94
103
  sub_total: number;
95
104
  tax: number | null;
96
105
  total: number;
106
+ discounts?: InvoicingSubscriptionEstimateDiscountPayload[];
97
107
  due_date?: string;
98
108
  invoice_date?: string;
99
109
  }
@@ -63,11 +63,59 @@ const localDataToSaveInvoicingSubscriptionPayload = (localData, shippingAddress)
63
63
  };
64
64
  exports.localDataToSaveInvoicingSubscriptionPayload = localDataToSaveInvoicingSubscriptionPayload;
65
65
  const toEstimateDate = (value) => value != null && value !== '' ? (0, zeniDayJS_1.date)(value) : undefined;
66
+ const isItemLevelCoupon = (discount) => discount.entity_type === 'item_level_coupon';
67
+ const isCoupon = (discount) => discount.entity_type === 'document_level_coupon' ||
68
+ discount.entity_type === 'item_level_coupon';
69
+ /**
70
+ * Name the coupon behind a line's discount. Exact when the API links a coupon
71
+ * row to its line (`line_item_id`); otherwise only when a single item-level
72
+ * coupon applies, since then every discounted line got that one.
73
+ */
74
+ const lineDiscountLabel = (lineItem, discounts) => {
75
+ if (lineItem.discount_amount <= 0) {
76
+ return undefined;
77
+ }
78
+ const itemCoupons = discounts.filter(isItemLevelCoupon);
79
+ const linked = lineItem.id != null
80
+ ? itemCoupons.filter((coupon) => coupon.line_item_id === lineItem.id)
81
+ : [];
82
+ if (linked.length > 0) {
83
+ return [...new Set(linked.map((coupon) => coupon.description))].join(', ');
84
+ }
85
+ const names = [...new Set(itemCoupons.map((coupon) => coupon.description))];
86
+ return names.length === 1 ? names[0] : undefined;
87
+ };
88
+ /**
89
+ * One row per coupon. An item-level coupon comes back once per line it
90
+ * discounted, so its rows are summed; listing each would read as the same
91
+ * discount taken several times.
92
+ */
93
+ const mergeCouponRows = (discounts) => {
94
+ const rowsByCoupon = new Map();
95
+ discounts.filter(isCoupon).forEach((coupon) => {
96
+ // The API sends `entity_id: ""` when it has none; key those by name so
97
+ // two unrelated coupons are not merged into one row.
98
+ const couponKey = coupon.entity_id != null && coupon.entity_id !== ''
99
+ ? coupon.entity_id
100
+ : coupon.description;
101
+ const key = `${coupon.entity_type}:${couponKey}`;
102
+ const row = rowsByCoupon.get(key);
103
+ rowsByCoupon.set(key, {
104
+ amount: Math.round(((row?.amount ?? 0) + coupon.amount) * 100) / 100,
105
+ description: coupon.description,
106
+ });
107
+ });
108
+ return [...rowsByCoupon.values()];
109
+ };
66
110
  const mapInvoiceEstimate = (payload, money) => payload == null
67
111
  ? undefined
68
112
  : {
69
113
  amountDue: money(payload.amount_due),
70
114
  couponDiscountTotal: money(payload.coupon_discount_total),
115
+ coupons: mergeCouponRows(payload.discounts ?? []).map((coupon) => ({
116
+ amount: money(coupon.amount),
117
+ description: coupon.description,
118
+ })),
71
119
  creditsApplied: money(payload.credits_applied),
72
120
  dueDate: toEstimateDate(payload.due_date),
73
121
  grossSubTotal: money(payload.gross_sub_total),
@@ -78,6 +126,8 @@ const mapInvoiceEstimate = (payload, money) => payload == null
78
126
  dateTo: toEstimateDate(lineItem.date_to),
79
127
  description: lineItem.description,
80
128
  discountAmount: money(lineItem.discount_amount),
129
+ discountLabel: lineDiscountLabel(lineItem, payload.discounts ?? []),
130
+ grossAmount: money(lineItem.gross_amount ?? lineItem.amount),
81
131
  quantity: lineItem.quantity,
82
132
  taxAmount: lineItem.tax_amount != null
83
133
  ? money(lineItem.tax_amount)
@@ -41,19 +41,32 @@ export interface InvoicingSubscriptionFormLocalData {
41
41
  setupFee?: Amount;
42
42
  }
43
43
  export interface InvoicingSubscriptionEstimateLineItem {
44
+ /** After the line's own (item-level) coupons. */
44
45
  amount: Amount;
45
46
  description: string;
47
+ /** Item-level coupons on this line; invoice-level coupons are not spread here. */
46
48
  discountAmount: Amount;
49
+ /** Before any coupon, so the lines add up to `grossSubTotal`. */
50
+ grossAmount: Amount;
47
51
  quantity: number;
48
52
  unitAmount: Amount;
49
53
  dateFrom?: ZeniDate;
50
54
  dateTo?: ZeniDate;
55
+ /** The coupon behind `discountAmount`, when it can be told; see the mapper. */
56
+ discountLabel?: string;
51
57
  taxAmount?: Amount;
52
58
  }
59
+ /** A coupon the estimate applies, named, for the totals. */
60
+ export interface InvoicingSubscriptionEstimateCoupon {
61
+ amount: Amount;
62
+ description: string;
63
+ }
53
64
  /** An invoice the save would raise now, or the next one it would raise later. */
54
65
  export interface InvoicingSubscriptionInvoiceEstimate {
55
66
  amountDue: Amount;
56
67
  couponDiscountTotal: Amount;
68
+ /** Item- and invoice-level coupons, one row each; they sum to `couponDiscountTotal`. */
69
+ coupons: InvoicingSubscriptionEstimateCoupon[];
57
70
  /** Refundable credits plus excess payments; promotional credits are separate. */
58
71
  creditsApplied: Amount;
59
72
  grossSubTotal: Amount;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeniai/client-epic-state",
3
- "version": "5.2.80",
3
+ "version": "5.2.82",
4
4
  "description": "Shared module between Web & Mobile containing required abstractions for state management, async network communication. ",
5
5
  "main": "lib/index.js",
6
6
  "module": "lib/esm/index.js",