@blamejs/blamejs-shop 0.4.78 → 0.4.79
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 +2 -0
- package/lib/asset-manifest.json +1 -1
- package/lib/inventory-receive.js +18 -5
- package/lib/smart-restocking.js +5 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.4.x
|
|
10
10
|
|
|
11
|
+
- v0.4.79 (2026-06-21) — **Reversing a receipt no longer mints phantom stock, and restock recommendations accept lowercase location codes.** Two inventory correctness fixes. Reversing an applied goods-receipt now rolls back exactly the quantity the reversal removed: when a SKU had already been decremented elsewhere to below the received quantity, the reversal's forward step removes only what's on hand, and a mid-reversal failure now compensates by that exact amount instead of adding back the full received quantity — which previously minted phantom stock. Separately, restock recommendations now accept lowercase and dotted location codes, matching the location-code format the rest of the inventory surface already accepts; a stricter uppercase-only pattern had been rejecting valid codes. No configuration changes; upgrade to pick them up. **Fixed:** *Reversing a goods-receipt rolls back the exact quantity removed* — Receipt reversal now records how many units each line's decrement actually removed (floored at the quantity on hand) and, if a later line in the same reversal fails, compensates the already-decremented lines by that exact amount. Previously the forward decrement clamped at zero while the compensation added back the full received quantity, so reversing a receipt for a SKU that had since been drawn down below the received amount could leave more stock on hand than before the reversal. · *Restock recommendations accept lowercase and dotted location codes* — smart-restocking now validates location codes against the same pattern the inventory-locations primitive uses (mixed case, with dot/underscore/hyphen), so a recommendation request for a location like "aisle-3.bin-2" is accepted. A divergent uppercase-only pattern had been rejecting valid location codes that every other inventory operation accepts.
|
|
12
|
+
|
|
11
13
|
- v0.4.78 (2026-06-21) — **Voiding a gift card no longer clobbers a concurrent redemption, and a credit order can't be charged twice.** Two money-safety fixes that make terminal transitions exactly-once. Voiding a gift card is now a single atomic transition guarded on the card still being active, so a void that races a redemption can no longer overwrite the redemption's terminal state and silently discard the spent balance — whichever transition lands first wins and the other is refused. Separately, charging a credit-line order is now idempotent per order: the charge guard refuses a second charge for an order that already has one (and a retry returns the original charge rather than erroring), so the same order can't be charged twice against the customer's credit line. No configuration changes; upgrade to pick them up. **Fixed:** *Voiding a gift card is an atomic, fenced transition* — Gift-card void is now a single guarded UPDATE that only matches a card still in the active state, mirroring redeem. A void that races a redemption (or an expiry) can no longer overwrite the card's terminal state — the prior read-decide-write could void a card a concurrent redemption had just spent, discarding the remaining-balance debit. A void that loses the race is refused (or returns idempotently if the card was already voided). · *A credit-line order is charged at most once* — creditLimits.chargeOrder now refuses a second charge for an order that already carries one, enforced in the same atomic statement as the credit-cap check. A retried charge for an already-charged order returns the original charge instead of erroring or charging the line a second time, so a duplicate capture or retry can't double-draw the customer's credit.
|
|
12
14
|
|
|
13
15
|
- v0.4.77 (2026-06-21) — **Exactly-once hardening for payment capture, auto-replenishment, gift redemption, and carrier-credential rotation.** Four correctness fixes that make money- and inventory-moving state transitions happen exactly once under concurrency or delayed settlement. A PayPal order is no longer marked paid when the capture itself is still pending — settlement is now read from the capture status, not the order envelope, so a delayed or under-review payment leaves the order pending until funds actually settle. Overlapping auto-replenishment ticks can no longer both fire the same policy: the cadence advance is now an atomic claim, so a vendor purchase order is composed and submitted once per window instead of being double-cut. Redeeming one gift-subscription link twice — two clicks racing within milliseconds — now mints exactly one subscription: the redemption claims the gift before creating the subscription, and rolls the claim back if the mint fails. And two carrier-credential rotations firing at once can no longer both commit; the rotation now compare-and-swaps on the observed key, so the loser is refused instead of clobbering the winner's fresh key and stranding the previous-key grace window. No configuration changes; upgrade to pick them up. **Fixed:** *A PayPal order is marked paid only when the capture itself completes* — Capturing a PayPal order now gates the paid transition on the per-capture status rather than the order envelope. The order-level status can read COMPLETED while the capture is still PENDING (a delayed-settlement or under-review payment), which previously marked the order paid before the money settled. Any non-completed capture now leaves the order pending and reports the capture as unhandled so the checkout surfaces the unsettled state. · *Overlapping auto-replenishment ticks no longer double-submit a purchase order* — The replenishment scheduler advances a policy's run cadence with a single conditional claim that is the serialization point: two ticks firing at once contend on the policy row, only one matches the due-window predicate, and the vendor purchase order is composed and submitted exactly once per window. Previously the cadence was an in-memory check followed by an unconditional stamp, so two overlapping ticks both passed the check and cut duplicate POs. · *Redeeming a gift-subscription link twice creates exactly one subscription* — Gift redemption now claims the gift's pending→redeemed transition before creating the recipient's subscription, and only the winner of that atomic claim mints. A leaked or double-clicked redemption link presented twice within milliseconds therefore creates one subscription, not two; if the subscription mint then fails, the claim is rolled back so the gift can be redeemed again rather than being stranded as redeemed-with-no-subscription. · *Concurrent carrier-credential rotations no longer over-issue or strand credentials* — Rotating a shipping-carrier account's API credentials now compare-and-swaps on the observed key and account state, so two rotations firing at once can't both commit. The loser is refused with a rotation-conflict error instead of overwriting the winner's freshly minted key and leaving the previous-key grace window pointing at a key no deployed worker holds.
|
package/lib/asset-manifest.json
CHANGED
package/lib/inventory-receive.js
CHANGED
|
@@ -432,12 +432,25 @@ function create(opts) {
|
|
|
432
432
|
for (var i = 0; i < receipt.lines.length; i += 1) {
|
|
433
433
|
var l = receipt.lines[i];
|
|
434
434
|
var ts = _now();
|
|
435
|
+
// Capture how many units the decrement ACTUALLY removes: a prior
|
|
436
|
+
// out-of-band decrement can leave stock below qty_received, so the
|
|
437
|
+
// removable amount is floored at what's on hand. Recording the real
|
|
438
|
+
// delta (not qty_received) is what makes a compensating add-back on
|
|
439
|
+
// a mid-loop failure an exact inverse — the old `MAX(0,...)` clamp
|
|
440
|
+
// removed fewer units than the compensation later added back, minting
|
|
441
|
+
// phantom stock. `removed <= prior`, so the subtraction never goes
|
|
442
|
+
// negative and the clamp is no longer needed.
|
|
443
|
+
var priorRow = (await query(
|
|
444
|
+
"SELECT stock_on_hand FROM inventory WHERE sku = ?1", [l.sku],
|
|
445
|
+
)).rows[0];
|
|
446
|
+
var prior = priorRow ? Number(priorRow.stock_on_hand) : 0;
|
|
447
|
+
var removed = Math.min(l.qty_received, prior);
|
|
435
448
|
await query(
|
|
436
|
-
"UPDATE inventory SET stock_on_hand =
|
|
437
|
-
[
|
|
449
|
+
"UPDATE inventory SET stock_on_hand = stock_on_hand - ?1, updated_at = ?2 WHERE sku = ?3",
|
|
450
|
+
[removed, ts, l.sku],
|
|
438
451
|
);
|
|
439
|
-
decremented.push(l);
|
|
440
|
-
stockChanges.push({ sku: l.sku, qty: -
|
|
452
|
+
decremented.push({ sku: l.sku, removed: removed });
|
|
453
|
+
stockChanges.push({ sku: l.sku, qty: -removed });
|
|
441
454
|
}
|
|
442
455
|
} catch (e) {
|
|
443
456
|
// Compensate the decrements that already landed and release the claim so
|
|
@@ -449,7 +462,7 @@ function create(opts) {
|
|
|
449
462
|
try {
|
|
450
463
|
await query(
|
|
451
464
|
"UPDATE inventory SET stock_on_hand = stock_on_hand + ?1, updated_at = ?2 WHERE sku = ?3",
|
|
452
|
-
[dl.
|
|
465
|
+
[dl.removed, _now(), dl.sku],
|
|
453
466
|
);
|
|
454
467
|
} catch (_compErr) { /* best-effort compensation; the claim release below restores retryability */ }
|
|
455
468
|
}
|
package/lib/smart-restocking.js
CHANGED
|
@@ -149,7 +149,10 @@ var b = require("./vendor/blamejs");
|
|
|
149
149
|
|
|
150
150
|
var SLUG_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
151
151
|
var SKU_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
152
|
-
|
|
152
|
+
// Mirrors inventory-locations.js CODE_RE exactly — a divergent stricter
|
|
153
|
+
// (uppercase-only, no dot) pattern here rejected valid lowercase/dotted
|
|
154
|
+
// location codes that the rest of the inventory surface accepts.
|
|
155
|
+
var LOCATION_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
153
156
|
|
|
154
157
|
var MAX_HOLDING_BPS = 100000; // 1000% / yr — operator's upper bound; the migration CHECK enforces too
|
|
155
158
|
var MAX_ORDERING_COST_MINOR = 1000000000; // 1e9 minor units — sanity ceiling
|
|
@@ -233,7 +236,7 @@ function _sku(s, label) {
|
|
|
233
236
|
function _locationOrNull(s) {
|
|
234
237
|
if (s == null) return null;
|
|
235
238
|
if (typeof s !== "string" || !LOCATION_RE.test(s)) {
|
|
236
|
-
throw new TypeError("smart-restocking: location_code must match /^[A-
|
|
239
|
+
throw new TypeError("smart-restocking: location_code must match /^[A-Za-z0-9][A-Za-z0-9._-]*$/ (1..64 chars)");
|
|
237
240
|
}
|
|
238
241
|
return s;
|
|
239
242
|
}
|
package/package.json
CHANGED