- Produces: `ApiRequest.getOtherUrlWithHeader(apiName, param, headers, callback)` and `ApiRequest.patchOtherUrlWithHeader(apiName, param, headers, callback)` — both call `callback(err, body, statusCode)` (3rd arg is the HTTP status code, `undefined` on network-level `err`). `ApiRequest.postOtherUrlWithHeader` (existing function) is extended to also pass `statusCode` as a 3rd callback arg — existing callers (`core.server.controller.js:2041`) are unaffected since they only read the first 2 args.
- Produces: `ApiRequest.getOtherUrlWithHeader(apiName, param, headers, callback)` and `ApiRequest.patchOtherUrlWithHeader(apiName, param, headers, callback)` — both call `callback(err, body, statusCode)` (3rd arg is the HTTP status code, `undefined` on network-level `err`). `ApiRequest.postOtherUrlWithHeader` (existing function) is extended to also pass `statusCode` as a 3rd callback arg — existing callers (`core.server.controller.js:2041`) are unaffected since they only read the first 2 args.
- Produces: `SobanleClient.login(callback)` → `(err, token)`, always performs a real login call and refreshes the cache. `SobanleClient.getToken(callback)` → `(err, token)`, returns the cached token if not expired, otherwise calls `exports.login`. `SobanleClient._resetTokenCacheForTest()` — test-only helper that clears the module-level cache between scratch-script scenarios.
- Produces: `SobanleClient.login(callback)` → `(err, token)`, always performs a real login call and refreshes the cache. `SobanleClient.getToken(callback)` → `(err, token)`, returns the cached token if not expired, otherwise calls `exports.login`. `SobanleClient._resetTokenCacheForTest()` — test-only helper that clears the module-level cache between scratch-script scenarios.
- Internal calls to `login`/`getToken` from other functions in this file (later tasks) MUST go through `exports.login(...)` / `exports.getToken(...)`, not a local reference, so tests can monkey-patch them.
- Internal calls to `login`/`getToken` from other functions in this file (later tasks) MUST go through `exports.login(...)` / `exports.getToken(...)`, not a local reference, so tests can monkey-patch them.
- Produces: `SobanleClient.getProducts(callback)` → `(err, products)`. `SobanleClient.createSale(lines, customerData, callback)` → `(err, { orderId, orderTotal })`. `SobanleClient.changeSaleStatus(orderId, callback)` → `(err)`. All three transparently retry once (re-login, then retry the original call) on a `401` response.
- Produces: `SobanleClient.getProducts(callback)` → `(err, products)`. `SobanleClient.createSale(lines, customerData, callback)` → `(err, { orderId, orderTotal })`. `SobanleClient.changeSaleStatus(orderId, callback)` → `(err)`. All three transparently retry once (re-login, then retry the original call) on a `401` response.
- Produces: `SobanleClient.createAutoOrder(customerData, targetAmount, callback)` → `(err, { orderId, orderTotal })`. Fetches products, picks a combo via `productComboPicker.pickProductCombo`, creates the sale. Fails with `NO_PRODUCT_COMBO_MATCH` without ever calling `createSale` if no combo fits.
- Produces: `SobanleClient.createAutoOrder(customerData, targetAmount, callback)` → `(err, { orderId, orderTotal })`. Fetches products, picks a combo via `productComboPicker.pickProductCombo`, creates the sale. Fails with `NO_PRODUCT_COMBO_MATCH` without ever calling `createSale` if no combo fits.
- Consumes: `exports.getProducts`, `exports.createSale` from Task 5; `pickProductCombo` from Task 2.
- Consumes: `exports.getProducts`, `exports.createSale` from Task 5; `pickProductCombo` from Task 2.
- Produces: after `epayIPN` marks a transaction `success`, it fire-and-forgets a call to `SobanleClient.changeSaleStatus(tx.sobanleOrderId, ...)`. A failure there is logged only — the IPN still responds `200 {code:"00", data:"Success"}` immediately, unaffected by the Sổ Bán Lẻ call's outcome or latency.
- Produces: after `epayIPN` marks a transaction `success`, it fire-and-forgets a call to `SobanleClient.changeSaleStatus(tx.sobanleOrderId, ...)`. A failure there is logged only — the IPN still responds `200 {code:"00", data:"Success"}` immediately, unaffected by the Sổ Bán Lẻ call's outcome or latency.
...
@@ -1333,16 +1406,18 @@ Expected: fails — `changeSaleStatus called with correct orderId: false` (`chan
...
@@ -1333,16 +1406,18 @@ Expected: fails — `changeSaleStatus called with correct orderId: false` (`chan
Inside the `AdminTransaction.updateOne(...)` callback (after the `updateErr` check, before `return res.status(200)...`), add:
Inside the `AdminTransaction.updateOne(...)` callback (after the `updateErr` check, before `return res.status(200)...`), add: