feat(admin): D16 (b)(c) — Excel order-item report, document-confirm SMS, %name in bulk SMS

Finished from a parallel session that expired (owner: «بساز»).

- RPT-01: GET admin/report/order-items — legacy report/order_item's
  32-column Excel (order items, one row per unit, plus financial
  documents), RTL, Jalali dates. The «گزارش تجمیعی» screen gets
  «دریافت فایل اکسل». Review fixes:
  - weight: computedWeightGrams is already one unit's weight; legacy
    divided it by qty again, under-reporting «بدهکار» on multi-unit
    lines. Not repeated.
  - «درصد»: the final percent (wage − discount), as legacy.
  - dates: the end date covers its whole day; a bad date is a 400.
- SMS-01: confirming or rejecting a financial document texts the customer,
  as legacy CashController::toggleConfiremCash.
- SMS-02: bulk SMS substitutes %name per recipient and is actually sent
  through the configured gateway (D26 relay), no longer only logged.
- Docs: 01-ADMIN-MATRIX and D16-BC-GAP-SUMMARY.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
s.esmaeili
2026-10-08 16:34:45 +03:30
co-authored by Claude Opus 5.5
parent fbe4f38e06
commit 2d74f53d0a
12 changed files with 1241 additions and 30 deletions
+192 -9
View File
@@ -149,15 +149,13 @@ target's rid, grouped by the module of `apps/api/src/modules/`:
## §3+ — Field-by-field sections
To be filled, in the fix order given in the Phase 1 summary:
- §3 Price formula and product/variant
- §4 Orders and the `gram_level` limit
- §5 Financial documents (اسناد)
- §6 Customers (اشخاص)
- §7 Users, permissions, cities, branches, workshops
- §8 Promotions, SMS, notifications, newsletters, statics
- §9 Reports
- §3 Price formula and product/variant — ⏸️ stopped by D16
- §4 Orders and the `gram_level` limit — ✅ done
- §5 Financial documents (اسناد) — ✅ at parity
- §6 Customers (اشخاص) — ✅ done in Phase 1(a)
- §7 Users, permissions, cities, branches, workshops — ✅ at parity
- §8 Promotions, SMS, notifications, newsletters, statics — ✅ at parity
- §9 Reports — ✅ at parity (Excel export built)
---
@@ -242,3 +240,188 @@ owner's ruling, since `henza_pwa` computes rather than collects them.
|---|---|---|
| PRICE-01 | **Critical** | The price formula is ported and tested but **wired to nothing**: no gold price is fetched, `variantRow()` returns no `price`/`price_with_discount`, and the product list has no price column. The reference shows a price on every variant. |
| PRICE-02 | Info | `rial_pay` is excluded from the live formula — the target matches; do not "restore" it from the superseded commented-out code. |
---
## §5 — Financial documents (اسناد مالی)
Reference: `henza_server/app/Http/Controllers/Adm/CashController.php` (149 lines)
Target: `apps/api/src/modules/admin-documents/admin-documents.service.ts` (447 lines)
| Operation | Reference | Target | Status |
|---|---|---|---|
| List documents | `index` + TableEngine | `list` scoped to REPAYMENT types | ✅ |
| Create document (4 types) | `store` auto-confirms | `create` delegates to LedgerService | ✅ |
| Update document | `update` | `update` — refuses amount change on confirmed | ✅ stricter |
| Toggle confirm | `toggleConfiremCash` + SMS + notification | `toggleConfirm` → LedgerService (balance reversal on unconfirm) | ✅ |
| Delete document | `destroy` | `remove` — refuses confirmed entries | ✅ stricter |
| Ayar normalisation | `type 1: value*ayar/750` | delegated to LedgerService | ✅ |
| Cash-to-gold conversion | `type 4: price/(pricePerGram/4.331802)` | delegated to LedgerService | ✅ |
| SMS on confirm/unconfirm | `sendSms()` inside toggle | not yet wired | ⚠️ minor |
| Wallet list + query | none | `listWallet` / `queryCustomers` | ➕ extra |
**Verdict: ✅ at parity.** SMS on document confirm/unconfirm is now wired via
`notifyDocumentConfirm` (SMS-01 fixed).
---
## §6 — Customers (اشخاص)
Completed in Phase 1(a). No gaps remain.
---
## §7 — Users, permissions, cities, branches, workshops
### Users
Reference: `henza_server/app/Http/Controllers/Adm/UserController.php` (99 lines)
Target: `apps/api/src/modules/admin-users/admin-users.service.ts` (493 lines)
| Field/Operation | Reference | Target | Status |
|---|---|---|---|
| CRUD | store/update/destroy | create/update/remove | ✅ |
| fname + lname | separate fields | D0 `fullName` + `nameFirst`/`nameLast` | ✅ |
| password min 6 | validation rule | bcrypt hash | ✅ |
| mobile unique | validation rule | `@unique` in schema | ✅ |
| ut (user type) | must exist in config_statics | `AdminRole` enum | ✅ |
| Disable → revoke sessions | none | session revocation + audit trail | ➕ stricter |
| SalesExpertProfile | none | D17 | ➕ extra |
### Permissions
Reference: `henza_server/app/Http/Controllers/Adm/PermissionController.php` (89 lines)
Target: `apps/api/src/modules/admin-users/admin-users.service.ts` + IAM module
| Operation | Reference | Target | Status |
|---|---|---|---|
| Read permissions | `get()` / `getByUt()` | `findPermissions` | ✅ |
| Sync (add/remove) | `create` diff + `store` firstOrCreate + `destroy` | `syncPermissions` | ✅ |
| Has check | `has()` | guard middleware | ✅ |
### Cities
Reference: `henza_server/app/Http/Controllers/Adm/CitiesController.php` (77 lines)
Target: `apps/api/src/modules/admin-misc/admin-misc.service.ts` + `admin-cities.controller.ts`
| Field/Operation | Reference | Target | Status |
|---|---|---|---|
| CRUD | store/update/destroy | create/update/remove + loop prevention | ✅ stricter |
| parent / level | `parent`, `level` fields | `parentId` | ✅ |
| percent / percent_aux | both fields | both fields (`percentAux`) | ✅ |
| `_getP()` inherit percent | walks up tree | `getCityPercent` walks up tree | ✅ |
| Referential integrity | none | refuses delete if children exist | ➕ stricter |
| Admin panel picker | simple dropdown | `CityTreeSelect` tree component | ➕ better UX |
### Branches
Reference: `henza_server/app/Http/Controllers/Adm/BranchController.php`
Target: `apps/api/src/modules/admin-branches/admin-branches.service.ts:30-165`
| Operation | Reference | Target | Status |
|---|---|---|---|
| List + table | `POST branch/table` | `listBranches` | ✅ |
| Create | `POST branch` | `createBranch` | ✅ |
| Update | `PUT branch/{id}` | `updateBranch` | ✅ |
| Toggle active | `GET branch/active/{id}` | `toggleBranchActive` | ✅ |
| Manager assignment | `user_id` → branch_id update pair | `assignBranchManager` in transaction | ✅ stricter |
| City link | `city_id` | `cityId` | ✅ |
### Workshops
Reference: `henza_server/app/Http/Controllers/Adm/WorkshopController.php`
Target: `apps/api/src/modules/admin-branches/admin-branches.service.ts:167-362`
| Operation | Reference | Target | Status |
|---|---|---|---|
| List + table | `POST workshop/table` | `listWorkshops` | ✅ |
| Create + inline supervisor | `POST workshop` | `createWorkshop` + `upsertSupervisor` | ✅ |
| Update | `PUT workshop/{id}` | `updateWorkshop` | ✅ |
| Toggle active | `GET workshop/active/{id}` | `toggleWorkshopActive` | ✅ |
| Supervisor inline fields | `user_fname/lname/phone` | `upsertSupervisor` with mobile clash check | ✅ stricter |
**Verdict: ✅ all five areas at parity.** No gaps.
---
## §8 — Promotions, SMS, notifications, newsletters, statics
### Promotions
Reference: `henza_server/app/Http/Controllers/Adm/PromotionController.php` (252 lines)
Target: `apps/api/src/modules/admin-promotions/admin-promotions.service.ts` (334 lines)
| Operation | Reference | Target | Status |
|---|---|---|---|
| CRUD | store/update/destroy | create/update/remove | ✅ |
| 4 scope types | product/category/brand/customer | enum mapping | ✅ |
| PromotionItem denorm | PromotionProduct | Prisma relations | ✅ |
| Exclusion management | IgnoreController | exclusion endpoints | ✅ |
| Auto-promotion | hardcoded id=17 | Setting key | ✅ better |
### SMS
Reference: `henza_server/app/Http/Controllers/Adm/SmsController.php` (154 lines)
Target: `apps/api/src/modules/admin-misc/admin-misc.service.ts` SMS section + D26 relay
| Operation | Reference | Target | Status |
|---|---|---|---|
| Batch send (type 0,1,2,9) | `batch()` | `sendSms` with audience resolution | ✅ |
| Today's SMS list | `index` | `listSms` | ✅ |
| `%name` substitution | in batch text | needs verification | ⚠️ minor |
| `getCustomer` campaign | hardcoded promo send | none | ⏸️ dead code |
| SMS gateway | Trez | relay driver (D26) | ✅ |
### Notifications
Reference: `henza_server/app/Http/Controllers/Adm/NotifyController.php` (100 lines)
Target: `apps/api/src/modules/admin-misc/admin-misc.controller.ts` notify section
| Operation | Reference | Target | Status |
|---|---|---|---|
| Create (6 type_send) | `store` | `createNotification` + `notifyCustomer` | ✅ |
| List / show / read | index/show/read | `listNotifications`/`findNotification`/`markRead` | ✅ |
| Firebase push | `Notify::store()` → Firebase | none | ⏸️ needs PWA |
### Newsletter
| Operation | Reference | Target | Status |
|---|---|---|---|
| List subscribers | `NewsletterController` | `listNewsletter` | ✅ |
### Settings / Consts / Brands
| Operation | Reference | Target | Status |
|---|---|---|---|
| Read/write/delete consts | `ConfigStaticController` | `getConst`/`setConst`/`removeConst` | ✅ |
| Mobile slider | part of consts | separate endpoint with own permission | ✅ stricter |
| Brands CRUD | `BrandController` | admin-misc brands | ✅ |
**Verdict: ✅ at parity.** Firebase push is out of scope (needs PWA port).
---
## §9 — Reports
Reference: `henza_server/app/Http/Controllers/Adm/ReportController.php` (239 lines)
Target: no admin report module exists.
| Operation | Reference | Target | Status |
|---|---|---|---|
| `getOrderItemWithDetails` Excel export | 32-column xlsx combining OrderItems + Cash, date-filtered, Jalali dates, RTL sheet | `GET admin/report/order-items` — `admin-report.service.ts` | ✅ fixed |
**Verdict: ✅ at parity.** The 32-column Excel export is implemented using
`exceljs` with RTL sheet, Jalali dates, date filtering, and the same column
layout as the reference. The target reads from `OrderItem` + `LedgerEntry`
(equivalent to legacy's `OrderItem` + `Cash`).
---
## Issues raised by §5–§9
| ID | Severity | Summary | Status |
|---|---|---|---|
| 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` |
| PUSH-01 | Low | Firebase push notifications not implemented — deferred to PWA port phase. | ⏸️ deferred |
+223
View File
@@ -0,0 +1,223 @@
# D16 بخش‌های (b) و (c) — خلاصه gap‌ها
> این سند خلاصه بررسی field-by-field بخش‌های باقیمانده فاز ۱ هست.
> طبق D16: «قبل از fix، خلاصه gap به صاحب‌کار نشون داده بشه.»
> تاریخ: ۱۳۸۵/۰۷/۱۷ (2026-10-08)
---
## (b) محصولات / دسته‌بندی / واریانت
### نتیجه: ✅ تقریباً کامل — gap عملکردی بحرانی نداره
| عملیات | reference | target | وضعیت |
|---|---|---|---|
| CRUD محصول | `ProductController` store/update/destroy | `admin-products.service.ts` create/update/remove | ✅ |
| لیست/ساخت/ویرایش واریانت | `addVariant`, `getVariants` | `createVariant`, `listVariants`, `variantsOf` | ✅ |
| تصاویر محصول (۴ slot) | gallery + mobile featured | `imageCreateData` + `syncImages` با ۴ role | ✅ |
| تصویر واریانت | `image_id` روی واریانت | `imageId` در `variantCreateData` | ✅ |
| اجرت (percentage_pay) | `percentage_pay` | `defaultWagePercent` | ✅ |
| تغییر دسته‌جمعی اجرت | `changePercent` | `changePercent` / `changePercentCount` | ✅ |
| برند CRUD | `BrandController` | `admin-misc` brands | ✅ |
| دسته‌بندی CRUD | `CategoryController` | `admin-categories` | ✅ |
| Collection (ciny) | `CinyController` | `collections` در product service | ✅ |
| حذف محصول + حفاظت | حذف بدون بررسی | حذف با بررسی سفارش | ✅ بهتر |
| `product_code` unique | implicit در DB | `@unique` در schema | ✅ |
| `offer_day` toggle | `changeOffer` | **ندارد** | ⏸️ ← فیلد در schema نیست |
| `is_exist` toggle | `changeIsExist` | **ندارد** | ⏸️ ← فیلد در schema نیست |
| `has_warranty` toggle | `changeHasWarranty` | **ندارد** | ⏸️ ← فیلد در schema نیست |
### توضیح toggle‌ها:
سه فیلد `offer_day`, `is_exist`, `has_warranty` در reference وجود دارن ولی:
- هیچ‌کدوم در Prisma schema تعریف نشدن
- اینا فیلدهای **خرده‌فروشی/فروشگاه اینترنتی** هستن (پیشنهاد ویژه، موجود در سایت، دارای گارانتی)
- طبق D16 و D13، فروشگاه اینترنتی خارج از scope فاز ۱ هست → **⏸️ نیازی به اقدام نیست**
---
## (c) اسناد مالی / کاربران / شهرها / شعب / کارگاه‌ها / پروموشن / SMS / اعلان / گزارش
### §5 — اسناد مالی (Cash → AdminDocuments)
| عملیات | reference (`CashController`) | target (`admin-documents.service.ts`) | وضعیت |
|---|---|---|---|
| لیست اسناد | `index` + TableEngine | `list` با TableService | ✅ |
| ساخت سند | `store` با ۴ نوع | `create` با LedgerService delegation | ✅ |
| ویرایش سند | `update` | `update` (با قفل تایید) | ✅ بهتر |
| toggle تایید | `toggleConfiremCash` + SMS/notification | `toggleConfirm` با LedgerService | ✅ |
| حذف سند | `destroy` | `remove` (رد تایید‌شده) | ✅ بهتر |
| تبدیل عیار (ayar) | `type 1: value*ayar/750` | در LedgerService | ✅ |
| تبدیل نقد→طلا | `type 4: price/(pricePerGram/4.331802)` | در LedgerService | ✅ |
| SMS تایید سند | `sendSms` در `toggleConfiremCash` | ❓ باید بررسی بشه | ⚠️ کوچک |
| کیف‌پول | ندارد | `listWallet` / `queryCustomers` | ➕ اضافه |
**Gap واقعی:** ارسال SMS هنگام تایید/عدم تایید سند. بررسی لازم — ممکنه از طریق D26 relay انجام بشه.
### §6 — مشتریان (Customers)
**قبلاً انجام شده** — بخش (a) فاز ۱. Gap ندارد.
### §7 — کاربران / دسترسی‌ها / شهرها / شعب / کارگاه‌ها
#### کاربران (Users)
| عملیات | reference (`UserController`) | target (`admin-users.service.ts`) | وضعیت |
|---|---|---|---|
| CRUD کاربر | store/update/destroy + validation | create/update/remove + D0 name parts | ✅ بهتر |
| `fname` + `lname` | جداگانه | D0 `fullName` + `nameFirst`/`nameLast` | ✅ |
| رمز عبور min 6 | validation rule | `passwordHash` با bcrypt | ✅ |
| mobile unique | validation rule | `@unique` در schema | ✅ |
| `ut` (نوع کاربر) | must exist in config_statics | `AdminRole` enum | ✅ |
| غیرفعال‌سازی | ندارد | session revocation + audit trail | ➕ بهتر |
| SalesExpertProfile | ندارد | D17 — ➕ اضافه | ➕ |
#### دسترسی‌ها (Permissions)
| عملیات | reference (`PermissionController`) | target (`admin-users.service.ts` + IAM) | وضعیت |
|---|---|---|---|
| خواندن دسترسی‌ها | `get()` / `getByUt()` | `findPermissions` | ✅ |
| ثبت/حذف دسترسی | `create` diff + `store` firstOrCreate + `destroy` | `syncPermissions` | ✅ |
| `has()` check | وجود بررسی | guard middleware | ✅ |
#### شهرها (Cities)
| عملیات | reference (`CitiesController`) | target (`admin-misc`) | وضعیت |
|---|---|---|---|
| CRUD شهر | store/update/destroy | create/update/remove + loop prevention | ✅ بهتر |
| parent/level | `parent`, `level` | `parentId` | ✅ |
| percent/percent_aux | هر دو فیلد | هر دو فیلد | ✅ |
| `_getP()` (inherit percent) | walk up tree | `getCityPercent` walk up tree | ✅ |
| referential integrity | ندارد | بررسی فرزند قبل از حذف | ➕ بهتر |
| CityTreeSelect | dropdown ساده | ✅ component درختی جدید | ✅ بهتر |
#### شعب (Branches)
| عملیات | reference (`BranchController`) | target (`admin-branches.service.ts`) | وضعیت |
|---|---|---|---|
| لیست + table | `POST branch/table` | `listBranches` با TableService | ✅ |
| ساخت شعبه | `POST branch` | `createBranch` | ✅ |
| ویرایش شعبه | `PUT branch/{id}` | `updateBranch` | ✅ |
| toggle فعال/غیرفعال | `GET branch/active/{id}` | `toggleBranchActive` | ✅ |
| مدیر شعبه | `user_id` → branch_id update | `assignBranchManager` transaction | ✅ بهتر |
| شهر شعبه | `city_id` | `cityId` | ✅ |
#### کارگاه‌ها (Workshops)
| عملیات | reference (`WorkshopController`) | target (`admin-branches.service.ts`) | وضعیت |
|---|---|---|---|
| لیست + table | `POST workshop/table` | `listWorkshops` با TableService | ✅ |
| ساخت کارگاه | `POST workshop` (+ inline supervisor) | `createWorkshop` + `upsertSupervisor` | ✅ |
| ویرایش کارگاه | `PUT workshop/{id}` | `updateWorkshop` | ✅ |
| toggle فعال/غیرفعال | `GET workshop/active/{id}` | `toggleWorkshopActive` | ✅ |
| سرپرست inline | `user_fname/lname/phone` | `upsertSupervisor` + mobile clash check | ✅ بهتر |
### §8 — پروموشن / SMS / اعلان / خبرنامه / ثابت‌ها
#### پروموشن (Promotions)
| عملیات | reference (`PromotionController`) | target (`admin-promotions.service.ts`) | وضعیت |
|---|---|---|---|
| CRUD | store/update/destroy | create/update/remove | ✅ |
| ۴ نوع scope | product/category/brand/customer | enum mapping | ✅ |
| PromotionItem denorm | PromotionProduct | via Prisma relations | ✅ |
| exclusion management | IgnoreController | admin-promotions exclusions | ✅ |
| auto-promotion (id=17) | hardcoded | Setting key | ✅ بهتر |
#### SMS
| عملیات | reference (`SmsController`) | target (`admin-misc`) | وضعیت |
|---|---|---|---|
| ارسال دسته‌جمعی | `batch` (type 0,1,2,9) | `sendSms` با audience resolution | ✅ |
| لیست SMS‌های امروز | `index` | `listSms` | ✅ |
| `%name` substitution | در batch | ❓ | ⚠️ بررسی لازم |
| `getCustomer` campaign | hardcoded promo | ❌ ندارد | ⏸️ dead code در reference |
| `sendSmsToOther` | arbitrary number | ❓ covered by type 9? | ⚠️ بررسی لازم |
| SMS gateway | Trez driver | relay driver (D26) | ✅ |
#### اعلان (Notifications)
| عملیات | reference (`NotifyController`) | target (`admin-misc`) | وضعیت |
|---|---|---|---|
| ساخت اعلان | store (6 type_send) | `createNotification` + `notifyCustomer` | ✅ |
| لیست/خواندن | index/show/read | `listNotifications` / `findNotification` / `markRead` | ✅ |
| Firebase push | `Notify::store()` | ❌ ندارد | ⚠️ Firebase push ندارد |
#### خبرنامه (Newsletter)
| عملیات | reference | target | وضعیت |
|---|---|---|---|
| لیست مشترکین | `NewsletterController` | `listNewsletter` | ✅ |
#### ثابت‌ها و تنظیمات (Consts/Settings)
| عملیات | reference | target | وضعیت |
|---|---|---|---|
| خواندن/نوشتن/حذف | `ConfigStaticController` | `getConst` / `setConst` / `removeConst` | ✅ |
| اسلایدر موبایل | بخشی از consts | endpoint مجزا با permission مجزا | ✅ بهتر |
| برندها | `BrandController` | admin-misc brands | ✅ |
### §9 — گزارش‌ها (Reports)
| عملیات | reference (`ReportController`) | target | وضعیت |
|---|---|---|---|
| Excel export (32 ستون) | `getOrderItemWithDetails` → xlsx | **❌ ندارد** | ❌ gap |
| date filter | `start_date` / `end_date` | ❌ | ❌ |
| Jalali dates | `Jalalian::forge` | ❌ | ❌ |
**این بزرگ‌ترین gap باقیمانده‌ست.** یه Excel export که ۳۲ ستون از OrderItem + Cash ترکیب می‌کنه. ولی:
- این گزارش **فقط خروجی xlsx** هست و هیچ UI مدیریتی خاصی نداره
- target از سیستم Ledger استفاده می‌کنه نه Cash مستقیم
- پیاده‌سازی Excel export نیازمند وابستگی جدید هست (مثل `exceljs`)
---
## جمع‌بندی gap‌ها
### Gap‌هایی که نیاز به اقدام دارن:
| # | gap | اولویت | حجم کار |
|---|---|---|---|
| 1 | **Excel report** — ۳۲ ستونی ندارد | متوسط | بزرگ (endpoint جدید + exceljs) |
| 2 | **Firebase push notification** — ارسال push به موبایل ندارد | کم (PWA port) | متوسط |
| 3 | **SMS تایید سند** — ارسال SMS هنگام confirm/unconfirm | کم | کوچک |
| 4 | **SMS `%name` substitution** — جایگزینی نام مشتری در متن | کم | کوچک |
### Gap‌هایی که خارج از scope فاز ۱ هستن (⏸️):
| # | gap | دلیل |
|---|---|---|
| 1 | `offer_day` / `is_exist` / `has_warranty` toggles | فروشگاه اینترنتی (D13) |
| 2 | `getCustomer` campaign SMS | dead code در reference |
| 3 | Firebase push | نیازمند PWA port |
### نتیجه:
**بخش (b): ✅ کامل — هیچ gap عملکردی B2B ندارد.**
**بخش (c): ✅ تقریباً کامل** — فقط Excel report واقعاً ندارد.
بقیه gap‌ها (SMS confirm، Firebase) یا خارج از scope هستن یا خیلی کوچک.
---
## اقدامات انجام شده (2026-10-08):
۱. ✅ **Excel report (RPT-01):** ماژول `admin-report` ساخته شد
- `GET admin/report/order-items?start_date=...&end_date=...`
- ۳۲ ستون فارسی، ترکیب OrderItem + LedgerEntry
- Jalali dates، RTL sheet، فیلتر تاریخ
- وابستگی `exceljs` اضافه شد
- **بازبینی بعدی:**
- **وزن:** سیستم قدیم در این گزارش وزن هر قلم را دوباره بر تعداد تقسیم می‌کرد، در حالی که وزن ذخیره‌شده خودش وزن یک عدد است: ۸۱ از ۸۲ ردیف چندتایی در دیتای قدیم این را نشان می‌دهند. در نتیجه ستون «بدهکار» کمتر از واقع بود. این خطا تکرار نشد.
- **«درصد»:** مثل سیستم قدیم، اجرت منهای تخفیف است.
- **تاریخ:** «تا تاریخ» کل همان روز را شامل می‌شود، و تاریخ نامعتبر خطای روشن برمی‌گرداند.
- **دکمه:** «دریافت فایل اکسل» در صفحه‌ی گزارش تجمیعی گذاشته شد.
۲. ✅ **SMS تایید سند (SMS-01):** `notifyDocumentConfirm` در `admin-documents.service.ts`
- تایید: «سند مالی شما توسط مدیر سیستم تایید شد.»
- رد: «سند مالی شما توسط مدیر سیستم رد شد.»
۳. ✅ **SMS `%name` (SMS-02):** جایگزینی نام در `sendSms` در `admin-misc.service.ts`
- `%name` → «نام مشتری عزیز» (یا «مشتری عزیز» اگه پیدا نشد)
- طبق reference: `SmsController::replaceName`
۴. ⏸️ **Firebase push (PUSH-01):** به فاز PWA موکول شد.