34 lines
5.9 KiB
Markdown
34 lines
5.9 KiB
Markdown
## 1. Persistence and shared contract
|
|
|
|
- [ ] 1.1 Add migration `0016_membership_messaging.sql`: `member_levels` (bilingual `name` JSONB `{en, zh}`, `icon`, unique integer `growth_threshold`, bilingual `benefits` JSONB `{en, zh}`), `growth_logs` (append-only `user_id`, `delta`, running `growth_total`, `reason`, `reference_type`/`reference_id` order reference with a partial unique index per user and reference), `messages` (`user_id`, `kind`, bilingual `title`/`body` JSONB `{en, zh}`, `reference_type`/`reference_id`, `status` `unread`/`read`, `deleted_at` soft delete, partial unique index per user/kind/reference), a `users.level` column referencing `member_levels`, and listing/counting indexes.
|
|
- [ ] 1.2 Add shared types (`MemberLevel`, `MemberLevelInput`, `MembershipStatus`, `GrowthLogEntry`, `Message`, `MessageListQuery`) and `@vmall/shared` methods `getMembership`, `listGrowthLogs`, `listMessages`, `markMessageRead`, `markAllMessagesRead`, `deleteMessage`, `getUnreadCount`, and `admin.listMemberLevels`, `admin.createMemberLevel`, `admin.updateMemberLevel`, `admin.deleteMemberLevel` with all amounts and growth values as integers and i18n content as `{en, zh}` JSONB.
|
|
- [ ] 1.3 Implement `apps/api/src/modules/membership/` repository, service, DTO, handlers, and module registration: admin-role-gated member-level CRUD routes and customer-scoped membership status and growth-ledger routes, services returning `ApiResult<Dto>`.
|
|
- [ ] 1.4 Implement `apps/api/src/modules/messaging/` repository, service, DTO, handlers, and module registration: customer-scoped message list (paginated, optional unread-only filter), mark-read, mark-all-read, soft delete, and unread-count routes returning `ApiResult<Dto>`.
|
|
|
|
## 2. Services and behavioral tests
|
|
|
|
- [ ] 2.1 Implement growth accrual on order completion (customer confirms receipt): convert the order's realized paid amount to the base currency with integer minor-unit arithmetic (truncated whole units, no floating point), append exactly one ledger entry per order, and update `users.level` in the same transaction with a guarded `UPDATE ... WHERE` that only moves the customer to a strictly higher-threshold level.
|
|
- [ ] 2.2 Wire idempotent message emission into the order, fulfillment, and refund (after-sale) transitions: order payment success emits `order_paid`, shipment dispatch emits `order_shipped`, and refund completion emits `refund_completed`, each a guarded insert keyed by user, kind, and reference with bilingual `{en, zh}` title and body naming the order.
|
|
- [ ] 2.3 Add behavioral coverage in `apps/api/tests/membership.rs` and `apps/api/tests/messaging.rs` (fixtures from `tests/common/mod.rs`) for upgrade boundaries: growth exactly at a threshold upgrades, growth below every threshold holds no level, one accrual jumping two thresholds lands on the highest qualifying level, and repeated completion events accrue and upgrade exactly once.
|
|
- [ ] 2.4 Extend the behavioral coverage for event triggers and read semantics: each of the three events creates exactly one correctly referenced message, re-run handlers do not duplicate, mark-read touches only `unread` rows (idempotent single and all-read), soft delete is idempotent and excluded from lists and unread counts, and cross-user message access fails.
|
|
|
|
## 3. Mall member and message surfaces
|
|
|
|
- [ ] 3.1 Implement the `getMembership`, `listGrowthLogs`, `listMessages`, `markMessageRead`, `markAllMessagesRead`, `deleteMessage`, and `getUnreadCount` methods in `apps/mall/mock/api.ts` with deterministic per-session fixture state and the same idempotent read/delete semantics.
|
|
- [ ] 3.2 Add the `membership` and `messaging` domains with their exact shared-client method picks to the Mall API selection and enable them in the default live runtime configuration.
|
|
- [ ] 3.3 Add or adjust bilingual level, growth, message, badge, and failure strings through the existing Mall locale source without per-page hard-coded copy.
|
|
- [ ] 3.4 Build `apps/mall/pages/user/membership.vue`: current level name/icon/benefits, growth total, progress to the next threshold, and paginated growth history from the shared contract, with fixed-adapter parity and no fixture imports.
|
|
- [ ] 3.5 Build `apps/mall/pages/user/messages.vue`: paginated message list with unread-only filter, open/mark-read, mark-all-read, and delete actions that persist through the API and refresh list and counts.
|
|
- [ ] 3.6 Add the top-bar unread badge on the shell message entry: unread count on page entry and after read/mark-all/delete actions, absent for anonymous shoppers, linking to the message center.
|
|
|
|
## 4. Admin member-level management
|
|
|
|
- [ ] 4.1 Build `apps/admin/pages/member-levels.vue`: levels in threshold order with create/edit/delete forms over bilingual name, icon, growth threshold, and benefits through the shared `admin` level methods, surfacing the delete rejection for levels in use.
|
|
- [ ] 4.2 Register the member-levels entry in the authenticated admin console navigation beside existing platform operations.
|
|
|
|
## 5. Verification
|
|
|
|
- [ ] 5.1 Run the `apps/api/tests/` integration suites with the `tests/common/mod.rs` fixtures — the new `membership.rs` and `messaging.rs` plus the affected `orders.rs`, `order_service.rs`, and `points.rs` suites — proving upgrade boundaries, event triggers, and read semantics against the shared test database.
|
|
- [ ] 5.2 Browser-smoke the running API, Mall, and admin console: confirm receipt upgrades the level and appends the growth entry, the three system events land one message each, message-center read/mark-all/delete update lists and counts, the top-bar badge tracks unread count, and admin level CRUD with in-use delete rejection works end to end.
|
|
- [ ] 5.3 Build the affected frontends because the shared API contract changes: `pnpm --filter @vmall/mall build`, `pnpm --filter @vmall/admin build`, and `pnpm --filter @vmall/shop-admin build`.
|
|
- [ ] 5.4 Run `openspec change validate add-membership-messaging --strict` and `openspec validate --all --strict` and fix findings until both pass. |