gymmonk-schema 0.46.1 → 0.46.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -47,8 +47,28 @@ import { z } from 'zod';
47
47
  /**
48
48
  * The STORED lifecycle column.
49
49
  *
50
- * `invited` is legacy and no longer written by anything; it is kept in the
51
- * union so old rows still parse.
50
+ * `pending` is where every membership STARTS, whoever created it. Choosing a
51
+ * plan is not the same event as paying for one, and the two used to be welded
52
+ * together: the moment a member tapped a plan it went live, and the moment an
53
+ * owner assigned one at the desk it went live, whether or not any money had
54
+ * changed hands. A gym then had no way to tell a paid member from an optimistic
55
+ * one, and no lever to withhold access from the second.
56
+ *
57
+ * The only transition out of `pending` is the gym marking the payment done.
58
+ * That is deliberately a HUMAN act by the owner or a manager rather than a
59
+ * gateway callback: these gyms take cash at the desk and UPI transfers on a
60
+ * phone, so the person who knows the money arrived is the person at the gym.
61
+ *
62
+ * `rejected` is the other way out, for a screenshot that shows nothing, the
63
+ * wrong amount, or a transfer that never landed. It is a dead end — the member
64
+ * requests again rather than the gym reopening a closed decision.
65
+ *
66
+ * `invited` predates all of this and nothing writes it. Left in the union
67
+ * because removing a value from a stored enum is not worth a migration, and old
68
+ * rows still have to parse.
69
+ *
70
+ * `expiring` is NOT here — it is derived from `endDate` at the edge. See
71
+ * `DisplayMembershipStatus`.
52
72
  */
