Owner review 46 — lane C: dues, members, the club 기록, participants with fees, participation windows (2026-10-19)
Status: Reference · Date: 2026-10-19
Read-only research for owner review 46, marks M10, M11, M16, M17, M25, M26. Paths are repo-relative; hub = docs/twomore_design_system/wireframes/flat-v2-hub.jsx, la = flat-v2-list-a.jsx. Two claims re-checked by the lead on 2026-10-19: expo-clipboard is in no package.json (a native copy button needs a native module and a build), and rsvps has an arrival (available_from_round, arrives_at, 00426) but no departure column.
1. Now
Dues
- The ledger is
dues, one row per (club, user, year, month) (00009_dues.sql:2-15); status unpaid|paid|partial|waived (00001_enums.sql:10).due_dateand apaid_amountrollup ofpayment_historyreceipts came later (00405:96-149). A member's 입금 완료 is a pending receipt a manager confirms (00414:184-263); the payer never confirms their own (00536); bulk confirm exists (00648). - Frequency is a label only.
clubs.dues_amount,dues_day(00003:11-12) anddues_cyclemonthly|quarterly|semiannual|yearly, "labels/wizard only" (00490:10-16). No dues screen reads the cycle; the UI is a month picker; generation is manual, one month, a flat amount (packages/features/clubs/src/club-dues/use-club-dues-actions.ts:307-337). Reminders exist (signals_tick_dues, 00405:517-590); auto-generation does not. - Account:
club_bank_accounts, one per club, readable by active members (00412:15-37). Copy is web-only; native gets selectable text and a hint (packages/app/src/presentation/components/copyable-value.tsx:1-16, 85-97). - Absent: an expense/income ledger, a club balance, escrow. The nearest is the legacy
sessions.cost_totalsplit (docs/canon/payments.md:11). PortOne checkout is scaffolded; club settlement is "a separate design" with a 3% fee (docs/guides/portone-payments.md:76-81). The retention store covers dues, payment_history and session_payment only (00542:42-46). - The Strip's 확인 대기/미납/완납 counts (hub 2479-2486; la 2016-2033) repeat the section-head counts right below them; production already dropped its StatStrip for that reason (
packages/features/clubs/src/club-dues/sections/dues-list-headers.tsx:8-19). Review 29 faulted a Strip as "how much, not what to do or for whom" (owner-review-29-rulings-2026-10-15.md:53). Empty states hub 2446-2468; 멤버를 초대해 보세요 is a lone Notice above an empty feed (hub 2212-2227).
Session fees and participants
session_payments: one hold per (session, user), pending|paid|waived (00059:1-13);methodmanual|portone andsubmitted_at(00301:32-36);refund_state(00307:31); a settle-before-play match gate (00384:45-87).- Participants and fees are separate routes (la 3697-3788, 3795-3857); the fee row is hand-built with no avatar (
packages/features/sessions/src/session-payments/payment-row.tsx:47-83).
Members and 복식 파트너 추천
- The pane: caption, a 복식 파트너 추천 task cell, role groups, then the officials' 멤버 관리 (2) cell (hub 2414-2433;
packages/features/clubs/src/club-detail/members-pane.tsx:225-236). - The full page stacks 심사 대상, 추천할 만한 게스트, two tool cells and the roster as peer sections (la 1607-1654), against the page grammar's "Modules… three rows at most" (hub 383).
- The suggester is
rankPartnerCandidates: ELO proximity 40, chemistry 30, trust 15, attendance 15 (packages/app/src/domain/rules/partner-matching.rules.ts:1-9, 178-201).
순위 and 조
RankingTabshows the global rating filtered to members; 조별 only partitions that list and never renumbers within a group (packages/features/clubs/src/club-detail/ranking-tab.tsx:153-204). 조 is an ELO rank partition intoclub_groupswith a manual pin (00476:51-66, 133-138;club-groups.rules.ts:156-209). No per-조 results exist.season_recordsandget_season_standingsexist (00065, 00099) with no writer outside seed scripts and no UI hook found. A standings engine exists for tournaments (tournament.rules.ts:278).- 00692 added club-scoped
player_matchups_by_formatandplayer_partner_records, readable by club members (00692:70-92); friendlies are skipped. The adapter reads one player at a time, 200 rows max (player-format-records.supabase.ts:18-46).
Attendance and each player's times
- Attendance is derived after completion: appearing in a match = attended; confirmed with matches but none played = a no_show strike; a zero-match session = everyone attended; only when
attendance_tracking_enabled(00444:201-338). GPS check-in was retired (00384:1-38). confirm_attendanceis owner/admin only (00405:1222-1257); the 출석 확인 entry shows for in_progress|completed (host-tools-gates.ts:71-74); the board's job reads "After a session" (la 4177).- Arrival exists, departure does not:
rsvps.available_from_round,arrives_at,sessions.late_arrival_policy(00426:37-46), host-written only (00426:72-87), inside the add-round sheet (use-arrival-availability.ts:147-167).arrives_atis informational; its round is an estimate (arrives-at-to-round.ts:34-42); the defaultequal_rest_forwardis unimplemented (late-arrival-hint.ts:1-16). Early exit is reactive:forfeit_sessionswaps in a substitute (00683:499-675). - Generation reads windows as
availabilityMapintoisAvailable(id, round)(rotation.rules.ts:227-233); unavailable players bench without taking a fairness slot (resolve-pool.ts:43-45);computeAttendanceViabilityforecasts per round (feasibility.ts:120-177).
2. References (2025–2026)
- 스매시 (Smash) by smaxh Corp. is the "smaxh" the owner means (smaxh.com, support@smaxh.com; high confidence). Paid since Aug 2025 at about ₩1,000 per transaction; in-app payment plus no-show deposits (보증금) raised turnout and manners; 62% of users prefer deposit matches (https://www.unicornfactory.co.kr/article/2026051113245672552). Clubs get voting, AI brackets, 전적/랭크 (https://smaxh.com/ko-KR). Not verified: when deposits are held, released or forfeited, and club-level settlement.
- Spond: per-request paid/unpaid/overdue/failed with filters and reminders (https://help.spond.com/club/en/articles/188096-managing-payment-requests-in-spond-club); subscriptions with billing dates (https://www.spond.com/news-and-blog/subscription-payments-in-spond-club/); attendance logged once the event starts (https://help.spond.com/en/articles/25877-how-to-track-and-trace-event-attendees).
- TeamSnap: invoice batches, offline payments keyed by hand, statuses visible to the team (https://helpme.teamsnap.com/article/1798-creating-team-issued-invoices). Heja: amount + due date, payer bears the fee, weekly payout (https://help.heja.io/en/articles/3195641-how-do-fees-payments-work). SportEasy: per-member status, offline entries, transfer reconciliation (https://www.sporteasy.net/en/clubs/features/payments-and-fees/).
- 카카오뱅크 모임통장: 회비규칙 (amount and day, auto alert), a 미입금 view with bulk requests (https://www.sidae.com/article/2023020115501547094). 토스뱅크 동아리통장 (2025): purpose, per-person amount and deadline; a pay link; paid/unpaid split automatically (https://www.smarttoday.co.kr/ko-kr/articles/79552). Splitwise: running balances, settle up, activity feed with edit history, CSV.
- Playtomic: a split payment holds the card on the booking owner; others pay until 2 h after the match; the shortfall is charged to the owner (https://playerhelp.playtomic.com/hc/en-gb/articles/19832089052561).
- Late and early players: Pickleheads regenerates each round from who is available (https://www.pickleheads.com/guides/complete-round-robin-guide); PB Play adds late arrivals and removes early leavers without breaking the rest rotation; Americano Padel adds players and marks sit-outs; 360Player defaults accepted to attended and flags non-responders.
3. Proposal
(a) Dues: two pages, one grammar. 내 회비 (member): one Block for the open period ("분기 회비 90,000원 · 7/1~9/30 · 7/5까지 · D-3 · 미납"); 입금 계좌 as one row with a trailing copy icon button (needs expo-clipboard and a build; until then the share sheet); CTA 입금 완료 알리기; history one row per period. 클럽 회비·장부 (officials see everything, members an aggregate): a 잔액 Block with a 월/분기/연 Choice; one progress line "₩510,000 / ₩600,000 · 17/20명" replaces the Strip; rows with verbs (확인 대기 → 확인; 미납 → 알림 보내기, "마지막 알림 9/5"); a 장부 Feed of dated income and expense rows (category, receipt photo, running balance) with 지출 추가 for officials; CSV export; a transparency setting (members see totals and the unpaid count; names go to officials only). Cadence: club_dues_plans (name, amount, cadence monthly|quarterly|semiannual|yearly|one_time|every_n_months|per_session, due rule, grace, proration, applies to role/조); dues gains plan_id, period_start/end, label; rows generated at period start; per-session dues reuse session_payments. Escrow later extends the hold (settlement_state held|released|refunded, payout batches; release on attended, refund on cancel, forfeit on no-show per club policy; needs a PG sub-merchant or 지급대행 and legal review). Empty states become setup steps for officials (계좌 등록, 회비 설정, 첫 일정, 멤버 초대) and a calm line for members.
(b) Members. The pane is the roster only (role groups, 조 tag, search). Verbs leave the list: one bounded tonal 할 일 row appears when the viewer can act ("가입 신청 2건 · 심사하기") and opens a 가입 심사 page (심사 대상, 추천할 만한 게스트, 게스트 지명); 단톡방 and 초대 go to the header ⋯. An action is time-bound, disappears when done and carries a verb; the list is persistent people with ⋯ for rare actions. 복식 파트너 추천 leaves the pane; rankPartnerCandidates becomes a 파트너 module in 기록 or the 대진 flow.
(c) The club 기록. Same grammar as the personal 기록 (hub 3511-3539): a season Block (경기 수, 활동 멤버, 평균 출석); Modules 순위 전체|조별 (a per-조 table 경기 승 패 승률, reusing calculateStandings with each 조 as a pool), 베스트 파트너 (≥ 5 together), 라이벌/천적 (most played, most lopsided, ≥ 5), 출석·꾸준함, 상승세; doors 선수별 전적 and 통계; Feed 경기 기록. Data: club-scoped bulk reads of the 00692 tables (an RPC beyond 200 rows), a decision on friendlies, a per-match 조 snapshot and a season window, a season_records writer or compute on read.
(d) Participants + fees: one route, PersonRow only (avatar, name, one sub line of state and time, a badge only for 호스트/게스트/예비); sections follow what the host must do (입금 확인 중 with bulk confirm, 입금 필요, 참석 확정, 예비, 취소 with refund state); managers see money, others names and times; the Strip becomes one line ("6명 · 입금 확인 3 · 필요 2"); footer 정산 완료; the split moves to the header ⋯. No new tables: join rsvps and session_payments.
(e) Confirmation with time windows. 출석 확인 becomes 참석 확인, open from T-2h (reuse the session_starting_soon cron, 00385:545-573) to the end; states 미응답, 확정, 도착함, 먼저 감; post-completion derivation (00444) stays as the audit trail. rsvps gains leaves_at, available_until_round, checked_in_at, checked_out_at (the participant declares their window and taps 도착 or 먼저 갈게요; the host edits anyone; 00433's allowlist and confirm_attendance widen accordingly); wall-clock is canonical, rounds derive from the actual round start. Engine: availabilityMap becomes {fromRound, untilRound}; the pre-start screen shows the per-round forecast ("09:00 4명 → 1면 / 09:30 8명 → 2면"); fairness per available round; implement or drop equal_rest_forward. On the day: a T-2h prompt "오늘 몇 시부터 칠 수 있나요?" (정시 / 30분 늦게 / 일찍 가야 해요 / 시간 선택); rows read "09:30 도착 예정"; 도착 enters the next round; 먼저 갈게요 removes the player after their time; at start + 15 m the host sees 미도착 and marks 불참.
Migration gaps: dues plans and periods; a club ledger (opening balance, aggregate RPC, retention record type); a reminder log; escrow settlement and payouts; rsvps windows and check-in; a match 조 snapshot and season writer. Not migrations: the expo-clipboard build, bulk record reads, the session-fee bulk-confirm RPC.
Not verified: Smash's hold and release rules; the legal basis for holding funds; Band's current dues UI; 플랩풋볼 rules; whether season_records has a writer outside seed or edge code.