import { ok, err } from "@tailor-platform/erp-kit/core"; import type { Transaction, Updateable } from "../generated/kysely-tailordb"; import { SiteNotFoundError, InvalidNameError, DuplicateNameError, InvalidTypeError, MissingRequiredFieldsError, InvalidCountryError, InvalidTimezoneError, } from "../lib/errors.generated"; export type UpdateSiteInput = { siteId: string; name?: string; type?: string; street?: string; city?: string; state?: string | null; postalCode?: string; country?: string; timezone?: string; }; const VALID_SITE_TYPES = ["OFFICE", "WAREHOUSE", "STORE", "FACTORY", "DISTRIBUTION_CENTER"]; function isValidCountryCode(code: string): boolean { try { const displayNames = new Intl.DisplayNames(["en"], { type: "region" }); const name = displayNames.of(code); return name !== undefined && name !== code; } catch { return false; } } function isValidTimezone(tz: string): boolean { try { Intl.DateTimeFormat(undefined, { timeZone: tz }); return true; } catch { return false; } } /** * Function: updateSite * * Modifies the details of an existing site, including its name, type, address, * and timezone. Only provided fields are updated. */ export async function run>( db: Transaction, input: UpdateSiteInput & Omit, "status">, ) { const { siteId, name, type, street, city, state, postalCode, country, timezone, ...customFields } = input; // 1. Find site with forUpdate() const existingSite = await db .selectFrom("Site") .selectAll() .where("id", "=", siteId) .forUpdate() .executeTakeFirst(); if (!existingSite) { return err(new SiteNotFoundError(siteId)); } // 2. If name provided and empty, error if (name?.trim() === "") { return err(new InvalidNameError(name)); } // 3. If name changed, check uniqueness in company if (name !== undefined && name !== existingSite.name) { const duplicate = await db .selectFrom("Site") .selectAll() .where("name", "=", name) .where("companyId", "=", existingSite.companyId) .forUpdate() .executeTakeFirst(); if (duplicate) { return err(new DuplicateNameError(name)); } } // 4. If type provided, validate in allowed list if (type !== undefined && !VALID_SITE_TYPES.includes(type)) { return err(new InvalidTypeError(type)); } // 5. If address fields provided and empty, error if (street?.trim() === "") { return err(new MissingRequiredFieldsError("street")); } if (city?.trim() === "") { return err(new MissingRequiredFieldsError("city")); } if (postalCode?.trim() === "") { return err(new MissingRequiredFieldsError("postalCode")); } if (country?.trim() === "") { return err(new MissingRequiredFieldsError("country")); } // 6. If country provided, validate if (country !== undefined && !isValidCountryCode(country)) { return err(new InvalidCountryError(country)); } // 7. If timezone provided, validate if (timezone !== undefined && !isValidTimezone(timezone)) { return err(new InvalidTimezoneError(timezone)); } // 8. Build update object — strip reserved model columns from customFields const RESERVED_KEYS = new Set([ "id", "companyId", "status", "name", "type", "street", "city", "state", "postalCode", "country", "timezone", "createdAt", "updatedAt", ]); const safeCustomFields: Record = {}; for (const [key, value] of Object.entries(customFields as Record)) { if (!RESERVED_KEYS.has(key)) { safeCustomFields[key] = value; } } const updates: Updateable<"Site"> = { ...(safeCustomFields as Updateable<"Site">), }; if (name !== undefined) updates.name = name; if (type !== undefined) updates.type = type; if (street !== undefined) updates.street = street; if (city !== undefined) updates.city = city; if (state !== undefined) updates.state = state; if (postalCode !== undefined) updates.postalCode = postalCode; if (country !== undefined) updates.country = country; if (timezone !== undefined) updates.timezone = timezone; if (Object.keys(updates).length === 0) { return ok({ site: existingSite }); } // 9. Update const site = await db .updateTable("Site") .set(updates) .where("id", "=", siteId) .returningAll() .executeTakeFirstOrThrow(); return ok({ site }); }