Commit Graph
16 Commits
Author SHA1 Message Date
efrilmandClaude Opus 5.5 2c9753fae7 feat(loyalty): remove paying with EnakPoint
EnakPoint can only be redeemed for vouchers now: it can no longer pay for
orders and is never cashed out (docs/enakgame-prd.md §3.2, EG-001,
EG-002). No order was ever paid with EnakPoint, so there is no data to
move.

Removed:
- POST /customer/wallet/payment-code, POST /customer/orders/:id/pay-with-points
  and GET /orders/:id/point-payment/preview, with their processors,
  repositories, services, handlers and tests.
- The point payment method type: paying, splitting and refunding with it,
  the outlet filter on the method list, and the system-method guard.
- points and payment_code on CreatePayment; points_used and point_value
  on payments; accepts_point_payment on the customer outlets.
- The outlet point_payment settings. A PUT that still sends them is
  rejected as an unknown field.
- The EnakPoint split in the payment method analytics.
- PAYMENT and PAYMENT_REFUND from the wallet type rules. Tests that used
  them as a generic EnakPoint debit use REWARD_REDEEM.
- The EnakPoint-paid part from the earning basis, which is
  subtotal − discount again.

Migration 000102 drops the trigger, the point methods and their index,
the payments columns, and the outlet settings, and restores the method
type CHECK without point. payments.payment_method_id is ON DELETE
RESTRICT, so it fails rather than lose a payment made with EnakPoint.

The integration docs list the removed endpoints and fields, and the
EnakPoint & EnakCoin PRD and tasks note what is superseded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:48:29 +07:00
efrilm b0ef226af1 feat: add printer types 2026-10-01 22:26:44 +07:00
efrilm 582dc75543 Reapply "feat(loyalty): EnakPoint & EnakCoin" (#32)
This reverts commit 4e24f9bbb0.
2026-09-30 15:31:44 +07:00
efrilmandClaude Opus 5.5 4e24f9bbb0 Revert "feat(loyalty): EnakPoint & EnakCoin" (#32)
This reverts merge commit 645da30, returning main to f0ff59f.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:16:15 +07:00
efrilmandClaude Opus 5.5 4b3beaed41 feat(loyalty): pay orders with EnakPoint at the cashier
Adds paying with the EnakPoint method (docs/prd-point-coin.md F9, K7,
PC-305).

POST /payments with the EnakPoint method now takes points and the
customer's payment code and goes through PointPaymentProcessor instead of
the generic path, which would record a payment without taking any balance.
After checking the order, its customer (not walk-in, active), the outlet
(accepts EnakPoint, minimum) and the method, it redeems the code, then in
one transaction locks the order row and the wallet, recomputes the F9
limits from fresh data, inserts the payment with points_used and the frozen
point_value, writes the PAYMENT ledger row (key payment:{id}, the outlet,
the cashier) and updates the order. The limits are
min(balance, floor(min(remaining, total × max_payment_percent / 100 − paid
with EnakPoint) / point_value)) in cents, so EnakPoint never pays more than
what is left and gives no change.

Unlike the generic CreatePayment, which always marks the order paid, an
EnakPoint payment leaves it partial with the right remaining amount until
it is settled, so the rest can be paid in cash. Settling it triggers
earning, whose basis leaves out the EnakPoint part. Splitting with the
EnakPoint method is refused. Refusals answer 400. The payment response
carries points_used and point_value for the receipt.

GET /orders/:id/point-payment/preview returns eligibility, balance, point
value and the maximum for the use-maximum button.

The payment and order repositories write outside transactions, so this
path uses its own repository that joins it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 11:46:31 +07:00
efrilmandClaude Opus 5.5 fc97c78300 feat(loyalty): show what an order earned on the order and receipt
Adds points_earned and coins_earned to the order response
(docs/prd-point-coin.md F3, PC-205). The POS prints the receipt from this
response, so the receipt gets them too.

The values are the sums of the order's EARN rows, read in one query for a
list of orders. They are filled for create, add items, update, detail and
list, and are 0 for an order that earned nothing. UpdateOrder earns before
building its response, so a payment completed there already shows the
earning. A failure to read them is logged and leaves them at 0 rather than
failing the order read. The self-order session listing reads orders
directly from the repository and still shows 0.

The two order hooks are merged into one OrderLoyalty interface (paid,
refunded, earned by orders) with a single SetLoyalty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:50:37 +07:00
efrilmandClaude Opus 5 992bb04816 feat(order): support weight-based products
Products like fish are sold per weighing (4.2 ons, 5.6 ons), which the
order line could not represent: quantity is INTEGER and prices are always
computed as quantity * unit_price.

Model one weighing as one order line. quantity stays INTEGER and keeps
meaning "how many items"; the measured amount goes into a new nullable
order_items.weight, and the line is priced weight * unit_price. Two
weighings of the same product are two lines, never merged into one.

Keeping quantity integral avoids float comparisons in void, refund and
split bill, where accumulated rounding error would silently misbehave —
"1.4 + 1.4 + 1.4" is not 4.2 in float64, which would leave a fully paid
split-bill item marked unpaid.

BillableQuantity() is now the single place that decides between weight
and count; every price and cost calculation goes through it. Missing one
would bill a 4.2 ons fish as a single ons — wrong money, no error.

Two database constraints back the design: a weighed line always carries a
positive weight, and its quantity is pinned to 1. The latter also makes
void all-or-nothing for weighed lines, so the row-splitting branch can
never produce a zero-weight remainder row.

Also wires product.unit_id through the API, which was previously not
settable at all, and corrects the misleading comment on the request's
unit_price field — that value has never been used; price always comes
from the database.

Design notes and the audit of every price multiplication site are in
docs/rfc-weight-based-products.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 17:14:42 +07:00
Efril 84222fc7f4 update 2026-05-28 15:30:18 +07:00
Efril 957c1ae53d update order response 2026-05-25 20:28:24 +07:00
Efril 8efa644680 payment amount fix 0 2026-04-26 23:04:31 +07:00
Aditya Siregar 8e9c14b860 Add order items 2025-08-08 22:33:08 +07:00
Aditya Siregar fe4f17b34d Add ingredieints 2025-08-08 00:22:28 +07:00
Aditya Siregar 93a3b29ae9 Fix Split Bill 2025-08-07 22:45:02 +07:00
Aditya Siregar 44dc3fa61c Add void and printer type 2025-08-06 00:02:49 +07:00
Aditya Siregar 5741243425 Add Customer users 2025-08-03 23:55:51 +07:00
aditya.siregar 4f5950543e init 2025-07-18 20:10:29 +07:00