@amboras-dev/money 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +401 -2
- package/dist/ai.cjs +185 -0
- package/dist/ai.d.cts +97 -0
- package/dist/ai.d.ts +97 -0
- package/dist/ai.js +175 -0
- package/dist/allocate-CZRg4TOP.cjs +222 -0
- package/dist/allocate-X7_jAtOj.js +199 -0
- package/dist/amount-BV0O-aN8.cjs +241 -0
- package/dist/amount-BoYBMgK9.js +200 -0
- package/dist/arithmetic-BhNwisjb.js +132 -0
- package/dist/arithmetic-DjT4Kzgt.cjs +233 -0
- package/dist/billing.cjs +184 -0
- package/dist/billing.d.cts +81 -0
- package/dist/billing.d.ts +81 -0
- package/dist/billing.js +175 -0
- package/dist/commerce.cjs +536 -0
- package/dist/commerce.d.cts +293 -0
- package/dist/commerce.d.ts +293 -0
- package/dist/commerce.js +516 -0
- package/dist/format-BdPnijvn.js +99 -0
- package/dist/format-DLhno7eX.cjs +128 -0
- package/dist/fx.cjs +407 -0
- package/dist/fx.d.cts +157 -0
- package/dist/fx.d.ts +157 -0
- package/dist/fx.js +374 -0
- package/dist/index.cjs +352 -0
- package/dist/index.d.cts +300 -0
- package/dist/index.d.ts +300 -0
- package/dist/index.js +276 -0
- package/dist/input.cjs +163 -0
- package/dist/input.d.cts +203 -0
- package/dist/input.d.ts +203 -0
- package/dist/input.js +153 -0
- package/dist/iso-minor-CqkWDxHS.js +48 -0
- package/dist/iso-minor-wSwJN57T.cjs +65 -0
- package/dist/medusa-4K8N1pUc.d.cts +87 -0
- package/dist/medusa-DBXJXXvF.d.ts +87 -0
- package/dist/medusa-DqJY1qpj.js +116 -0
- package/dist/medusa-lRY9xlsf.cjs +175 -0
- package/dist/money-B-3aFjNJ.d.cts +248 -0
- package/dist/money-B-3aFjNJ.d.ts +248 -0
- package/dist/money-B3wH_Z4S.cjs +3990 -0
- package/dist/money-WhnGiI00.js +3583 -0
- package/dist/number-text-BWu67jUq.cjs +145 -0
- package/dist/number-text-DE-q4jO5.js +134 -0
- package/dist/own-D7544D3k.js +4 -0
- package/dist/own-oP9DXFWv.cjs +9 -0
- package/dist/percent-C0InC2t2.cjs +43 -0
- package/dist/percent-D1VB9xXG.js +20 -0
- package/dist/rate-CVeMii4a.cjs +217 -0
- package/dist/rate-DBPiYgT1.d.ts +63 -0
- package/dist/rate-DicStBue.js +152 -0
- package/dist/rate-tqK8ijG0.d.cts +63 -0
- package/dist/reporting-CG-ai2sM.d.cts +31 -0
- package/dist/reporting-tpGJbqYp.d.ts +31 -0
- package/dist/retail-1mu888J1.js +69 -0
- package/dist/retail-C_BPLvMj.d.cts +31 -0
- package/dist/retail-Cml8YSKg.cjs +86 -0
- package/dist/retail-DRUTTQoJ.d.ts +31 -0
- package/dist/rounding-DHBtNCHG.cjs +79 -0
- package/dist/rounding-g9rvye0k.js +50 -0
- package/dist/sql.cjs +290 -0
- package/dist/sql.d.cts +147 -0
- package/dist/sql.d.ts +147 -0
- package/dist/sql.js +271 -0
- package/dist/testing.cjs +61995 -0
- package/dist/testing.d.cts +125 -0
- package/dist/testing.d.ts +125 -0
- package/dist/testing.js +61986 -0
- package/dist/wire-Bhuut8cW.cjs +2097 -0
- package/dist/wire-CaUN9Vf_.d.ts +309 -0
- package/dist/wire-sW0yzqth.js +1888 -0
- package/dist/wire-xjYXv4go.d.cts +309 -0
- package/dist/wire.cjs +37 -0
- package/dist/wire.d.cts +2 -0
- package/dist/wire.d.ts +2 -0
- package/dist/wire.js +2 -0
- package/package.json +158 -3
package/dist/sql.js
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { $ as describeValue, A as storageScale, E as resolveCurrency, Q as MoneyError, u as CURRENCIES } from "./money-WhnGiI00.js";
|
|
2
|
+
import { r as checkRate } from "./rate-DicStBue.js";
|
|
3
|
+
import { t as priceRoundingParams } from "./retail-1mu888J1.js";
|
|
4
|
+
//#region src/sql/guard.ts
|
|
5
|
+
const STATEMENT_BREAKERS = /;|--|\/\*|\*\/|\0/;
|
|
6
|
+
const refuse = (name, value) => new MoneyError("parse", `${name} must be a SQL expression written in code, got ${describeValue(value)}`);
|
|
7
|
+
/** Code-written text, or `parse` when it is blank, could break out of the expression or holds a `?` placeholder. */
|
|
8
|
+
function fragment(value, name) {
|
|
9
|
+
if (typeof value !== "string" || value.trim() === "" || STATEMENT_BREAKERS.test(value) || value.includes("?")) throw refuse(name, value);
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
const isFragment = (value) => typeof value === "object" && value !== null && typeof value.sql === "string";
|
|
13
|
+
/**
|
|
14
|
+
* An expression argument as a SqlFragment: code-written text through `fragment`, or a fragment
|
|
15
|
+
* whose text could not break out and holds exactly one `?` per binding.
|
|
16
|
+
*/
|
|
17
|
+
function expression(value, name) {
|
|
18
|
+
if (!isFragment(value)) return {
|
|
19
|
+
sql: fragment(value, name),
|
|
20
|
+
bindings: []
|
|
21
|
+
};
|
|
22
|
+
const { sql: text, bindings } = value;
|
|
23
|
+
const placeholders = text.split("?").length - 1;
|
|
24
|
+
if (text.trim() === "" || STATEMENT_BREAKERS.test(text) || !Array.isArray(bindings) || bindings.length !== placeholders) throw refuse(name, text);
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
/** One placeholder bound to `value`. */
|
|
28
|
+
const bind = (value) => ({
|
|
29
|
+
sql: "?",
|
|
30
|
+
bindings: [value]
|
|
31
|
+
});
|
|
32
|
+
/** SQL text the package writes itself (keywords, registry code lists, digits): no bindings. */
|
|
33
|
+
const raw = (text) => ({
|
|
34
|
+
sql: String(text),
|
|
35
|
+
bindings: []
|
|
36
|
+
});
|
|
37
|
+
/** Template text joined with fragments, their bindings concatenated in the order they appear. */
|
|
38
|
+
function sql(strings, ...parts) {
|
|
39
|
+
let text = strings[0];
|
|
40
|
+
const bindings = [];
|
|
41
|
+
parts.forEach((part, i) => {
|
|
42
|
+
text += part.sql + strings[i + 1];
|
|
43
|
+
bindings.push(...part.bindings);
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
sql: text,
|
|
47
|
+
bindings
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** A JSON key or event name a builder binds, held to `/^[a-z][a-z0-9_]*$/`. */
|
|
51
|
+
function token(value, name) {
|
|
52
|
+
if (typeof value !== "string" || !/^[a-z][a-z0-9_]*$/.test(value)) throw new MoneyError("parse", `${name} must match /^[a-z][a-z0-9_]*$/, got ${describeValue(value)}`);
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
function dialectOf(value) {
|
|
56
|
+
if (value !== "pg" && value !== "clickhouse") throw new MoneyError("parse", `Unknown SQL dialect: ${describeValue(value)}`);
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/sql/lists.ts
|
|
61
|
+
const quoted = (pick) => CURRENCIES.filter(pick).map((info) => `'${info.code}'`).join(",");
|
|
62
|
+
/** Storage scale 1000: `'BHD','IQD','JOD','KWD','LYD','OMR','TND'`. */
|
|
63
|
+
const SCALE_1000_CODES = quoted((info) => info.storageScale === 1e3);
|
|
64
|
+
/** Display digits 0 (ISO). */
|
|
65
|
+
const DISPLAY_0_CODES = quoted((info) => info.displayDigits === 0);
|
|
66
|
+
/** Display digits 3 (ISO). */
|
|
67
|
+
const DISPLAY_3_CODES = quoted((info) => info.displayDigits === 3);
|
|
68
|
+
/** Medusa core `decimal_digits` 0 (payment-status epsilon 1). */
|
|
69
|
+
const MEDUSA_0_CODES = quoted((info) => info.medusaDigits === 0);
|
|
70
|
+
/** Medusa core `decimal_digits` 3 (payment-status epsilon 0.001). */
|
|
71
|
+
const MEDUSA_3_CODES = quoted((info) => info.medusaDigits === 3);
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/sql/amounts.ts
|
|
74
|
+
const CH_STORED_SCALE = 3;
|
|
75
|
+
const CH_ROUND_SCALE = 12;
|
|
76
|
+
/** The storage divisor of the currency in `codeExpr`, keyed by code (never `currency.decimal_digits`). */
|
|
77
|
+
function storageDivisorSql(codeExpr, dialect = "pg") {
|
|
78
|
+
const code = expression(codeExpr, "codeExpr");
|
|
79
|
+
const codes = raw(SCALE_1000_CODES);
|
|
80
|
+
return dialectOf(dialect) === "pg" ? sql`(CASE WHEN upper(${code}) IN (${codes}) THEN 1000 ELSE 100 END)` : sql`if(upper(${code}) IN (${codes}), 1000, 100)`;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* A stored amount as major units, exactly: Postgres numeric division, ClickHouse
|
|
84
|
+
* `toDecimal128(amount, 3)` over the divisor (a Decimal(38, 3), never a Float64).
|
|
85
|
+
*/
|
|
86
|
+
function storedToMajorSql(amountExpr, codeExpr, dialect = "pg") {
|
|
87
|
+
const amount = expression(amountExpr, "amountExpr");
|
|
88
|
+
const divisor = storageDivisorSql(codeExpr, dialect);
|
|
89
|
+
return dialect === "pg" ? sql`((${amount})::numeric / ${divisor})` : sql`(toDecimal128(${amount}, ${raw(CH_STORED_SCALE)}) / ${divisor})`;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* A major value rounded to the display digits of a constant currency or of the currency in a
|
|
93
|
+
* column, half away from zero (D1) in both dialects: Postgres `ROUND(numeric)`, ClickHouse
|
|
94
|
+
* `round()` on `toDecimal128(value, 12)` (a Float64 would round half-to-even).
|
|
95
|
+
*/
|
|
96
|
+
function roundMajorSql(expr, currency, dialect = "pg") {
|
|
97
|
+
const value = expression(expr, "expr");
|
|
98
|
+
const pg = dialectOf(dialect) === "pg";
|
|
99
|
+
const exact = pg ? sql`(${value})::numeric` : sql`toDecimal128(${value}, ${raw(CH_ROUND_SCALE)})`;
|
|
100
|
+
let digits;
|
|
101
|
+
if (typeof currency === "string") digits = raw(resolveCurrency(currency, { allowHistoric: true }).displayDigits);
|
|
102
|
+
else {
|
|
103
|
+
const code = expression(currency?.codeExpr, "codeExpr");
|
|
104
|
+
const zero = raw(DISPLAY_0_CODES);
|
|
105
|
+
const three = raw(DISPLAY_3_CODES);
|
|
106
|
+
digits = pg ? sql`(CASE WHEN upper(${code}) IN (${zero}) THEN 0 WHEN upper(${code}) IN (${three}) THEN 3 ELSE 2 END)` : sql`multiIf(upper(${code}) IN (${zero}), 0, upper(${code}) IN (${three}), 3, 2)`;
|
|
107
|
+
}
|
|
108
|
+
return pg ? sql`ROUND(${exact}, ${digits})` : sql`round(${exact}, ${digits})`;
|
|
109
|
+
}
|
|
110
|
+
/** Medusa core's payment-status epsilon for the currency in `codeExpr` (the `medusaEpsilon` table): 1, 0.001 or 0.01. */
|
|
111
|
+
function medusaEpsilonSql(codeExpr) {
|
|
112
|
+
const code = expression(codeExpr, "codeExpr");
|
|
113
|
+
return sql`(CASE WHEN upper(${code}) IN (${raw(MEDUSA_0_CODES)}) THEN 1 WHEN upper(${code}) IN (${raw(MEDUSA_3_CODES)}) THEN 0.001 ELSE 0.01 END)`;
|
|
114
|
+
}
|
|
115
|
+
/** `a - b` within Medusa's epsilon for the currency (captured / refunded tolerance). */
|
|
116
|
+
function withinEpsilonSql(aExpr, bExpr, codeExpr) {
|
|
117
|
+
return sql`((${expression(aExpr, "aExpr")}) - (${expression(bExpr, "bExpr")}) <= ${medusaEpsilonSql(codeExpr)})`;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Decimal text as numeric, NULL when it is not plain decimal (`signed` default true allows a leading `-`).
|
|
121
|
+
* The pattern spells optional parts as `{0,1}`: a literal `?` would read as a knex placeholder.
|
|
122
|
+
*/
|
|
123
|
+
function parseDecimalSql(expr, options = {}) {
|
|
124
|
+
const value = expression(expr, "expr");
|
|
125
|
+
return sql`(CASE WHEN (${value}) ~ '${raw(options.signed === false ? "^[0-9]+(\\.[0-9]+){0,1}$" : "^-{0,1}[0-9]+(\\.[0-9]+){0,1}$")}' THEN (${value})::numeric END)`;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* A Medusa summary total from a `totals` jsonb: `raw_<key>.value` first (exact), then `<key>`,
|
|
129
|
+
* as `readStoredField` reads it; `''` is NULL. `nullAs: 0` (default) turns a missing total
|
|
130
|
+
* into 0, `nullAs: null` keeps NULL. `key` must match `/^[a-z][a-z0-9_]*$/` (else `parse`); both
|
|
131
|
+
* JSON keys are bindings.
|
|
132
|
+
*/
|
|
133
|
+
function summaryAmountSql(totalsExpr, key, options = {}) {
|
|
134
|
+
const totals = expression(totalsExpr, "totalsExpr");
|
|
135
|
+
const k = token(key, "key");
|
|
136
|
+
const value = sql`NULLIF(COALESCE(${totals} -> ${bind(`raw_${k}`)}::text ->> 'value', ${totals} ->> ${bind(k)}::text), '')::numeric`;
|
|
137
|
+
return options.nullAs === null ? value : sql`COALESCE(${value}, 0)`;
|
|
138
|
+
}
|
|
139
|
+
/** Medusa's raw BigNumber column for a stored amount expression. */
|
|
140
|
+
function rawAmountJsonSql(expr) {
|
|
141
|
+
return sql`jsonb_build_object('value', (${expression(expr, "expr")})::text, 'precision', 20)`;
|
|
142
|
+
}
|
|
143
|
+
/** A currency code column normalised the way Medusa stores it. */
|
|
144
|
+
function normalizeCurrencySql(expr) {
|
|
145
|
+
return sql`lower(btrim(${expression(expr, "expr")}))`;
|
|
146
|
+
}
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/sql/clickhouse.ts
|
|
149
|
+
/**
|
|
150
|
+
* The SELECT list and GROUP BY of per-currency purchase revenue in stored units. The event name
|
|
151
|
+
* is a ClickHouse query parameter (`{revenue_event:String}`, pass `params` as `query_params`),
|
|
152
|
+
* never inlined. GROUP BY names the `currency` alias: ClickHouse resolves aliases first, so
|
|
153
|
+
* `GROUP BY lower(currency)` over an unqualified `currency` column fails with NOT_AN_AGGREGATE
|
|
154
|
+
* (verified on 24.8).
|
|
155
|
+
*/
|
|
156
|
+
function chEventRevenueByCurrencySql(cols = {}) {
|
|
157
|
+
const value = fragment(cols.value ?? "value", "value");
|
|
158
|
+
const currency = fragment(cols.currency ?? "currency", "currency");
|
|
159
|
+
const eventType = fragment(cols.eventType ?? "event_type", "eventType");
|
|
160
|
+
const event = token(cols.event ?? "purchase", "event");
|
|
161
|
+
return {
|
|
162
|
+
select: `lower(${currency}) AS currency, sumIf(${value}, ${eventType} = {revenue_event:String}) AS revenue_stored`,
|
|
163
|
+
groupBy: "currency",
|
|
164
|
+
params: { revenue_event: event }
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region src/sql/ddl.ts
|
|
169
|
+
/** The column type of a stored amount. */
|
|
170
|
+
const STORAGE_AMOUNT_SQL_TYPE = "BIGINT";
|
|
171
|
+
/** A column for DDL, which Postgres never parameterises: a fragment with bindings is `parse`. */
|
|
172
|
+
function ddlColumn(column) {
|
|
173
|
+
const col = expression(column, "column");
|
|
174
|
+
if (col.bindings.length > 0) throw new MoneyError("parse", `DDL takes no bindings, got ${col.bindings.length} for ${col.sql}`);
|
|
175
|
+
return col;
|
|
176
|
+
}
|
|
177
|
+
/** Lower-case three-letter currency codes only. */
|
|
178
|
+
function currencyCodeCheckSql(column) {
|
|
179
|
+
const col = ddlColumn(column);
|
|
180
|
+
return sql`CHECK (${col} = lower(${col}) AND char_length(${col}) = 3)`;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* The CHECK constraint of a stored-amount column: a finite (and, by default, non-negative)
|
|
184
|
+
* amount or NULL.
|
|
185
|
+
* - `'bigint'` (default): a BIGINT is always finite, so only the sign is checked:
|
|
186
|
+
* `CHECK (<col> IS NULL OR <col> >= 0)`. With `nonNegative: false` nothing is left to
|
|
187
|
+
* check and the result is `CHECK (true)`, so callers can still splice it unconditionally.
|
|
188
|
+
* Comparing a BIGINT with `'NaN'` would fail with `invalid input syntax for type bigint`.
|
|
189
|
+
* - `'numeric'`: migration 106's form with both special-value halves compared as text
|
|
190
|
+
* (`<col>::text <> 'NaN' AND <col>::text <> 'Infinity' AND <col> >= 0`). NaN sorts above
|
|
191
|
+
* every number, so the sign clause alone would let it through. With `nonNegative: false`
|
|
192
|
+
* `'-Infinity'` is refused too, so the amount stays finite.
|
|
193
|
+
*/
|
|
194
|
+
function storageAmountCheckSql(column, options = {}) {
|
|
195
|
+
const col = ddlColumn(column);
|
|
196
|
+
const nonNegative = options.nonNegative !== false;
|
|
197
|
+
const columnType = options.columnType ?? "bigint";
|
|
198
|
+
if (columnType !== "bigint" && columnType !== "numeric") throw new MoneyError("amount_invalid", `columnType must be 'bigint' or 'numeric', got ${describeValue(columnType)}`);
|
|
199
|
+
if (columnType === "bigint") return nonNegative ? sql`CHECK (${col} IS NULL OR ${col} >= 0)` : sql`CHECK (true)`;
|
|
200
|
+
return sql`CHECK (${col} IS NULL OR (${col}::text <> 'NaN' AND ${col}::text <> 'Infinity' AND ${nonNegative ? sql`${col} >= 0` : sql`${col}::text <> '-Infinity'`}))`;
|
|
201
|
+
}
|
|
202
|
+
/** USD to credits, `ceil(usd x 100)` (D14), the twin of `billing.usdToCredits`. */
|
|
203
|
+
function usdToCreditsSql(expr) {
|
|
204
|
+
return sql`CEIL((${expression(expr, "expr")})::numeric * ${raw(storageScale("USD"))})::int`;
|
|
205
|
+
}
|
|
206
|
+
/** A yearly price as its monthly equivalent, half away from zero, the twin of `billing.monthlyEquivalent`. */
|
|
207
|
+
function monthlyEquivalentSql(expr) {
|
|
208
|
+
return sql`ROUND((${expression(expr, "expr")})::numeric / 12)`;
|
|
209
|
+
}
|
|
210
|
+
//#endregion
|
|
211
|
+
//#region src/sql/fx.ts
|
|
212
|
+
function factorsJson(table) {
|
|
213
|
+
resolveCurrency(table?.base, { allowHistoric: true });
|
|
214
|
+
return JSON.stringify(table.factors);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* A stored amount in the currency of `codeExpr` converted with the bound factor table
|
|
218
|
+
* (`fx.storeFactors`): `output: 'stored'` rounds half away from zero to a storage unit of the
|
|
219
|
+
* base per row; `'major'` gives exact major units of the base. A missing factor is NULL
|
|
220
|
+
* (dropped by SUM, flagged by `reportingCompleteSql`), never 1.
|
|
221
|
+
*/
|
|
222
|
+
function toReportingSql(amountExpr, codeExpr, table, options) {
|
|
223
|
+
const amount = expression(amountExpr, "amountExpr");
|
|
224
|
+
const code = expression(codeExpr, "codeExpr");
|
|
225
|
+
const factor = sql`(${bind(factorsJson(table))}::jsonb ->> lower(${code}))::numeric`;
|
|
226
|
+
if (options?.output === "major") return sql`((${amount})::numeric * ${factor} / ${raw(resolveCurrency(table.base, { allowHistoric: true }).storageScale)})`;
|
|
227
|
+
if (options?.output !== "stored") throw new MoneyError("parse", `output must be 'stored' or 'major', got ${describeValue(options?.output)}`);
|
|
228
|
+
return sql`round((${amount})::numeric * ${factor})`;
|
|
229
|
+
}
|
|
230
|
+
/** True when every row's currency has a factor (`bool_and`); domain filters stay at the call site. */
|
|
231
|
+
function reportingCompleteSql(codeExpr, table) {
|
|
232
|
+
const code = expression(codeExpr, "codeExpr");
|
|
233
|
+
return sql`bool_and((${bind(factorsJson(table))}::jsonb ->> lower(${code})) IS NOT NULL)`;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* A stored amount converted by `factor` (from `fx.convertFactor`, scale ratio included; its
|
|
237
|
+
* numerator and denominator are bindings) into `to`, rounded once to the currency unit (whole
|
|
238
|
+
* units for zero-decimal), then `retail: 'nearest'` (the market charm) if asked.
|
|
239
|
+
*/
|
|
240
|
+
function convertStoredSql(amountExpr, factor, options) {
|
|
241
|
+
const amount = expression(amountExpr, "amountExpr");
|
|
242
|
+
const { numerator, denominator } = checkRate(factor);
|
|
243
|
+
const { unit, majorUnit, charm } = priceRoundingParams(options?.to);
|
|
244
|
+
const exact = sql`(${amount})::numeric * ${bind(String(numerator))}::numeric / ${bind(String(denominator))}::numeric`;
|
|
245
|
+
const x = unit === 1 ? sql`ROUND(${exact})` : sql`(ROUND(${exact} / ${raw(unit)}) * ${raw(unit)})`;
|
|
246
|
+
const retail = options.retail ?? "none";
|
|
247
|
+
if (retail === "none") return x;
|
|
248
|
+
if (retail !== "nearest") throw new MoneyError("amount_invalid", `SQL retail rounding must be 'none' or 'nearest', got ${describeValue(retail)}`);
|
|
249
|
+
if (charm === null) {
|
|
250
|
+
const step = raw(100 * majorUnit);
|
|
251
|
+
return sql`(CASE WHEN ${x} <= 0 THEN ${x} ELSE CEIL(${x} / ${step}) * ${step} END)`;
|
|
252
|
+
}
|
|
253
|
+
const major = raw(majorUnit);
|
|
254
|
+
const whole = sql`(FLOOR(${x} / ${major}) * ${major})`;
|
|
255
|
+
return sql`(CASE WHEN ${x} <= 0 THEN ${x} WHEN ${x} <= ${whole} + ${raw(charm)} THEN ${whole} + ${raw(charm)} ELSE ${whole} + ${raw(majorUnit + charm)} END)`;
|
|
256
|
+
}
|
|
257
|
+
/** `convertStoredSql` with the factor and rounding read from columns, so one statement converts many currencies. */
|
|
258
|
+
function convertStoredByColumnsSql(amountExpr, cols) {
|
|
259
|
+
const amount = expression(amountExpr, "amountExpr");
|
|
260
|
+
const n = expression(cols?.numerator, "numerator");
|
|
261
|
+
const d = expression(cols.denominator, "denominator");
|
|
262
|
+
const unit = expression(cols.unit, "unit");
|
|
263
|
+
const major = expression(cols.majorUnit, "majorUnit");
|
|
264
|
+
const charm = expression(cols.charm, "charm");
|
|
265
|
+
const retail = expression(cols.retail, "retail");
|
|
266
|
+
const x = sql`(ROUND((${amount})::numeric * ${n} / ${d} / ${unit}) * ${unit})`;
|
|
267
|
+
const whole = sql`(FLOOR(${x} / ${major}) * ${major})`;
|
|
268
|
+
return sql`(CASE WHEN ${retail} = 'none' THEN ${x} WHEN ${x} <= 0 THEN ${x} WHEN ${charm} IS NULL THEN CEIL(${x} / (100 * ${major})) * (100 * ${major}) WHEN ${x} <= ${whole} + ${charm} THEN ${whole} + ${charm} ELSE ${whole} + ${major} + ${charm} END)`;
|
|
269
|
+
}
|
|
270
|
+
//#endregion
|
|
271
|
+
export { STORAGE_AMOUNT_SQL_TYPE, chEventRevenueByCurrencySql, convertStoredByColumnsSql, convertStoredSql, currencyCodeCheckSql, medusaEpsilonSql, monthlyEquivalentSql, normalizeCurrencySql, parseDecimalSql, rawAmountJsonSql, reportingCompleteSql, roundMajorSql, storageAmountCheckSql, storageDivisorSql, storedToMajorSql, summaryAmountSql, toReportingSql, usdToCreditsSql, withinEpsilonSql };
|