Files
vmall/openspec/changes/archive/2026-09-25-add-membership-messaging/tasks.md
T
james 9904696e76 feat: wave 2 migration (P3, P5, P7 openspec changes)
Implements, verifies, and archives the three remaining Wave 2 changes from
openspec/MIGRATION-PLAN.md.

- add-wallet-settlement (P3): demo recharge, guarded withdrawal freeze and
  one-time admin review, paginated own fund entries, idempotent per-shop
  weekly/monthly settlement statements with commission rate and one-time
  payout confirmation.
- add-merchant-onboarding (P5): personal/enterprise applications with one live
  application per user, guarded review with mandatory rejection reason, and
  transactional shop + owner provisioning returning one-time credentials;
  mall onboarding/status pages and an admin review console.
- add-membership-messaging (P7): platform member levels, append-only growth
  accrual on order completion with guarded one-way leveling, order/shipment/
  refund system messages with unread/read state and soft deletion, plus the
  mall header unread badge.

Backend: migrations 0019-0023, new wallet, settlement, merchant_onboarding,
membership and messaging modules, event hooks in order/fulfillment/aftersale,
and integration suites for each. Shared contract extended and all three
frontends updated; code indexes, domain docs, backend guidelines and the
migration tracker synced.

Verification: cargo test -p vmall-api green twice consecutively; mall, admin
and shop-admin builds pass; browser smoke on every new surface; openspec
validate --all --strict green (33 passed).

The three changes share the @vmall/shared contract, the mall mock adapter and
per-app locale/nav files, so they are committed together to keep every commit
buildable.
2026-09-25 15:25:29 +00:00

6.0 KiB

1. Persistence and shared contract

  • 1.1 Add migration 0023_membership_messaging.sql (renumbered from the proposed 0016; 0019-0022 were taken): 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.