import { CreateCountrySchema, CreateMetroCitySchema, UpdateCountrySchema, UpdateMetroCitySchema, } from "@capakraken/shared"; import type { Prisma } from "@capakraken/db"; import { z } from "zod"; import { findUniqueOrThrow } from "../db/helpers.js"; import { createAuditEntry } from "../lib/audit.js"; import type { TRPCContext } from "../trpc.js"; import { assertCountryCodeAvailable, assertMetroCityDeletable, buildCountryCreateData, buildCountryListWhere, buildCountryUpdateData, buildMetroCityCreateData, buildMetroCityUpdateData, findCountryByIdentifier, } from "./country-support.js"; type CountryProcedureContext = Pick; type CountryIdentifierReadModel = { id: string; code: string; name: string; isActive: boolean; dailyWorkingHours: number; }; type CountryDetailReadModel = CountryIdentifierReadModel & { scheduleRules?: Prisma.JsonValue | null; metroCities: Array<{ id: string; name: string; countryId: string }>; _count: { resources: number }; }; function withAuditUser(userId: string | undefined) { return userId ? { userId } : {}; } export const countryListInputSchema = z.object({ isActive: z.boolean().optional(), }).optional(); export const countryIdentifierInputSchema = z.object({ identifier: z.string().trim().min(1), }); export const countryIdInputSchema = z.object({ id: z.string(), }); export const metroCityIdInputSchema = z.object({ id: z.string(), }); export const countryUpdateInputSchema = z.object({ id: z.string(), data: UpdateCountrySchema, }); export const metroCityUpdateInputSchema = z.object({ id: z.string(), data: UpdateMetroCitySchema, }); type CountryListInput = z.infer; type CountryIdentifierInput = z.infer; type CountryIdInput = z.infer; type CreateCountryInput = z.infer; type UpdateCountryInput = z.infer; type CreateMetroCityInput = z.infer; type UpdateMetroCityInput = z.infer; type MetroCityIdInput = z.infer; export async function listCountries(ctx: CountryProcedureContext, input: CountryListInput) { return ctx.db.country.findMany({ where: buildCountryListWhere(input ?? {}), include: { metroCities: { orderBy: { name: "asc" } } }, orderBy: { name: "asc" }, }); } export async function resolveCountryByIdentifier( ctx: CountryProcedureContext, input: CountryIdentifierInput, ) { return findCountryByIdentifier(ctx.db, input.identifier, { select: { id: true, code: true, name: true, isActive: true, dailyWorkingHours: true, }, }); } export async function getCountryByIdentifier( ctx: CountryProcedureContext, input: CountryIdentifierInput, ) { return findCountryByIdentifier(ctx.db, input.identifier, { include: { metroCities: { orderBy: { name: "asc" } }, _count: { select: { resources: true } }, }, }); } export async function getCountryById(ctx: CountryProcedureContext, input: CountryIdInput) { return findUniqueOrThrow( ctx.db.country.findUnique({ where: { id: input.id }, include: { metroCities: { orderBy: { name: "asc" } }, _count: { select: { resources: true } }, }, }), "Country", ); } export async function getMetroCityById( ctx: CountryProcedureContext, input: MetroCityIdInput, ) { return findUniqueOrThrow( ctx.db.metroCity.findUnique({ where: { id: input.id }, select: { id: true, name: true, countryId: true }, }), "Metro city", ); } export async function createCountry(ctx: CountryProcedureContext, input: CreateCountryInput) { await assertCountryCodeAvailable(ctx.db, input.code); const created = await ctx.db.country.create({ data: buildCountryCreateData(input), include: { metroCities: true }, }); void createAuditEntry({ db: ctx.db, entityType: "Country", entityId: created.id, entityName: created.name, action: "CREATE", ...withAuditUser(ctx.dbUser?.id), after: created as unknown as Record, source: "ui", }); return created; } export async function updateCountry(ctx: CountryProcedureContext, input: UpdateCountryInput) { const existing = await findUniqueOrThrow( ctx.db.country.findUnique({ where: { id: input.id } }), "Country", ); if (input.data.code && input.data.code !== existing.code) { await assertCountryCodeAvailable(ctx.db, input.data.code, existing.id); } const before = existing as unknown as Record; const updated = await ctx.db.country.update({ where: { id: input.id }, data: buildCountryUpdateData(input.data), include: { metroCities: true }, }); void createAuditEntry({ db: ctx.db, entityType: "Country", entityId: updated.id, entityName: updated.name, action: "UPDATE", ...withAuditUser(ctx.dbUser?.id), before, after: updated as unknown as Record, source: "ui", }); return updated; } export async function createMetroCity(ctx: CountryProcedureContext, input: CreateMetroCityInput) { await findUniqueOrThrow( ctx.db.country.findUnique({ where: { id: input.countryId } }), "Country", ); const created = await ctx.db.metroCity.create({ data: buildMetroCityCreateData(input), }); void createAuditEntry({ db: ctx.db, entityType: "MetroCity", entityId: created.id, entityName: created.name, action: "CREATE", ...withAuditUser(ctx.dbUser?.id), after: created as unknown as Record, source: "ui", }); return created; } export async function updateMetroCity( ctx: CountryProcedureContext, input: UpdateMetroCityInput, ) { const existing = await findUniqueOrThrow( ctx.db.metroCity.findUnique({ where: { id: input.id } }), "Metro city", ); const before = existing as unknown as Record; const updated = await ctx.db.metroCity.update({ where: { id: input.id }, data: buildMetroCityUpdateData(input.data), }); void createAuditEntry({ db: ctx.db, entityType: "MetroCity", entityId: updated.id, entityName: updated.name, action: "UPDATE", ...withAuditUser(ctx.dbUser?.id), before, after: updated as unknown as Record, source: "ui", }); return updated; } export async function deleteMetroCity( ctx: CountryProcedureContext, input: MetroCityIdInput, ) { const city = await findUniqueOrThrow( ctx.db.metroCity.findUnique({ where: { id: input.id }, include: { _count: { select: { resources: true } } }, }), "Metro city", ); assertMetroCityDeletable(city); await ctx.db.metroCity.delete({ where: { id: input.id } }); void createAuditEntry({ db: ctx.db, entityType: "MetroCity", entityId: city.id, entityName: city.name, action: "DELETE", ...withAuditUser(ctx.dbUser?.id), before: city as unknown as Record, source: "ui", }); return { success: true, id: city.id, name: city.name }; }