Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
-- TICKET-42: a vendég neme, a csak nőknek szóló szolgáltatás (női köredzés) és a havi tagság
-- szolgáltatása. Férfi vendégnek csak nőknek szóló bérlet / tagság nem adható el, és vele nem
-- léptethető be (a szabály a közös `womenOnlyAccess`, packages/shared/src/gender.ts).

-- CreateEnum
CREATE TYPE "Gender" AS ENUM ('FEMALE', 'MALE', 'UNSPECIFIED');

-- AlterTable
ALTER TABLE "guests" ADD COLUMN "gender" "Gender" NOT NULL DEFAULT 'UNSPECIFIED';

-- AlterTable
ALTER TABLE "services" ADD COLUMN "women_only" BOOLEAN NOT NULL DEFAULT false;

-- AlterTable
ALTER TABLE "memberships" ADD COLUMN "service_id" TEXT;

-- AddForeignKey
ALTER TABLE "memberships" ADD CONSTRAINT "memberships_service_id_fkey" FOREIGN KEY ("service_id") REFERENCES "services"("id") ON DELETE SET NULL ON UPDATE CASCADE;

-- Adatátvezetés 1: a meglévő vendégek neme a keresztnév alapján, ugyanazzal a névlistával és
-- normalizálással, mint az `inferGenderFromFirstName` (első tag, kisbetű, ékezet nélkül). Amit
-- nem ismer fel (pl. „Élő”), „nincs megadva” (UNSPECIFIED) marad. A listák egyezését az API
-- `gender.spec.ts` tesztje ellenőrzi.
-- FEMALE_FIRST_NAMES
UPDATE "guests" SET "gender" = 'FEMALE'
WHERE translate(
lower(regexp_replace(btrim("first_name"), '[[:space:]-].*$', '')),
'ÁÉÍÓÖŐÚÜŰáéíóöőúüű',
'aeiooouuuaeiooouuu'
) IN (
'adel', 'adrienn', 'agnes', 'agota', 'alexandra', 'aliz', 'amalia', 'andrea', 'angela',
'aniko', 'anita', 'anna', 'aranka', 'barbara', 'beata', 'beatrix', 'bernadett', 'bettina',
'bianka', 'boglarka', 'borbala', 'boroka', 'brigitta', 'cecilia', 'cintia', 'csenge',
'csilla', 'dalma', 'diana', 'dominika', 'dora', 'dorina', 'dorka', 'dorottya', 'edina',
'edit', 'eleonora', 'ella', 'emese', 'emilia', 'emma', 'eniko', 'erika', 'erzsebet',
'eszter', 'etelka', 'eva', 'evelin', 'fanni', 'flora', 'franciska', 'fruzsina', 'gabriella',
'georgina', 'gizella', 'greta', 'gyongyi', 'hajnalka', 'hanna', 'hedvig', 'helga',
'henrietta', 'ibolya', 'ildiko', 'ilona', 'iren', 'irma', 'ivett', 'izabella', 'janka',
'jazmin', 'johanna', 'jolan', 'judit', 'julia', 'julianna', 'kamilla', 'karolina', 'kata',
'katalin', 'kinga', 'kincso', 'kitti', 'klara', 'klaudia', 'kornelia', 'kriszta',
'krisztina', 'lara', 'laura', 'lea', 'lena', 'lenke', 'lili', 'liliana', 'lilla', 'linda',
'livia', 'luca', 'lujza', 'magdolna', 'maja', 'margit', 'maria', 'mariann', 'marianna',
'marietta', 'marta', 'melinda', 'mira', 'monika', 'natalia', 'nikoletta', 'nikolett',
'noemi', 'nora', 'olga', 'orsolya', 'panna', 'patricia', 'petra', 'piroska', 'ramona',
'rebeka', 'regina', 'reka', 'renata', 'rita', 'roza', 'rozalia', 'sara', 'sarolta',
'szabina', 'szilvia', 'szofia', 'szonja', 'tamara', 'terez', 'teodora', 'timea', 'tunde',
'valentina', 'valeria', 'vanda', 'vera', 'veronika', 'viktoria', 'vilma', 'viola', 'virag',
'vivien', 'zita', 'zoe', 'zsanett', 'zsofi', 'zsofia', 'zsuzsa', 'zsuzsanna'
);