53
73
  export declare const membershipStatusSchema: z.ZodEnum<{
54
74
  invited: "invited";
@@ -1 +1 @@
1
- {"version":3,"file":"membership-status.d.ts","sourceRoot":"","sources":["../src/membership-status.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"membership-status.d.ts","sourceRoot":"","sources":["../src/membership-status.ts"],"names":[],"mappings":"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"}
@@ -50,8 +50,28 @@ import { z } from 'zod';
50
50
  /**
51
51
  * The STORED lifecycle column.
52
52
  *
53
- * `invited` is legacy and no longer written by anything; it is kept in the
54
- * union so old rows still parse.
53
+ * `pending` is where every membership STARTS, whoever created it. Choosing a
54
+ * plan is not the same event as paying for one, and the two used to be welded
55
+ * together: the moment a member tapped a plan it went live, and the moment an
56
+ * owner assigned one at the desk it went live, whether or not any money had
57
+ * changed hands. A gym then had no way to tell a paid member from an optimistic
58
+ * one, and no lever to withhold access from the second.
59
+ *
60
+ * The only transition out of `pending` is the gym marking the payment done.
61
+ * That is deliberately a HUMAN act by the owner or a manager rather than a
62
+ * gateway callback: these gyms take cash at the desk and UPI transfers on a
63
+ * phone, so the person who knows the money arrived is the person at the gym.
64
+ *
65
+ * `rejected` is the other way out, for a screenshot that shows nothing, the
66
+ * wrong amount, or a transfer that never landed. It is a dead end — the member
67
+ * requests again rather than the gym reopening a closed decision.
68
+ *
69
+ * `invited` predates all of this and nothing writes it. Left in the union
70
+ * because removing a value from a stored enum is not worth a migration, and old
71
+ * rows still have to parse.
72
+ *
73
+ * `expiring` is NOT here — it is derived from `endDate` at the edge. See
74
+ * `DisplayMembershipStatus`.
55
75
  */
56
76
  export const membershipStatusSchema = z.enum([
57
77
  'pending',
@@ -1 +1 @@
1
- {"version":3,"file":"membership-status.js","sourceRoot":"","sources":["../src/membership-status.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Membership status, all of it, in one place\n * ============================================================\n * Everything the product knows about \"where does this member stand\" lives\n * here: the stored lifecycle, the effective one, the words shown on screen,\n * the money reading, and the three thresholds that decide \"soon\".\n *\n * ── Why this module exists ──────────────────────────────────────────────────\n * These facts were spread across five files in two repos. The stored enum and\n * the effective rule were in `membership.ts`; the DISPLAY vocabulary and its\n * derivation were in the web client's `shared/lib/format.ts`, out of the\n * backend's reach; the labels were inline in a pill component; the fee\n * vocabulary was in an owner-console file and took a web VIEW TYPE as its\n * input, so nothing else could call it. Two of the web's own feature folders\n * had also declared `MembershipStatus` as an alias of the DISPLAY type — the\n * same name as the stored one, meaning something different, in the same\n * codebase.\n *\n * ── There are TWO axes, and collapsing them would lose information ──────────\n * They are correlated, not identical, and they answer different questions:\n *\n * LIFECYCLE can this person train today? (the door asks this)\n * FEES does this person owe money? (the owner's roster asks this)\n *\n * A member part-way through a term they have half-paid is `active` on the\n * first axis and `unpaid` on the second, and gyms take part payments every\n * week. One flat enum would have to pick a side.\n *\n * The axes relate like this, and `readFeeStatus` encodes exactly this table:\n *\n * lifecycle money owed → fee status\n * active no → paid (or due-soon near the end of the term)\n * active yes → unpaid\n * expired yes → overdue\n * expired no → lapsed\n * pending n/a → pending (the GYM owes the next move)\n * none n/a → no-plan (the OWNER owes the next move)\n *\n * ── What is NOT here ────────────────────────────────────────────────────────\n * Colours. A tone is a Tailwind class, it belongs to whichever client is\n * painting, and there is exactly one consumer of each. The words live here\n * because two surfaces must never call the same state different things.\n *\n * @module gymmonk-schema/membership-status\n */\n\nimport { z } from 'zod';\n\n// ═══════════════════════════════════════════════════════════════════════════\n// AXIS 1 — LIFECYCLE: can this member train?\n// ═══════════════════════════════════════════════════════════════════════════\n\n/**\n * The STORED lifecycle column.\n *\n * `invited` is legacy and no longer written by anything; it is kept in the\n * union so old rows still parse.\n */\nexport const membershipStatusSchema = z.enum([\n 'pending',\n 'rejected',\n 'invited',\n 'active',\n 'paused',\n 'expired',\n 'cancelled',\n]);\nexport type MembershipStatus = z.infer<typeof membershipStatusSchema>;\n\n/** The fields any status question needs. Deliberately not a full membership. */\nexport interface MembershipStatusInput {\n status: MembershipStatus;\n endDate: string;\n pausedFrom?: string | null;\n pausedTo?: string | null;\n}\n\n/**\n * What a membership IS right now, as against what was last written to its\n * `status` column.\n *\n * ── Why this has to exist ───────────────────────────────────────────────────\n * Two facts about this system make the stored status untrustworthy on its own:\n *\n * 1. NOTHING sweeps memberships to `expired`. There is no job. A member who\n * stopped paying in March keeps `status: 'active'` in the database all\n * year, so the term's END DATE is the only honest answer.\n * 2. NOTHING resumes a pause. `pausedTo` was written and then never read by\n * any code path, so a two-week pause locked a member out permanently and\n * the only way back was an owner noticing and flipping the roster switch\n * by hand.\n *\n * ── What is NOT re-derived ──────────────────────────────────────────────────\n * `pending`, `rejected`, `cancelled` and `expired` are all decisions a PERSON\n * made, and are returned untouched. `expired` matters most: an owner\n * deactivating a member sets exactly that, and re-deriving it back to `active`\n * because the term still has days left would silently overrule them.\n *\n * `today` is passed in rather than read from the clock, because the answer is\n * the GYM's day — see `gymDayOf` on the server and `istDayOf` on the client.\n * `YYYY-MM-DD` compares lexicographically, so these are exact calendar-day\n * comparisons with no instant arithmetic and no zone hidden inside.\n */\nexport function effectiveMembershipStatus(\n membership: MembershipStatusInput,\n today: string,\n): MembershipStatus {\n const { status, endDate, pausedFrom, pausedTo } = membership;\n\n // Decided by a person. Never second-guessed.\n if (\n status === 'pending' ||\n status === 'rejected' ||\n status === 'cancelled' ||\n status === 'expired' ||\n status === 'invited'\n ) {\n return status;\n }\n\n /**\n * A pause is a WINDOW, not a switch somebody has to remember to turn off.\n *\n * Reading it as a window is what makes the resume happen without a scheduled\n * job: before it, the plan runs; inside it, the member is out; after it, they\n * are simply back, on the end date the pause already pushed out for them.\n * A pause with no end recorded is treated as still running, because the safe\n * failure for a missing bound is to keep the member out rather than to let\n * an unbounded pause silently expire.\n */\n if (status === 'paused') {\n if (!pausedTo) return 'paused';\n const startedYet = !pausedFrom || today >= pausedFrom;\n if (startedYet && today <= pausedTo) return 'paused';\n }\n\n return endDate >= today ? 'active' : 'expired';\n}\n\n/** Can this member train today? The one question the door asks. */\nexport function isMembershipLive(membership: MembershipStatusInput, today: string): boolean {\n return effectiveMembershipStatus(membership, today) === 'active';\n}\n\n// ─── Lifecycle, as the UI says it ───────────────────────────────────────────\n\n/**\n * How close to `endDate` a live membership starts reading as \"expiring\".\n *\n * A PRESENTATION band and nothing more: the door never sees `expiring`, and a\n * member inside this window is fully `active` as far as access is concerned.\n * Distinct from `FEE_DUE_SOON_DAYS`, which is a much tighter window because it\n * gates a payment reminder rather than a colour.\n */\nexport const MEMBERSHIP_EXPIRING_WINDOW_DAYS = 30;\n\n/**\n * How far ahead the owner's home ALERT looks for memberships about to lapse.\n *\n * Deliberately much tighter than the pill's band, and the reason all three\n * windows are declared together here rather than as a `const` in whichever\n * file happened to need one:\n *\n * MEMBERSHIP_EXPIRING_WINDOW_DAYS 30 paints a pill amber; the roster's\n * \"Expiring Soon\" segment and filter\n * MEMBERSHIP_EXPIRY_ALERT_DAYS 7 the one-line banner on owner home,\n * which says \"expiring this week\"\n * FEE_DUE_SOON_DAYS 3 arms a payment reminder\n *\n * They are different NUMBERS because they drive different actions — a colour,\n * a banner and a message to a member should not all fire at once — but they\n * are the same QUESTION, so they belong side by side where the next person to\n * change one can see the other two.\n */\nexport const MEMBERSHIP_EXPIRY_ALERT_DAYS = 7;\n\n/**\n * The lifecycle states a SCREEN distinguishes.\n *\n * `cancelled` and `invited` are absent on purpose — both read as `expired` to\n * a viewer, and a pill that said \"Cancelled\" next to one that said \"Expired\"\n * would imply a difference the member cannot act on differently.\n */\nexport type DisplayMembershipStatus =\n | 'pending'\n | 'rejected'\n | 'active'\n | 'expiring'\n | 'paused'\n | 'expired';\n\n/**\n * The lifecycle, narrowed onto what a screen shows.\n *\n * Adds exactly one thing over `effectiveMembershipStatus`: the `expiring`\n * warning band. Everything else is a straight mapping, so a screen and the\n * door can never disagree about whether somebody is live — only about how\n * loudly to say it.\n */\nexport function displayMembershipStatus(\n membership: MembershipStatusInput,\n today: string,\n): DisplayMembershipStatus {\n const effective = effectiveMembershipStatus(membership, today);\n\n switch (effective) {\n case 'pending':\n return 'pending';\n case 'rejected':\n return 'rejected';\n case 'paused':\n return 'paused';\n case 'expired':\n case 'cancelled':\n return 'expired';\n default: {\n // Live. The only question left is how close the end is.\n const left = daysBetween(today, membership.endDate);\n return left <= MEMBERSHIP_EXPIRING_WINDOW_DAYS ? 'expiring' : 'active';\n }\n }\n}\n\n/**\n * Is this display state one where the member can still train?\n *\n * `expiring` is LIVE. It is a warning band, not a lifecycle state — the term\n * has days left and the door lets them in — so anything filtering or counting\n * \"active members\" has to include it. Written down here because that is\n * exactly the kind of rule each list would otherwise re-derive slightly\n * differently, and one of them would forget `expiring` and quietly hide every\n * member in their last month.\n */\nexport function isLiveDisplayStatus(status: DisplayMembershipStatus): boolean {\n return status === 'active' || status === 'expiring';\n}\n\n/**\n * The words on the lifecycle pill.\n *\n * Here rather than in the component because two surfaces calling the same\n * state different things is the failure this module exists to prevent.\n * \"Awaiting payment\" is deliberately not \"Pending\": pending is what the\n * database calls it, and the member wants to know who they are waiting on.\n */\nexport const MEMBERSHIP_STATUS_LABEL: Record<DisplayMembershipStatus, string> = {\n pending: 'Awaiting payment',\n rejected: 'Not confirmed',\n active: 'Active',\n expiring: 'Expiring',\n paused: 'Paused',\n expired: 'Expired',\n};\n\n// ═══════════════════════════════════════════════════════════════════════════\n// AXIS 2 — FEES: does this member owe money?\n// ═══════════════════════════════════════════════════════════════════════════\n\n/** How close to the end of a term a member starts reading as \"due soon\". */\nexport const FEE_DUE_SOON_DAYS = 3;\n\n/**\n * Where a member stands on money, as one phrase an owner can scan down a list.\n *\n * Every state exists because it needs a DIFFERENT action. This began life as\n * \"paid or not\", which collapsed five unrelated situations into one green\n * chip: a member with no plan, a member whose payment was waiting on the GYM,\n * and a member who had genuinely settled all read \"Paid\", so the roster asked\n * the owner to do nothing about two people who needed something done.\n *\n * overdue money owed, term already ended → chase them\n * unpaid money owed, term still running → chase them\n * rejected the gym turned their payment down → talk to them\n * pending they have paid, the GYM must confirm → owner confirms\n * lapsed term ended, nothing sold to replace → chase a renewal\n * due-soon ends within a few days → chase a renewal\n * no-plan never been sold a membership → owner assigns one\n * cancelled they ended it and left → nothing to collect\n * paid settled, with time left on the term → nothing\n */\nexport type FeeStatus =\n | 'overdue'\n | 'unpaid'\n | 'rejected'\n | 'pending'\n | 'lapsed'\n | 'due-soon'\n | 'no-plan'\n | 'cancelled'\n | 'paid';\n\n/**\n * What the fee question needs, as plain facts.\n *\n * Deliberately NOT a view model. This used to take the owner console's roster\n * card type, which is why nothing else in the product could ask the question —\n * including the backend, which computes two of these fields in the first place.\n */\nexport interface FeeStatusInput {\n /** The stored lifecycle, or null when no membership was ever sold. */\n membershipStatus: MembershipStatus | null;\n /** The server's own judgement: money outstanding AND the term already ended. */\n overdueDays?: number | null;\n /** Still owed on the current term, in whole rupees. */\n balanceDueInr: number;\n /** End of the current term, `YYYY-MM-DD`, or null when there is none. */\n expiresOn?: string | null;\n}\n\nexport interface FeeReading {\n status: FeeStatus;\n /**\n * Does this need the owner's eye? Drives the chip's colour.\n *\n * Deliberately NOT the same question as `chase`. A member waiting on the gym\n * to confirm their payment needs attention and must not be sent a reminder —\n * they have already paid, and the outstanding job is the owner's.\n */\n attention: boolean;\n /**\n * Is there something to remind this member ABOUT?\n *\n * Gates the reminder action. False wherever the next move belongs to the gym\n * rather than the member, which is what stops an owner messaging somebody\n * \"your fees are overdue\" while their payment sits in the gym's own\n * confirmation queue.\n */\n chase: boolean;\n /** Whole days until the term ends; negative once past, null when no term. */\n daysLeft: number | null;\n /** Days past the end of an unpaid term, when that is what happened. */\n overdueDays: number | null;\n /** Still owed, in whole rupees. Zero unless the status says otherwise. */\n amountDueInr: number;\n}\n\n/**\n * The fee reading for one member.\n *\n * Ordered by whose move it is and how hard the fact is. `overdueDays` is the\n * server's own judgement — money outstanding AND the term already ended — so\n * it is trusted rather than recomputed here.\n */\nexport function readFeeStatus(input: FeeStatusInput, today: string): FeeReading {\n const { membershipStatus, balanceDueInr } = input;\n const overdueDays = input.overdueDays ?? null;\n const base = { daysLeft: null, overdueDays: null, amountDueInr: 0 } as const;\n\n /**\n * Never sold a membership. Checked FIRST, because every money question below\n * presumes one exists — and a member with no plan cannot be behind on a fee\n * nobody has charged them.\n */\n if (membershipStatus === null) {\n return { ...base, status: 'no-plan', attention: true, chase: false };\n }\n\n /**\n * Waiting on the GYM, not on the member.\n *\n * Ahead of the balance check on purpose: a pending membership carries its\n * full price as outstanding, so this member would otherwise read as owing\n * money and be sent a payment reminder — for money they have already handed\n * over and the gym has simply not confirmed yet.\n */\n if (membershipStatus === 'pending') {\n return { ...base, status: 'pending', attention: true, chase: false };\n }\n\n /** The gym looked at their payment and turned it down. */\n if (membershipStatus === 'rejected') {\n return { ...base, status: 'rejected', attention: true, chase: true };\n }\n\n /**\n * They ended it themselves and left.\n *\n * Checked before the date logic, which would otherwise see a term end in the\n * past and read them as \"Renewal due\" — with a reminder ready to tell\n * somebody who deliberately quit that their membership has expired.\n */\n if (membershipStatus === 'cancelled') {\n return { ...base, status: 'cancelled', attention: false, chase: false };\n }\n\n // Late: the server has already established both halves of that.\n if (overdueDays && overdueDays > 0) {\n return {\n ...base,\n status: 'overdue',\n attention: true,\n chase: true,\n overdueDays,\n amountDueInr: balanceDueInr,\n };\n }\n\n /**\n * Owes money, but the term is still running.\n *\n * A real state and NOT the same as overdue: a gym that takes half now and\n * half on Friday has members here every week, and calling them late would be\n * wrong.\n */\n if (balanceDueInr > 0) {\n return { ...base, status: 'unpaid', attention: true, chase: true, amountDueInr: balanceDueInr };\n }\n\n const daysLeft = input.expiresOn ? daysBetween(today, input.expiresOn) : null;\n\n /**\n * The term ran out and nothing was sold to replace it.\n *\n * Nothing is OUTSTANDING — a term nobody assigned was never invoiced — so\n * this used to fall through and read \"Paid\", in green, for the member most\n * worth chasing on the whole roster.\n */\n if (daysLeft !== null && daysLeft < 0) {\n return { ...base, status: 'lapsed', attention: true, chase: true, daysLeft };\n }\n\n if (daysLeft !== null && daysLeft >= 0 && daysLeft <= FEE_DUE_SOON_DAYS) {\n return { ...base, status: 'due-soon', attention: true, chase: true, daysLeft };\n }\n\n /**\n * Settled, with time left on the term. The only genuinely quiet state, and\n * the only one that earns a green chip.\n */\n return { ...base, status: 'paid', attention: false, chase: false, daysLeft };\n}\n\n/**\n * The words on the fee chip.\n *\n * Functions rather than plain strings because three of these states have to\n * name a number, and the AMOUNT is formatted by whoever is rendering — a\n * client knows how to write ₹1,200 for its locale and this package does not.\n * The wording still lives here, so no surface can invent its own phrasing for\n * a state another surface already names.\n */\nexport const FEE_STATUS_LABEL: Record<FeeStatus, (reading: FeeReading, amount: string) => string> =\n {\n overdue: (r, amount) => `${amount} overdue ${plural(r.overdueDays ?? 0, 'day')}`,\n unpaid: (_r, amount) => `${amount} due`,\n rejected: () => 'Payment declined',\n pending: () => 'Confirm payment',\n lapsed: () => 'Renewal due',\n 'due-soon': (r) =>\n r.daysLeft === 0 ? 'Due today' : `Due in ${plural(r.daysLeft ?? 0, 'day')}`,\n 'no-plan': () => 'No plan yet',\n cancelled: () => 'Cancelled',\n paid: () => 'Paid',\n };\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Shared helpers\n// ═══════════════════════════════════════════════════════════════════════════\n\n/**\n * Whole days from `from` to `to`, both `YYYY-MM-DD`.\n *\n * Anchored at UTC midnight on BOTH sides, so the subtraction is whole days and\n * carries no zone of its own — the caller has already decided which day\n * \"today\" is, in the gym's timezone, and this must not second-guess that.\n */\nexport function daysBetween(from: string, to: string): number {\n const a = Date.parse(`${from}T00:00:00.000Z`);\n const b = Date.parse(`${to}T00:00:00.000Z`);\n if (Number.isNaN(a) || Number.isNaN(b)) return 0;\n return Math.round((b - a) / 86_400_000);\n}\n\n/** `2, 'day'` → `\"2 days\"`. */\nfunction plural(n: number, word: string): string {\n return n === 1 ? `${n} ${word}` : `${n} ${word}s`;\n}\n"]}
1
+ {"version":3,"file":"membership-status.js","sourceRoot":"","sources":["../src/membership-status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,8EAA8E;AAC9E,6CAA6C;AAC7C,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,IAAI,CAAC;IAC3C,SAAS;IACT,UAAU;IACV,SAAS;IACT,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,WAAW;CACZ,CAAC,CAAC;AAWH;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,yBAAyB,CACvC,UAAiC,EACjC,KAAa;IAEb,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,UAAU,CAAC;IAE7D,6CAA6C;IAC7C,IACE,MAAM,KAAK,SAAS;QACpB,MAAM,KAAK,UAAU;QACrB,MAAM,KAAK,WAAW;QACtB,MAAM,KAAK,SAAS;QACpB,MAAM,KAAK,SAAS,EACpB,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;;;;;;;;OASG;IACH,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;QACxB,IAAI,CAAC,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAC/B,MAAM,UAAU,GAAG,CAAC,UAAU,IAAI,KAAK,IAAI,UAAU,CAAC;QACtD,IAAI,UAAU,IAAI,KAAK,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;IACvD,CAAC;IAED,OAAO,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;AACjD,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,gBAAgB,CAAC,UAAiC,EAAE,KAAa;IAC/E,OAAO,yBAAyB,CAAC,UAAU,EAAE,KAAK,CAAC,KAAK,QAAQ,CAAC;AACnE,CAAC;AAED,+EAA+E;AAE/E;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,EAAE,CAAC;AAElD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,CAAC;AAiB9C;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CACrC,UAAiC,EACjC,KAAa;IAEb,MAAM,SAAS,GAAG,yBAAyB,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAE/D,QAAQ,SAAS,EAAE,CAAC;QAClB,KAAK,SAAS;YACZ,OAAO,SAAS,CAAC;QACnB,KAAK,UAAU;YACb,OAAO,UAAU,CAAC;QACpB,KAAK,QAAQ;YACX,OAAO,QAAQ,CAAC;QAClB,KAAK,SAAS,CAAC;QACf,KAAK,WAAW;YACd,OAAO,SAAS,CAAC;QACnB,OAAO,CAAC,CAAC,CAAC;YACR,wDAAwD;YACxD,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;YACpD,OAAO,IAAI,IAAI,+BAA+B,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC;QACzE,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAA+B;IACjE,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,UAAU,CAAC;AACtD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAA4C;IAC9E,OAAO,EAAE,kBAAkB;IAC3B,QAAQ,EAAE,eAAe;IACzB,MAAM,EAAE,QAAQ;IAChB,QAAQ,EAAE,UAAU;IACpB,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,SAAS;CACnB,CAAC;AAEF,8EAA8E;AAC9E,6CAA6C;AAC7C,8EAA8E;AAE9E,4EAA4E;AAC5E,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC;AA6EnC;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,KAAqB,EAAE,KAAa;IAChE,MAAM,EAAE,gBAAgB,EAAE,aAAa,EAAE,GAAG,KAAK,CAAC;IAClD,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,IAAI,IAAI,CAAC;IAC9C,MAAM,IAAI,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,EAAW,CAAC;IAE7E;;;;OAIG;IACH,IAAI,gBAAgB,KAAK,IAAI,EAAE,CAAC;QAC9B,OAAO,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IACvE,CAAC;IAED;;;;;;;OAOG;IACH,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;QACnC,OAAO,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IACvE,CAAC;IAED,0DAA0D;IAC1D,IAAI,gBAAgB,KAAK,UAAU,EAAE,CAAC;QACpC,OAAO,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACvE,CAAC;IAED;;;;;;OAMG;IACH,IAAI,gBAAgB,KAAK,WAAW,EAAE,CAAC;QACrC,OAAO,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAC1E,CAAC;IAED,gEAAgE;IAChE,IAAI,WAAW,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;QACnC,OAAO;YACL,GAAG,IAAI;YACP,MAAM,EAAE,SAAS;YACjB,SAAS,EAAE,IAAI;YACf,KAAK,EAAE,IAAI;YACX,WAAW;YACX,YAAY,EAAE,aAAa;SAC5B,CAAC;IACJ,CAAC;IAED;;;;;;OAMG;IACH,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,aAAa,EAAE,CAAC;IAClG,CAAC;IAED,MAAM,QAAQ,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAE9E;;;;;;OAMG;IACH,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;QACtC,OAAO,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAC/E,CAAC;IAED,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,IAAI,CAAC,IAAI,QAAQ,IAAI,iBAAiB,EAAE,CAAC;QACxE,OAAO,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjF,CAAC;IAED;;;OAGG;IACH,OAAO,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;AAC/E,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAC3B;IACE,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,GAAG,MAAM,YAAY,MAAM,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,EAAE,KAAK,CAAC,EAAE;IAChF,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,GAAG,MAAM,MAAM;IACvC,QAAQ,EAAE,GAAG,EAAE,CAAC,kBAAkB;IAClC,OAAO,EAAE,GAAG,EAAE,CAAC,iBAAiB;IAChC,MAAM,EAAE,GAAG,EAAE,CAAC,aAAa;IAC3B,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAChB,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,MAAM,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,EAAE,KAAK,CAAC,EAAE;IAC7E,SAAS,EAAE,GAAG,EAAE,CAAC,aAAa;IAC9B,SAAS,EAAE,GAAG,EAAE,CAAC,WAAW;IAC5B,IAAI,EAAE,GAAG,EAAE,CAAC,MAAM;CACnB,CAAC;AAEJ,8EAA8E;AAC9E,iBAAiB;AACjB,8EAA8E;AAE9E;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,EAAU;IAClD,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,gBAAgB,CAAC,CAAC;IAC9C,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;IAC5C,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IACjD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC;AAC1C,CAAC;AAED,+BAA+B;AAC/B,SAAS,MAAM,CAAC,CAAS,EAAE,IAAY;IACrC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC;AACpD,CAAC","sourcesContent":["/**\n * gymmonk-schema — Membership status, all of it, in one place\n * ============================================================\n * Everything the product knows about \"where does this member stand\" lives\n * here: the stored lifecycle, the effective one, the words shown on screen,\n * the money reading, and the three thresholds that decide \"soon\".\n *\n * ── Why this module exists ──────────────────────────────────────────────────\n * These facts were spread across five files in two repos. The stored enum and\n * the effective rule were in `membership.ts`; the DISPLAY vocabulary and its\n * derivation were in the web client's `shared/lib/format.ts`, out of the\n * backend's reach; the labels were inline in a pill component; the fee\n * vocabulary was in an owner-console file and took a web VIEW TYPE as its\n * input, so nothing else could call it. Two of the web's own feature folders\n * had also declared `MembershipStatus` as an alias of the DISPLAY type — the\n * same name as the stored one, meaning something different, in the same\n * codebase.\n *\n * ── There are TWO axes, and collapsing them would lose information ──────────\n * They are correlated, not identical, and they answer different questions:\n *\n * LIFECYCLE can this person train today? (the door asks this)\n * FEES does this person owe money? (the owner's roster asks this)\n *\n * A member part-way through a term they have half-paid is `active` on the\n * first axis and `unpaid` on the second, and gyms take part payments every\n * week. One flat enum would have to pick a side.\n *\n * The axes relate like this, and `readFeeStatus` encodes exactly this table:\n *\n * lifecycle money owed → fee status\n * active no → paid (or due-soon near the end of the term)\n * active yes → unpaid\n * expired yes → overdue\n * expired no → lapsed\n * pending n/a → pending (the GYM owes the next move)\n * none n/a → no-plan (the OWNER owes the next move)\n *\n * ── What is NOT here ────────────────────────────────────────────────────────\n * Colours. A tone is a Tailwind class, it belongs to whichever client is\n * painting, and there is exactly one consumer of each. The words live here\n * because two surfaces must never call the same state different things.\n *\n * @module gymmonk-schema/membership-status\n */\n\nimport { z } from 'zod';\n\n// ═══════════════════════════════════════════════════════════════════════════\n// AXIS 1 — LIFECYCLE: can this member train?\n// ═══════════════════════════════════════════════════════════════════════════\n\n/**\n * The STORED lifecycle column.\n *\n * `pending` is where every membership STARTS, whoever created it. Choosing a\n * plan is not the same event as paying for one, and the two used to be welded\n * together: the moment a member tapped a plan it went live, and the moment an\n * owner assigned one at the desk it went live, whether or not any money had\n * changed hands. A gym then had no way to tell a paid member from an optimistic\n * one, and no lever to withhold access from the second.\n *\n * The only transition out of `pending` is the gym marking the payment done.\n * That is deliberately a HUMAN act by the owner or a manager rather than a\n * gateway callback: these gyms take cash at the desk and UPI transfers on a\n * phone, so the person who knows the money arrived is the person at the gym.\n *\n * `rejected` is the other way out, for a screenshot that shows nothing, the\n * wrong amount, or a transfer that never landed. It is a dead end — the member\n * requests again rather than the gym reopening a closed decision.\n *\n * `invited` predates all of this and nothing writes it. Left in the union\n * because removing a value from a stored enum is not worth a migration, and old\n * rows still have to parse.\n *\n * `expiring` is NOT here — it is derived from `endDate` at the edge. See\n * `DisplayMembershipStatus`.\n */\nexport const membershipStatusSchema = z.enum([\n 'pending',\n 'rejected',\n 'invited',\n 'active',\n 'paused',\n 'expired',\n 'cancelled',\n]);\nexport type MembershipStatus = z.infer<typeof membershipStatusSchema>;\n\n/** The fields any status question needs. Deliberately not a full membership. */\nexport interface MembershipStatusInput {\n status: MembershipStatus;\n endDate: string;\n pausedFrom?: string | null;\n pausedTo?: string | null;\n}\n\n/**\n * What a membership IS right now, as against what was last written to its\n * `status` column.\n *\n * ── Why this has to exist ───────────────────────────────────────────────────\n * Two facts about this system make the stored status untrustworthy on its own:\n *\n * 1. NOTHING sweeps memberships to `expired`. There is no job. A member who\n * stopped paying in March keeps `status: 'active'` in the database all\n * year, so the term's END DATE is the only honest answer.\n * 2. NOTHING resumes a pause. `pausedTo` was written and then never read by\n * any code path, so a two-week pause locked a member out permanently and\n * the only way back was an owner noticing and flipping the roster switch\n * by hand.\n *\n * ── What is NOT re-derived ──────────────────────────────────────────────────\n * `pending`, `rejected`, `cancelled` and `expired` are all decisions a PERSON\n * made, and are returned untouched. `expired` matters most: an owner\n * deactivating a member sets exactly that, and re-deriving it back to `active`\n * because the term still has days left would silently overrule them.\n *\n * `today` is passed in rather than read from the clock, because the answer is\n * the GYM's day — see `gymDayOf` on the server and `istDayOf` on the client.\n * `YYYY-MM-DD` compares lexicographically, so these are exact calendar-day\n * comparisons with no instant arithmetic and no zone hidden inside.\n */\nexport function effectiveMembershipStatus(\n membership: MembershipStatusInput,\n today: string,\n): MembershipStatus {\n const { status, endDate, pausedFrom, pausedTo } = membership;\n\n // Decided by a person. Never second-guessed.\n if (\n status === 'pending' ||\n status === 'rejected' ||\n status === 'cancelled' ||\n status === 'expired' ||\n status === 'invited'\n ) {\n return status;\n }\n\n /**\n * A pause is a WINDOW, not a switch somebody has to remember to turn off.\n *\n * Reading it as a window is what makes the resume happen without a scheduled\n * job: before it, the plan runs; inside it, the member is out; after it, they\n * are simply back, on the end date the pause already pushed out for them.\n * A pause with no end recorded is treated as still running, because the safe\n * failure for a missing bound is to keep the member out rather than to let\n * an unbounded pause silently expire.\n */\n if (status === 'paused') {\n if (!pausedTo) return 'paused';\n const startedYet = !pausedFrom || today >= pausedFrom;\n if (startedYet && today <= pausedTo) return 'paused';\n }\n\n return endDate >= today ? 'active' : 'expired';\n}\n\n/** Can this member train today? The one question the door asks. */\nexport function isMembershipLive(membership: MembershipStatusInput, today: string): boolean {\n return effectiveMembershipStatus(membership, today) === 'active';\n}\n\n// ─── Lifecycle, as the UI says it ───────────────────────────────────────────\n\n/**\n * How close to `endDate` a live membership starts reading as \"expiring\".\n *\n * A PRESENTATION band and nothing more: the door never sees `expiring`, and a\n * member inside this window is fully `active` as far as access is concerned.\n * Distinct from `FEE_DUE_SOON_DAYS`, which is a much tighter window because it\n * gates a payment reminder rather than a colour.\n */\nexport const MEMBERSHIP_EXPIRING_WINDOW_DAYS = 30;\n\n/**\n * How far ahead the owner's home ALERT looks for memberships about to lapse.\n *\n * Deliberately much tighter than the pill's band, and the reason all three\n * windows are declared together here rather than as a `const` in whichever\n * file happened to need one:\n *\n * MEMBERSHIP_EXPIRING_WINDOW_DAYS 30 paints a pill amber; the roster's\n * \"Expiring Soon\" segment and filter\n * MEMBERSHIP_EXPIRY_ALERT_DAYS 7 the one-line banner on owner home,\n * which says \"expiring this week\"\n * FEE_DUE_SOON_DAYS 3 arms a payment reminder\n *\n * They are different NUMBERS because they drive different actions — a colour,\n * a banner and a message to a member should not all fire at once — but they\n * are the same QUESTION, so they belong side by side where the next person to\n * change one can see the other two.\n */\nexport const MEMBERSHIP_EXPIRY_ALERT_DAYS = 7;\n\n/**\n * The lifecycle states a SCREEN distinguishes.\n *\n * `cancelled` and `invited` are absent on purpose — both read as `expired` to\n * a viewer, and a pill that said \"Cancelled\" next to one that said \"Expired\"\n * would imply a difference the member cannot act on differently.\n */\nexport type DisplayMembershipStatus =\n | 'pending'\n | 'rejected'\n | 'active'\n | 'expiring'\n | 'paused'\n | 'expired';\n\n/**\n * The lifecycle, narrowed onto what a screen shows.\n *\n * Adds exactly one thing over `effectiveMembershipStatus`: the `expiring`\n * warning band. Everything else is a straight mapping, so a screen and the\n * door can never disagree about whether somebody is live — only about how\n * loudly to say it.\n */\nexport function displayMembershipStatus(\n membership: MembershipStatusInput,\n today: string,\n): DisplayMembershipStatus {\n const effective = effectiveMembershipStatus(membership, today);\n\n switch (effective) {\n case 'pending':\n return 'pending';\n case 'rejected':\n return 'rejected';\n case 'paused':\n return 'paused';\n case 'expired':\n case 'cancelled':\n return 'expired';\n default: {\n // Live. The only question left is how close the end is.\n const left = daysBetween(today, membership.endDate);\n return left <= MEMBERSHIP_EXPIRING_WINDOW_DAYS ? 'expiring' : 'active';\n }\n }\n}\n\n/**\n * Is this display state one where the member can still train?\n *\n * `expiring` is LIVE. It is a warning band, not a lifecycle state — the term\n * has days left and the door lets them in — so anything filtering or counting\n * \"active members\" has to include it. Written down here because that is\n * exactly the kind of rule each list would otherwise re-derive slightly\n * differently, and one of them would forget `expiring` and quietly hide every\n * member in their last month.\n */\nexport function isLiveDisplayStatus(status: DisplayMembershipStatus): boolean {\n return status === 'active' || status === 'expiring';\n}\n\n/**\n * The words on the lifecycle pill.\n *\n * Here rather than in the component because two surfaces calling the same\n * state different things is the failure this module exists to prevent.\n * \"Awaiting payment\" is deliberately not \"Pending\": pending is what the\n * database calls it, and the member wants to know who they are waiting on.\n */\nexport const MEMBERSHIP_STATUS_LABEL: Record<DisplayMembershipStatus, string> = {\n pending: 'Awaiting payment',\n rejected: 'Not confirmed',\n active: 'Active',\n expiring: 'Expiring',\n paused: 'Paused',\n expired: 'Expired',\n};\n\n// ═══════════════════════════════════════════════════════════════════════════\n// AXIS 2 — FEES: does this member owe money?\n// ═══════════════════════════════════════════════════════════════════════════\n\n/** How close to the end of a term a member starts reading as \"due soon\". */\nexport const FEE_DUE_SOON_DAYS = 3;\n\n/**\n * Where a member stands on money, as one phrase an owner can scan down a list.\n *\n * Every state exists because it needs a DIFFERENT action. This began life as\n * \"paid or not\", which collapsed five unrelated situations into one green\n * chip: a member with no plan, a member whose payment was waiting on the GYM,\n * and a member who had genuinely settled all read \"Paid\", so the roster asked\n * the owner to do nothing about two people who needed something done.\n *\n * overdue money owed, term already ended → chase them\n * unpaid money owed, term still running → chase them\n * rejected the gym turned their payment down → talk to them\n * pending they have paid, the GYM must confirm → owner confirms\n * lapsed term ended, nothing sold to replace → chase a renewal\n * due-soon ends within a few days → chase a renewal\n * no-plan never been sold a membership → owner assigns one\n * cancelled they ended it and left → nothing to collect\n * paid settled, with time left on the term → nothing\n */\nexport type FeeStatus =\n | 'overdue'\n | 'unpaid'\n | 'rejected'\n | 'pending'\n | 'lapsed'\n | 'due-soon'\n | 'no-plan'\n | 'cancelled'\n | 'paid';\n\n/**\n * What the fee question needs, as plain facts.\n *\n * Deliberately NOT a view model. This used to take the owner console's roster\n * card type, which is why nothing else in the product could ask the question —\n * including the backend, which computes two of these fields in the first place.\n */\nexport interface FeeStatusInput {\n /** The stored lifecycle, or null when no membership was ever sold. */\n membershipStatus: MembershipStatus | null;\n /** The server's own judgement: money outstanding AND the term already ended. */\n overdueDays?: number | null;\n /** Still owed on the current term, in whole rupees. */\n balanceDueInr: number;\n /** End of the current term, `YYYY-MM-DD`, or null when there is none. */\n expiresOn?: string | null;\n}\n\nexport interface FeeReading {\n status: FeeStatus;\n /**\n * Does this need the owner's eye? Drives the chip's colour.\n *\n * Deliberately NOT the same question as `chase`. A member waiting on the gym\n * to confirm their payment needs attention and must not be sent a reminder —\n * they have already paid, and the outstanding job is the owner's.\n */\n attention: boolean;\n /**\n * Is there something to remind this member ABOUT?\n *\n * Gates the reminder action. False wherever the next move belongs to the gym\n * rather than the member, which is what stops an owner messaging somebody\n * \"your fees are overdue\" while their payment sits in the gym's own\n * confirmation queue.\n */\n chase: boolean;\n /** Whole days until the term ends; negative once past, null when no term. */\n daysLeft: number | null;\n /** Days past the end of an unpaid term, when that is what happened. */\n overdueDays: number | null;\n /** Still owed, in whole rupees. Zero unless the status says otherwise. */\n amountDueInr: number;\n}\n\n/**\n * The fee reading for one member.\n *\n * Ordered by whose move it is and how hard the fact is. `overdueDays` is the\n * server's own judgement — money outstanding AND the term already ended — so\n * it is trusted rather than recomputed here.\n */\nexport function readFeeStatus(input: FeeStatusInput, today: string): FeeReading {\n const { membershipStatus, balanceDueInr } = input;\n const overdueDays = input.overdueDays ?? null;\n const base = { daysLeft: null, overdueDays: null, amountDueInr: 0 } as const;\n\n /**\n * Never sold a membership. Checked FIRST, because every money question below\n * presumes one exists — and a member with no plan cannot be behind on a fee\n * nobody has charged them.\n */\n if (membershipStatus === null) {\n return { ...base, status: 'no-plan', attention: true, chase: false };\n }\n\n /**\n * Waiting on the GYM, not on the member.\n *\n * Ahead of the balance check on purpose: a pending membership carries its\n * full price as outstanding, so this member would otherwise read as owing\n * money and be sent a payment reminder — for money they have already handed\n * over and the gym has simply not confirmed yet.\n */\n if (membershipStatus === 'pending') {\n return { ...base, status: 'pending', attention: true, chase: false };\n }\n\n /** The gym looked at their payment and turned it down. */\n if (membershipStatus === 'rejected') {\n return { ...base, status: 'rejected', attention: true, chase: true };\n }\n\n /**\n * They ended it themselves and left.\n *\n * Checked before the date logic, which would otherwise see a term end in the\n * past and read them as \"Renewal due\" — with a reminder ready to tell\n * somebody who deliberately quit that their membership has expired.\n */\n if (membershipStatus === 'cancelled') {\n return { ...base, status: 'cancelled', attention: false, chase: false };\n }\n\n // Late: the server has already established both halves of that.\n if (overdueDays && overdueDays > 0) {\n return {\n ...base,\n status: 'overdue',\n attention: true,\n chase: true,\n overdueDays,\n amountDueInr: balanceDueInr,\n };\n }\n\n /**\n * Owes money, but the term is still running.\n *\n * A real state and NOT the same as overdue: a gym that takes half now and\n * half on Friday has members here every week, and calling them late would be\n * wrong.\n */\n if (balanceDueInr > 0) {\n return { ...base, status: 'unpaid', attention: true, chase: true, amountDueInr: balanceDueInr };\n }\n\n const daysLeft = input.expiresOn ? daysBetween(today, input.expiresOn) : null;\n\n /**\n * The term ran out and nothing was sold to replace it.\n *\n * Nothing is OUTSTANDING — a term nobody assigned was never invoiced — so\n * this used to fall through and read \"Paid\", in green, for the member most\n * worth chasing on the whole roster.\n */\n if (daysLeft !== null && daysLeft < 0) {\n return { ...base, status: 'lapsed', attention: true, chase: true, daysLeft };\n }\n\n if (daysLeft !== null && daysLeft >= 0 && daysLeft <= FEE_DUE_SOON_DAYS) {\n return { ...base, status: 'due-soon', attention: true, chase: true, daysLeft };\n }\n\n /**\n * Settled, with time left on the term. The only genuinely quiet state, and\n * the only one that earns a green chip.\n */\n return { ...base, status: 'paid', attention: false, chase: false, daysLeft };\n}\n\n/**\n * The words on the fee chip.\n *\n * Functions rather than plain strings because three of these states have to\n * name a number, and the AMOUNT is formatted by whoever is rendering — a\n * client knows how to write ₹1,200 for its locale and this package does not.\n * The wording still lives here, so no surface can invent its own phrasing for\n * a state another surface already names.\n */\nexport const FEE_STATUS_LABEL: Record<FeeStatus, (reading: FeeReading, amount: string) => string> =\n {\n overdue: (r, amount) => `${amount} overdue ${plural(r.overdueDays ?? 0, 'day')}`,\n unpaid: (_r, amount) => `${amount} due`,\n rejected: () => 'Payment declined',\n pending: () => 'Confirm payment',\n lapsed: () => 'Renewal due',\n 'due-soon': (r) =>\n r.daysLeft === 0 ? 'Due today' : `Due in ${plural(r.daysLeft ?? 0, 'day')}`,\n 'no-plan': () => 'No plan yet',\n cancelled: () => 'Cancelled',\n paid: () => 'Paid',\n };\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Shared helpers\n// ═══════════════════════════════════════════════════════════════════════════\n\n/**\n * Whole days from `from` to `to`, both `YYYY-MM-DD`.\n *\n * Anchored at UTC midnight on BOTH sides, so the subtraction is whole days and\n * carries no zone of its own — the caller has already decided which day\n * \"today\" is, in the gym's timezone, and this must not second-guess that.\n */\nexport function daysBetween(from: string, to: string): number {\n const a = Date.parse(`${from}T00:00:00.000Z`);\n const b = Date.parse(`${to}T00:00:00.000Z`);\n if (Number.isNaN(a) || Number.isNaN(b)) return 0;\n return Math.round((b - a) / 86_400_000);\n}\n\n/** `2, 'day'` → `\"2 days\"`. */\nfunction plural(n: number, word: string): string {\n return n === 1 ? `${n} ${word}` : `${n} ${word}s`;\n}\n"]}
@@ -12,30 +12,6 @@
12
12
  */
13
13
  import { z } from 'zod';
14
14
  export { effectiveMembershipStatus, isMembershipLive, type MembershipStatus, membershipStatusSchema, } from './membership-status.js';
15
- /**
16
- * Membership lifecycle.
17
- *
18
- * `pending` is where every membership STARTS, whoever created it. Choosing a
19
- * plan is not the same event as paying for one, and until this release the two
20
- * were welded together: the moment a member tapped a plan it went live, and the
21
- * moment an owner assigned one at the desk it went live, whether or not any
22
- * money had changed hands. A gym then had no way to tell a paid member from an
23
- * optimistic one, and no lever to withhold access from the second.
24
- *
25
- * The only transition out of `pending` is the gym marking the payment done.
26
- * That is deliberately a HUMAN act by the owner or a manager rather than a
27
- * gateway callback: these gyms take cash at the desk and UPI transfers on a
28
- * phone, so the person who knows the money arrived is the person at the gym.
29
- *
30
- * `rejected` is the other way out, for a screenshot that shows nothing, the
31
- * wrong amount, or a transfer that never landed. It is a dead end — the
32
- * member requests again rather than the gym reopening a closed decision.
33
- *
34
- * `invited` predates all of this and nothing writes it. Left in place because
35
- * removing a value from a stored enum is not worth a migration.
36
- *
37
- * `expiring` is NOT stored — it is derived from `endDate` at the edge.
38
- */
39
15
  export declare const paymentMethodSchema: z.ZodEnum<{
40
16
  upi: "upi";
41
17
  cash: "cash";
@@ -1 +1 @@
1
- {"version":3,"file":"membership.d.ts","sourceRoot":"","sources":["../src/membership.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,OAAO,EACL,yBAAyB,EACzB,gBAAgB,EAChB,KAAK,gBAAgB,EACrB,sBAAsB,GACvB,MAAM,wBAAwB,CAAC;AAahC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,eAAO,MAAM,mBAAmB;;;;EAAkC,CAAC;AACnE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,eAAO,MAAM,mBAAmB;;;;EAA2C,CAAC;AAC5E,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;iBAQxB,CAAC;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAIpD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAoE3B,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAI1D;;;GAGG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;iBAiBrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAI9E;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kCAAkC;;;;;;;;;iBAiB7C,CAAC;AACH,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kCAAkC,CAAC,CAAC;AAE9F;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oBAAoB;;;;iBAI/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAQrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E,6DAA6D;AAC7D,eAAO,MAAM,iCAAiC;;iBAE5C,CAAC;AACH,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iCAAiC,CAAC,CAAC;AAI5F,eAAO,MAAM,mBAAmB;;;;;EAAuC,CAAC;AACxE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,eAAO,MAAM,yBAAyB;;;;;;;;;iBASlC,CAAC;AACL,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAI5E,8EAA8E;AAC9E,eAAO,MAAM,0BAA0B;;;iBAGrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAI9E,eAAO,MAAM,wBAAwB;;;;;;;;iBAInC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
1
+ {"version":3,"file":"membership.d.ts","sourceRoot":"","sources":["../src/membership.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,OAAO,EACL,yBAAyB,EACzB,gBAAgB,EAChB,KAAK,gBAAgB,EACrB,sBAAsB,GACvB,MAAM,wBAAwB,CAAC;AAahC,eAAO,MAAM,mBAAmB;;;;EAAkC,CAAC;AACnE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,eAAO,MAAM,mBAAmB;;;;EAA2C,CAAC;AAC5E,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;iBAQxB,CAAC;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAIpD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAoE3B,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAI1D;;;GAGG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;iBAiBrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAI9E;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kCAAkC;;;;;;;;;iBAiB7C,CAAC;AACH,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kCAAkC,CAAC,CAAC;AAE9F;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oBAAoB;;;;iBAI/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAQrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E,6DAA6D;AAC7D,eAAO,MAAM,iCAAiC;;iBAE5C,CAAC;AACH,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iCAAiC,CAAC,CAAC;AAI5F,eAAO,MAAM,mBAAmB;;;;;EAAuC,CAAC;AACxE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,eAAO,MAAM,yBAAyB;;;;;;;;;iBASlC,CAAC;AACL,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAI5E,8EAA8E;AAC9E,eAAO,MAAM,0BAA0B;;;iBAGrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAI9E,eAAO,MAAM,wBAAwB;;;;;;;;iBAInC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
@@ -20,31 +20,6 @@ import { z } from 'zod';
20
20
  export { effectiveMembershipStatus, isMembershipLive, membershipStatusSchema, } from './membership-status.js';
21
21
  import { isoDateSchema, isoDateTimeSchema, objectIdSchema, optionalTrimmedString, priceInrSchema, } from './common.js';
22
22
  import { membershipStatusSchema } from './membership-status.js';
23
- // ─── Status ──────────────────────────────────────────────────────────────────
24
- /**
25
- * Membership lifecycle.
26
- *
27
- * `pending` is where every membership STARTS, whoever created it. Choosing a
28
- * plan is not the same event as paying for one, and until this release the two
29
- * were welded together: the moment a member tapped a plan it went live, and the
30
- * moment an owner assigned one at the desk it went live, whether or not any
31
- * money had changed hands. A gym then had no way to tell a paid member from an
32
- * optimistic one, and no lever to withhold access from the second.
33
- *
34
- * The only transition out of `pending` is the gym marking the payment done.
35
- * That is deliberately a HUMAN act by the owner or a manager rather than a
36
- * gateway callback: these gyms take cash at the desk and UPI transfers on a
37
- * phone, so the person who knows the money arrived is the person at the gym.
38
- *
39
- * `rejected` is the other way out, for a screenshot that shows nothing, the
40
- * wrong amount, or a transfer that never landed. It is a dead end — the
41
- * member requests again rather than the gym reopening a closed decision.
42
- *
43
- * `invited` predates all of this and nothing writes it. Left in place because
44
- * removing a value from a stored enum is not worth a migration.
45
- *
46
- * `expiring` is NOT stored — it is derived from `endDate` at the edge.
47
- */
48
23
  // ─── Payments ────────────────────────────────────────────────────────────────
49
24
  export const paymentMethodSchema = z.enum(['upi', 'cash', 'card']);
50
25
  export const paymentStatusSchema = z.enum(['success', 'failed', 'pending']);
@@ -1 +1 @@
1
- {"version":3,"file":"membership.js","sourceRoot":"","sources":["../src/membership.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Membership & payments\n * =======================================\n * A member's live membership at a center: which plan/tenure, its lifecycle\n * status and dates, pause windows, and the payment ledger. v1 is single-center,\n * so one active membership per user.\n *\n * The \"expiring\" state the UI shows is DERIVED from `endDate` at the edge; the\n * stored `status` is the true lifecycle state.\n *\n * @module gymmonk-schema/membership\n */\n\nimport { z } from 'zod';\n\n/*\n * Membership STATUS moved to its own module — see `membership-status.ts` for\n * why. Re-exported here so the many call sites that reach for the stored enum\n * alongside the membership DTO do not all have to change import paths, and so\n * there is still exactly one definition behind both spellings.\n */\nexport {\n effectiveMembershipStatus,\n isMembershipLive,\n type MembershipStatus,\n membershipStatusSchema,\n} from './membership-status.js';\n\nimport {\n isoDateSchema,\n isoDateTimeSchema,\n objectIdSchema,\n optionalTrimmedString,\n priceInrSchema,\n} from './common.js';\nimport { membershipStatusSchema } from './membership-status.js';\n\n// ─── Status ──────────────────────────────────────────────────────────────────\n\n/**\n * Membership lifecycle.\n *\n * `pending` is where every membership STARTS, whoever created it. Choosing a\n * plan is not the same event as paying for one, and until this release the two\n * were welded together: the moment a member tapped a plan it went live, and the\n * moment an owner assigned one at the desk it went live, whether or not any\n * money had changed hands. A gym then had no way to tell a paid member from an\n * optimistic one, and no lever to withhold access from the second.\n *\n * The only transition out of `pending` is the gym marking the payment done.\n * That is deliberately a HUMAN act by the owner or a manager rather than a\n * gateway callback: these gyms take cash at the desk and UPI transfers on a\n * phone, so the person who knows the money arrived is the person at the gym.\n *\n * `rejected` is the other way out, for a screenshot that shows nothing, the\n * wrong amount, or a transfer that never landed. It is a dead end — the\n * member requests again rather than the gym reopening a closed decision.\n *\n * `invited` predates all of this and nothing writes it. Left in place because\n * removing a value from a stored enum is not worth a migration.\n *\n * `expiring` is NOT stored — it is derived from `endDate` at the edge.\n */\n\n// ─── Payments ────────────────────────────────────────────────────────────────\n\nexport const paymentMethodSchema = z.enum(['upi', 'cash', 'card']);\nexport type PaymentMethod = z.infer<typeof paymentMethodSchema>;\n\nexport const paymentStatusSchema = z.enum(['success', 'failed', 'pending']);\nexport type PaymentStatus = z.infer<typeof paymentStatusSchema>;\n\nexport const paymentSchema = z.object({\n id: objectIdSchema,\n membershipId: objectIdSchema,\n amountInr: priceInrSchema,\n method: paymentMethodSchema,\n status: paymentStatusSchema,\n paidAt: isoDateTimeSchema,\n note: z.string().nullable(),\n});\nexport type Payment = z.infer<typeof paymentSchema>;\n\n// ─── Entity (API response) ───────────────────────────────────────────────────\n\nexport const membershipSchema = z.object({\n id: objectIdSchema,\n /**\n * The enrolment this plan was sold against.\n *\n * The link that makes a plan belong to a stint at a gym rather than to a\n * person. It replaces a `(centerId, memberCode)` pair that had to be matched\n * by hand in three places, and it is what lets a plan be sold to somebody the\n * desk has only just written down — an enrolment exists from that moment,\n * whereas an account may not exist for weeks.\n */\n enrolmentId: objectIdSchema,\n /**\n * The account, denormalised from the enrolment for member-facing queries.\n * NULL until they claim, for the same reason it is null on the enrolment.\n */\n userId: objectIdSchema.nullable(),\n centerId: objectIdSchema,\n planId: objectIdSchema,\n planName: z.string(),\n /**\n * Which variant of the plan was bought, and how it read at the time.\n *\n * A plain string, not a fixed tenure enum: variant ids are minted per plan\n * now, so a gym can sell a 10-day pass. The LABEL is a snapshot for the same\n * reason a payment records what it was for — the owner may rename or delete\n * the variant later, and a membership must still be able to say what was\n * sold.\n */\n variantId: z.string(),\n variantLabel: z.string(),\n priceInr: priceInrSchema,\n status: membershipStatusSchema,\n /**\n * The term, PROVISIONAL while the membership is pending.\n *\n * Dated from the member's JOINING DATE, not from the day either party\n * pressed a button. Terms are a cycle — somebody who joined on the 4th runs\n * 4 Jul→4 Aug, 4 Aug→4 Sep — so a gym confirming a week late records the\n * month that was owed rather than starting a fresh one from today. Dating\n * them from the confirmation, which is what this used to do, walked every\n * member's renewal day forward by however long the desk took to look.\n *\n * Written at request time so every screen that reads them keeps working, and\n * recomputed on confirmation in case the cycle moved in between.\n */\n startDate: isoDateSchema,\n endDate: isoDateSchema,\n pausedFrom: isoDateSchema.nullable(),\n pausedTo: isoDateSchema.nullable(),\n /**\n * The screenshot the member attached, if they attached one.\n *\n * Optional on purpose. A member paying cash at the desk has nothing to\n * screenshot, and refusing to accept their request until they produce one\n * would lock the commonest case out of the flow entirely.\n */\n paymentProofUrl: z.string().nullable(),\n /** When the plan was chosen. Distinct from `createdAt` only in intent. */\n requestedAt: isoDateTimeSchema,\n /** When the gym marked the payment done or turned it down. */\n decidedAt: isoDateTimeSchema.nullable(),\n /** Who decided. Null while pending. */\n decidedBy: objectIdSchema.nullable(),\n /** Why it was turned down, in the gym's words. */\n decisionNote: z.string().nullable(),\n createdAt: isoDateTimeSchema,\n updatedAt: isoDateTimeSchema,\n});\nexport type Membership = z.infer<typeof membershipSchema>;\n\n// ─── Assign / renew / upgrade ────────────────────────────────────────────────\n\n/**\n * Assign a plan to a member (new membership, renewal, or upgrade). Records the\n * charge in one shot; omit `payment` for an unpaid/invited assignment.\n */\nexport const assignMembershipBodySchema = z.object({\n planId: objectIdSchema,\n variantId: z.string().trim().min(1),\n /** Explicit start; defaults to today server-side when omitted. */\n startDate: isoDateSchema.optional(),\n /**\n * A screenshot of the transfer, uploaded to storage first and passed as its\n * URL. Optional — see `paymentProofUrl` on the entity.\n */\n paymentProofUrl: z.string().trim().max(2000).optional(),\n payment: z\n .object({\n amountInr: priceInrSchema,\n method: paymentMethodSchema,\n status: paymentStatusSchema.default('success'),\n })\n .optional(),\n});\nexport type AssignMembershipBody = z.infer<typeof assignMembershipBodySchema>;\n\n// ——— Confirm / reject the payment ————————————————————————\n\n/**\n * The gym marking a pending membership's payment as done, which is the single\n * act that activates it.\n *\n * It carries the LEDGER entry rather than being a bare approval, because those\n * are the same event: the owner is asserting that this much money arrived by\n * this route. Splitting them would let a membership go live with nothing\n * written down, which is exactly the hole this feature exists to close.\n *\n * The amount is not forced to equal the plan price. A gym that takes half now\n * and half on Friday still wants the member training, and the balance is\n * already tracked against the term.\n */\nexport const confirmMembershipPaymentBodySchema = z.object({\n amountInr: priceInrSchema,\n method: paymentMethodSchema,\n note: optionalTrimmedString(240),\n /**\n * The term the owner BELIEVES they are paying for, echoed back from what the\n * screen showed them.\n *\n * The server derives the term itself and never trusts this as input — it is\n * a guard, not a parameter. The owner is told \"this covers 3 Jul to 3 Aug\"\n * and then presses a button; if a second manager confirms a term in the\n * seconds between, the derived term moves on and this one no longer matches.\n * Without the echo that owner silently pays for a DIFFERENT month than the\n * one they read, and the gym's ledger disagrees with the conversation that\n * produced it. Optional so a client that shows no term still works.\n */\n expectedTermStart: isoDateSchema.optional(),\n});\nexport type ConfirmMembershipPaymentBody = z.infer<typeof confirmMembershipPaymentBodySchema>;\n\n/**\n * One billing term, dated from the member's JOINING DATE.\n *\n * Terms are a derived cycle, not free-form dates the desk types in: a member\n * who joined on the 4th is billed 4 Jul→4 Aug, 4 Aug→4 Sep, and so on, each\n * one the length of the variant they bought. Nobody picks a start date,\n * because every gym already answers \"which month is this for?\" the same way,\n * and letting two people type it produces two answers.\n *\n * `isOverdue` is the term having already ended — the owner marking a month\n * they forgot at the time. That is a normal, expected act rather than an\n * error, so it is surfaced as a fact about the term rather than a warning.\n */\nexport const membershipTermSchema = z.object({\n startDate: isoDateSchema,\n endDate: isoDateSchema,\n isOverdue: z.boolean(),\n});\nexport type MembershipTerm = z.infer<typeof membershipTermSchema>;\n\n/**\n * One row of the gym's confirmation queue.\n *\n * The membership plus just enough of the person to act on it. The owner is\n * deciding about a HUMAN — \"did Ravi's transfer land\" — and a list of plan\n * names and amounts with no names attached is not something anybody can\n * answer. Kept to four fields rather than embedding the whole roster row,\n * because this list is read on every visit to the members screen.\n */\nexport const pendingMembershipRowSchema = z.object({\n membership: membershipSchema,\n member: z.object({\n enrolmentId: objectIdSchema,\n gymMemberCode: z.string(),\n name: z.string(),\n avatarUrl: z.string().nullable(),\n }),\n});\nexport type PendingMembershipRow = z.infer<typeof pendingMembershipRowSchema>;\n\n/** Turning a request down. The reason reaches the member. */\nexport const rejectMembershipPaymentBodySchema = z.object({\n reason: optionalTrimmedString(240),\n});\nexport type RejectMembershipPaymentBody = z.infer<typeof rejectMembershipPaymentBodySchema>;\n\n// ─── Pause ───────────────────────────────────────────────────────────────────\n\nexport const pauseDurationSchema = z.enum(['1w', '2w', '1m', 'custom']);\nexport type PauseDuration = z.infer<typeof pauseDurationSchema>;\n\nexport const pauseMembershipBodySchema = z\n .object({\n duration: pauseDurationSchema,\n customFrom: isoDateSchema.optional(),\n customTo: isoDateSchema.optional(),\n })\n .refine((v) => v.duration !== 'custom' || (v.customFrom && v.customTo), {\n message: 'Pick both pause dates',\n path: ['customTo'],\n });\nexport type PauseMembershipBody = z.infer<typeof pauseMembershipBodySchema>;\n\n// ─── Cancel ──────────────────────────────────────────────────────────────────\n\n/** Cancellation requires echoing the member code as a safety confirmation. */\nexport const cancelMembershipBodySchema = z.object({\n confirmationCode: z.string().trim().min(1),\n reason: optionalTrimmedString(240),\n});\nexport type CancelMembershipBody = z.infer<typeof cancelMembershipBodySchema>;\n\n// ─── Collect an offline payment ──────────────────────────────────────────────\n\nexport const collectPaymentBodySchema = z.object({\n amountInr: priceInrSchema,\n method: paymentMethodSchema,\n note: optionalTrimmedString(240),\n});\nexport type CollectPaymentBody = z.infer<typeof collectPaymentBodySchema>;\n"]}
1
+ {"version":3,"file":"membership.js","sourceRoot":"","sources":["../src/membership.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Membership & payments\n * =======================================\n * A member's live membership at a center: which plan/tenure, its lifecycle\n * status and dates, pause windows, and the payment ledger. v1 is single-center,\n * so one active membership per user.\n *\n * The \"expiring\" state the UI shows is DERIVED from `endDate` at the edge; the\n * stored `status` is the true lifecycle state.\n *\n * @module gymmonk-schema/membership\n */\n\nimport { z } from 'zod';\n\n/*\n * Membership STATUS moved to its own module — see `membership-status.ts` for\n * why. Re-exported here so the many call sites that reach for the stored enum\n * alongside the membership DTO do not all have to change import paths, and so\n * there is still exactly one definition behind both spellings.\n */\nexport {\n effectiveMembershipStatus,\n isMembershipLive,\n type MembershipStatus,\n membershipStatusSchema,\n} from './membership-status.js';\n\nimport {\n isoDateSchema,\n isoDateTimeSchema,\n objectIdSchema,\n optionalTrimmedString,\n priceInrSchema,\n} from './common.js';\nimport { membershipStatusSchema } from './membership-status.js';\n\n// ─── Payments ────────────────────────────────────────────────────────────────\n\nexport const paymentMethodSchema = z.enum(['upi', 'cash', 'card']);\nexport type PaymentMethod = z.infer<typeof paymentMethodSchema>;\n\nexport const paymentStatusSchema = z.enum(['success', 'failed', 'pending']);\nexport type PaymentStatus = z.infer<typeof paymentStatusSchema>;\n\nexport const paymentSchema = z.object({\n id: objectIdSchema,\n membershipId: objectIdSchema,\n amountInr: priceInrSchema,\n method: paymentMethodSchema,\n status: paymentStatusSchema,\n paidAt: isoDateTimeSchema,\n note: z.string().nullable(),\n});\nexport type Payment = z.infer<typeof paymentSchema>;\n\n// ─── Entity (API response) ───────────────────────────────────────────────────\n\nexport const membershipSchema = z.object({\n id: objectIdSchema,\n /**\n * The enrolment this plan was sold against.\n *\n * The link that makes a plan belong to a stint at a gym rather than to a\n * person. It replaces a `(centerId, memberCode)` pair that had to be matched\n * by hand in three places, and it is what lets a plan be sold to somebody the\n * desk has only just written down — an enrolment exists from that moment,\n * whereas an account may not exist for weeks.\n */\n enrolmentId: objectIdSchema,\n /**\n * The account, denormalised from the enrolment for member-facing queries.\n * NULL until they claim, for the same reason it is null on the enrolment.\n */\n userId: objectIdSchema.nullable(),\n centerId: objectIdSchema,\n planId: objectIdSchema,\n planName: z.string(),\n /**\n * Which variant of the plan was bought, and how it read at the time.\n *\n * A plain string, not a fixed tenure enum: variant ids are minted per plan\n * now, so a gym can sell a 10-day pass. The LABEL is a snapshot for the same\n * reason a payment records what it was for — the owner may rename or delete\n * the variant later, and a membership must still be able to say what was\n * sold.\n */\n variantId: z.string(),\n variantLabel: z.string(),\n priceInr: priceInrSchema,\n status: membershipStatusSchema,\n /**\n * The term, PROVISIONAL while the membership is pending.\n *\n * Dated from the member's JOINING DATE, not from the day either party\n * pressed a button. Terms are a cycle — somebody who joined on the 4th runs\n * 4 Jul→4 Aug, 4 Aug→4 Sep — so a gym confirming a week late records the\n * month that was owed rather than starting a fresh one from today. Dating\n * them from the confirmation, which is what this used to do, walked every\n * member's renewal day forward by however long the desk took to look.\n *\n * Written at request time so every screen that reads them keeps working, and\n * recomputed on confirmation in case the cycle moved in between.\n */\n startDate: isoDateSchema,\n endDate: isoDateSchema,\n pausedFrom: isoDateSchema.nullable(),\n pausedTo: isoDateSchema.nullable(),\n /**\n * The screenshot the member attached, if they attached one.\n *\n * Optional on purpose. A member paying cash at the desk has nothing to\n * screenshot, and refusing to accept their request until they produce one\n * would lock the commonest case out of the flow entirely.\n */\n paymentProofUrl: z.string().nullable(),\n /** When the plan was chosen. Distinct from `createdAt` only in intent. */\n requestedAt: isoDateTimeSchema,\n /** When the gym marked the payment done or turned it down. */\n decidedAt: isoDateTimeSchema.nullable(),\n /** Who decided. Null while pending. */\n decidedBy: objectIdSchema.nullable(),\n /** Why it was turned down, in the gym's words. */\n decisionNote: z.string().nullable(),\n createdAt: isoDateTimeSchema,\n updatedAt: isoDateTimeSchema,\n});\nexport type Membership = z.infer<typeof membershipSchema>;\n\n// ─── Assign / renew / upgrade ────────────────────────────────────────────────\n\n/**\n * Assign a plan to a member (new membership, renewal, or upgrade). Records the\n * charge in one shot; omit `payment` for an unpaid/invited assignment.\n */\nexport const assignMembershipBodySchema = z.object({\n planId: objectIdSchema,\n variantId: z.string().trim().min(1),\n /** Explicit start; defaults to today server-side when omitted. */\n startDate: isoDateSchema.optional(),\n /**\n * A screenshot of the transfer, uploaded to storage first and passed as its\n * URL. Optional — see `paymentProofUrl` on the entity.\n */\n paymentProofUrl: z.string().trim().max(2000).optional(),\n payment: z\n .object({\n amountInr: priceInrSchema,\n method: paymentMethodSchema,\n status: paymentStatusSchema.default('success'),\n })\n .optional(),\n});\nexport type AssignMembershipBody = z.infer<typeof assignMembershipBodySchema>;\n\n// ——— Confirm / reject the payment ————————————————————————\n\n/**\n * The gym marking a pending membership's payment as done, which is the single\n * act that activates it.\n *\n * It carries the LEDGER entry rather than being a bare approval, because those\n * are the same event: the owner is asserting that this much money arrived by\n * this route. Splitting them would let a membership go live with nothing\n * written down, which is exactly the hole this feature exists to close.\n *\n * The amount is not forced to equal the plan price. A gym that takes half now\n * and half on Friday still wants the member training, and the balance is\n * already tracked against the term.\n */\nexport const confirmMembershipPaymentBodySchema = z.object({\n amountInr: priceInrSchema,\n method: paymentMethodSchema,\n note: optionalTrimmedString(240),\n /**\n * The term the owner BELIEVES they are paying for, echoed back from what the\n * screen showed them.\n *\n * The server derives the term itself and never trusts this as input — it is\n * a guard, not a parameter. The owner is told \"this covers 3 Jul to 3 Aug\"\n * and then presses a button; if a second manager confirms a term in the\n * seconds between, the derived term moves on and this one no longer matches.\n * Without the echo that owner silently pays for a DIFFERENT month than the\n * one they read, and the gym's ledger disagrees with the conversation that\n * produced it. Optional so a client that shows no term still works.\n */\n expectedTermStart: isoDateSchema.optional(),\n});\nexport type ConfirmMembershipPaymentBody = z.infer<typeof confirmMembershipPaymentBodySchema>;\n\n/**\n * One billing term, dated from the member's JOINING DATE.\n *\n * Terms are a derived cycle, not free-form dates the desk types in: a member\n * who joined on the 4th is billed 4 Jul→4 Aug, 4 Aug→4 Sep, and so on, each\n * one the length of the variant they bought. Nobody picks a start date,\n * because every gym already answers \"which month is this for?\" the same way,\n * and letting two people type it produces two answers.\n *\n * `isOverdue` is the term having already ended — the owner marking a month\n * they forgot at the time. That is a normal, expected act rather than an\n * error, so it is surfaced as a fact about the term rather than a warning.\n */\nexport const membershipTermSchema = z.object({\n startDate: isoDateSchema,\n endDate: isoDateSchema,\n isOverdue: z.boolean(),\n});\nexport type MembershipTerm = z.infer<typeof membershipTermSchema>;\n\n/**\n * One row of the gym's confirmation queue.\n *\n * The membership plus just enough of the person to act on it. The owner is\n * deciding about a HUMAN — \"did Ravi's transfer land\" — and a list of plan\n * names and amounts with no names attached is not something anybody can\n * answer. Kept to four fields rather than embedding the whole roster row,\n * because this list is read on every visit to the members screen.\n */\nexport const pendingMembershipRowSchema = z.object({\n membership: membershipSchema,\n member: z.object({\n enrolmentId: objectIdSchema,\n gymMemberCode: z.string(),\n name: z.string(),\n avatarUrl: z.string().nullable(),\n }),\n});\nexport type PendingMembershipRow = z.infer<typeof pendingMembershipRowSchema>;\n\n/** Turning a request down. The reason reaches the member. */\nexport const rejectMembershipPaymentBodySchema = z.object({\n reason: optionalTrimmedString(240),\n});\nexport type RejectMembershipPaymentBody = z.infer<typeof rejectMembershipPaymentBodySchema>;\n\n// ─── Pause ───────────────────────────────────────────────────────────────────\n\nexport const pauseDurationSchema = z.enum(['1w', '2w', '1m', 'custom']);\nexport type PauseDuration = z.infer<typeof pauseDurationSchema>;\n\nexport const pauseMembershipBodySchema = z\n .object({\n duration: pauseDurationSchema,\n customFrom: isoDateSchema.optional(),\n customTo: isoDateSchema.optional(),\n })\n .refine((v) => v.duration !== 'custom' || (v.customFrom && v.customTo), {\n message: 'Pick both pause dates',\n path: ['customTo'],\n });\nexport type PauseMembershipBody = z.infer<typeof pauseMembershipBodySchema>;\n\n// ─── Cancel ──────────────────────────────────────────────────────────────────\n\n/** Cancellation requires echoing the member code as a safety confirmation. */\nexport const cancelMembershipBodySchema = z.object({\n confirmationCode: z.string().trim().min(1),\n reason: optionalTrimmedString(240),\n});\nexport type CancelMembershipBody = z.infer<typeof cancelMembershipBodySchema>;\n\n// ─── Collect an offline payment ──────────────────────────────────────────────\n\nexport const collectPaymentBodySchema = z.object({\n amountInr: priceInrSchema,\n method: paymentMethodSchema,\n note: optionalTrimmedString(240),\n});\nexport type CollectPaymentBody = z.infer<typeof collectPaymentBodySchema>;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gymmonk-schema",
3
- "version": "0.46.1",
3
+ "version": "0.46.2",
4
4
  "description": "Shared Zod schemas, enums and domain types for GymMonk (fitness SaaS) — single source of truth (SSOT) consumed by gymmonk-backend and gymmonk-web-client.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",