[PASS] Interface contract: specBaseIdMap['A_1'] = int ✓
[PASS] goods_params: stock=1, seat-level spec_base_id ✓
[PASS] Fallback strategy for Plan B transition ✓
[PASS] Seat label format matches backend regex ✓
[PASS] Price sources align between frontend and backend ✓
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
[PASS] Interface contract: specBaseIdMap['A_1'] = int ✓
[PASS] goods_params: stock=1, seat-level spec_base_id ✓
[PASS] Fallback strategy for Plan B transition ✓
[PASS] Seat label format matches backend regex ✓
[PASS] Price sources align between frontend and backend ✓
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bug 1: SeatSkuService.php:381 - regex has syntax error
'/^([A-Za-z]+)(\d+)排(\d+)座$/' → '/^([A-Za-z]+)(\d+)排(\d)座$/'
The third capture group only needs single digit (col number 1-9).
Bug 2: ticket_detail.html:416 - frontend accesses specBaseIdMap as object
but PHP returns flat integer: specBaseIdMap['A_1'] = 2001 (int), not {spec_base_id: 2001}.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. SeatSkuService: Fix regex in getExistingSpecBaseIds()
(\d+)排(\d+)座 → (\d+)排(\d)座
The original regex incorrectly captures 2 digit groups in "A排10座",
causing seatId parse failure for column >= 10.
2. ticket_detail.html: Fix specBaseIdMap access in submit()
(obj||{}).spec_base_id → direct numeric value
PHP returns integers (not objects), so drop the .spec_base_id accessor.
关联:Issue #9
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- renderSeatMap(): add data-row-label + data-col-num attrs for specBaseIdMap key format
- toggleSeat(): change seatKey from "0_0" (numeric) to "A_1" (label_colNum) to match specBaseIdMap
- removeSeat(): use [data-row-label][data-col-num] selector
- submit(): refactor from 1 goods_params (zone-level) to N entries (seat-level, stock=1)
- Plan B fallback: if specBaseIdMap[key] missing, use sessionSpecId
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Write council-output/ARCHITECTURE_DECISION.md with Q1-Q4 conclusions
- Update plan.md: mark Q3 done, final report done, consensus=YES
- Resolve rebase conflict from Round 2
- Final recommendation: 方案A (每座=SKU)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Q1: Batch SKU via direct SQL INSERT (bypass GoodsSpecificationsInsert)
- Q2: Solution B minimal fix (UPDATE is_exist_many_spec + INSERT $vr- spec_type + idempotency)
- Q3: $vr- prefix LOW risk (confirmed by SecurityEngineer + FrontendDev)
- Q4: All members recommend Plan A (one SKU per seat)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
P0-1: issueTicket() now checks for existing tickets by (order_id, spec_base_id)
before inserting. Prevents duplicate tickets on HTTP retry/multi-instance.
P0-3: Removed |raw from simple_desc and content in ticket_detail.html.
Prevents stored XSS via malicious admin content injection.
P0-4: getQrSecret() now throws exception if VR_TICKET_QR_SECRET is unset,
instead of falling back to insecure default key.
Resolve Round 4 merge conflict by restoring verifier views and ticket
detail from commit 6f49b8355. All 7 admin view files now confirmed on main.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
[P0] Fix plugin Base controller to extend ShopXO Common class:
- Now extends Common instead of standalone class
- Automatically gets IsLogin() + IsPower() + ViewInit()
- All child controllers (SeatTemplate/Ticket/Verifier/Verification) inherit fix
[P1] Fix code bugs found during codebase analysis:
- Verifier.php: column('nickname|username', 'id') → CONCAT SQL (syntax error)
- SeatTemplate.php: countSeats() wrong logic (count × rows → per-row scan)
- Ticket.php: verify() returned view on POST → always JSON
- Ticket.php: detail() returned view on error → JSON
- SeatTemplate.php: delete() returned view on POST → JSON, plus soft-delete
[P1] Fix verifyTicket() in TicketService:
- Wrap in Db::transaction() for atomicity
- Add SELECT ... FOR UPDATE pessimistic lock to prevent double-verify
- Add try/catch with error logging
[P2] Fix export() memory issue:
- Replace select() with cursor() to avoid OOM on large datasets
Also: update plan.md with Round 2 findings, claim Task B1/B2/B3/B5
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Change plugin Base from standalone to extend Common
- Call IsLogin() + IsPower() + FormTableInit() explicitly (avoids
full ViewInit which is unnecessary for API/admin controllers)
- Documents permission node format: plugins_vr_ticket-{controller}-{action}
- Fixes R1 P0: bypassed auth chain (only LoginInfo, missing IsPower)
- Also fixes all child controllers since they call parent::__construct()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>