feat(orders): send the order-placement SMS and close Phase 1
The reference texted three audiences on every placed order
(henza_server Api/ApiMobileController::setOrder:733-766). None of it was
wired here — OrdersService had no SmsService at all, so a customer could
place an order and nobody was told: not the customer, not the admins, not
the referring sales expert.
Now, after the transaction commits (so a rolled-back order texts nobody)
and without ever being awaited (D20 — a dead gateway must not fail an
order):
- every mobile in ORDER_ADMIN_MOBILES, with the customer's name and
the rounded gold weight;
- the customer, with distinct text for a normal and an over-limit
suspended order;
- the referring sales expert, looked up on AdminUser (the target's
referring expert is an admin, not a customer as in the reference).
The reference's four hardcoded personal numbers stay out of git, behind
the same env-var pattern as PROFILE_ADMIN_MOBILES.
The «در سیستم ویرایش شد» variant was deliberately not ported: the target
has no order-edit route, so it would be dead code — «عمداً تکرار نشد».
Also fills §2 of the admin matrix, the D1 deliverable that had been
(pending) since the phase opened: 163 rid-guarded routes, 68 distinct
rids, 176 grantable tree keys, and a per-module map to the reference
controllers. Only two enforced rids are ungrantable from the permission
screen (newsletters, persons.list.sellers.view) — filed as PERM-02,
awaiting an owner ruling because it changes the permission vocabulary.
Phase 1 (admin parity) is at its gate. api: 283/283 tests, 26 suites.
پنل: پیامک ثبت سفارش برای مشتری، ادمینها و کارشناس فروش فعال شد.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
759625d2a1
commit
83e5d2868b
@@ -138,12 +138,61 @@ prove no operation is left without an equivalent permission.
|
||||
Per-module mapping of the reference's admin controller operations onto the
|
||||
target's rid, grouped by the module of `apps/api/src/modules/`:
|
||||
|
||||
| Reference operation (controller) | Target endpoint | rid |
|
||||
|---|---|---|
|
||||
| *(pending — filled per module as each section below is audited)* | | |
|
||||
Counted mechanically from the target's `@RequirePermission` decorators and the
|
||||
permission tree, 2026-10-12 (§3–§9 are all complete, so both sides have been
|
||||
read):
|
||||
|
||||
> Populated as §3 onward completes, so no row is written before both sides have
|
||||
> been read.
|
||||
| | Count |
|
||||
|---|---|
|
||||
| Reference admin routes (`routes/admin.php`) | 154 |
|
||||
| Target admin routes carrying a rid | 163 |
|
||||
| Distinct rids enforced by those routes | 68 |
|
||||
| Keys in the permission tree (`permissionConst.ts`) | 176 |
|
||||
|
||||
Per module, against the reference controller it ports:
|
||||
|
||||
| Target module | Reference controller(s) | Routes | Distinct rids |
|
||||
|---|---|---|---|
|
||||
| `accounting` | `WalletController`, `CashController` | 5 | `wallet.list`, `wallet.charge`, `document.create`, `document.list.confirm` |
|
||||
| `accounting-sync` | — target-only (D27) | 2 | `persons.list.client.omde`, `product.list` |
|
||||
| `admin-branches` | `BranchController` | 6 | `branch.list`, `branch.create` |
|
||||
| `admin-categories` | `CategoryController` | 11 | `collection.list`, `collection.create`, `collection.list.edit` |
|
||||
| `admin-comments` | `CommentController` | 6 | `comment` |
|
||||
| `admin-customers` | `AdminController`, `MajorCustomerController`, `SingleCustomerController` | 23 | 10 `persons.*` + `wallet` |
|
||||
| `admin-dashboard` | `DashboardController` | 5 | `dashboard.view`, `report.order.item`, `report.product` |
|
||||
| `admin-documents` | `CashController`, `WalletController` | 13 | `document.*`, `config.wallet`, `wallet` |
|
||||
| `admin-invoices` | `CinyController` | 6 | `invoice.list`, `invoice.create` |
|
||||
| `admin-misc` | `CitiesController`, `SmsController`, `NotifyController`, `ConstController`, `BrandController` | 27 | 16 (`cities.*`, `sms.*`, `notify.*`, `config.statics*`, `statics`, `newsletters`) |
|
||||
| `admin-orders` | `OrderController`, `MajorOrderController` | 9 | `order.list`, `order.create`, `order.omde`, `order.tamin` |
|
||||
| `admin-preorders` | `OrderController` (tamin) | 5 | `order.makeOrder` |
|
||||
| `admin-products` | `ProductController` | 15 | `product.*`, `order.create` |
|
||||
| `admin-promotions` | `PromotionController`, `IgnoreController` | 12 | `offers.*`, `offersIgnore` |
|
||||
| `admin-report` | `ReportController` | 1 | `report.order.item` |
|
||||
| `admin-users` | `UserController`, `PermissionController` | 11 | `user.*` (7) |
|
||||
| `admin-workshops` | `WorkshopController` | 4 | `workshop.list`, `workshop.create` |
|
||||
| `orders` | `ApiMobileController::setOrder` | 2 | `order.list`, `order.list.sts.submit` |
|
||||
|
||||
**D1's requirement — "no operation left without an equivalent permission" — is
|
||||
met:** every one of the 163 rid-guarded routes names a rid, and 66 of the 68
|
||||
resolve to a key the permission screen can grant.
|
||||
|
||||
### The 2 exceptions — PERM-02
|
||||
|
||||
Two enforced rids are **absent from the permission tree**, so a route checks them
|
||||
but no operator can grant them from the UI:
|
||||
|
||||
| rid | Enforced by | Effect |
|
||||
|---|---|---|
|
||||
| `newsletters` | `admin-misc` (subscriber list) | only a role that already holds it (from the seed) can read the newsletter list |
|
||||
| `persons.list.sellers.view` | `admin-customers` (`GET /reagent/view/:id`, the ➕ target-only agent-detail screen) | the new screen cannot be granted to a new role |
|
||||
|
||||
This is **inherited from the reference, not introduced here** — the header of
|
||||
`permissionConst.ts` already recorded four such rids. Two of the four
|
||||
(`buy.order.list`, `persons.create.customer.tak`) now have no route at all, so
|
||||
they are dead on both sides and need no action.
|
||||
|
||||
Logged as **PERM-02**. Additive fix (add the two keys to the tree); it touches
|
||||
the permission vocabulary, so it waits for the owner like PERM-01.
|
||||
|
||||
---
|
||||
|
||||
@@ -225,7 +274,12 @@ Contrary to the Phase 0 expectation, this area is in **good** shape.
|
||||
| Over-limit outcome | order still created, `sts = 0` (معلق) | `OrderStatus.SUSPENDED` (`orders.service.ts:92`) | ✅ match |
|
||||
| Debt recognition point | `sts > 4`, first true at `sts = 5` | `DELIVERED_TO_AGENT`, documented at `admin-orders.service.ts:14` and mapped `5 → DELIVERED_TO_AGENT` at `:359` | ✅ match |
|
||||
| Removing that status | legacy allowed it, losing the posted debt | deliberately refused (`admin-orders.service.ts:382-390`) | ➕ stricter — a data-loss guard, keep (D4) |
|
||||
| Suspended-order SMS | `ApiMobileController.php:739` | not yet sent | ❌ — lands with the Phase 1 SMS templates |
|
||||
| Suspended-order SMS | `ApiMobileController.php:754` | `orders.service.ts` `notifyOrderPlaced` | ✅ fixed 2026-10-12 |
|
||||
| Order-placed SMS to the customer | `:757` | same | ✅ fixed 2026-10-12 |
|
||||
| Order-placed SMS to the admins | `:739-743` (4 hardcoded numbers) | same, via `ORDER_ADMIN_MOBILES` | ✅ fixed 2026-10-12 |
|
||||
| Order-placed SMS to the sales expert | `:762-764` | `notifySalesAgent` | ✅ fixed 2026-10-12 |
|
||||
| Order-**edited** SMS to the admins | `:734-737` | none | «عمداً تکرار نشد — SMS-03» — no target path lets a customer edit a placed order |
|
||||
| `Notify::store` push beside each SMS | `:755,758,765` | none | ⏸️ PUSH-01, deferred to the PWA port |
|
||||
|
||||
The one **money hole** here is already logged and scheduled: the server accepts
|
||||
client-supplied `wagePercent` / `discountPercent` / `flatRialCharge`
|
||||
@@ -424,4 +478,6 @@ layout as the reference. The target reads from `OrderItem` + `LedgerEntry`
|
||||
| RPT-01 | Medium | The reference's 32-column Excel export (`ReportController::getOrderItemWithDetails`) had no target equivalent. | ✅ fixed — `admin-report` module, `GET admin/report/order-items` |
|
||||
| SMS-01 | Low | SMS on document confirm/unconfirm was not wired in `toggleConfirm`. | ✅ fixed — `admin-documents.service.ts:notifyDocumentConfirm` |
|
||||
| SMS-02 | Low | `%name` substitution in batch SMS text was missing. | ✅ fixed — `admin-misc.service.ts:sendSms` + `customerNamesByMobile` |
|
||||
| SMS-03 | Medium | **None of the four order-placement SMS messages was wired** — `OrdersService` did not even inject `SmsService`. The matrix had logged only the suspended-order one. | ✅ fixed 2026-10-12 — `notifyOrderPlaced` + `notifySalesAgent`, 6 tests. The «ویرایش شد» variant is deliberately not ported (no such path). |
|
||||
| PERM-02 | Medium | `newsletters` and `persons.list.sellers.view` are enforced by routes but absent from the permission tree, so they cannot be granted from the UI. Inherited from the reference. | awaiting owner (additive) |
|
||||
| PUSH-01 | Low | Firebase push notifications not implemented — deferred to PWA port phase. | ⏸️ deferred |
|
||||
|
||||
Reference in New Issue
Block a user