-- MALE_FIRST_NAMES
UPDATE "guests" SET "gender" = 'MALE'
WHERE translate(
lower(regexp_replace(btrim("first_name"), '[[:space:]-].*$', '')),
'ÁÉÍÓÖŐÚÜŰáéíóöőúüű',
'aeiooouuuaeiooouuu'
) IN (
'abel', 'adam', 'adrian', 'akos', 'albert', 'alex', 'andor', 'andras', 'antal', 'arnold',
'aron', 'arpad', 'attila', 'balazs', 'balint', 'barna', 'barnabas', 'bela', 'bence',
'benedek', 'benjamin', 'bertalan', 'botond', 'csaba', 'csongor', 'daniel', 'david', 'denes',
'dezso', 'dominik', 'domonkos', 'ede', 'elek', 'elemer', 'emil', 'endre', 'erik', 'erno',
'ervin', 'ferenc', 'gabor', 'gaspar', 'gellert', 'gergely', 'gergo', 'geza', 'gusztav',
'gyorgy', 'gyula', 'henrik', 'hunor', 'ignac', 'imre', 'istvan', 'ivan', 'jakab', 'janos',
'jeno', 'jozsef', 'kalman', 'karoly', 'kende', 'kevin', 'kolos', 'kornel', 'kristof',
'krisztian', 'lajos', 'laszlo', 'levente', 'lorand', 'lorant', 'lorinc', 'marcell', 'mario',
'mark', 'martin', 'marton', 'mate', 'matyas', 'menyhert', 'mihaly', 'miklos', 'milan',
'nandor', 'nimrod', 'noel', 'norbert', 'oliver', 'otto', 'pal', 'patrik', 'peter', 'rezso',
'richard', 'robert', 'roland', 'rudolf', 'samuel', 'sandor', 'sebestyen', 'simon',
'szabolcs', 'szilard', 'szilveszter', 'tamas', 'tibor', 'tivadar', 'vencel', 'viktor',
'vilmos', 'vince', 'zalan', 'zoltan', 'zsigmond', 'zsolt', 'zsombor'
);

-- Adatátvezetés 2: a köredzés (CIRCUIT kategóriájú „Köredzés” / „Női köredzés”) szolgáltatás csak nőknek szól.
UPDATE "services" SET "women_only" = true
WHERE "category" = 'CIRCUIT'
AND translate(lower("name"), 'ÁÉÍÓÖŐÚÜŰáéíóöőúüű', 'aeiooouuuaeiooouuu') LIKE '%koredzes%';

-- Adatátvezetés 3: a meglévő havi tagságok a stúdió köredzés szolgáltatásához kötődnek
-- (`pickCircuitMembershipService`: CIRCUIT kategória, az aktív elöl, azon belül a legrégebbi).
UPDATE "memberships" AS m SET "service_id" = (
SELECT s."id" FROM "services" AS s
WHERE s."studio_id" = m."studio_id" AND s."category" = 'CIRCUIT'
ORDER BY s."is_active" DESC, s."created_at" ASC
LIMIT 1
)
WHERE m."service_id" IS NULL;
18 changes: 16 additions & 2 deletions apps/api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ enum GuestStatus {
PAUSED
}

// TICKET-42: a csak nőknek szóló szolgáltatásokhoz (női köredzés).
enum Gender {
FEMALE
MALE
UNSPECIFIED
}

