The old 售后 flow was an order-refund appendage: tickets were hard-bound to an
order number, users could only file them from 「我的订单」against a paid order,
and the admin action bottomed out at 「标记订单已退款」. It could not carry
account questions or feature requests that have nothing to do with an order.
Replace it with a general ticket system modelled on 魔方财务云's idcsmart_ticket:
title + department + description are required, linking an order is optional
(user's own paid orders only). Add departments, custom statuses, preset replies
and internal admin notes as four configuration surfaces. Conversation history
moves off the ticket row's JSON log into dedicated ticket_reply rows, which
unlocks quoted replies. Front end gets a 工单中心 panel under 我的服务; back end
moves from 支付中心 → 售后服务 to users.php → 工单管理.
Per CLAUDE.md's rewrite-over-compatibility rule: drop the old table, build new
ones, delete the old code. No dual paths, no data migration. The upstream
plugin's upstream/downstream delivery and internal-ticket conversion are
deliberately left out — both are multi-provider platform concerns with no
meaning on a single WordPress site.
Data: order_aftersale becomes six aksrt_ticket* tables (ticket, ticket_reply,
ticket_status, ticket_type, ticket_prereply, ticket_note). SCHEMA_VERSION
2.6.0 -> 3.0.0. Status and department seeds are idempotent; the default status
「待接单」is flagged as non-editable.
Domain: new src/Ticket/ (13 files, all under 460 lines). Attachments reuse
UserController::rest_upload_comment_image with a ticket-specific permission
callback, mirroring the existing /report/upload-image route rather than
growing a second upload path.
Notifications go through AKSRT\Messages\Message::send() and deep-link to the
right surface; email/SMS stay out of scope since the theme's mail and SMS
stacks are separate implementations.
Refund gate opens up: marking an order refunded no longer requires an in-flight
ticket, only that the order is paid.
Also generalise a hardcoded special case in tools/audit-admin-contracts.php —
schema domain inference now reads the parent directory instead of matching
"PostList" by name, so new leaf schema files need no per-name exception.
Verification:
php tools/audit-admin-contracts.php exit 0, errors=0
node --test (widget-system + admin) 664 pass / 0 fail
php -l over all new/changed files clean
node --check over all JS clean
npm run build + build:public clean
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The poster modal shell already matched the reference theme, but the canvas
drawing diverged from zibll's poster-share.js rebate branch in four places:
- No 推荐人 tags layer. The reference paints a red #fc4e1f 24px line at
img_height+160; AKSRT dropped the layer entirely, so the poster lost the
referrer attribution that identifies whose link it is.
- The logo slot painted text. The reference contains the real logo image
into (80, height-190, 300, 100); AKSRT drew the site name as a 30px string
plus a hardcoded 长按识别二维码访问 caption in that slot.
- Bottom copy was hardcoded. The reference reads share_desc; now sourced
from the new pay_rebate_poster_desc_bottom field with share_desc fallback.
- No banner fallback. The reference walks banner → banner_default →
banner_spare; a single broken URL left the top half of the poster black.
Also aligns the week label to the reference's 周 prefix (was 星期日) and ports
drawTextOverflow for wrapping/truncation, which retires the local wrapText.
Adds pay_rebate_poster_banner and pay_rebate_poster_desc_bottom to both the
Vue and PHP schemas; rest_me now returns logo, compatible and referrer_name.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Aligns affiliate commission with the reference theme: commission is credited
to a dedicated COMMISSION asset pool the moment an order is paid, removing the
7-day settlement freeze, the settlement cron, and the settlement_days setting.
- Ledger: add AssetType::COMMISSION (isolated from the recharge balance wallet)
- CommissionLedger: credit-on-record + opportunistic debt repayment; refunds
claw back the full commission from the pool, carrying any shortfall as debt
(aksrt_commission_debt) that future commission pays down first
- Withdrawal: make asset-aware via a source discriminator (commission vs
balance); each pool keeps independent daily limits, pending gate, and
reject-refund. Balance withdrawal (recharge + creator income) is unchanged
- Fix pay_rebate_self_s: honor self-commission at purchase time
- Revenue card: show pool balance + 累计/已提现 (drop 待结算), debt when present
- Schema 2.0.0 -> 2.1.0: add commission_debt, drop freeze columns
Opening the 推广收益 tab was slow because every read path ran settlement
and two COUNT aggregates before the browser could show the link.
- profile-revenue.php: the link input now carries a server-rendered value from
ReferralAttribution::create_link(), so 复制链接 works on first paint instead
of after a REST round trip
- CommissionLedger::overview() no longer calls lazy_settle_due(); the
lazy-settlement hook and its static flag are gone. Settlement runs only on
the existing hourly aksrt_affiliate_settle_due cron, which uses a larger
batch (100 vs 20) and would otherwise re-run up to 20 locked
SELECT..FOR UPDATE + write cycles inside a GET request
- ReferralAttribution::rest_me() drops the lazy_settle_due() call and the
stats() result; stats() itself is deleted — its two COUNTs (one of them
COUNT(DISTINCT source_user_id)) had no consumer left once the 推广人数 row
was removed
- credits.js: reads the link from the already-rendered input and only fetches
/revenue/me for poster config and commission ratio. A failed fetch no longer
blocks copying — it silently skips the poster enhancement
- tests: the lazy-settlement contract is inverted to assert the read path stays
read-only and cron stays wired; new assertions pin the server-rendered value
and the no-COUNT REST payload
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Restriction: add is_suspended/get_active_suspend; comment block now covers
suspend (小黑屋) in addition to mute, with reason in the deny message
- FrontSubmitSave::persist: block post/draft/edit submission for suspended
users (reference-theme semantics: 小黑屋 loses all publishing rights)
- report.js: close icon switched from fa-times to the SVG sprite ic-close
(#icon-close), same as reference theme; textareas gain autoHeight growth
(hidden 'other reason' box skips the first measurement)
- WeChatNotify: NOTIFY_TYPES now includes report/system so report results
and account restriction notices fan out to WeChat template messages;
generic title/description fallback for template values
- contract tests: +3 tests (suspend enforcement, modal polish, wechat types)
- remove unused AuthReader::invitation_mode and ReferralAttribution
helpers (get_or_create_code / validate_ref); contract test now
asserts the canonical invit_code_s reader in CreditsRuntime
- drop legacy aksrt_invite table from uninstall cleanup list
- track referral code creation time (aksrt_referral_code_created)
and surface it in the referral ops REST list
- formalize ops list headers (归属用户/推广码/启用状态/推广用户数/
有效付费用户数/创建时间) and right-align count columns