@blamejs/blamejs-shop 0.0.72 → 0.0.75
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/CHANGELOG.md +6 -0
- package/lib/announcement-bar.js +753 -0
- package/lib/banner-ab-tests.js +806 -0
- package/lib/bin-locations.js +791 -0
- package/lib/blog-articles.js +1173 -0
- package/lib/carrier-accounts.js +805 -0
- package/lib/cart-recovery.js +1133 -0
- package/lib/category-navigation.js +934 -0
- package/lib/consent-ledger.js +539 -0
- package/lib/customer-impersonation.js +743 -0
- package/lib/customer-merge.js +879 -0
- package/lib/demand-forecast.js +1121 -0
- package/lib/dispute-resolution.js +886 -0
- package/lib/email-ab-tests.js +918 -0
- package/lib/email-engagement-score.js +649 -0
- package/lib/event-log.js +713 -0
- package/lib/fulfillment-sla.js +791 -0
- package/lib/index.js +41 -0
- package/lib/inventory-audits.js +852 -0
- package/lib/line-gift-wrap.js +430 -0
- package/lib/marketing-budget.js +792 -0
- package/lib/operator-activity-feed.js +977 -0
- package/lib/operator-approvals.js +942 -0
- package/lib/operator-help-center.js +1020 -0
- package/lib/operator-inbox.js +889 -0
- package/lib/operator-sessions.js +701 -0
- package/lib/order-exchanges.js +602 -0
- package/lib/product-compare.js +804 -0
- package/lib/pwa-manifest.js +1005 -0
- package/lib/referral-leaderboard.js +612 -0
- package/lib/sales-tax-filings.js +807 -0
- package/lib/search-ranking.js +859 -0
- package/lib/shipping-insurance.js +757 -0
- package/lib/shrinkage-report.js +1182 -0
- package/lib/sidebar-widgets.js +952 -0
- package/lib/smart-restocking.js +1048 -0
- package/lib/stock-receipts.js +834 -0
- package/lib/subscription-analytics.js +1032 -0
- package/lib/suggestion-box.js +921 -0
- package/lib/tax-remittance.js +625 -0
- package/lib/vendor-invoices.js +1021 -0
- package/lib/winback-campaigns.js +1350 -0
- package/lib/wishlist-digest.js +1133 -0
- package/package.json +1 -1
|
@@ -0,0 +1,807 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module shop.salesTaxFilings
|
|
4
|
+
* @title Sales tax filings — periodic remittance preparation
|
|
5
|
+
*
|
|
6
|
+
* @intro
|
|
7
|
+
* Sales tax obligations don't end at checkout — every jurisdiction
|
|
8
|
+
* the operator collected tax for expects a periodic filing (monthly,
|
|
9
|
+
* quarterly, or annually) that reconciles the operator's books to
|
|
10
|
+
* the authority's. This primitive owns the lifecycle of one such
|
|
11
|
+
* filing.
|
|
12
|
+
*
|
|
13
|
+
* The shape:
|
|
14
|
+
*
|
|
15
|
+
* 1. `defineFilingPeriod({ jurisdiction, kind, period_start,
|
|
16
|
+
* period_end, due_date })` opens a row in `draft` status. One
|
|
17
|
+
* (jurisdiction, kind, period_start) tuple yields one row — a
|
|
18
|
+
* UNIQUE index at the DB boundary refuses a duplicate open
|
|
19
|
+
* even if two operators race the call.
|
|
20
|
+
*
|
|
21
|
+
* 2. `computeFiling({ filing_id })` snapshots the orders that
|
|
22
|
+
* fell inside the period and computes:
|
|
23
|
+
*
|
|
24
|
+
* gross_revenue_minor — sum of order subtotals in window
|
|
25
|
+
* taxable_revenue_minor — gross minus exempt-customer revenue
|
|
26
|
+
* exempt_revenue_minor — revenue attributed to customers
|
|
27
|
+
* with an approved tax-exemption for
|
|
28
|
+
* the filing's jurisdiction
|
|
29
|
+
* tax_collected_minor — sum of order tax_minor in window
|
|
30
|
+
* tax_owed_minor — re-derived from taxable revenue +
|
|
31
|
+
* rates effective during the window
|
|
32
|
+
* by_rate_breakdown — per-rate-bps split of taxable
|
|
33
|
+
* revenue + collected tax (operators
|
|
34
|
+
* need this for the filing form)
|
|
35
|
+
*
|
|
36
|
+
* Status moves draft -> computed. The snapshot is rerunnable;
|
|
37
|
+
* re-running overwrites the snapshot until status leaves
|
|
38
|
+
* `computed`.
|
|
39
|
+
*
|
|
40
|
+
* 3. `recordSubmission({ filing_id, submission_ref, submitted_at,
|
|
41
|
+
* submitted_by })` moves computed -> submitted. `submission_ref`
|
|
42
|
+
* is the authority's confirmation number (DR-123-456); the
|
|
43
|
+
* primitive does not interpret it beyond a length + control-
|
|
44
|
+
* byte gate.
|
|
45
|
+
*
|
|
46
|
+
* 4. `recordPayment({ filing_id, payment_minor, payment_ref,
|
|
47
|
+
* paid_at })` moves submitted -> paid. Payment minor doesn't
|
|
48
|
+
* have to equal tax_owed_minor — a jurisdiction occasionally
|
|
49
|
+
* accepts a partial / installment payment; the audit trail
|
|
50
|
+
* preserves both numbers.
|
|
51
|
+
*
|
|
52
|
+
* 5. `markAmended({ filing_id, reason })` moves a row that has
|
|
53
|
+
* landed in `computed`, `submitted`, or `paid` back to an
|
|
54
|
+
* explicit `amended` status. The original snapshot stays; the
|
|
55
|
+
* reason captures why. Operators re-open a new filing for the
|
|
56
|
+
* same period via defineFilingPeriod with the same tuple — the
|
|
57
|
+
* UNIQUE index prevents this without first amending the
|
|
58
|
+
* existing row.
|
|
59
|
+
*
|
|
60
|
+
* Order composition:
|
|
61
|
+
*
|
|
62
|
+
* The primitive walks orders that landed inside the window via
|
|
63
|
+
* `query()` against the project's `orders` schema (`status`,
|
|
64
|
+
* `currency`, `subtotal_minor`, `tax_minor`, `ship_to_json`).
|
|
65
|
+
* The window predicate uses `created_at` so a late-arriving
|
|
66
|
+
* fulfillment doesn't pull an old order back into a new filing.
|
|
67
|
+
*
|
|
68
|
+
* Orders contribute when their `ship_to.country` (and, for
|
|
69
|
+
* subdivision-keyed jurisdictions, `ship_to.region`) matches the
|
|
70
|
+
* filing's jurisdiction. Cancelled / refunded orders are excluded
|
|
71
|
+
* so the filing reflects net sales the authority cares about.
|
|
72
|
+
*
|
|
73
|
+
* Exemption composition:
|
|
74
|
+
*
|
|
75
|
+
* When `taxExempt` is wired, each contributing order's
|
|
76
|
+
* customer_id is checked via `taxExempt.isExempt({ customer_id,
|
|
77
|
+
* jurisdiction })`. Exempt customers shift revenue from the
|
|
78
|
+
* taxable bucket to the exempt bucket; they don't drop from the
|
|
79
|
+
* filing entirely (the authority wants to see the gross + the
|
|
80
|
+
* exempt split). Anonymous orders (no customer_id) are never
|
|
81
|
+
* exempt.
|
|
82
|
+
*
|
|
83
|
+
* Rate composition:
|
|
84
|
+
*
|
|
85
|
+
* When `taxRates` is wired, the rates effective during the window
|
|
86
|
+
* for the filing's jurisdiction are loaded via raw query against
|
|
87
|
+
* `tax_rates`. The per-rate breakdown attributes each order's
|
|
88
|
+
* contribution to the (single) rate row that covered its
|
|
89
|
+
* created_at timestamp. Orders whose created_at falls inside the
|
|
90
|
+
* window but outside every rate row's effective range fall into
|
|
91
|
+
* the special `__none__` breakdown key — operators see the gap
|
|
92
|
+
* before they file.
|
|
93
|
+
*
|
|
94
|
+
* Composes:
|
|
95
|
+
* - `b.guardUuid` — strict UUID gate on filing_id.
|
|
96
|
+
* - `b.uuid.v7` — filing row primary key (monotonic
|
|
97
|
+
* lexicographic so list-by-id sorts by open
|
|
98
|
+
* order).
|
|
99
|
+
*
|
|
100
|
+
* Storage: `migrations-d1/0184_sales_tax_filings.sql`.
|
|
101
|
+
*
|
|
102
|
+
* @primitive salesTaxFilings
|
|
103
|
+
* @related order, taxRates, taxExempt, b.guardUuid, b.uuid
|
|
104
|
+
*/
|
|
105
|
+
|
|
106
|
+
var bShop;
|
|
107
|
+
function _b() {
|
|
108
|
+
if (!bShop) bShop = require("./index");
|
|
109
|
+
return bShop.framework;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---- constants ----------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
var KINDS = Object.freeze(["monthly", "quarterly", "annual"]);
|
|
115
|
+
var STATUSES = Object.freeze(["draft", "computed", "submitted", "paid", "amended"]);
|
|
116
|
+
|
|
117
|
+
var JURISDICTION_RE = /^[A-Z]{2}(-[A-Z0-9]{1,3})?$/;
|
|
118
|
+
|
|
119
|
+
var MAX_SUBMISSION_REF_LEN = 200;
|
|
120
|
+
var MAX_PAYMENT_REF_LEN = 200;
|
|
121
|
+
var MAX_SUBMITTED_BY_LEN = 200;
|
|
122
|
+
var MAX_AMENDED_REASON_LEN = 1000;
|
|
123
|
+
|
|
124
|
+
var DEFAULT_LIMIT = 50;
|
|
125
|
+
var MAX_LIMIT = 500;
|
|
126
|
+
var MAX_DAYS_AHEAD = 365;
|
|
127
|
+
|
|
128
|
+
var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
129
|
+
|
|
130
|
+
// FSM transition map. The keys are (from_status, action) → to_status.
|
|
131
|
+
// Defined as a table so a misuse surfaces with the actual current
|
|
132
|
+
// status in the error rather than an "unexpected" surprise from a
|
|
133
|
+
// silent UPDATE that no-ops.
|
|
134
|
+
var FSM_TRANSITIONS = Object.freeze({
|
|
135
|
+
"draft|compute": "computed",
|
|
136
|
+
"computed|compute": "computed", // recompute pre-submit allowed
|
|
137
|
+
"computed|submit": "submitted",
|
|
138
|
+
"submitted|pay": "paid",
|
|
139
|
+
"computed|amend": "amended",
|
|
140
|
+
"submitted|amend": "amended",
|
|
141
|
+
"paid|amend": "amended",
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// ---- monotonic clock ----------------------------------------------------
|
|
145
|
+
//
|
|
146
|
+
// Filings move through their lifecycle one operator action at a time
|
|
147
|
+
// and the audit trail (created_at / computed_at / submitted_at /
|
|
148
|
+
// paid_at / amended_at / updated_at) wants a strictly-increasing
|
|
149
|
+
// timeline so a sort-by-timestamp read returns events in the order
|
|
150
|
+
// they were issued. On a fast machine a defineFilingPeriod followed
|
|
151
|
+
// immediately by computeFiling can land in the same Date.now() bucket;
|
|
152
|
+
// bumping by 1ms on a tie keeps the ordering deterministic without an
|
|
153
|
+
// extra tiebreaker column.
|
|
154
|
+
|
|
155
|
+
var _lastTs = 0;
|
|
156
|
+
function _now() {
|
|
157
|
+
var t = Date.now();
|
|
158
|
+
if (t <= _lastTs) { t = _lastTs + 1; }
|
|
159
|
+
_lastTs = t;
|
|
160
|
+
return t;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ---- validators ---------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
function _jurisdiction(s) {
|
|
166
|
+
if (typeof s !== "string" || !JURISDICTION_RE.test(s)) {
|
|
167
|
+
throw new TypeError(
|
|
168
|
+
"salesTaxFilings: jurisdiction must match /^[A-Z]{2}(-[A-Z0-9]{1,3})?$/ " +
|
|
169
|
+
"(ISO 3166-1 alpha-2 + optional ISO 3166-2 subdivision), got " +
|
|
170
|
+
JSON.stringify(s)
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
return s;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function _kind(s) {
|
|
177
|
+
if (typeof s !== "string" || KINDS.indexOf(s) < 0) {
|
|
178
|
+
throw new TypeError("salesTaxFilings: kind must be one of " + KINDS.join(", "));
|
|
179
|
+
}
|
|
180
|
+
return s;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function _status(s, label) {
|
|
184
|
+
if (typeof s !== "string" || STATUSES.indexOf(s) < 0) {
|
|
185
|
+
throw new TypeError("salesTaxFilings: " + (label || "status") +
|
|
186
|
+
" must be one of " + STATUSES.join(", "));
|
|
187
|
+
}
|
|
188
|
+
return s;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function _epoch(n, label) {
|
|
192
|
+
if (!Number.isInteger(n) || n < 0) {
|
|
193
|
+
throw new TypeError("salesTaxFilings: " + label +
|
|
194
|
+
" must be a non-negative integer (ms epoch)");
|
|
195
|
+
}
|
|
196
|
+
return n;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function _epochOpt(n, label) {
|
|
200
|
+
if (n == null) return null;
|
|
201
|
+
return _epoch(n, label);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function _nonNegInt(n, label) {
|
|
205
|
+
if (!Number.isInteger(n) || n < 0) {
|
|
206
|
+
throw new TypeError("salesTaxFilings: " + label +
|
|
207
|
+
" must be a non-negative integer (minor units)");
|
|
208
|
+
}
|
|
209
|
+
return n;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function _shortText(s, label, max) {
|
|
213
|
+
if (typeof s !== "string" || !s.length || s.length > max) {
|
|
214
|
+
throw new TypeError("salesTaxFilings: " + label +
|
|
215
|
+
" must be a non-empty string <= " + max + " chars");
|
|
216
|
+
}
|
|
217
|
+
if (CONTROL_BYTE_RE.test(s)) {
|
|
218
|
+
throw new TypeError("salesTaxFilings: " + label + " must not contain control bytes");
|
|
219
|
+
}
|
|
220
|
+
return s;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function _filingId(s) {
|
|
224
|
+
try { return _b().guardUuid.sanitize(s, { profile: "strict" }); }
|
|
225
|
+
catch (e) {
|
|
226
|
+
throw new TypeError("salesTaxFilings: filing_id — " +
|
|
227
|
+
(e && e.message || "invalid UUID"));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function _limit(n) {
|
|
232
|
+
if (n == null) return DEFAULT_LIMIT;
|
|
233
|
+
if (!Number.isInteger(n) || n <= 0 || n > MAX_LIMIT) {
|
|
234
|
+
throw new TypeError("salesTaxFilings: limit must be an integer in [1, " + MAX_LIMIT + "]");
|
|
235
|
+
}
|
|
236
|
+
return n;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function _daysAhead(n) {
|
|
240
|
+
if (!Number.isInteger(n) || n <= 0 || n > MAX_DAYS_AHEAD) {
|
|
241
|
+
throw new TypeError("salesTaxFilings: days_ahead must be an integer in [1, " +
|
|
242
|
+
MAX_DAYS_AHEAD + "]");
|
|
243
|
+
}
|
|
244
|
+
return n;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ---- jurisdiction coverage ---------------------------------------------
|
|
248
|
+
//
|
|
249
|
+
// An order with ship_to.country = "US" and ship_to.region = "CA"
|
|
250
|
+
// satisfies a "US-CA" filing AND a "US" filing. Match rule: split
|
|
251
|
+
// the filing jurisdiction on the dash; the country must match
|
|
252
|
+
// ship_to.country; if a subdivision is present, ship_to.region (or
|
|
253
|
+
// ship_to.subdivision / ship_to.state — accept any of the common
|
|
254
|
+
// keys) must match it.
|
|
255
|
+
|
|
256
|
+
function _shipToMatches(shipTo, jurisdiction) {
|
|
257
|
+
if (!shipTo || typeof shipTo !== "object") return false;
|
|
258
|
+
var country = typeof shipTo.country === "string" ? shipTo.country : null;
|
|
259
|
+
if (!country) return false;
|
|
260
|
+
var dash = jurisdiction.indexOf("-");
|
|
261
|
+
if (dash < 0) {
|
|
262
|
+
return country === jurisdiction;
|
|
263
|
+
}
|
|
264
|
+
var jurCountry = jurisdiction.slice(0, dash);
|
|
265
|
+
var jurSub = jurisdiction.slice(dash + 1);
|
|
266
|
+
if (country !== jurCountry) return false;
|
|
267
|
+
var sub = shipTo.region != null ? shipTo.region :
|
|
268
|
+
shipTo.subdivision != null ? shipTo.subdivision :
|
|
269
|
+
shipTo.state != null ? shipTo.state :
|
|
270
|
+
null;
|
|
271
|
+
return sub === jurSub;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ---- factory ------------------------------------------------------------
|
|
275
|
+
|
|
276
|
+
function create(opts) {
|
|
277
|
+
opts = opts || {};
|
|
278
|
+
var query = opts.query;
|
|
279
|
+
if (!query) {
|
|
280
|
+
query = function (sql, params) { return _b().externalDb.query(sql, params); };
|
|
281
|
+
}
|
|
282
|
+
// Optional composition handles. When omitted, the primitive degrades
|
|
283
|
+
// gracefully — exemption checks become no-ops, the per-rate
|
|
284
|
+
// breakdown collapses to a single `__unknown__` key.
|
|
285
|
+
var taxExemptApi = opts.taxExempt || null;
|
|
286
|
+
var taxRatesApi = opts.taxRates || null;
|
|
287
|
+
// `order` is reserved for future use (e.g. listing filing-eligible
|
|
288
|
+
// orders directly via the primitive's API instead of raw SQL). The
|
|
289
|
+
// current implementation queries the `orders` table directly because
|
|
290
|
+
// the read shape (filter by created_at + status + ship_to_json) is
|
|
291
|
+
// not exposed on order.js.
|
|
292
|
+
// (no-op assignment to keep the option in the factory signature
|
|
293
|
+
// documented without lint complaining about an unused var)
|
|
294
|
+
if (opts.order) { /* reserved */ }
|
|
295
|
+
|
|
296
|
+
// ---- internal helpers -------------------------------------------------
|
|
297
|
+
|
|
298
|
+
function _decodeFiling(row) {
|
|
299
|
+
if (!row) return null;
|
|
300
|
+
var breakdown;
|
|
301
|
+
try { breakdown = JSON.parse(row.breakdown_json); }
|
|
302
|
+
catch (_e) { breakdown = {}; }
|
|
303
|
+
return {
|
|
304
|
+
id: row.id,
|
|
305
|
+
jurisdiction: row.jurisdiction,
|
|
306
|
+
kind: row.kind,
|
|
307
|
+
period_start: Number(row.period_start),
|
|
308
|
+
period_end: Number(row.period_end),
|
|
309
|
+
due_date: Number(row.due_date),
|
|
310
|
+
status: row.status,
|
|
311
|
+
gross_revenue_minor: Number(row.gross_revenue_minor),
|
|
312
|
+
taxable_revenue_minor: Number(row.taxable_revenue_minor),
|
|
313
|
+
exempt_revenue_minor: Number(row.exempt_revenue_minor),
|
|
314
|
+
tax_collected_minor: Number(row.tax_collected_minor),
|
|
315
|
+
tax_owed_minor: Number(row.tax_owed_minor),
|
|
316
|
+
by_rate_breakdown: breakdown,
|
|
317
|
+
submission_ref: row.submission_ref,
|
|
318
|
+
submitted_at: row.submitted_at != null ? Number(row.submitted_at) : null,
|
|
319
|
+
submitted_by: row.submitted_by,
|
|
320
|
+
payment_minor: row.payment_minor != null ? Number(row.payment_minor) : null,
|
|
321
|
+
payment_ref: row.payment_ref,
|
|
322
|
+
paid_at: row.paid_at != null ? Number(row.paid_at) : null,
|
|
323
|
+
amended_at: row.amended_at != null ? Number(row.amended_at) : null,
|
|
324
|
+
amended_reason: row.amended_reason,
|
|
325
|
+
computed_at: row.computed_at != null ? Number(row.computed_at) : null,
|
|
326
|
+
created_at: Number(row.created_at),
|
|
327
|
+
updated_at: Number(row.updated_at),
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async function _getRaw(id) {
|
|
332
|
+
var r = await query("SELECT * FROM sales_tax_filings WHERE id = ?1", [id]);
|
|
333
|
+
return r.rows[0] || null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function _expect(row, action) {
|
|
337
|
+
if (!row) {
|
|
338
|
+
var miss = new Error("salesTaxFilings: filing not found");
|
|
339
|
+
miss.code = "SALES_TAX_FILING_NOT_FOUND";
|
|
340
|
+
throw miss;
|
|
341
|
+
}
|
|
342
|
+
var key = row.status + "|" + action;
|
|
343
|
+
var next = FSM_TRANSITIONS[key];
|
|
344
|
+
if (!next) {
|
|
345
|
+
var bad = new Error("salesTaxFilings: action '" + action +
|
|
346
|
+
"' not permitted from status '" + row.status + "'");
|
|
347
|
+
bad.code = "SALES_TAX_FILING_BAD_TRANSITION";
|
|
348
|
+
throw bad;
|
|
349
|
+
}
|
|
350
|
+
return next;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Load orders that fall inside the filing window AND ship to the
|
|
354
|
+
// filing's jurisdiction. The DB-level filter is intentionally
|
|
355
|
+
// narrow on (created_at, status) — the ship_to_json match runs in
|
|
356
|
+
// JS because the schema stores ship_to as opaque JSON. For typical
|
|
357
|
+
// filing-period volumes (thousands of orders) this is comfortably
|
|
358
|
+
// fast; high-volume operators can layer a column projection later
|
|
359
|
+
// without changing the primitive's contract.
|
|
360
|
+
async function _ordersInWindow(jurisdiction, periodStart, periodEnd) {
|
|
361
|
+
var sql =
|
|
362
|
+
"SELECT id, customer_id, currency, subtotal_minor, tax_minor, " +
|
|
363
|
+
" ship_to_json, status, created_at " +
|
|
364
|
+
"FROM orders " +
|
|
365
|
+
"WHERE created_at >= ?1 AND created_at < ?2 " +
|
|
366
|
+
" AND status IN ('paid', 'fulfilling', 'shipped', 'delivered')";
|
|
367
|
+
var r = await query(sql, [periodStart, periodEnd]);
|
|
368
|
+
var out = [];
|
|
369
|
+
for (var i = 0; i < r.rows.length; i += 1) {
|
|
370
|
+
var row = r.rows[i];
|
|
371
|
+
var shipTo;
|
|
372
|
+
try { shipTo = JSON.parse(row.ship_to_json); }
|
|
373
|
+
catch (_e) { shipTo = null; }
|
|
374
|
+
if (!_shipToMatches(shipTo, jurisdiction)) continue;
|
|
375
|
+
out.push({
|
|
376
|
+
id: row.id,
|
|
377
|
+
customer_id: row.customer_id,
|
|
378
|
+
currency: row.currency,
|
|
379
|
+
subtotal_minor: Number(row.subtotal_minor),
|
|
380
|
+
tax_minor: Number(row.tax_minor),
|
|
381
|
+
status: row.status,
|
|
382
|
+
created_at: Number(row.created_at),
|
|
383
|
+
ship_to: shipTo,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
return out;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async function _ratesInWindow(jurisdiction, periodStart, periodEnd) {
|
|
390
|
+
// Pull every tax_rates row for the jurisdiction that overlaps the
|
|
391
|
+
// window. The lib filters down to the rate covering each order's
|
|
392
|
+
// created_at; the SQL is widely permissive so a future query
|
|
393
|
+
// optimization stays in one place.
|
|
394
|
+
if (!taxRatesApi) return [];
|
|
395
|
+
try {
|
|
396
|
+
var r = await query(
|
|
397
|
+
"SELECT id, jurisdiction, category, rate_bps, effective_from, effective_until " +
|
|
398
|
+
"FROM tax_rates " +
|
|
399
|
+
"WHERE jurisdiction = ?1 AND archived_at IS NULL " +
|
|
400
|
+
" AND effective_from < ?2 " +
|
|
401
|
+
" AND (effective_until IS NULL OR effective_until > ?3)",
|
|
402
|
+
[jurisdiction, periodEnd, periodStart],
|
|
403
|
+
);
|
|
404
|
+
var out = [];
|
|
405
|
+
for (var i = 0; i < r.rows.length; i += 1) {
|
|
406
|
+
var row = r.rows[i];
|
|
407
|
+
out.push({
|
|
408
|
+
id: row.id,
|
|
409
|
+
rate_bps: Number(row.rate_bps),
|
|
410
|
+
effective_from: Number(row.effective_from),
|
|
411
|
+
effective_until: row.effective_until == null ? null : Number(row.effective_until),
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
return out;
|
|
415
|
+
} catch (_e) {
|
|
416
|
+
// Schema may not have tax_rates table in the harness — degrade
|
|
417
|
+
// to no per-rate breakdown rather than failing the filing.
|
|
418
|
+
return [];
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function _rateForOrder(rates, orderCreatedAt) {
|
|
423
|
+
for (var i = 0; i < rates.length; i += 1) {
|
|
424
|
+
var r = rates[i];
|
|
425
|
+
if (r.effective_from <= orderCreatedAt &&
|
|
426
|
+
(r.effective_until == null || r.effective_until > orderCreatedAt)) {
|
|
427
|
+
return r;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ---- defineFilingPeriod -----------------------------------------------
|
|
434
|
+
|
|
435
|
+
async function defineFilingPeriod(input) {
|
|
436
|
+
if (!input || typeof input !== "object") {
|
|
437
|
+
throw new TypeError("salesTaxFilings.defineFilingPeriod: input object required");
|
|
438
|
+
}
|
|
439
|
+
var jurisdiction = _jurisdiction(input.jurisdiction);
|
|
440
|
+
var kind = _kind(input.kind);
|
|
441
|
+
var periodStart = _epoch(input.period_start, "period_start");
|
|
442
|
+
var periodEnd = _epoch(input.period_end, "period_end");
|
|
443
|
+
if (periodEnd <= periodStart) {
|
|
444
|
+
throw new TypeError("salesTaxFilings.defineFilingPeriod: period_end must be > period_start");
|
|
445
|
+
}
|
|
446
|
+
var dueDate = _epoch(input.due_date, "due_date");
|
|
447
|
+
if (dueDate < periodEnd) {
|
|
448
|
+
throw new TypeError("salesTaxFilings.defineFilingPeriod: due_date must be >= period_end");
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
var id = _b().uuid.v7();
|
|
452
|
+
var ts = _now();
|
|
453
|
+
try {
|
|
454
|
+
await query(
|
|
455
|
+
"INSERT INTO sales_tax_filings " +
|
|
456
|
+
"(id, jurisdiction, kind, period_start, period_end, due_date, status, " +
|
|
457
|
+
" gross_revenue_minor, taxable_revenue_minor, exempt_revenue_minor, " +
|
|
458
|
+
" tax_collected_minor, tax_owed_minor, breakdown_json, " +
|
|
459
|
+
" created_at, updated_at) " +
|
|
460
|
+
"VALUES (?1, ?2, ?3, ?4, ?5, ?6, 'draft', 0, 0, 0, 0, 0, '{}', ?7, ?7)",
|
|
461
|
+
[id, jurisdiction, kind, periodStart, periodEnd, dueDate, ts],
|
|
462
|
+
);
|
|
463
|
+
} catch (e) {
|
|
464
|
+
// Surface a recognizable error code on the UNIQUE collision so
|
|
465
|
+
// the operator can detect "this period is already open" without
|
|
466
|
+
// pattern-matching the underlying driver's error message.
|
|
467
|
+
var msg = String(e && e.message || e);
|
|
468
|
+
if (/UNIQUE|unique/i.test(msg)) {
|
|
469
|
+
var dup = new Error("salesTaxFilings.defineFilingPeriod: filing already exists for " +
|
|
470
|
+
jurisdiction + " " + kind + " period_start=" + periodStart);
|
|
471
|
+
dup.code = "SALES_TAX_FILING_DUPLICATE";
|
|
472
|
+
dup.cause = e;
|
|
473
|
+
throw dup;
|
|
474
|
+
}
|
|
475
|
+
throw e;
|
|
476
|
+
}
|
|
477
|
+
return _decodeFiling(await _getRaw(id));
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// ---- computeFiling ----------------------------------------------------
|
|
481
|
+
|
|
482
|
+
async function computeFiling(input) {
|
|
483
|
+
if (!input || typeof input !== "object") {
|
|
484
|
+
throw new TypeError("salesTaxFilings.computeFiling: input object required");
|
|
485
|
+
}
|
|
486
|
+
var id = _filingId(input.filing_id);
|
|
487
|
+
var raw = await _getRaw(id);
|
|
488
|
+
var next = _expect(raw, "compute");
|
|
489
|
+
|
|
490
|
+
var orders = await _ordersInWindow(raw.jurisdiction, Number(raw.period_start), Number(raw.period_end));
|
|
491
|
+
var rates = await _ratesInWindow(raw.jurisdiction, Number(raw.period_start), Number(raw.period_end));
|
|
492
|
+
|
|
493
|
+
// Resolve exemption per unique customer in a single pass so
|
|
494
|
+
// multiple orders from the same exempt customer don't refire the
|
|
495
|
+
// taxExempt.isExempt call.
|
|
496
|
+
var exemptByCustomer = Object.create(null);
|
|
497
|
+
if (taxExemptApi && typeof taxExemptApi.isExempt === "function") {
|
|
498
|
+
var seen = Object.create(null);
|
|
499
|
+
for (var oi = 0; oi < orders.length; oi += 1) {
|
|
500
|
+
var custId = orders[oi].customer_id;
|
|
501
|
+
if (!custId) continue;
|
|
502
|
+
if (seen[custId]) continue;
|
|
503
|
+
seen[custId] = true;
|
|
504
|
+
try {
|
|
505
|
+
exemptByCustomer[custId] = await taxExemptApi.isExempt({
|
|
506
|
+
customer_id: custId,
|
|
507
|
+
jurisdiction: raw.jurisdiction,
|
|
508
|
+
});
|
|
509
|
+
} catch (_e) {
|
|
510
|
+
// Exemption-check failure leaves the customer in the
|
|
511
|
+
// taxable bucket — surfaces in the filing rather than
|
|
512
|
+
// silently flipping an exempt customer back to taxable
|
|
513
|
+
// post-fact via the caller's retry.
|
|
514
|
+
exemptByCustomer[custId] = false;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
var gross = 0;
|
|
520
|
+
var taxable = 0;
|
|
521
|
+
var exempt = 0;
|
|
522
|
+
var collected = 0;
|
|
523
|
+
var breakdown = Object.create(null); // rate_bps (or "__none__") -> { taxable_minor, tax_minor, order_count }
|
|
524
|
+
|
|
525
|
+
function _bucket(key) {
|
|
526
|
+
if (!breakdown[key]) {
|
|
527
|
+
breakdown[key] = { taxable_minor: 0, tax_minor: 0, order_count: 0 };
|
|
528
|
+
}
|
|
529
|
+
return breakdown[key];
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
for (var i = 0; i < orders.length; i += 1) {
|
|
533
|
+
var o = orders[i];
|
|
534
|
+
gross += o.subtotal_minor;
|
|
535
|
+
collected += o.tax_minor;
|
|
536
|
+
var isExempt = o.customer_id && exemptByCustomer[o.customer_id] === true;
|
|
537
|
+
if (isExempt) {
|
|
538
|
+
exempt += o.subtotal_minor;
|
|
539
|
+
} else {
|
|
540
|
+
taxable += o.subtotal_minor;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Per-rate breakdown — only taxable contributions land in the
|
|
544
|
+
// rate-keyed bucket. Exempt revenue gets its own
|
|
545
|
+
// pseudo-bucket so the operator sees the magnitude without
|
|
546
|
+
// double-counting against a rate row.
|
|
547
|
+
var bucketKey;
|
|
548
|
+
if (isExempt) {
|
|
549
|
+
bucketKey = "__exempt__";
|
|
550
|
+
} else {
|
|
551
|
+
var rate = _rateForOrder(rates, o.created_at);
|
|
552
|
+
bucketKey = rate ? String(rate.rate_bps) : "__none__";
|
|
553
|
+
}
|
|
554
|
+
var b = _bucket(bucketKey);
|
|
555
|
+
if (isExempt) {
|
|
556
|
+
b.taxable_minor += 0;
|
|
557
|
+
} else {
|
|
558
|
+
b.taxable_minor += o.subtotal_minor;
|
|
559
|
+
b.tax_minor += o.tax_minor;
|
|
560
|
+
}
|
|
561
|
+
b.order_count += 1;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Owed: re-derive from per-rate taxable totals using rate_bps.
|
|
565
|
+
// Integer math: taxable * bps / 10000, banker's-rounded so
|
|
566
|
+
// half-cent ties don't drift the sum. (Same rounding rule as
|
|
567
|
+
// b.tax — banker's rounding is the conservative choice when the
|
|
568
|
+
// primitive may aggregate millions of orders.)
|
|
569
|
+
var owed = 0;
|
|
570
|
+
var rateKeys = Object.keys(breakdown);
|
|
571
|
+
for (var rk = 0; rk < rateKeys.length; rk += 1) {
|
|
572
|
+
var key = rateKeys[rk];
|
|
573
|
+
if (key === "__none__" || key === "__exempt__") continue;
|
|
574
|
+
var bps = parseInt(key, 10);
|
|
575
|
+
if (!Number.isFinite(bps)) continue;
|
|
576
|
+
var bucket = breakdown[key];
|
|
577
|
+
var rawOwed = bucket.taxable_minor * bps / 10000;
|
|
578
|
+
// Banker's round
|
|
579
|
+
var floored = Math.floor(rawOwed);
|
|
580
|
+
var frac = rawOwed - floored;
|
|
581
|
+
var rounded;
|
|
582
|
+
if (frac > 0.5) rounded = floored + 1;
|
|
583
|
+
else if (frac < 0.5) rounded = floored;
|
|
584
|
+
else rounded = (floored % 2 === 0) ? floored : floored + 1;
|
|
585
|
+
owed += rounded;
|
|
586
|
+
}
|
|
587
|
+
// When no rate rows resolved (taxRatesApi absent / no rates
|
|
588
|
+
// defined) fall back to collected as the owed figure. Operators
|
|
589
|
+
// see the bare collected number rather than a misleading zero.
|
|
590
|
+
if (rates.length === 0 || rateKeys.every(function (k) { return k === "__none__" || k === "__exempt__"; })) {
|
|
591
|
+
owed = collected;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
var ts = _now();
|
|
595
|
+
await query(
|
|
596
|
+
"UPDATE sales_tax_filings SET " +
|
|
597
|
+
"status = ?1, gross_revenue_minor = ?2, taxable_revenue_minor = ?3, " +
|
|
598
|
+
"exempt_revenue_minor = ?4, tax_collected_minor = ?5, tax_owed_minor = ?6, " +
|
|
599
|
+
"breakdown_json = ?7, computed_at = ?8, updated_at = ?8 " +
|
|
600
|
+
"WHERE id = ?9",
|
|
601
|
+
[next, gross, taxable, exempt, collected, owed, JSON.stringify(breakdown), ts, id],
|
|
602
|
+
);
|
|
603
|
+
return _decodeFiling(await _getRaw(id));
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// ---- recordSubmission -------------------------------------------------
|
|
607
|
+
|
|
608
|
+
async function recordSubmission(input) {
|
|
609
|
+
if (!input || typeof input !== "object") {
|
|
610
|
+
throw new TypeError("salesTaxFilings.recordSubmission: input object required");
|
|
611
|
+
}
|
|
612
|
+
var id = _filingId(input.filing_id);
|
|
613
|
+
var submissionRef = _shortText(input.submission_ref, "submission_ref", MAX_SUBMISSION_REF_LEN);
|
|
614
|
+
var submittedAt = input.submitted_at == null ? _now()
|
|
615
|
+
: _epoch(input.submitted_at, "submitted_at");
|
|
616
|
+
var submittedBy = _shortText(input.submitted_by, "submitted_by", MAX_SUBMITTED_BY_LEN);
|
|
617
|
+
|
|
618
|
+
var raw = await _getRaw(id);
|
|
619
|
+
var next = _expect(raw, "submit");
|
|
620
|
+
var ts = _now();
|
|
621
|
+
await query(
|
|
622
|
+
"UPDATE sales_tax_filings SET " +
|
|
623
|
+
"status = ?1, submission_ref = ?2, submitted_at = ?3, submitted_by = ?4, " +
|
|
624
|
+
"updated_at = ?5 WHERE id = ?6",
|
|
625
|
+
[next, submissionRef, submittedAt, submittedBy, ts, id],
|
|
626
|
+
);
|
|
627
|
+
return _decodeFiling(await _getRaw(id));
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// ---- recordPayment ----------------------------------------------------
|
|
631
|
+
|
|
632
|
+
async function recordPayment(input) {
|
|
633
|
+
if (!input || typeof input !== "object") {
|
|
634
|
+
throw new TypeError("salesTaxFilings.recordPayment: input object required");
|
|
635
|
+
}
|
|
636
|
+
var id = _filingId(input.filing_id);
|
|
637
|
+
var amount = _nonNegInt(input.payment_minor, "payment_minor");
|
|
638
|
+
var paymentRef = _shortText(input.payment_ref, "payment_ref", MAX_PAYMENT_REF_LEN);
|
|
639
|
+
var paidAt = input.paid_at == null ? _now() : _epoch(input.paid_at, "paid_at");
|
|
640
|
+
|
|
641
|
+
var raw = await _getRaw(id);
|
|
642
|
+
var next = _expect(raw, "pay");
|
|
643
|
+
var ts = _now();
|
|
644
|
+
await query(
|
|
645
|
+
"UPDATE sales_tax_filings SET " +
|
|
646
|
+
"status = ?1, payment_minor = ?2, payment_ref = ?3, paid_at = ?4, " +
|
|
647
|
+
"updated_at = ?5 WHERE id = ?6",
|
|
648
|
+
[next, amount, paymentRef, paidAt, ts, id],
|
|
649
|
+
);
|
|
650
|
+
return _decodeFiling(await _getRaw(id));
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// ---- markAmended ------------------------------------------------------
|
|
654
|
+
|
|
655
|
+
async function markAmended(input) {
|
|
656
|
+
if (!input || typeof input !== "object") {
|
|
657
|
+
throw new TypeError("salesTaxFilings.markAmended: input object required");
|
|
658
|
+
}
|
|
659
|
+
var id = _filingId(input.filing_id);
|
|
660
|
+
var reason = _shortText(input.reason, "reason", MAX_AMENDED_REASON_LEN);
|
|
661
|
+
|
|
662
|
+
var raw = await _getRaw(id);
|
|
663
|
+
var next = _expect(raw, "amend");
|
|
664
|
+
var ts = _now();
|
|
665
|
+
await query(
|
|
666
|
+
"UPDATE sales_tax_filings SET " +
|
|
667
|
+
"status = ?1, amended_reason = ?2, amended_at = ?3, updated_at = ?3 " +
|
|
668
|
+
"WHERE id = ?4",
|
|
669
|
+
[next, reason, ts, id],
|
|
670
|
+
);
|
|
671
|
+
return _decodeFiling(await _getRaw(id));
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// ---- getFiling --------------------------------------------------------
|
|
675
|
+
|
|
676
|
+
async function getFiling(id) {
|
|
677
|
+
var cid = _filingId(id);
|
|
678
|
+
return _decodeFiling(await _getRaw(cid));
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// ---- listFilings ------------------------------------------------------
|
|
682
|
+
|
|
683
|
+
async function listFilings(listOpts) {
|
|
684
|
+
listOpts = listOpts || {};
|
|
685
|
+
var jurisdiction = listOpts.jurisdiction == null ? null : _jurisdiction(listOpts.jurisdiction);
|
|
686
|
+
var status = listOpts.status == null ? null : _status(listOpts.status, "status");
|
|
687
|
+
var limit = _limit(listOpts.limit);
|
|
688
|
+
|
|
689
|
+
var where = [];
|
|
690
|
+
var params = [];
|
|
691
|
+
var idx = 1;
|
|
692
|
+
if (jurisdiction) { where.push("jurisdiction = ?" + idx); params.push(jurisdiction); idx += 1; }
|
|
693
|
+
if (status) { where.push("status = ?" + idx); params.push(status); idx += 1; }
|
|
694
|
+
var sql = "SELECT * FROM sales_tax_filings" +
|
|
695
|
+
(where.length ? " WHERE " + where.join(" AND ") : "") +
|
|
696
|
+
" ORDER BY due_date ASC, id ASC LIMIT ?" + idx;
|
|
697
|
+
params.push(limit);
|
|
698
|
+
|
|
699
|
+
var r = await query(sql, params);
|
|
700
|
+
var out = [];
|
|
701
|
+
for (var i = 0; i < r.rows.length; i += 1) {
|
|
702
|
+
out.push(_decodeFiling(r.rows[i]));
|
|
703
|
+
}
|
|
704
|
+
return out;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// ---- auditReportForJurisdiction ---------------------------------------
|
|
708
|
+
|
|
709
|
+
async function auditReportForJurisdiction(input) {
|
|
710
|
+
if (!input || typeof input !== "object") {
|
|
711
|
+
throw new TypeError("salesTaxFilings.auditReportForJurisdiction: input object required");
|
|
712
|
+
}
|
|
713
|
+
var jurisdiction = _jurisdiction(input.jurisdiction);
|
|
714
|
+
var from = _epoch(input.from, "from");
|
|
715
|
+
var to = _epoch(input.to, "to");
|
|
716
|
+
if (to <= from) {
|
|
717
|
+
throw new TypeError("salesTaxFilings.auditReportForJurisdiction: to must be > from");
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Pull every filing whose period intersects [from, to]. Intersect
|
|
721
|
+
// rule: filing.period_start < to AND filing.period_end > from.
|
|
722
|
+
var r = await query(
|
|
723
|
+
"SELECT * FROM sales_tax_filings " +
|
|
724
|
+
"WHERE jurisdiction = ?1 AND period_start < ?2 AND period_end > ?3 " +
|
|
725
|
+
"ORDER BY period_start ASC",
|
|
726
|
+
[jurisdiction, to, from],
|
|
727
|
+
);
|
|
728
|
+
|
|
729
|
+
var filings = [];
|
|
730
|
+
var totalGross = 0;
|
|
731
|
+
var totalTaxable = 0;
|
|
732
|
+
var totalExempt = 0;
|
|
733
|
+
var totalCollected = 0;
|
|
734
|
+
var totalOwed = 0;
|
|
735
|
+
var totalPaid = 0;
|
|
736
|
+
for (var i = 0; i < r.rows.length; i += 1) {
|
|
737
|
+
var f = _decodeFiling(r.rows[i]);
|
|
738
|
+
filings.push(f);
|
|
739
|
+
totalGross += f.gross_revenue_minor;
|
|
740
|
+
totalTaxable += f.taxable_revenue_minor;
|
|
741
|
+
totalExempt += f.exempt_revenue_minor;
|
|
742
|
+
totalCollected += f.tax_collected_minor;
|
|
743
|
+
totalOwed += f.tax_owed_minor;
|
|
744
|
+
if (f.payment_minor != null) totalPaid += f.payment_minor;
|
|
745
|
+
}
|
|
746
|
+
return {
|
|
747
|
+
jurisdiction: jurisdiction,
|
|
748
|
+
from: from,
|
|
749
|
+
to: to,
|
|
750
|
+
filing_count: filings.length,
|
|
751
|
+
filings: filings,
|
|
752
|
+
total_gross_revenue_minor: totalGross,
|
|
753
|
+
total_taxable_revenue_minor: totalTaxable,
|
|
754
|
+
total_exempt_revenue_minor: totalExempt,
|
|
755
|
+
total_tax_collected_minor: totalCollected,
|
|
756
|
+
total_tax_owed_minor: totalOwed,
|
|
757
|
+
total_tax_paid_minor: totalPaid,
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// ---- upcomingDue ------------------------------------------------------
|
|
762
|
+
|
|
763
|
+
async function upcomingDue(input) {
|
|
764
|
+
if (!input || typeof input !== "object") {
|
|
765
|
+
throw new TypeError("salesTaxFilings.upcomingDue: input object required");
|
|
766
|
+
}
|
|
767
|
+
var days = _daysAhead(input.days_ahead);
|
|
768
|
+
var nowTs = input.now == null ? _now() : _epoch(input.now, "now");
|
|
769
|
+
var horizon = nowTs + (days * 24 * 60 * 60 * 1000);
|
|
770
|
+
|
|
771
|
+
var r = await query(
|
|
772
|
+
"SELECT * FROM sales_tax_filings " +
|
|
773
|
+
"WHERE status IN ('draft', 'computed', 'submitted') " +
|
|
774
|
+
" AND due_date <= ?1 " +
|
|
775
|
+
"ORDER BY due_date ASC, id ASC",
|
|
776
|
+
[horizon],
|
|
777
|
+
);
|
|
778
|
+
var out = [];
|
|
779
|
+
for (var i = 0; i < r.rows.length; i += 1) {
|
|
780
|
+
out.push(_decodeFiling(r.rows[i]));
|
|
781
|
+
}
|
|
782
|
+
return out;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
return {
|
|
786
|
+
KINDS: KINDS.slice(),
|
|
787
|
+
STATUSES: STATUSES.slice(),
|
|
788
|
+
JURISDICTION_RE: JURISDICTION_RE,
|
|
789
|
+
|
|
790
|
+
defineFilingPeriod: defineFilingPeriod,
|
|
791
|
+
computeFiling: computeFiling,
|
|
792
|
+
recordSubmission: recordSubmission,
|
|
793
|
+
recordPayment: recordPayment,
|
|
794
|
+
markAmended: markAmended,
|
|
795
|
+
getFiling: getFiling,
|
|
796
|
+
listFilings: listFilings,
|
|
797
|
+
auditReportForJurisdiction: auditReportForJurisdiction,
|
|
798
|
+
upcomingDue: upcomingDue,
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
module.exports = {
|
|
803
|
+
create: create,
|
|
804
|
+
KINDS: KINDS,
|
|
805
|
+
STATUSES: STATUSES,
|
|
806
|
+
JURISDICTION_RE: JURISDICTION_RE,
|
|
807
|
+
};
|