enum ServiceCategory {
CIRCUIT
COSMETIC
Expand Down Expand Up @@ -113,6 +120,7 @@ model Guest {
phone String?
birthDate DateTime? @map("birth_date")
nameDay String? @map("name_day")
gender Gender @default(UNSPECIFIED)
status GuestStatus @default(ACTIVE)
notes String?
isActive Boolean @default(true) @map("is_active")
Expand Down Expand Up @@ -143,13 +151,16 @@ model Service {
category ServiceCategory @default(BODY_SHAPING)
durationMinutes Int @default(30) @map("duration_minutes")
priceHuf Int @default(0) @map("price_huf")
/// Csak nőknek szól: férfi vendégnek nem adható el, vele nem léptethető be (TICKET-42).
womenOnly Boolean @default(false) @map("women_only")
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")

passTemplates PassTemplate[]
guestPasses GuestPass[]
passUsages PassUsageLog[]
photos GuestPhoto[]
memberships Membership[]

@@index([studioId, isActive])
@@map("services")
Expand All @@ -159,6 +170,8 @@ model Membership {
id String @id @default(uuid())
studioId String @default("default-studio") @map("studio_id")
guestId String @map("guest_id")
/// A tagság szolgáltatása (a stúdió köredzése); a csak nőknek szóló korlátozás innen jön.
serviceId String? @map("service_id")
monthlyFeeHuf Int @map("monthly_fee_huf")
startDate DateTime @map("start_date")
dueDay Int @default(10) @map("due_day")
Expand All @@ -168,8 +181,9 @@ model Membership {
resumedAt DateTime? @map("resumed_at")
createdAt DateTime @default(now()) @map("created_at")

guest Guest @relation(fields: [guestId], references: [id], onDelete: Cascade)
dues MembershipDue[]
guest Guest @relation(fields: [guestId], references: [id], onDelete: Cascade)
service Service? @relation(fields: [serviceId], references: [id], onDelete: SetNull)
dues MembershipDue[]

@@index([guestId, status])
@@map("memberships")
Expand Down
30 changes: 27 additions & 3 deletions apps/api/prisma/seed.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
DocumentStatus,
DocumentType,
Gender,
GuestStatus,
MembershipDueStatus,
MembershipStatus,
Expand Down Expand Up @@ -83,7 +84,8 @@
const LEGACY_STAFF_EMAILS = ['admin@reformstudio.hu', 'recepcio@reformstudio.hu'];

const SERVICES = [
{ name: 'Női Köredzés (Havi Tagság)', category: ServiceCategory.CIRCUIT, durationMinutes: 45, priceHuf: 22000 },
// TICKET-42: a köredzés csak nőknek szól (férfi vendégnek nem adható el, vele nem léptethető be).
{ name: 'Női Köredzés (Havi Tagság)', category: ServiceCategory.CIRCUIT, durationMinutes: 45, priceHuf: 22000, womenOnly: true },
{ name: 'Hypoxi', category: ServiceCategory.BODY_SHAPING, durationMinutes: 30, priceHuf: 9500 },
{ name: 'Reformer Pilates', category: ServiceCategory.BODY_SHAPING, durationMinutes: 50, priceHuf: 6500 },
{ name: 'Ultrahangos Kavitációs Zsírbontás', category: ServiceCategory.BODY_SHAPING, durationMinutes: 40, priceHuf: 12000 },
Expand Down Expand Up @@ -155,6 +157,7 @@
phone: string;
birthDate: Date;
nameDay: string;
gender: Gender;
status: GuestStatus;
notes: string;
createdDaysAgo: number;
Expand All @@ -174,6 +177,7 @@
phone: '+36 30 412 8891',
birthDate: dateOnly(1991, today.month, today.day), // mai születésnapos (KPI)
nameDay: '07-26',
gender: Gender.FEMALE,
status: GuestStatus.ACTIVE,
notes: 'Köredzés havi tag + Hypoxi alakformáló kúra (kedd/csütörtök)',
createdDaysAgo: 90,
Expand Down Expand Up @@ -341,6 +345,7 @@
phone: '+36 70 331 5520',
birthDate: dateOnly(1988, 4, 12),
nameDay: todayMmDd, // mai névnapos (KPI)
gender: Gender.MALE,
status: GuestStatus.ACTIVE,
notes: '1-5 napos tagdíj késés + utolsó 1 alkalmas bérlet teszteléséhez',
createdDaysAgo: 45,
Expand Down Expand Up @@ -369,6 +374,7 @@
phone: '+36 20 981 1140',
birthDate: dateOnly(1995, 11, 8),
nameDay: '05-24',
gender: Gender.FEMALE,
status: GuestStatus.ACTIVE,
notes: 'Érvényes Reformer bérlet (2 alkalom maradt), tartozás nélkül',
createdDaysAgo: 60,
Expand All @@ -390,6 +396,7 @@
phone: '+36 30 774 2019',
birthDate: dateOnly(1984, 2, 19),
nameDay: '03-24',
gender: Gender.MALE,
status: GuestStatus.ACTIVE,
notes: '15 nap feletti tartozás + lejárt bérlet teszteléséhez',
createdDaysAgo: 70,
Expand Down Expand Up @@ -417,6 +424,7 @@
phone: '+36 30 611 9082',
birthDate: dateOnly(1993, 9, 14),
nameDay: '05-15',
gender: Gender.FEMALE,
status: GuestStatus.ACTIVE,
notes: '6-15 napos részbefizetett tagdíj + kimerült (0 alkalmas) bérlet',
createdDaysAgo: 60,
Expand Down Expand Up @@ -445,6 +453,7 @@
phone: '+36 20 449 3210',
birthDate: dateOnly(1990, 6, 5),
nameDay: '02-06',
gender: Gender.FEMALE,
status: GuestStatus.PAUSED,
notes: 'Síbaleset miatt szüneteltetett tagság és fagyasztott bérlet',
createdDaysAgo: 20,
Expand Down Expand Up @@ -534,7 +543,7 @@
const template = templates.get(spec.templateName);
const serviceId = services.get(spec.serviceName);
if (!template || !serviceId) {
throw new Error(`Hiányzó sablon vagy szolgáltatás: ${spec.templateName}`);

Check warning on line 546 in apps/api/prisma/seed.ts

View workflow job for this annotation

GitHub Actions / Gate (typecheck, lint, teszt, build)

Nyers `throw new Error(...)` helyett használd az `appError(...)`-t (hibakód-rendszer)
}
const totalPasses = (await tx.passTemplate.findUniqueOrThrow({ where: { id: template.id } }))
.totalPasses;
Expand Down Expand Up @@ -625,7 +634,13 @@
}
}

async function createMembership(tx: Tx, guestId: string, staffId: string, spec: MembershipSpec) {
async function createMembership(
tx: Tx,
guestId: string,
staffId: string,
spec: MembershipSpec,
serviceId: string | null,
) {
const first = billing(spec.billingDaysAgo);
const pausedAt = spec.pausedDaysAgo !== undefined ? daysAgo(spec.pausedDaysAgo) : null;
// Ugyanaz az ütemezés, mint a `createMembership` / havi pótlás az API-n.
Expand All @@ -643,6 +658,7 @@
data: {
studioId,
guestId,
serviceId,
monthlyFeeHuf: spec.monthlyFeeHuf,
startDate: first.dueDate,
dueDay: first.dueDay,
Expand Down Expand Up @@ -709,14 +725,22 @@
phone: spec.phone,
birthDate: spec.birthDate,
nameDay: spec.nameDay,
gender: spec.gender,
status: spec.status,
notes: spec.notes,
createdAt: daysAgo(spec.createdDaysAgo),
},
});

if (spec.membership) {
await createMembership(tx, guest.id, staffId, spec.membership);
// A havi tagság a köredzés szolgáltatáshoz kötődik (TICKET-42).
await createMembership(
tx,
guest.id,
staffId,
spec.membership,
services.get('Női Köredzés (Havi Tagság)') ?? null,
);
}
for (const pass of spec.passes) {
await createPass(tx, guest.id, staffId, pass, templates, services);
Expand Down
Loading
Loading