@zeniai/client-epic-state 5.2.74 → 5.2.76
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/lib/commonStateTypes/common.d.ts +3 -0
- package/lib/commonStateTypes/common.js +10 -1
- package/lib/entity/invoicing/coupon/couponSelector.d.ts +1 -0
- package/lib/entity/invoicing/coupon/couponSelector.js +5 -0
- package/lib/epic.d.ts +2 -1
- package/lib/epic.js +2 -0
- package/lib/esm/commonStateTypes/common.js +8 -0
- package/lib/esm/entity/invoicing/coupon/couponSelector.js +4 -0
- package/lib/esm/epic.js +2 -0
- package/lib/esm/index.js +3 -3
- package/lib/esm/view/companyTaskManagerView/epics/fetchCompanyTaskManagerViewEpic.js +140 -9
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewPayload.js +55 -0
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer.js +35 -1
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.js +53 -1
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionEstimateEpic.js +49 -0
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +18 -13
- package/lib/esm/view/taskManager/taskListView/taskListSelector.js +70 -19
- package/lib/index.d.ts +5 -5
- package/lib/index.js +18 -15
- package/lib/view/companyTaskManagerView/epics/fetchCompanyTaskManagerViewEpic.d.ts +1 -1
- package/lib/view/companyTaskManagerView/epics/fetchCompanyTaskManagerViewEpic.js +140 -9
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewPayload.d.ts +46 -1
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewPayload.js +57 -1
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer.d.ts +10 -2
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer.js +36 -2
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.d.ts +20 -1
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.js +55 -2
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewState.d.ts +58 -0
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionEstimateEpic.d.ts +7 -0
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionEstimateEpic.js +53 -0
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.d.ts +8 -0
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +21 -14
- package/lib/view/taskManager/taskListView/taskListSelector.d.ts +13 -2
- package/lib/view/taskManager/taskListView/taskListSelector.js +70 -19
- package/package.json +1 -1
|
@@ -7,19 +7,41 @@ const sortOrderPayload_1 = require("../../../commonPayloadTypes/sortOrderPayload
|
|
|
7
7
|
const tagReducer_1 = require("../../../entity/tag/tagReducer");
|
|
8
8
|
const taskReducer_1 = require("../../../entity/task/taskReducer");
|
|
9
9
|
const responsePayload_1 = require("../../../responsePayload");
|
|
10
|
+
const zeniDayJS_1 = require("../../../zeniDayJS");
|
|
10
11
|
const companyTaskManagerViewReducer_1 = require("../companyTaskManagerViewReducer");
|
|
11
12
|
const DEFAULT_PAGE_SIZE = 100;
|
|
12
|
-
|
|
13
|
+
// Mutates the accumulator rather than spreading it: spreading rebuilds the whole
|
|
14
|
+
// map on every entry, which is O(n^2) over a tenant's tag list.
|
|
15
|
+
const toTagNameByID = (state) => Object.entries(state.tagState.tagByID).reduce((result, [tagId, tag]) => {
|
|
16
|
+
const name = tag?.name;
|
|
17
|
+
if (name != null && name !== '') {
|
|
18
|
+
result[tagId] = name;
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}, {});
|
|
22
|
+
const hasTagCategory = (filters) => (filters?.categories ?? []).some((category) => category.field === 'tag');
|
|
23
|
+
const fetchCompanyTaskManagerViewEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(companyTaskManagerViewReducer_1.fetchCompanyTaskManagerView.match), (0, operators_1.switchMap)((action) => {
|
|
13
24
|
const { from: fromOffset, searchString, sortKey, sortOrder, filters, } = action.payload;
|
|
14
25
|
const queryValue = {
|
|
15
26
|
size: DEFAULT_PAGE_SIZE,
|
|
16
27
|
from: fromOffset,
|
|
17
28
|
global_search: searchString ?? '',
|
|
29
|
+
// TasksEsSearch defaults this to true, which AND-injects an
|
|
30
|
+
// `assignees = <caller>` filter. Cockpit's task list is a cross-tenant
|
|
31
|
+
// view grouped by company (and offers an Assignee filter), so it must
|
|
32
|
+
// NOT be scoped to the operator — otherwise every category filter
|
|
33
|
+
// AND's down to their own tasks and returns nothing. The metrics panel
|
|
34
|
+
// deliberately passes `true`; this list deliberately passes `false`.
|
|
35
|
+
is_filter_by_logged_in_user: false,
|
|
18
36
|
sort_by: toSortKeyPayload(sortKey),
|
|
19
37
|
sort_order: (0, sortOrderPayload_1.getSortOrder)(sortOrder),
|
|
20
38
|
filter_join: filters?.categoryCombinationOperator === 'AND' ? 'and' : 'or',
|
|
21
39
|
...(filters?.categories != null && filters.categories.length > 0
|
|
22
|
-
? {
|
|
40
|
+
? {
|
|
41
|
+
// Only walk the tag map when a tag filter is actually in play;
|
|
42
|
+
// otherwise every dispatch pays for a map nothing reads.
|
|
43
|
+
filter_by: toFilterByPayload(filters, hasTagCategory(filters) ? toTagNameByID(state$.value) : {}),
|
|
44
|
+
}
|
|
23
45
|
: {}),
|
|
24
46
|
task_group_scopes: filters?.taskGroupScopes != null && filters.taskGroupScopes.length > 0
|
|
25
47
|
? filters.taskGroupScopes
|
|
@@ -67,14 +89,123 @@ const toSortKeyPayload = (sortKey) => {
|
|
|
67
89
|
return sortKey;
|
|
68
90
|
}
|
|
69
91
|
};
|
|
70
|
-
|
|
92
|
+
// Cockpit filter-category keys are UI identifiers; the ES index uses its own
|
|
93
|
+
// field names (see TasksEsSearch.get). Anything not listed here already matches
|
|
94
|
+
// the contract (assignees, priority, status, company_id).
|
|
95
|
+
const ES_FIELD_BY_CATEGORY = {
|
|
96
|
+
creationDate: 'create_time',
|
|
97
|
+
dueDate: 'due_date',
|
|
98
|
+
tag: 'tags',
|
|
99
|
+
};
|
|
100
|
+
// ES rejects `is` / `is_not` on date fields — they only accept `between`, whose
|
|
101
|
+
// values are the [start, end] pair the dateRange picker already emits.
|
|
102
|
+
const ES_DATE_FIELDS = new Set(['create_time', 'due_date']);
|
|
103
|
+
// The cockpit date picker hands over ZeniDate objects; once filters have round-
|
|
104
|
+
// tripped through redux they can arrive as ISO or date-only strings instead.
|
|
105
|
+
// Both have to reach ES as instants: `create_time`/`update_time` carry a real
|
|
106
|
+
// timestamp, so a date-only upper bound lands on midnight and drops everything
|
|
107
|
+
// created later that day — a single-day Created filter would return almost
|
|
108
|
+
// nothing, which is the empty state this epic exists to fix.
|
|
109
|
+
// A real check rather than `value as ZeniDate`: the cast would silently lie
|
|
110
|
+
// about a raw Date, a number, or anything else redux hands back, leaving the
|
|
111
|
+
// runtime guard as the only thing standing between us and a crash.
|
|
112
|
+
const isZeniDateLike = (value) => value != null &&
|
|
113
|
+
typeof value.isValid === 'function' &&
|
|
114
|
+
typeof value.format === 'function';
|
|
115
|
+
const CALENDAR_DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
|
|
116
|
+
const toDateBound = (value, edge) => {
|
|
117
|
+
// A bare `YYYY-MM-DD` carries no instant to misread — use it verbatim. This
|
|
118
|
+
// is the only genuinely timezone-proof shape, which is why it is worth a
|
|
119
|
+
// fast path of its own.
|
|
120
|
+
if (typeof value === 'string' && CALENDAR_DATE_ONLY.test(value)) {
|
|
121
|
+
return edge === 'start'
|
|
122
|
+
? `${value}T00:00:00.000Z`
|
|
123
|
+
: `${value}T23:59:59.999Z`;
|
|
124
|
+
}
|
|
125
|
+
// Everything else came from the picker, and the picker emits LOCAL midnight:
|
|
126
|
+
// `ZeniDatePicker` hands react-datepicker's local-midnight Date to
|
|
127
|
+
// `zeniDate(date.toISOString())`. So the instant encodes "midnight on the day
|
|
128
|
+
// the user clicked, in the user's zone" — and the day they clicked is
|
|
129
|
+
// recovered by reading it locally, never in UTC.
|
|
130
|
+
//
|
|
131
|
+
// Reading it in UTC subtracts the offset for every UTC+ operator: clicking
|
|
132
|
+
// "Mar 4" in Asia/Kolkata stores 2026-03-03T18:30:00Z, which is the 3rd in
|
|
133
|
+
// UTC. A single-day filter then returns the wrong day's tasks rather than
|
|
134
|
+
// none, and US-based testing never sees it.
|
|
135
|
+
const parsed = typeof value === 'string' ? (0, zeniDayJS_1.date)(value) : value;
|
|
136
|
+
if (!isZeniDateLike(parsed) || !parsed.isValid()) {
|
|
137
|
+
// Drop the row rather than send garbage: ES rejects the whole request on
|
|
138
|
+
// one bad bound, taking the other filters down with it.
|
|
139
|
+
return '';
|
|
140
|
+
}
|
|
141
|
+
const ymd = parsed.format('YYYY-MM-DD');
|
|
142
|
+
return edge === 'start' ? `${ymd}T00:00:00.000Z` : `${ymd}T23:59:59.999Z`;
|
|
143
|
+
};
|
|
144
|
+
const toFilterByPayload = (filters, tagNameByID) => {
|
|
71
145
|
if (filters.categories == null) {
|
|
72
146
|
return [];
|
|
73
147
|
}
|
|
74
|
-
return filters.categories.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
148
|
+
return filters.categories.flatMap((category) => {
|
|
149
|
+
const categoryField = category.field ?? '';
|
|
150
|
+
const field = ES_FIELD_BY_CATEGORY[categoryField] ?? categoryField;
|
|
151
|
+
// The dropdown is keyed by tagId, but ES matches `tags` on the tag NAME,
|
|
152
|
+
// so resolve through the tag entity this epic itself populates. Unknown ids
|
|
153
|
+
// fall through unchanged rather than being dropped.
|
|
154
|
+
const isDateField = ES_DATE_FIELDS.has(field);
|
|
155
|
+
// `between` is exactly [from, to]. A half-filled picker (one value) makes ES
|
|
156
|
+
// reject the query outright, and a longer array would quietly bind extras to
|
|
157
|
+
// `end` — both land the user back on an unexplained empty page, which is the
|
|
158
|
+
// failure this epic exists to remove. Drop the row instead.
|
|
159
|
+
if (isDateField && category.values.length !== 2) {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
const values = category.values.map((value, index) => {
|
|
163
|
+
if (isDateField) {
|
|
164
|
+
// [from, to] — widen the upper bound to the end of its day.
|
|
165
|
+
return toDateBound(value, index === 0 ? 'start' : 'end');
|
|
166
|
+
}
|
|
167
|
+
const raw = String(value);
|
|
168
|
+
if (field !== 'tags') {
|
|
169
|
+
return raw;
|
|
170
|
+
}
|
|
171
|
+
// An id we cannot resolve would reach ES verbatim and match nothing —
|
|
172
|
+
// another silent empty page. Can happen when a filter is hydrated from the
|
|
173
|
+
// URL before `updateTags` has populated `tagByID`, so keep the value
|
|
174
|
+
// (dropping it would widen the result set) but make it visible in logs.
|
|
175
|
+
const tagName = tagNameByID[raw];
|
|
176
|
+
if (tagName == null) {
|
|
177
|
+
console.debug(`[taskManager] unresolved tagId "${raw}" sent to ES verbatim; expect no matches`);
|
|
178
|
+
}
|
|
179
|
+
return tagName ?? raw;
|
|
180
|
+
});
|
|
181
|
+
// `toDateBound` returns '' for a value it cannot parse. Honour that here:
|
|
182
|
+
// emitting a `between` with an empty bound fails the whole ES query, which
|
|
183
|
+
// takes every OTHER filter in the same request down with it — the silent
|
|
184
|
+
// empty page this epic exists to remove, from one unparseable value that a
|
|
185
|
+
// URL-hydrated filter can supply.
|
|
186
|
+
if (isDateField && values.some((value) => value === '')) {
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
// ES has no date-exclusion operator: `is`/`is_not` are rejected on date
|
|
190
|
+
// fields, and `between` can only select a range, never exclude one. The
|
|
191
|
+
// operator dropdown still offers "is not" for every category, so an excluded
|
|
192
|
+
// date range would otherwise be sent as a plain `between` — returning exactly
|
|
193
|
+
// the rows the user asked to hide. Drop the row instead: a superset is
|
|
194
|
+
// wrong-but-obvious, an inverted result is wrong-and-invisible.
|
|
195
|
+
if (isDateField && category.matchingOperator !== 'equal') {
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
198
|
+
return [
|
|
199
|
+
{
|
|
200
|
+
field,
|
|
201
|
+
operator: isDateField
|
|
202
|
+
? 'between'
|
|
203
|
+
: category.matchingOperator === 'equal'
|
|
204
|
+
? 'is'
|
|
205
|
+
: 'is_not',
|
|
206
|
+
values,
|
|
207
|
+
join: category.valuesCombinationOperator === 'ALL' ? 'and' : 'or',
|
|
208
|
+
},
|
|
209
|
+
];
|
|
210
|
+
});
|
|
80
211
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { InvoicingSubscriptionPayload } from '../../../entity/invoicing/invoicingSubscription/invoicingSubscriptionPayload';
|
|
2
2
|
import { ZeniAPIResponse } from '../../../responsePayload';
|
|
3
3
|
import { AddressUpdatableInfo } from '../../addressView/addressViewState';
|
|
4
|
-
import { InvoicingSubscriptionFormLocalData } from './editInvoicingSubscriptionDetailViewState';
|
|
4
|
+
import { InvoicingSubscriptionEstimate, InvoicingSubscriptionFormLocalData } from './editInvoicingSubscriptionDetailViewState';
|
|
5
5
|
export type SaveInvoicingSubscriptionResponse = ZeniAPIResponse<InvoicingSubscriptionPayload>;
|
|
6
6
|
/**
|
|
7
7
|
* Build the create/update subscription request body from the form draft. The
|
|
@@ -74,3 +74,48 @@ export type ScheduleChargeBody = {
|
|
|
74
74
|
};
|
|
75
75
|
export type InvoicingSubscriptionActionBody = ApplyCouponBody | RemoveCouponBody | AddChargesBody | PauseSubscriptionBody | CancelSubscriptionBody | ChangePlanBody | ChangeTermEndBody | ScheduleChargeBody | Record<string, never>;
|
|
76
76
|
export type InvoicingSubscriptionDetailResponse = ZeniAPIResponse<InvoicingSubscriptionPayload>;
|
|
77
|
+
interface InvoicingSubscriptionEstimateLineItemPayload {
|
|
78
|
+
amount: number;
|
|
79
|
+
description: string;
|
|
80
|
+
discount_amount: number;
|
|
81
|
+
quantity: number;
|
|
82
|
+
unit_amount: number;
|
|
83
|
+
date_from?: string;
|
|
84
|
+
date_to?: string;
|
|
85
|
+
tax_amount?: number | null;
|
|
86
|
+
}
|
|
87
|
+
interface InvoicingSubscriptionInvoiceEstimatePayload {
|
|
88
|
+
amount_due: number;
|
|
89
|
+
coupon_discount_total: number;
|
|
90
|
+
credits_applied: number;
|
|
91
|
+
gross_sub_total: number;
|
|
92
|
+
line_items: InvoicingSubscriptionEstimateLineItemPayload[];
|
|
93
|
+
promotional_credits_applied: number;
|
|
94
|
+
sub_total: number;
|
|
95
|
+
tax: number | null;
|
|
96
|
+
total: number;
|
|
97
|
+
due_date?: string;
|
|
98
|
+
invoice_date?: string;
|
|
99
|
+
}
|
|
100
|
+
export interface InvoicingSubscriptionEstimatePayload {
|
|
101
|
+
currency_code: string;
|
|
102
|
+
currency_symbol: string;
|
|
103
|
+
invoice_estimate: InvoicingSubscriptionInvoiceEstimatePayload | null;
|
|
104
|
+
next_invoice_estimate: InvoicingSubscriptionInvoiceEstimatePayload | null;
|
|
105
|
+
proration_estimate: {
|
|
106
|
+
document_type: string;
|
|
107
|
+
invoice_estimate: InvoicingSubscriptionInvoiceEstimatePayload | null;
|
|
108
|
+
net: number;
|
|
109
|
+
} | null;
|
|
110
|
+
reason: string;
|
|
111
|
+
subscription_estimate: {
|
|
112
|
+
becomes_non_renewing_after_this_term?: boolean;
|
|
113
|
+
current_term_end?: string;
|
|
114
|
+
current_term_start?: string;
|
|
115
|
+
next_billing_at?: string | null;
|
|
116
|
+
} | null;
|
|
117
|
+
will_be_invoiced: boolean;
|
|
118
|
+
}
|
|
119
|
+
export type InvoicingSubscriptionEstimateResponse = ZeniAPIResponse<InvoicingSubscriptionEstimatePayload>;
|
|
120
|
+
export declare const mapInvoicingSubscriptionEstimatePayloadToState: (payload: InvoicingSubscriptionEstimatePayload) => InvoicingSubscriptionEstimate;
|
|
121
|
+
export {};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.localDataToSaveInvoicingSubscriptionPayload = void 0;
|
|
3
|
+
exports.mapInvoicingSubscriptionEstimatePayloadToState = exports.localDataToSaveInvoicingSubscriptionPayload = void 0;
|
|
4
|
+
const amount_1 = require("../../../commonStateTypes/amount");
|
|
5
|
+
const zeniDayJS_1 = require("../../../zeniDayJS");
|
|
4
6
|
const addressViewPayload_1 = require("../../addressView/addressViewPayload");
|
|
5
7
|
const invoicingSubscriptionFormConfig_1 = require("./invoicingSubscriptionFormConfig");
|
|
6
8
|
/**
|
|
@@ -60,3 +62,57 @@ const localDataToSaveInvoicingSubscriptionPayload = (localData, shippingAddress)
|
|
|
60
62
|
};
|
|
61
63
|
};
|
|
62
64
|
exports.localDataToSaveInvoicingSubscriptionPayload = localDataToSaveInvoicingSubscriptionPayload;
|
|
65
|
+
const toEstimateDate = (value) => value != null && value !== '' ? (0, zeniDayJS_1.date)(value) : undefined;
|
|
66
|
+
const mapInvoiceEstimate = (payload, money) => payload == null
|
|
67
|
+
? undefined
|
|
68
|
+
: {
|
|
69
|
+
amountDue: money(payload.amount_due),
|
|
70
|
+
couponDiscountTotal: money(payload.coupon_discount_total),
|
|
71
|
+
creditsApplied: money(payload.credits_applied),
|
|
72
|
+
dueDate: toEstimateDate(payload.due_date),
|
|
73
|
+
grossSubTotal: money(payload.gross_sub_total),
|
|
74
|
+
invoiceDate: toEstimateDate(payload.invoice_date),
|
|
75
|
+
lineItems: payload.line_items.map((lineItem) => ({
|
|
76
|
+
amount: money(lineItem.amount),
|
|
77
|
+
dateFrom: toEstimateDate(lineItem.date_from),
|
|
78
|
+
dateTo: toEstimateDate(lineItem.date_to),
|
|
79
|
+
description: lineItem.description,
|
|
80
|
+
discountAmount: money(lineItem.discount_amount),
|
|
81
|
+
quantity: lineItem.quantity,
|
|
82
|
+
taxAmount: lineItem.tax_amount != null
|
|
83
|
+
? money(lineItem.tax_amount)
|
|
84
|
+
: undefined,
|
|
85
|
+
unitAmount: money(lineItem.unit_amount),
|
|
86
|
+
})),
|
|
87
|
+
promotionalCreditsApplied: money(payload.promotional_credits_applied),
|
|
88
|
+
subTotal: money(payload.sub_total),
|
|
89
|
+
tax: payload.tax != null ? money(payload.tax) : undefined,
|
|
90
|
+
total: money(payload.total),
|
|
91
|
+
};
|
|
92
|
+
const mapInvoicingSubscriptionEstimatePayloadToState = (payload) => {
|
|
93
|
+
const money = (amount) => (0, amount_1.toAmount)(amount, payload.currency_code, payload.currency_symbol);
|
|
94
|
+
const term = payload.subscription_estimate;
|
|
95
|
+
const proration = payload.proration_estimate;
|
|
96
|
+
return {
|
|
97
|
+
invoice: mapInvoiceEstimate(payload.invoice_estimate, money),
|
|
98
|
+
nextInvoice: mapInvoiceEstimate(payload.next_invoice_estimate, money),
|
|
99
|
+
proration: proration != null
|
|
100
|
+
? {
|
|
101
|
+
documentType: proration.document_type,
|
|
102
|
+
invoice: mapInvoiceEstimate(proration.invoice_estimate, money),
|
|
103
|
+
net: money(proration.net),
|
|
104
|
+
}
|
|
105
|
+
: undefined,
|
|
106
|
+
reason: payload.reason,
|
|
107
|
+
term: term != null
|
|
108
|
+
? {
|
|
109
|
+
becomesNonRenewing: term.becomes_non_renewing_after_this_term === true,
|
|
110
|
+
currentTermEnd: toEstimateDate(term.current_term_end),
|
|
111
|
+
currentTermStart: toEstimateDate(term.current_term_start),
|
|
112
|
+
nextBillingAt: toEstimateDate(term.next_billing_at),
|
|
113
|
+
}
|
|
114
|
+
: undefined,
|
|
115
|
+
willBeInvoiced: payload.will_be_invoiced,
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
exports.mapInvoicingSubscriptionEstimatePayloadToState = mapInvoicingSubscriptionEstimatePayloadToState;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ID } from '../../../commonStateTypes/common';
|
|
2
2
|
import { ZeniAPIStatus } from '../../../responsePayload';
|
|
3
3
|
import { InvoicingSubscriptionActionBody } from './editInvoicingSubscriptionDetailViewPayload';
|
|
4
|
-
import { EditInvoicingSubscriptionDetailViewState, InvoicingSubscriptionFormLocalData } from './editInvoicingSubscriptionDetailViewState';
|
|
4
|
+
import { EditInvoicingSubscriptionDetailViewState, InvoicingSubscriptionEstimate, InvoicingSubscriptionFormLocalData } from './editInvoicingSubscriptionDetailViewState';
|
|
5
5
|
/** Push the full form draft into CES (edit tree when `id` is present). */
|
|
6
6
|
export interface UpdateInvoicingSubscriptionFormDraftPayload {
|
|
7
7
|
localData: InvoicingSubscriptionFormLocalData;
|
|
@@ -59,6 +59,14 @@ export declare const runInvoicingSubscriptionAction: import("@reduxjs/toolkit").
|
|
|
59
59
|
id?: ID;
|
|
60
60
|
} | undefined, "editInvoicingSubscriptionDetailView/resetEditInvoicingSubscriptionDetailView">, initializeInvoicingSubscriptionAddress: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[payload: InitializeInvoicingSubscriptionAddressPayload], InitializeInvoicingSubscriptionAddressPayload, "editInvoicingSubscriptionDetailView/initializeInvoicingSubscriptionAddress", never, never>, claimInvoicingSubscriptionShippingAddress: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
61
61
|
id: ID;
|
|
62
|
-
}, "editInvoicingSubscriptionDetailView/claimInvoicingSubscriptionShippingAddress">, syncInvoicingSubscriptionCoupons: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[payload: SyncInvoicingSubscriptionCouponsPayload], SyncInvoicingSubscriptionCouponsPayload, "editInvoicingSubscriptionDetailView/syncInvoicingSubscriptionCoupons", never, never
|
|
62
|
+
}, "editInvoicingSubscriptionDetailView/claimInvoicingSubscriptionShippingAddress">, syncInvoicingSubscriptionCoupons: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[payload: SyncInvoicingSubscriptionCouponsPayload], SyncInvoicingSubscriptionCouponsPayload, "editInvoicingSubscriptionDetailView/syncInvoicingSubscriptionCoupons", never, never>, fetchInvoicingSubscriptionEstimate: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
63
|
+
id?: ID;
|
|
64
|
+
}, "editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionEstimate">, updateInvoicingSubscriptionEstimate: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
65
|
+
estimate: InvoicingSubscriptionEstimate;
|
|
66
|
+
id?: ID;
|
|
67
|
+
}, "editInvoicingSubscriptionDetailView/updateInvoicingSubscriptionEstimate">, updateInvoicingSubscriptionEstimateFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
68
|
+
status: ZeniAPIStatus;
|
|
69
|
+
id?: ID;
|
|
70
|
+
}, "editInvoicingSubscriptionDetailView/updateInvoicingSubscriptionEstimateFailure">, clearInvoicingSubscriptionEstimate: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"editInvoicingSubscriptionDetailView/clearInvoicingSubscriptionEstimate">;
|
|
63
71
|
declare const _default: import("redux").Reducer<EditInvoicingSubscriptionDetailViewState>;
|
|
64
72
|
export default _default;
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var _a;
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.syncInvoicingSubscriptionCoupons = exports.claimInvoicingSubscriptionShippingAddress = exports.initializeInvoicingSubscriptionAddress = exports.resetEditInvoicingSubscriptionDetailView = exports.submitInvoicingSubscriptionFormFailure = exports.submitInvoicingSubscriptionFormSuccess = exports.submitInvoicingSubscriptionForm = exports.updateInvoicingSubscriptionFormDraft = exports.updateInvoicingSubscriptionDetailFailure = exports.updateInvoicingSubscriptionDetail = exports.seedInvoicingSubscriptionFormDraft = exports.fetchInvoicingSubscriptionFormPage = exports.fetchInvoicingSubscriptionDetailPage = exports.fetchInvoicingSubscriptionDetail = exports.runInvoicingSubscriptionActionSuccess = exports.runInvoicingSubscriptionActionFailure = exports.runInvoicingSubscriptionAction = exports.initialState = void 0;
|
|
4
|
+
exports.clearInvoicingSubscriptionEstimate = exports.updateInvoicingSubscriptionEstimateFailure = exports.updateInvoicingSubscriptionEstimate = exports.fetchInvoicingSubscriptionEstimate = exports.syncInvoicingSubscriptionCoupons = exports.claimInvoicingSubscriptionShippingAddress = exports.initializeInvoicingSubscriptionAddress = exports.resetEditInvoicingSubscriptionDetailView = exports.submitInvoicingSubscriptionFormFailure = exports.submitInvoicingSubscriptionFormSuccess = exports.submitInvoicingSubscriptionForm = exports.updateInvoicingSubscriptionFormDraft = exports.updateInvoicingSubscriptionDetailFailure = exports.updateInvoicingSubscriptionDetail = exports.seedInvoicingSubscriptionFormDraft = exports.fetchInvoicingSubscriptionFormPage = exports.fetchInvoicingSubscriptionDetailPage = exports.fetchInvoicingSubscriptionDetail = exports.runInvoicingSubscriptionActionSuccess = exports.runInvoicingSubscriptionActionFailure = exports.runInvoicingSubscriptionAction = exports.initialState = void 0;
|
|
5
5
|
const toolkit_1 = require("@reduxjs/toolkit");
|
|
6
6
|
const rootActions_1 = require("../../../rootActions");
|
|
7
7
|
const invoicingFormDraft_1 = require("../invoicingFormDraft");
|
|
8
8
|
const invoicingSubscriptionFormConfig_1 = require("./invoicingSubscriptionFormConfig");
|
|
9
|
+
const initialEstimateState = {
|
|
10
|
+
fetchState: { fetchState: 'Not-Started', error: undefined },
|
|
11
|
+
};
|
|
9
12
|
exports.initialState = {
|
|
10
13
|
fetchState: 'Not-Started',
|
|
11
14
|
error: undefined,
|
|
12
15
|
editDraftById: {},
|
|
16
|
+
estimateState: initialEstimateState,
|
|
13
17
|
actionStateById: {},
|
|
14
18
|
recordStateById: {},
|
|
15
19
|
shippingAddressOwnerId: undefined,
|
|
@@ -23,6 +27,36 @@ const editInvoicingSubscriptionDetailView = (0, toolkit_1.createSlice)({
|
|
|
23
27
|
name: 'editInvoicingSubscriptionDetailView',
|
|
24
28
|
initialState: exports.initialState,
|
|
25
29
|
reducers: {
|
|
30
|
+
// What saving the draft would bill (no id: create); see the estimate epic.
|
|
31
|
+
fetchInvoicingSubscriptionEstimate(draft, action) {
|
|
32
|
+
draft.estimateState = {
|
|
33
|
+
fetchState: { fetchState: 'In-Progress', error: undefined },
|
|
34
|
+
subscriptionId: action.payload.id,
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
// Dropped when the dialog has since moved to another subscription.
|
|
38
|
+
updateInvoicingSubscriptionEstimate(draft, action) {
|
|
39
|
+
if (draft.estimateState.subscriptionId !== action.payload.id) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
draft.estimateState.estimate = action.payload.estimate;
|
|
43
|
+
draft.estimateState.fetchState = {
|
|
44
|
+
fetchState: 'Completed',
|
|
45
|
+
error: undefined,
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
updateInvoicingSubscriptionEstimateFailure(draft, action) {
|
|
49
|
+
if (draft.estimateState.subscriptionId !== action.payload.id) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
draft.estimateState.fetchState = {
|
|
53
|
+
fetchState: 'Error',
|
|
54
|
+
error: action.payload.status,
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
clearInvoicingSubscriptionEstimate(draft) {
|
|
58
|
+
draft.estimateState = initialEstimateState;
|
|
59
|
+
},
|
|
26
60
|
runInvoicingSubscriptionAction(draft, action) {
|
|
27
61
|
draft.actionStateById[action.payload.id] = {
|
|
28
62
|
fetchState: 'In-Progress',
|
|
@@ -162,5 +196,5 @@ const editInvoicingSubscriptionDetailView = (0, toolkit_1.createSlice)({
|
|
|
162
196
|
});
|
|
163
197
|
},
|
|
164
198
|
});
|
|
165
|
-
_a = editInvoicingSubscriptionDetailView.actions, exports.runInvoicingSubscriptionAction = _a.runInvoicingSubscriptionAction, exports.runInvoicingSubscriptionActionFailure = _a.runInvoicingSubscriptionActionFailure, exports.runInvoicingSubscriptionActionSuccess = _a.runInvoicingSubscriptionActionSuccess, exports.fetchInvoicingSubscriptionDetail = _a.fetchInvoicingSubscriptionDetail, exports.fetchInvoicingSubscriptionDetailPage = _a.fetchInvoicingSubscriptionDetailPage, exports.fetchInvoicingSubscriptionFormPage = _a.fetchInvoicingSubscriptionFormPage, exports.seedInvoicingSubscriptionFormDraft = _a.seedInvoicingSubscriptionFormDraft, exports.updateInvoicingSubscriptionDetail = _a.updateInvoicingSubscriptionDetail, exports.updateInvoicingSubscriptionDetailFailure = _a.updateInvoicingSubscriptionDetailFailure, exports.updateInvoicingSubscriptionFormDraft = _a.updateInvoicingSubscriptionFormDraft, exports.submitInvoicingSubscriptionForm = _a.submitInvoicingSubscriptionForm, exports.submitInvoicingSubscriptionFormSuccess = _a.submitInvoicingSubscriptionFormSuccess, exports.submitInvoicingSubscriptionFormFailure = _a.submitInvoicingSubscriptionFormFailure, exports.resetEditInvoicingSubscriptionDetailView = _a.resetEditInvoicingSubscriptionDetailView, exports.initializeInvoicingSubscriptionAddress = _a.initializeInvoicingSubscriptionAddress, exports.claimInvoicingSubscriptionShippingAddress = _a.claimInvoicingSubscriptionShippingAddress, exports.syncInvoicingSubscriptionCoupons = _a.syncInvoicingSubscriptionCoupons;
|
|
199
|
+
_a = editInvoicingSubscriptionDetailView.actions, exports.runInvoicingSubscriptionAction = _a.runInvoicingSubscriptionAction, exports.runInvoicingSubscriptionActionFailure = _a.runInvoicingSubscriptionActionFailure, exports.runInvoicingSubscriptionActionSuccess = _a.runInvoicingSubscriptionActionSuccess, exports.fetchInvoicingSubscriptionDetail = _a.fetchInvoicingSubscriptionDetail, exports.fetchInvoicingSubscriptionDetailPage = _a.fetchInvoicingSubscriptionDetailPage, exports.fetchInvoicingSubscriptionFormPage = _a.fetchInvoicingSubscriptionFormPage, exports.seedInvoicingSubscriptionFormDraft = _a.seedInvoicingSubscriptionFormDraft, exports.updateInvoicingSubscriptionDetail = _a.updateInvoicingSubscriptionDetail, exports.updateInvoicingSubscriptionDetailFailure = _a.updateInvoicingSubscriptionDetailFailure, exports.updateInvoicingSubscriptionFormDraft = _a.updateInvoicingSubscriptionFormDraft, exports.submitInvoicingSubscriptionForm = _a.submitInvoicingSubscriptionForm, exports.submitInvoicingSubscriptionFormSuccess = _a.submitInvoicingSubscriptionFormSuccess, exports.submitInvoicingSubscriptionFormFailure = _a.submitInvoicingSubscriptionFormFailure, exports.resetEditInvoicingSubscriptionDetailView = _a.resetEditInvoicingSubscriptionDetailView, exports.initializeInvoicingSubscriptionAddress = _a.initializeInvoicingSubscriptionAddress, exports.claimInvoicingSubscriptionShippingAddress = _a.claimInvoicingSubscriptionShippingAddress, exports.syncInvoicingSubscriptionCoupons = _a.syncInvoicingSubscriptionCoupons, exports.fetchInvoicingSubscriptionEstimate = _a.fetchInvoicingSubscriptionEstimate, exports.updateInvoicingSubscriptionEstimate = _a.updateInvoicingSubscriptionEstimate, exports.updateInvoicingSubscriptionEstimateFailure = _a.updateInvoicingSubscriptionEstimateFailure, exports.clearInvoicingSubscriptionEstimate = _a.clearInvoicingSubscriptionEstimate;
|
|
166
200
|
exports.default = editInvoicingSubscriptionDetailView.reducer;
|
|
@@ -5,7 +5,8 @@ import { InvoicingCatalogPricePoint } from '../../../entity/invoicing/invoicingC
|
|
|
5
5
|
import { InvoicingFieldHelpByCode, InvoicingNamedOption, InvoicingSubscriptionLifecycleActionOption } from '../../../entity/invoicing/invoicingCommonPayload';
|
|
6
6
|
import { InvoicingSubscription } from '../../../entity/invoicing/invoicingSubscription/invoicingSubscriptionState';
|
|
7
7
|
import { RootState } from '../../../rootStateTypes';
|
|
8
|
-
import {
|
|
8
|
+
import { ZeniDate } from '../../../zeniDayJS';
|
|
9
|
+
import { InvoicingSubscriptionEstimateState, InvoicingSubscriptionFormLocalData, InvoicingSubscriptionInvoiceEstimate } from './editInvoicingSubscriptionDetailViewState';
|
|
9
10
|
/**
|
|
10
11
|
* Bundled view for the subscription create/edit form's save concern: the save
|
|
11
12
|
* fetch state/error, the id of the subscription last saved, and — when an
|
|
@@ -29,6 +30,22 @@ export interface EditInvoicingSubscriptionDetailView extends SelectorView {
|
|
|
29
30
|
* `{label, value}` records, so this stays free of any web-components
|
|
30
31
|
* dependency while leaving the screen nothing to derive.
|
|
31
32
|
*/
|
|
33
|
+
export type InvoicingSubscriptionEstimateKind = 'invoice_now' | 'later_invoice' | 'proration_credit' | 'proration_invoice' | 'reason_only';
|
|
34
|
+
export interface InvoicingSubscriptionEstimateView {
|
|
35
|
+
/** A later invoice quotes today's credit balances, which can change by then. */
|
|
36
|
+
creditsMayChange: boolean;
|
|
37
|
+
fetchState: FetchStateAndError;
|
|
38
|
+
nonRenewing: boolean;
|
|
39
|
+
/** The one invoice to show, whichever estimate it came from. */
|
|
40
|
+
invoice?: InvoicingSubscriptionInvoiceEstimate;
|
|
41
|
+
kind?: InvoicingSubscriptionEstimateKind;
|
|
42
|
+
nextBillingAt?: ZeniDate;
|
|
43
|
+
reason?: string;
|
|
44
|
+
storeCredit?: Amount;
|
|
45
|
+
termEnd?: ZeniDate;
|
|
46
|
+
termStart?: ZeniDate;
|
|
47
|
+
}
|
|
48
|
+
export declare const toInvoicingSubscriptionEstimateView: ({ estimate, fetchState, subscriptionId }: InvoicingSubscriptionEstimateState, invoicingSubscriptionID?: ID) => InvoicingSubscriptionEstimateView;
|
|
32
49
|
export interface InvoicingSubscriptionFormView extends SelectorView {
|
|
33
50
|
/** Lifecycle action in flight, folded in from the record view. */
|
|
34
51
|
actionState: FetchStateAndError;
|
|
@@ -43,6 +60,8 @@ export interface InvoicingSubscriptionFormView extends SelectorView {
|
|
|
43
60
|
charges: InvoicingSubscriptionOption[];
|
|
44
61
|
coupons: InvoicingSubscriptionOption[];
|
|
45
62
|
customers: InvoicingSubscriptionOption[];
|
|
63
|
+
/** The review dialog's cost estimate for saving this draft. */
|
|
64
|
+
estimate: InvoicingSubscriptionEstimateView;
|
|
46
65
|
fieldHelp: InvoicingFieldHelpByCode;
|
|
47
66
|
/**
|
|
48
67
|
* True while the subscription being edited is still on its way to the form.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getInvoicingSubscriptionFormView = exports.isInvoicingAddonPeriodCompatible = exports.getInvoicingItemsAllowedForPlan = exports.getEditInvoicingSubscriptionDetailViewState = void 0;
|
|
3
|
+
exports.getInvoicingSubscriptionFormView = exports.isInvoicingAddonPeriodCompatible = exports.getInvoicingItemsAllowedForPlan = exports.getEditInvoicingSubscriptionDetailViewState = exports.toInvoicingSubscriptionEstimateView = void 0;
|
|
4
4
|
const reduceFetchState_1 = require("../../../commonStateTypes/reduceFetchState");
|
|
5
5
|
const addressSelector_1 = require("../../../entity/address/addressSelector");
|
|
6
6
|
const invoicingSubscriptionSelector_1 = require("../../../entity/invoicing/invoicingSubscription/invoicingSubscriptionSelector");
|
|
@@ -12,6 +12,58 @@ const invoicingCustomerListViewSelector_1 = require("../invoicingCustomerListVie
|
|
|
12
12
|
const invoicingFormDraft_1 = require("../invoicingFormDraft");
|
|
13
13
|
const invoicingSubscriptionFormConfig_1 = require("./invoicingSubscriptionFormConfig");
|
|
14
14
|
const invoicingSubscriptionFormConfig_2 = require("./invoicingSubscriptionFormConfig");
|
|
15
|
+
const notStartedEstimateView = {
|
|
16
|
+
creditsMayChange: false,
|
|
17
|
+
fetchState: { fetchState: 'Not-Started', error: undefined },
|
|
18
|
+
nonRenewing: false,
|
|
19
|
+
};
|
|
20
|
+
const toInvoicingSubscriptionEstimateView = ({ estimate, fetchState, subscriptionId }, invoicingSubscriptionID) => {
|
|
21
|
+
// A slot asked for another subscription is not this form's estimate.
|
|
22
|
+
if (subscriptionId !== invoicingSubscriptionID) {
|
|
23
|
+
return notStartedEstimateView;
|
|
24
|
+
}
|
|
25
|
+
if (estimate == null) {
|
|
26
|
+
return { ...notStartedEstimateView, fetchState };
|
|
27
|
+
}
|
|
28
|
+
const { invoice, nextInvoice, proration, reason, term } = estimate;
|
|
29
|
+
const base = {
|
|
30
|
+
fetchState,
|
|
31
|
+
nonRenewing: term?.becomesNonRenewing === true,
|
|
32
|
+
nextBillingAt: term?.nextBillingAt,
|
|
33
|
+
reason: reason !== '' ? reason : undefined,
|
|
34
|
+
termEnd: term?.currentTermEnd,
|
|
35
|
+
termStart: term?.currentTermStart,
|
|
36
|
+
};
|
|
37
|
+
if (proration?.documentType === 'invoice' && proration.invoice != null) {
|
|
38
|
+
return {
|
|
39
|
+
...base,
|
|
40
|
+
creditsMayChange: false,
|
|
41
|
+
invoice: proration.invoice,
|
|
42
|
+
kind: 'proration_invoice',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (proration?.documentType === 'credit_note') {
|
|
46
|
+
return {
|
|
47
|
+
...base,
|
|
48
|
+
creditsMayChange: false,
|
|
49
|
+
kind: 'proration_credit',
|
|
50
|
+
storeCredit: { ...proration.net, amount: Math.abs(proration.net.amount) },
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (estimate.willBeInvoiced && invoice != null) {
|
|
54
|
+
return { ...base, creditsMayChange: false, invoice, kind: 'invoice_now' };
|
|
55
|
+
}
|
|
56
|
+
if (nextInvoice != null) {
|
|
57
|
+
return {
|
|
58
|
+
...base,
|
|
59
|
+
creditsMayChange: true,
|
|
60
|
+
invoice: nextInvoice,
|
|
61
|
+
kind: 'later_invoice',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return { ...base, creditsMayChange: false, kind: 'reason_only' };
|
|
65
|
+
};
|
|
66
|
+
exports.toInvoicingSubscriptionEstimateView = toInvoicingSubscriptionEstimateView;
|
|
15
67
|
const getEditInvoicingSubscriptionDetailViewState = (state, invoicingSubscriptionID) => {
|
|
16
68
|
const { error, fetchState, savedSubscriptionId } = state.editInvoicingSubscriptionDetailViewState;
|
|
17
69
|
const { subscriptionCommercialEditAllowedFromStatuses, subscriptionLifecycleActionOptions, subscriptionMetadataEditAllowedFromStatuses, } = (0, invoicingConfigViewSelector_1.getInvoicingConfigView)(state);
|
|
@@ -176,7 +228,7 @@ const toInvoicingCustomerOption = (customer) => ({
|
|
|
176
228
|
value: customer.id,
|
|
177
229
|
});
|
|
178
230
|
const getInvoicingSubscriptionFormView = (state, invoicingSubscriptionID) => {
|
|
179
|
-
const { editDraftById, error, fetchState, newSubscriptionDraft, savedSubscriptionId, } = state.editInvoicingSubscriptionDetailViewState;
|
|
231
|
+
const { editDraftById, error, estimateState, fetchState, newSubscriptionDraft, savedSubscriptionId, } = state.editInvoicingSubscriptionDetailViewState;
|
|
180
232
|
const mode = invoicingSubscriptionID != null ? 'edit' : 'create';
|
|
181
233
|
const subscription = invoicingSubscriptionID != null
|
|
182
234
|
? (0, invoicingSubscriptionSelector_1.getInvoicingSubscriptionById)(state.invoicingSubscriptionState, invoicingSubscriptionID)
|
|
@@ -230,6 +282,7 @@ const getInvoicingSubscriptionFormView = (state, invoicingSubscriptionID) => {
|
|
|
230
282
|
subscriptionMetadataEditAllowedFromStatuses: configState.subscriptionMetadataEditAllowedFromStatuses,
|
|
231
283
|
recordState,
|
|
232
284
|
isFormLoading: (0, invoicingFormDraft_1.isInvoicingFormDraftPending)(editDraftById, invoicingSubscriptionID),
|
|
285
|
+
estimate: (0, exports.toInvoicingSubscriptionEstimateView)(estimateState, invoicingSubscriptionID),
|
|
233
286
|
fieldHelp: (0, invoicingConfigViewSelector_1.invoicingFieldHelpForGroup)(state, 'subscription'),
|
|
234
287
|
addons: catalogItemOptionsOfType('addon'),
|
|
235
288
|
charges: catalogItemOptionsOfType('charge'),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Amount } from '../../../commonStateTypes/amount';
|
|
2
2
|
import { FetchStateAndError, FetchedState, ID } from '../../../commonStateTypes/common';
|
|
3
|
+
import { ZeniDate } from '../../../zeniDayJS';
|
|
3
4
|
import { InvoicingFormDraftById } from '../invoicingFormDraft';
|
|
4
5
|
/**
|
|
5
6
|
* Draft form state for the create/edit subscription form. Held in CES so the
|
|
@@ -39,10 +40,67 @@ export interface InvoicingSubscriptionFormLocalData {
|
|
|
39
40
|
/** Unset until the user enters one, so an explicit 0 stays distinguishable. */
|
|
40
41
|
setupFee?: Amount;
|
|
41
42
|
}
|
|
43
|
+
export interface InvoicingSubscriptionEstimateLineItem {
|
|
44
|
+
amount: Amount;
|
|
45
|
+
description: string;
|
|
46
|
+
discountAmount: Amount;
|
|
47
|
+
quantity: number;
|
|
48
|
+
unitAmount: Amount;
|
|
49
|
+
dateFrom?: ZeniDate;
|
|
50
|
+
dateTo?: ZeniDate;
|
|
51
|
+
taxAmount?: Amount;
|
|
52
|
+
}
|
|
53
|
+
/** An invoice the save would raise now, or the next one it would raise later. */
|
|
54
|
+
export interface InvoicingSubscriptionInvoiceEstimate {
|
|
55
|
+
amountDue: Amount;
|
|
56
|
+
couponDiscountTotal: Amount;
|
|
57
|
+
/** Refundable credits plus excess payments; promotional credits are separate. */
|
|
58
|
+
creditsApplied: Amount;
|
|
59
|
+
grossSubTotal: Amount;
|
|
60
|
+
lineItems: InvoicingSubscriptionEstimateLineItem[];
|
|
61
|
+
promotionalCreditsApplied: Amount;
|
|
62
|
+
/** After coupons and promotional credits. */
|
|
63
|
+
subTotal: Amount;
|
|
64
|
+
total: Amount;
|
|
65
|
+
dueDate?: ZeniDate;
|
|
66
|
+
invoiceDate?: ZeniDate;
|
|
67
|
+
/** Absent when the tax quote failed on a later invoice; Save still succeeds. */
|
|
68
|
+
tax?: Amount;
|
|
69
|
+
}
|
|
70
|
+
export interface InvoicingSubscriptionProrationEstimate {
|
|
71
|
+
/** `invoice`, `credit_note`, or `none` when the change nets to zero. */
|
|
72
|
+
documentType: string;
|
|
73
|
+
net: Amount;
|
|
74
|
+
invoice?: InvoicingSubscriptionInvoiceEstimate;
|
|
75
|
+
}
|
|
76
|
+
export interface InvoicingSubscriptionTermEstimate {
|
|
77
|
+
becomesNonRenewing: boolean;
|
|
78
|
+
currentTermEnd?: ZeniDate;
|
|
79
|
+
currentTermStart?: ZeniDate;
|
|
80
|
+
nextBillingAt?: ZeniDate;
|
|
81
|
+
}
|
|
82
|
+
/** What a save would bill, from `POST /subscriptions[/{id}]/estimate`. */
|
|
83
|
+
export interface InvoicingSubscriptionEstimate {
|
|
84
|
+
/** The service's sentence for why nothing is invoiced now; empty otherwise. */
|
|
85
|
+
reason: string;
|
|
86
|
+
willBeInvoiced: boolean;
|
|
87
|
+
invoice?: InvoicingSubscriptionInvoiceEstimate;
|
|
88
|
+
nextInvoice?: InvoicingSubscriptionInvoiceEstimate;
|
|
89
|
+
proration?: InvoicingSubscriptionProrationEstimate;
|
|
90
|
+
term?: InvoicingSubscriptionTermEstimate;
|
|
91
|
+
}
|
|
92
|
+
export interface InvoicingSubscriptionEstimateState {
|
|
93
|
+
fetchState: FetchStateAndError;
|
|
94
|
+
estimate?: InvoicingSubscriptionEstimate;
|
|
95
|
+
/** The subscription the estimate was asked for; absent for a new one. */
|
|
96
|
+
subscriptionId?: ID;
|
|
97
|
+
}
|
|
42
98
|
export interface EditInvoicingSubscriptionDetailViewState extends FetchedState {
|
|
43
99
|
/** Lifecycle action fetch state per subscription id. */
|
|
44
100
|
actionStateById: Record<ID, FetchStateAndError>;
|
|
45
101
|
editDraftById: InvoicingFormDraftById<InvoicingSubscriptionFormLocalData>;
|
|
102
|
+
/** The cost estimate shown before a save; nothing is saved by asking. */
|
|
103
|
+
estimateState: InvoicingSubscriptionEstimateState;
|
|
46
104
|
newSubscriptionDraft: InvoicingSubscriptionFormLocalData;
|
|
47
105
|
/** Detail-load state per subscription id. */
|
|
48
106
|
recordStateById: Record<ID, FetchStateAndError>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ActionsObservable, StateObservable } from 'redux-observable';
|
|
2
|
+
import { Observable } from 'rxjs';
|
|
3
|
+
import { RootState } from '../../../rootStateTypes';
|
|
4
|
+
import { ZeniAPI } from '../../../zeniAPI';
|
|
5
|
+
import { fetchInvoicingSubscriptionEstimate, updateInvoicingSubscriptionEstimate, updateInvoicingSubscriptionEstimateFailure } from './editInvoicingSubscriptionDetailViewReducer';
|
|
6
|
+
export type ActionType = ReturnType<typeof fetchInvoicingSubscriptionEstimate> | ReturnType<typeof updateInvoicingSubscriptionEstimate> | ReturnType<typeof updateInvoicingSubscriptionEstimateFailure>;
|
|
7
|
+
export declare const fetchInvoicingSubscriptionEstimateEpic: (actions$: ActionsObservable<ActionType>, state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => Observable<ActionType>;
|