@fonderie/customers 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +41 -0
- package/dist/index.cjs +2220 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +237 -0
- package/dist/index.d.ts +237 -0
- package/dist/index.js +2178 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations/index.js +7 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/migrations/sql/001_customers.sql +111 -0
- package/dist/migrations/sql/002_customers_sex.sql +2 -0
- package/dist/migrations/sql/003_customer_sequences.sql +6 -0
- package/dist/migrations/sql/004_customer_sequences_per_workspace.sql +1 -0
- package/dist/migrations/sql/005_drop_job_title.sql +1 -0
- package/dist/migrations/sql/006_unique_email_phone.sql +23 -0
- package/dist/migrations/sql/007_rename_archived_to_blacklisted.sql +13 -0
- package/dist/migrations/sql/008_blacklist_reason.sql +2 -0
- package/dist/migrations/sql/009_customer_relationships.sql +25 -0
- package/dist/migrations/sql/010_customer_labels.sql +75 -0
- package/dist/migrations/sql/011_label_fk_set_null.sql +17 -0
- package/dist/types.cjs +19 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +124 -0
- package/dist/types.d.ts +124 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/package.json +85 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/dtos/customer.ts","../src/models/customer.model.ts","../src/models/customer-relationship.model.ts","../src/models/customer-address.model.ts","../src/models/customer-email.model.ts","../src/models/customer-label.model.ts","../src/models/customer-note.model.ts","../src/models/customer-phone.model.ts","../src/models/customer-tag.model.ts","../src/routes.ts","../src/controllers/customer.controller.ts","../src/utils.ts","../src/controllers/customer-address.controller.ts","../src/controllers/customer-email.controller.ts","../src/controllers/customer-note.controller.ts","../src/controllers/customer-phone.controller.ts","../src/controllers/customer-tag.controller.ts","../src/controllers/customer-label.controller.ts","../src/controllers/customer-relationship.controller.ts","../src/module.ts"],"sourcesContent":["export const EVENT_KEYS = {\n\tcustomerCreated: 'fonderie.customer.created',\n\tcustomerUpdated: 'fonderie.customer.updated',\n\tcustomerDeleted: 'fonderie.customer.deleted',\n\tcustomerBlacklisted: 'fonderie.customer.blacklisted',\n\tcustomerUnblacklisted: 'fonderie.customer.unblacklisted',\n} as const;\n\nexport type CustomersEventKey = (typeof EVENT_KEYS)[keyof typeof EVENT_KEYS];\n\nexport const DEFAULT_REFERENCE_CODE_PREFIX = 'CLT';\n\nexport type ICustomersConfig = {\n\t/** Prefix used when auto-generating customer reference codes. Defaults to DEFAULT_REFERENCE_CODE_PREFIX. */\n\treferenceCodePrefix?: string;\n};\n","import { arrayOrEmpty, booleanOrFalse, dateOrEmpty, stringOrEmpty } from '@fonderie/core';\n\nimport type { CustomerSex } from '../types';\nimport type {\n\tIAddress,\n\tICustomer,\n\tICustomerAddress,\n\tICustomerDetail,\n\tICustomerDetailD2,\n\tICustomerEmail,\n\tICustomerNote,\n\tICustomerPhone,\n\tICustomerRelationship,\n\tICustomerRelationshipExpanded,\n\tICustomerRelationshipExpandedD2,\n\tICustomerShallow,\n\tICustomerTag,\n} from '../types';\n\nexport interface ICustomerDTO {\n\tid: string;\n\ttype: string;\n\tsex: CustomerSex;\n\tfirstName: string;\n\tlastName: string;\n\tcompanyName: string;\n\tavatarUrl: string;\n\tlocale: string;\n\treferenceCode: string;\n\tblacklisted: { status: boolean; reason: string | null };\n\tcreatedBy: string;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\nexport interface ICustomerRelationshipDTO {\n\tid: string;\n\trelatedId: string;\n\trelationship: string;\n\tisPrimary: boolean;\n\tcreatedAt: string;\n}\n\nexport interface ICustomerShallowDTO extends ICustomerDTO {\n\temails: ICustomerEmailDTO[];\n\tphones: ICustomerPhoneDTO[];\n\taddresses: ICustomerAddressDTO[];\n\tnotes: ICustomerNoteDTO[];\n\ttags: string[];\n}\n\n// Flat merge: relationship metadata + customer fields spread at the same level.\n// `id` is the relationship record id; `customerId` is the related customer's id.\nexport type ICustomerRelationshipExpandedDTO = Omit<ICustomerShallowDTO, 'id'> & {\n\tid: string;\n\tcustomerId: string;\n\trelationship: string;\n\tisPrimary: boolean;\n};\n\nexport interface ICustomerDetailDTO extends ICustomerDTO {\n\temails: ICustomerEmailDTO[];\n\tphones: ICustomerPhoneDTO[];\n\taddresses: ICustomerAddressDTO[];\n\tnotes: ICustomerNoteDTO[];\n\trelationships: ICustomerRelationshipExpandedDTO[];\n\ttags: string[];\n}\n\n// Depth-2 DTOs: each depth-1 relationship entry carries its own relationships array.\nexport type ICustomerRelationshipExpandedD2DTO = ICustomerRelationshipExpandedDTO & {\n\trelationships: ICustomerRelationshipExpandedDTO[];\n};\n\nexport interface ICustomerDetailD2DTO extends Omit<ICustomerDetailDTO, 'relationships'> {\n\trelationships: ICustomerRelationshipExpandedD2DTO[];\n}\n\nexport interface ICustomerEmailDTO {\n\tid: string;\n\temail: string;\n\tlabel: string;\n\tisPrimary: boolean;\n\tcreatedAt: string;\n}\n\nexport interface ICustomerPhoneDTO {\n\tid: string;\n\tphone: string;\n\tlabel: string;\n\tisPrimary: boolean;\n\tcreatedAt: string;\n}\n\nexport interface IAddressDTO {\n\tcountryIso: string;\n\tsubdivision1Iso: string;\n\tsubdivision2Iso: string;\n\tzipPostalCode: string;\n\tunit: string;\n\tline1: string;\n\tline2: string;\n}\n\nexport interface ICustomerAddressDTO {\n\tid: string;\n\tlabel: string;\n\tisPrimary: boolean;\n\taddress: IAddressDTO;\n}\n\nexport interface ICustomerNoteDTO {\n\tid: string;\n\tauthorId: string;\n\tbody: string;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\nexport interface ICustomerTagDTO {\n\ttag: string;\n}\n\nconst VALID_SEX: CustomerSex[] = ['UNKNOWN', 'MALE', 'FEMALE'];\n\nexport function toCustomerDTO(c: ICustomer): ICustomerDTO {\n\treturn {\n\t\tid: stringOrEmpty(c.id),\n\t\ttype: stringOrEmpty(c.type),\n\t\tsex: VALID_SEX.includes(c.sex as CustomerSex) ? (c.sex as CustomerSex) : 'UNKNOWN',\n\t\tfirstName: stringOrEmpty(c.firstName),\n\t\tlastName: stringOrEmpty(c.lastName),\n\t\tcompanyName: stringOrEmpty(c.companyName),\n\t\tavatarUrl: stringOrEmpty(c.avatarUrl),\n\t\tlocale: stringOrEmpty(c.locale),\n\t\treferenceCode: stringOrEmpty(c.referenceCode),\n\t\tblacklisted: { status: booleanOrFalse(c.isBlacklisted), reason: c.blacklistReason ?? null },\n\t\tcreatedBy: stringOrEmpty(c.createdBy),\n\t\tcreatedAt: dateOrEmpty(c.createdAt),\n\t\tupdatedAt: dateOrEmpty(c.updatedAt),\n\t};\n}\n\nexport function toCustomerRelationshipDTO(r: ICustomerRelationship): ICustomerRelationshipDTO {\n\treturn {\n\t\tid: stringOrEmpty(r.id),\n\t\trelatedId: stringOrEmpty(r.relatedId),\n\t\trelationship: stringOrEmpty(r.relationship),\n\t\tisPrimary: booleanOrFalse(r.isPrimary),\n\t\tcreatedAt: dateOrEmpty(r.createdAt),\n\t};\n}\n\nexport function toCustomerShallowDTO(c: ICustomerShallow): ICustomerShallowDTO {\n\treturn {\n\t\t...toCustomerDTO(c),\n\t\temails: arrayOrEmpty<ICustomerEmail>(c.emails).map(toCustomerEmailDTO),\n\t\tphones: arrayOrEmpty<ICustomerPhone>(c.phones).map(toCustomerPhoneDTO),\n\t\taddresses: arrayOrEmpty<ICustomerAddress>(c.addresses).map(toCustomerAddressDTO),\n\t\tnotes: arrayOrEmpty<ICustomerNote>(c.notes).map(toCustomerNoteDTO),\n\t\ttags: arrayOrEmpty<string>(c.tags),\n\t};\n}\n\nexport function toCustomerRelationshipExpandedDTO(r: ICustomerRelationshipExpanded): ICustomerRelationshipExpandedDTO {\n\tconst { id: customerId, ...customerFields } = toCustomerShallowDTO(r.customer);\n\treturn {\n\t\tid: stringOrEmpty(r.id),\n\t\tcustomerId,\n\t\trelationship: stringOrEmpty(r.relationship),\n\t\tisPrimary: booleanOrFalse(r.isPrimary),\n\t\t...customerFields,\n\t};\n}\n\nexport function toCustomerDetailDTO(c: ICustomerDetail): ICustomerDetailDTO {\n\treturn {\n\t\t...toCustomerDTO(c),\n\t\temails: arrayOrEmpty<ICustomerEmail>(c.emails).map(toCustomerEmailDTO),\n\t\tphones: arrayOrEmpty<ICustomerPhone>(c.phones).map(toCustomerPhoneDTO),\n\t\taddresses: arrayOrEmpty<ICustomerAddress>(c.addresses).map(toCustomerAddressDTO),\n\t\tnotes: arrayOrEmpty<ICustomerNote>(c.notes).map(toCustomerNoteDTO),\n\t\trelationships: arrayOrEmpty<ICustomerRelationshipExpanded>(c.relationships).map(toCustomerRelationshipExpandedDTO),\n\t\ttags: arrayOrEmpty<string>(c.tags),\n\t};\n}\n\nexport function toCustomerRelationshipExpandedD2DTO(r: ICustomerRelationshipExpandedD2): ICustomerRelationshipExpandedD2DTO {\n\tconst { id: customerId, ...customerFields } = toCustomerShallowDTO(r.customer);\n\treturn {\n\t\tid: stringOrEmpty(r.id),\n\t\tcustomerId,\n\t\trelationship: stringOrEmpty(r.relationship),\n\t\tisPrimary: booleanOrFalse(r.isPrimary),\n\t\t...customerFields,\n\t\trelationships: arrayOrEmpty<ICustomerRelationshipExpanded>(r.customer.relationships).map(toCustomerRelationshipExpandedDTO),\n\t};\n}\n\nexport function toCustomerDetailD2DTO(c: ICustomerDetailD2): ICustomerDetailD2DTO {\n\treturn {\n\t\t...toCustomerDTO(c),\n\t\temails: arrayOrEmpty<ICustomerEmail>(c.emails).map(toCustomerEmailDTO),\n\t\tphones: arrayOrEmpty<ICustomerPhone>(c.phones).map(toCustomerPhoneDTO),\n\t\taddresses: arrayOrEmpty<ICustomerAddress>(c.addresses).map(toCustomerAddressDTO),\n\t\tnotes: arrayOrEmpty<ICustomerNote>(c.notes).map(toCustomerNoteDTO),\n\t\trelationships: arrayOrEmpty<ICustomerRelationshipExpandedD2>(c.relationships).map(toCustomerRelationshipExpandedD2DTO),\n\t\ttags: arrayOrEmpty<string>(c.tags),\n\t};\n}\n\nexport function toAddressDTO(a: IAddress): IAddressDTO {\n\treturn {\n\t\tcountryIso: stringOrEmpty(a.countryIso),\n\t\tsubdivision1Iso: stringOrEmpty(a.subdivision1Iso),\n\t\tsubdivision2Iso: stringOrEmpty(a.subdivision2Iso),\n\t\tzipPostalCode: stringOrEmpty(a.zipPostalCode),\n\t\tunit: stringOrEmpty(a.unit),\n\t\tline1: stringOrEmpty(a.line1),\n\t\tline2: stringOrEmpty(a.line2),\n\t};\n}\n\nexport function toCustomerEmailDTO(e: ICustomerEmail): ICustomerEmailDTO {\n\treturn {\n\t\tid: stringOrEmpty(e.id),\n\t\temail: stringOrEmpty(e.email),\n\t\tlabel: stringOrEmpty(e.label),\n\t\tisPrimary: booleanOrFalse(e.isPrimary),\n\t\tcreatedAt: dateOrEmpty(e.createdAt),\n\t};\n}\n\nexport function toCustomerPhoneDTO(p: ICustomerPhone): ICustomerPhoneDTO {\n\treturn {\n\t\tid: stringOrEmpty(p.id),\n\t\tphone: stringOrEmpty(p.phone),\n\t\tlabel: stringOrEmpty(p.label),\n\t\tisPrimary: booleanOrFalse(p.isPrimary),\n\t\tcreatedAt: dateOrEmpty(p.createdAt),\n\t};\n}\n\nexport function toCustomerAddressDTO(ca: ICustomerAddress): ICustomerAddressDTO {\n\treturn {\n\t\tid: stringOrEmpty(ca.addrId),\n\t\tlabel: stringOrEmpty(ca.label),\n\t\tisPrimary: booleanOrFalse(ca.isPrimary),\n\t\taddress: toAddressDTO(ca.address),\n\t};\n}\n\nexport function toCustomerNoteDTO(n: ICustomerNote): ICustomerNoteDTO {\n\treturn {\n\t\tid: stringOrEmpty(n.id),\n\t\tauthorId: stringOrEmpty(n.authorId),\n\t\tbody: stringOrEmpty(n.body),\n\t\tcreatedAt: dateOrEmpty(n.createdAt),\n\t\tupdatedAt: dateOrEmpty(n.updatedAt),\n\t};\n}\n\nexport function toCustomerTagDTO(t: ICustomerTag): ICustomerTagDTO {\n\treturn {\n\t\ttag: stringOrEmpty(t.tag),\n\t};\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport { DEFAULT_REFERENCE_CODE_PREFIX } from '../config';\nimport type {\n\tICustomer,\n\tICustomerAddress,\n\tICustomerDetail,\n\tICustomerDetailD2,\n\tICustomerEmail,\n\tICustomerPhone,\n\tICustomerRelationship,\n\tICustomerRelationshipExpanded,\n\tICustomerShallow,\n\tICustomerShallowD2,\n} from '../types';\n\nfunction groupByCustomer<T extends { customerId: string }>(rows: T[]): Map<string, T[]> {\n\treturn rows.reduce((m, r) => {\n\t\tif (!m.has(r.customerId)) m.set(r.customerId, []);\n\t\tm.get(r.customerId)!.push(r);\n\t\treturn m;\n\t}, new Map<string, T[]>());\n}\n\nconst SELECT_CUSTOMER = `\n\tid,\n\tworkspace_id AS \"workspaceId\",\n\ttype,\n\tsex,\n\tfirst_name AS \"firstName\",\n\tlast_name AS \"lastName\",\n\tcompany_name AS \"companyName\",\n\tavatar_url AS \"avatarUrl\",\n\tlocale,\n\treference_code AS \"referenceCode\",\n\tis_blacklisted AS \"isBlacklisted\",\n\tblacklist_reason AS \"blacklistReason\",\n\tcreated_by AS \"createdBy\",\n\tcreated_at AS \"createdAt\",\n\tupdated_at AS \"updatedAt\"\n`;\n\nexport interface ListCustomersOpts {\n\tworkspaceId: string;\n\tsearch?: string | undefined;\n\tblacklisted?: boolean | undefined;\n\tlimit?: number | undefined;\n\toffset?: number | undefined;\n}\n\nexport interface CreateCustomerOpts {\n\tworkspaceId: string;\n\ttype?: string;\n\tsex?: string;\n\tfirstName?: string | null;\n\tlastName?: string | null;\n\tcompanyName?: string | null;\n\tavatarUrl?: string | null;\n\tlocale?: string;\n\t/** Explicit code to assign. Omit to auto-generate ({prefix}-0001, …). */\n\treferenceCode?: string;\n\t/** Prefix used when auto-generating. Defaults to 'CLT'. */\n\treferenceCodePrefix?: string;\n\tcreatedBy?: string | null;\n}\n\nexport interface UpdateCustomerOpts {\n\ttype?: string;\n\tsex?: string;\n\tfirstName?: string | null;\n\tlastName?: string | null;\n\tcompanyName?: string | null;\n\tavatarUrl?: string | null;\n\tlocale?: string;\n\t/** Explicit code to assign. Omit to keep existing or auto-generate if none. */\n\treferenceCode?: string;\n}\n\nexport class CustomerModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tprivate async allocateCode(workspaceId: string, prefix: string): Promise<string> {\n\t\tconst [row] = await this.store.query<{ nextVal: number }>(\n\t\t\t`INSERT INTO fonderie_customer_sequences (workspace_id, prefix, next_val)\n\t\t\t VALUES ($1, $2, 1)\n\t\t\t ON CONFLICT (workspace_id, prefix) DO UPDATE\n\t\t\t SET next_val = fonderie_customer_sequences.next_val + 1\n\t\t\t RETURNING next_val AS \"nextVal\"`,\n\t\t\t[workspaceId, prefix],\n\t\t);\n\t\treturn `${prefix}-${String(row!.nextVal).padStart(4, '0')}`;\n\t}\n\n\tasync list(opts: ListCustomersOpts): Promise<ICustomer[]> {\n\t\tconst conditions: string[] = ['workspace_id = $1'];\n\t\tconst params: unknown[] = [opts.workspaceId];\n\n\t\tif (opts.blacklisted !== undefined) {\n\t\t\tparams.push(opts.blacklisted);\n\t\t\tconditions.push(`is_blacklisted = $${params.length}`);\n\t\t}\n\n\t\tif (opts.search) {\n\t\t\tparams.push(`%${opts.search}%`);\n\t\t\tconst idx = params.length;\n\t\t\tconditions.push(\n\t\t\t\t`(first_name ILIKE $${idx} OR last_name ILIKE $${idx} OR company_name ILIKE $${idx} OR reference_code ILIKE $${idx})`,\n\t\t\t);\n\t\t}\n\n\t\tconst limit = Math.min(opts.limit ?? 50, 200);\n\t\tconst offset = opts.offset ?? 0;\n\t\tparams.push(limit, offset);\n\t\tconst limitIdx = params.length - 1;\n\t\tconst offsetIdx = params.length;\n\n\t\treturn this.store.query<ICustomer>(\n\t\t\t`SELECT ${SELECT_CUSTOMER}\n\t\t\t FROM fonderie_customers\n\t\t\t WHERE ${conditions.join(' AND ')}\n\t\t\t ORDER BY created_at DESC\n\t\t\t LIMIT $${limitIdx} OFFSET $${offsetIdx}`,\n\t\t\tparams,\n\t\t);\n\t}\n\n\tasync findById(id: string, workspaceId: string): Promise<ICustomer | null> {\n\t\tconst [row] = await this.store.query<ICustomer>(\n\t\t\t`SELECT ${SELECT_CUSTOMER}\n\t\t\t FROM fonderie_customers\n\t\t\t WHERE id = $1 AND workspace_id = $2`,\n\t\t\t[id, workspaceId],\n\t\t);\n\t\treturn row ?? null;\n\t}\n\n\tasync findDetail(id: string, workspaceId: string, depth: 2): Promise<ICustomerDetailD2 | null>;\n\tasync findDetail(id: string, workspaceId: string, depth?: 1): Promise<ICustomerDetail | null>;\n\tasync findDetail(id: string, workspaceId: string, depth = 1): Promise<ICustomerDetail | ICustomerDetailD2 | null> {\n\t\tconst [row] = await this.store.query<ICustomer>(\n\t\t\t`SELECT ${SELECT_CUSTOMER}\n\t\t\t FROM fonderie_customers\n\t\t\t WHERE id = $1 AND workspace_id = $2`,\n\t\t\t[id, workspaceId],\n\t\t);\n\t\tif (!row) return null;\n\n\t\tconst [emailRows, phoneRows, addressRows, noteRows, relationshipRows, tagRows] = await Promise.all([\n\t\t\tthis.store.query<ICustomerEmail>(\n\t\t\t\t`SELECT id,\n\t\t\t\t customer_id AS \"customerId\",\n\t\t\t\t email,\n\t\t\t\t label_id AS \"labelId\",\n\t\t\t\t (SELECT value FROM fonderie_customer_labels WHERE id = label_id) AS label,\n\t\t\t\t is_primary AS \"isPrimary\",\n\t\t\t\t created_at AS \"createdAt\"\n\t\t\t\t FROM fonderie_customer_emails\n\t\t\t\t WHERE customer_id = $1\n\t\t\t\t ORDER BY is_primary DESC, created_at ASC`,\n\t\t\t\t[id],\n\t\t\t),\n\t\t\tthis.store.query<ICustomerPhone>(\n\t\t\t\t`SELECT id,\n\t\t\t\t customer_id AS \"customerId\",\n\t\t\t\t phone,\n\t\t\t\t label_id AS \"labelId\",\n\t\t\t\t (SELECT value FROM fonderie_customer_labels WHERE id = label_id) AS label,\n\t\t\t\t is_primary AS \"isPrimary\",\n\t\t\t\t created_at AS \"createdAt\"\n\t\t\t\t FROM fonderie_customer_phones\n\t\t\t\t WHERE customer_id = $1\n\t\t\t\t ORDER BY is_primary DESC, created_at ASC`,\n\t\t\t\t[id],\n\t\t\t),\n\t\t\tthis.store.query<ICustomerAddress>(\n\t\t\t\t`SELECT ca.addr_id AS \"addrId\",\n\t\t\t\t ca.customer_id AS \"customerId\",\n\t\t\t\t ca.label_id AS \"labelId\",\n\t\t\t\t (SELECT value FROM fonderie_customer_labels WHERE id = ca.label_id) AS label,\n\t\t\t\t ca.is_primary AS \"isPrimary\",\n\t\t\t\t jsonb_build_object(\n\t\t\t\t 'id', a.id,\n\t\t\t\t 'countryIso', a.country_iso,\n\t\t\t\t 'subdivision1Iso', a.subdivision1_iso,\n\t\t\t\t 'subdivision2Iso', a.subdivision2_iso,\n\t\t\t\t 'zipPostalCode', a.zip_postal_code,\n\t\t\t\t 'unit', a.unit,\n\t\t\t\t 'line1', a.line1,\n\t\t\t\t 'line2', a.line2\n\t\t\t\t ) AS address\n\t\t\t\t FROM fonderie_customer_addresses ca\n\t\t\t\t JOIN fonderie_addresses a ON a.id = ca.addr_id\n\t\t\t\t WHERE ca.customer_id = $1\n\t\t\t\t ORDER BY ca.is_primary DESC`,\n\t\t\t\t[id],\n\t\t\t),\n\t\t\tthis.store.query<{\n\t\t\t\tid: string;\n\t\t\t\tcustomerId: string;\n\t\t\t\tauthorId: string | null;\n\t\t\t\tbody: string;\n\t\t\t\tcreatedAt: string;\n\t\t\t\tupdatedAt: string;\n\t\t\t}>(\n\t\t\t\t`SELECT id,\n\t\t\t\t customer_id AS \"customerId\",\n\t\t\t\t author_id AS \"authorId\",\n\t\t\t\t body,\n\t\t\t\t created_at AS \"createdAt\",\n\t\t\t\t updated_at AS \"updatedAt\"\n\t\t\t\t FROM fonderie_customer_notes\n\t\t\t\t WHERE customer_id = $1\n\t\t\t\t ORDER BY created_at DESC`,\n\t\t\t\t[id],\n\t\t\t),\n\t\t\tthis.store.query<ICustomerRelationship>(\n\t\t\t\t`SELECT id,\n\t\t\t\t workspace_id AS \"workspaceId\",\n\t\t\t\t customer_id AS \"customerId\",\n\t\t\t\t related_id AS \"relatedId\",\n\t\t\t\t relationship,\n\t\t\t\t is_primary AS \"isPrimary\",\n\t\t\t\t created_at AS \"createdAt\"\n\t\t\t\t FROM fonderie_customer_relationships\n\t\t\t\t WHERE customer_id = $1\n\t\t\t\t ORDER BY is_primary DESC, created_at ASC`,\n\t\t\t\t[id],\n\t\t\t),\n\t\t\tthis.store.query<{ tag: string }>(\n\t\t\t\t`SELECT tag FROM fonderie_customer_tags WHERE customer_id = $1 ORDER BY tag ASC`,\n\t\t\t\t[id],\n\t\t\t),\n\t\t]);\n\n\t\t// Batch-resolve related customers so the caller gets full detail in one request.\n\t\tconst relatedIds = relationshipRows.map((r) => r.relatedId);\n\t\tlet expandedRelationships: ICustomerRelationshipExpanded[] = [];\n\n\t\tif (relatedIds.length > 0) {\n\t\t\tconst [relCustomers, relEmails, relPhones, relAddresses, relNotes, relTags] = await Promise.all([\n\t\t\t\tthis.store.query<ICustomer>(\n\t\t\t\t\t`SELECT ${SELECT_CUSTOMER} FROM fonderie_customers WHERE id = ANY($1::uuid[]) AND workspace_id = $2`,\n\t\t\t\t\t[relatedIds, workspaceId],\n\t\t\t\t),\n\t\t\t\tthis.store.query<ICustomerEmail>(\n\t\t\t\t\t`SELECT id, customer_id AS \"customerId\", email, label_id AS \"labelId\",\n\t\t\t\t\t (SELECT value FROM fonderie_customer_labels WHERE id = label_id) AS label,\n\t\t\t\t\t is_primary AS \"isPrimary\", created_at AS \"createdAt\"\n\t\t\t\t\t FROM fonderie_customer_emails WHERE customer_id = ANY($1::uuid[]) ORDER BY is_primary DESC, created_at ASC`,\n\t\t\t\t\t[relatedIds],\n\t\t\t\t),\n\t\t\t\tthis.store.query<ICustomerPhone>(\n\t\t\t\t\t`SELECT id, customer_id AS \"customerId\", phone, label_id AS \"labelId\",\n\t\t\t\t\t (SELECT value FROM fonderie_customer_labels WHERE id = label_id) AS label,\n\t\t\t\t\t is_primary AS \"isPrimary\", created_at AS \"createdAt\"\n\t\t\t\t\t FROM fonderie_customer_phones WHERE customer_id = ANY($1::uuid[]) ORDER BY is_primary DESC, created_at ASC`,\n\t\t\t\t\t[relatedIds],\n\t\t\t\t),\n\t\t\t\tthis.store.query<ICustomerAddress>(\n\t\t\t\t\t`SELECT ca.addr_id AS \"addrId\",\n\t\t\t\t\t ca.customer_id AS \"customerId\",\n\t\t\t\t\t ca.label_id AS \"labelId\",\n\t\t\t\t\t (SELECT value FROM fonderie_customer_labels WHERE id = ca.label_id) AS label,\n\t\t\t\t\t ca.is_primary AS \"isPrimary\",\n\t\t\t\t\t jsonb_build_object(\n\t\t\t\t\t 'id', a.id,\n\t\t\t\t\t 'countryIso', a.country_iso,\n\t\t\t\t\t 'subdivision1Iso', a.subdivision1_iso,\n\t\t\t\t\t 'subdivision2Iso', a.subdivision2_iso,\n\t\t\t\t\t 'zipPostalCode', a.zip_postal_code,\n\t\t\t\t\t 'unit', a.unit,\n\t\t\t\t\t 'line1', a.line1,\n\t\t\t\t\t 'line2', a.line2\n\t\t\t\t\t ) AS address\n\t\t\t\t\t FROM fonderie_customer_addresses ca\n\t\t\t\t\t JOIN fonderie_addresses a ON a.id = ca.addr_id\n\t\t\t\t\t WHERE ca.customer_id = ANY($1::uuid[])\n\t\t\t\t\t ORDER BY ca.is_primary DESC`,\n\t\t\t\t\t[relatedIds],\n\t\t\t\t),\n\t\t\t\tthis.store.query<{ id: string; customerId: string; authorId: string | null; body: string; createdAt: string; updatedAt: string }>(\n\t\t\t\t\t`SELECT id, customer_id AS \"customerId\", author_id AS \"authorId\", body, created_at AS \"createdAt\", updated_at AS \"updatedAt\"\n\t\t\t\t\t FROM fonderie_customer_notes WHERE customer_id = ANY($1::uuid[]) ORDER BY created_at DESC`,\n\t\t\t\t\t[relatedIds],\n\t\t\t\t),\n\t\t\t\tthis.store.query<{ customerId: string; tag: string }>(\n\t\t\t\t\t`SELECT customer_id AS \"customerId\", tag FROM fonderie_customer_tags WHERE customer_id = ANY($1::uuid[]) ORDER BY tag ASC`,\n\t\t\t\t\t[relatedIds],\n\t\t\t\t),\n\t\t\t]);\n\n\t\t\tconst customerMap = new Map(relCustomers.map((c) => [c.id, c]));\n\n\t\t\tconst emailMap = groupByCustomer(relEmails);\n\t\t\tconst phoneMap = groupByCustomer(relPhones);\n\t\t\tconst addrMap = groupByCustomer(relAddresses);\n\t\t\tconst noteMap = groupByCustomer(relNotes);\n\t\t\tconst tagMap = relTags.reduce((m, t) => {\n\t\t\t\tif (!m.has(t.customerId)) m.set(t.customerId, []);\n\t\t\t\tm.get(t.customerId)!.push(t.tag);\n\t\t\t\treturn m;\n\t\t\t}, new Map<string, string[]>());\n\n\t\t\texpandedRelationships = relationshipRows.map((rel) => ({\n\t\t\t\tid: rel.id,\n\t\t\t\tworkspaceId: rel.workspaceId,\n\t\t\t\tcustomerId: rel.customerId,\n\t\t\t\trelationship: rel.relationship,\n\t\t\t\tisPrimary: rel.isPrimary,\n\t\t\t\tcreatedAt: rel.createdAt,\n\t\t\t\tcustomer: {\n\t\t\t\t\t...(customerMap.get(rel.relatedId) ?? ({ id: rel.relatedId } as ICustomer)),\n\t\t\t\t\temails: emailMap.get(rel.relatedId) ?? [],\n\t\t\t\t\tphones: phoneMap.get(rel.relatedId) ?? [],\n\t\t\t\t\taddresses: addrMap.get(rel.relatedId) ?? [],\n\t\t\t\t\tnotes: noteMap.get(rel.relatedId) ?? [],\n\t\t\t\t\ttags: tagMap.get(rel.relatedId) ?? [],\n\t\t\t\t} as ICustomerShallow,\n\t\t\t}));\n\t\t}\n\n\t\t// ── Depth-2: resolve relationships of relationships ────────────────────\n\t\tif (depth >= 2 && expandedRelationships.length > 0) {\n\t\t\tconst visited = new Set([id]); // guard against cycles back to root\n\t\t\tconst d1Ids = expandedRelationships.map((r) => r.customer.id);\n\n\t\t\tconst d2RelRows = await this.store.query<ICustomerRelationship>(\n\t\t\t\t`SELECT id,\n\t\t\t\t workspace_id AS \"workspaceId\",\n\t\t\t\t customer_id AS \"customerId\",\n\t\t\t\t related_id AS \"relatedId\",\n\t\t\t\t relationship,\n\t\t\t\t is_primary AS \"isPrimary\",\n\t\t\t\t created_at AS \"createdAt\"\n\t\t\t\t FROM fonderie_customer_relationships\n\t\t\t\t WHERE customer_id = ANY($1::uuid[])\n\t\t\t\t ORDER BY is_primary DESC, created_at ASC`,\n\t\t\t\t[d1Ids],\n\t\t\t);\n\n\t\t\tconst validD2Rows = d2RelRows.filter((r) => !visited.has(r.relatedId));\n\t\t\tconst d2Ids = [...new Set(validD2Rows.map((r) => r.relatedId))];\n\n\t\t\tlet d2CustomerMap = new Map<string, ICustomer>();\n\t\t\tlet d2EmailMap = new Map<string, ICustomerEmail[]>();\n\t\t\tlet d2PhoneMap = new Map<string, ICustomerPhone[]>();\n\t\t\tlet d2NoteMap = new Map<string, { id: string; customerId: string; authorId: string | null; body: string; createdAt: string; updatedAt: string }[]>();\n\t\t\tlet d2TagMap = new Map<string, string[]>();\n\n\t\t\tif (d2Ids.length > 0) {\n\t\t\t\t// Addresses are omitted at depth-2: family members share the parent tenant's\n\t\t\t\t// address and duplicating it inflates mobile payloads unnecessarily.\n\t\t\t\tconst [d2Customers, d2Emails, d2Phones, d2Notes, d2Tags] = await Promise.all([\n\t\t\t\t\tthis.store.query<ICustomer>(\n\t\t\t\t\t\t`SELECT ${SELECT_CUSTOMER} FROM fonderie_customers WHERE id = ANY($1::uuid[]) AND workspace_id = $2`,\n\t\t\t\t\t\t[d2Ids, workspaceId],\n\t\t\t\t\t),\n\t\t\t\t\tthis.store.query<ICustomerEmail>(\n\t\t\t\t\t\t`SELECT id, customer_id AS \"customerId\", email, label_id AS \"labelId\",\n\t\t\t\t\t\t (SELECT value FROM fonderie_customer_labels WHERE id = label_id) AS label,\n\t\t\t\t\t\t is_primary AS \"isPrimary\", created_at AS \"createdAt\"\n\t\t\t\t\t\t FROM fonderie_customer_emails WHERE customer_id = ANY($1::uuid[]) ORDER BY is_primary DESC, created_at ASC`,\n\t\t\t\t\t\t[d2Ids],\n\t\t\t\t\t),\n\t\t\t\t\tthis.store.query<ICustomerPhone>(\n\t\t\t\t\t\t`SELECT id, customer_id AS \"customerId\", phone, label_id AS \"labelId\",\n\t\t\t\t\t\t (SELECT value FROM fonderie_customer_labels WHERE id = label_id) AS label,\n\t\t\t\t\t\t is_primary AS \"isPrimary\", created_at AS \"createdAt\"\n\t\t\t\t\t\t FROM fonderie_customer_phones WHERE customer_id = ANY($1::uuid[]) ORDER BY is_primary DESC, created_at ASC`,\n\t\t\t\t\t\t[d2Ids],\n\t\t\t\t\t),\n\t\t\t\t\tthis.store.query<{ id: string; customerId: string; authorId: string | null; body: string; createdAt: string; updatedAt: string }>(\n\t\t\t\t\t\t`SELECT id, customer_id AS \"customerId\", author_id AS \"authorId\", body, created_at AS \"createdAt\", updated_at AS \"updatedAt\"\n\t\t\t\t\t\t FROM fonderie_customer_notes WHERE customer_id = ANY($1::uuid[]) ORDER BY created_at DESC`,\n\t\t\t\t\t\t[d2Ids],\n\t\t\t\t\t),\n\t\t\t\t\tthis.store.query<{ customerId: string; tag: string }>(\n\t\t\t\t\t\t`SELECT customer_id AS \"customerId\", tag FROM fonderie_customer_tags WHERE customer_id = ANY($1::uuid[]) ORDER BY tag ASC`,\n\t\t\t\t\t\t[d2Ids],\n\t\t\t\t\t),\n\t\t\t\t]);\n\n\t\t\t\td2CustomerMap = new Map(d2Customers.map((c) => [c.id, c]));\n\t\t\t\td2EmailMap = groupByCustomer(d2Emails);\n\t\t\t\td2PhoneMap = groupByCustomer(d2Phones);\n\t\t\t\td2NoteMap = groupByCustomer(d2Notes);\n\t\t\t\td2TagMap = d2Tags.reduce((m, t) => {\n\t\t\t\t\tif (!m.has(t.customerId)) m.set(t.customerId, []);\n\t\t\t\t\tm.get(t.customerId)!.push(t.tag);\n\t\t\t\t\treturn m;\n\t\t\t\t}, new Map<string, string[]>());\n\t\t\t}\n\n\t\t\tconst d2Relationships: ICustomerRelationshipExpanded[] = validD2Rows.map((d2rel) => ({\n\t\t\t\tid: d2rel.id,\n\t\t\t\tworkspaceId: d2rel.workspaceId,\n\t\t\t\tcustomerId: d2rel.customerId,\n\t\t\t\trelationship: d2rel.relationship,\n\t\t\t\tisPrimary: d2rel.isPrimary,\n\t\t\t\tcreatedAt: d2rel.createdAt,\n\t\t\t\tcustomer: {\n\t\t\t\t\t...(d2CustomerMap.get(d2rel.relatedId) ?? ({ id: d2rel.relatedId } as ICustomer)),\n\t\t\t\t\temails: d2EmailMap.get(d2rel.relatedId) ?? [],\n\t\t\t\t\tphones: d2PhoneMap.get(d2rel.relatedId) ?? [],\n\t\t\t\t\taddresses: [],\n\t\t\t\t\tnotes: d2NoteMap.get(d2rel.relatedId) ?? [],\n\t\t\t\t\ttags: d2TagMap.get(d2rel.relatedId) ?? [],\n\t\t\t\t} as ICustomerShallow,\n\t\t\t}));\n\n\t\t\tconst d2RelsByD1Id = groupByCustomer(d2Relationships);\n\n\t\t\tconst d2ExpandedRelationships = expandedRelationships.map((rel) => ({\n\t\t\t\t...rel,\n\t\t\t\tcustomer: {\n\t\t\t\t\t...rel.customer,\n\t\t\t\t\trelationships: d2RelsByD1Id.get(rel.customer.id) ?? [],\n\t\t\t\t} as ICustomerShallowD2,\n\t\t\t}));\n\n\t\t\treturn {\n\t\t\t\t...row,\n\t\t\t\temails: emailRows as unknown as ICustomerDetail['emails'],\n\t\t\t\tphones: phoneRows as unknown as ICustomerDetail['phones'],\n\t\t\t\taddresses: addressRows,\n\t\t\t\tnotes: noteRows as unknown as ICustomerDetail['notes'],\n\t\t\t\trelationships: d2ExpandedRelationships,\n\t\t\t\ttags: tagRows.map((t) => t.tag),\n\t\t\t} as ICustomerDetailD2;\n\t\t}\n\n\t\treturn {\n\t\t\t...row,\n\t\t\t// DB returns TEXT for label; cast to the narrow union that callers expect.\n\t\t\temails: emailRows as unknown as ICustomerDetail['emails'],\n\t\t\tphones: phoneRows as unknown as ICustomerDetail['phones'],\n\t\t\taddresses: addressRows,\n\t\t\tnotes: noteRows as unknown as ICustomerDetail['notes'],\n\t\t\trelationships: expandedRelationships,\n\t\t\ttags: tagRows.map((t) => t.tag),\n\t\t};\n\t}\n\n\tasync create(opts: CreateCustomerOpts): Promise<ICustomer> {\n\t\tconst referenceCode = opts.referenceCode\n\t\t\t?? await this.allocateCode(opts.workspaceId, opts.referenceCodePrefix ?? DEFAULT_REFERENCE_CODE_PREFIX);\n\n\t\tconst [row] = await this.store.query<ICustomer>(\n\t\t\t`INSERT INTO fonderie_customers\n\t\t\t (workspace_id, type, sex, first_name, last_name, company_name, avatar_url, locale, reference_code, created_by)\n\t\t\t VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)\n\t\t\t RETURNING ${SELECT_CUSTOMER}`,\n\t\t\t[\n\t\t\t\topts.workspaceId,\n\t\t\t\topts.type ?? 'individual',\n\t\t\t\topts.sex ?? 'UNKNOWN',\n\t\t\t\topts.firstName ?? null,\n\t\t\t\topts.lastName ?? null,\n\t\t\t\topts.companyName ?? null,\n\t\t\t\topts.avatarUrl ?? null,\n\t\t\t\topts.locale ?? 'en-US',\n\t\t\t\treferenceCode,\n\t\t\t\topts.createdBy ?? null,\n\t\t\t],\n\t\t);\n\t\tif (!row) throw new Error('Failed to create customer');\n\t\treturn row;\n\t}\n\n\tasync update(\n\t\tid: string,\n\t\tworkspaceId: string,\n\t\topts: UpdateCustomerOpts,\n\t\treferenceCodePrefix = DEFAULT_REFERENCE_CODE_PREFIX,\n\t): Promise<ICustomer | null> {\n\t\t// Auto-assign a reference code if the customer doesn't have one yet.\n\t\tlet autoCode: string | undefined;\n\t\tif (!opts.referenceCode) {\n\t\t\tconst current = await this.findById(id, workspaceId);\n\t\t\tif (!current) return null;\n\t\t\tif (!current.referenceCode) {\n\t\t\t\tautoCode = await this.allocateCode(workspaceId, referenceCodePrefix);\n\t\t\t}\n\t\t}\n\n\t\tconst sets: string[] = ['updated_at = now()'];\n\t\tconst params: unknown[] = [id, workspaceId];\n\n\t\tif (opts.type !== undefined) {\n\t\t\tparams.push(opts.type);\n\t\t\tsets.push(`type = $${params.length}`);\n\t\t}\n\t\tif (opts.sex !== undefined) {\n\t\t\tparams.push(opts.sex);\n\t\t\tsets.push(`sex = $${params.length}`);\n\t\t}\n\t\tif (opts.firstName !== undefined) {\n\t\t\tparams.push(opts.firstName);\n\t\t\tsets.push(`first_name = $${params.length}`);\n\t\t}\n\t\tif (opts.lastName !== undefined) {\n\t\t\tparams.push(opts.lastName);\n\t\t\tsets.push(`last_name = $${params.length}`);\n\t\t}\n\t\tif (opts.companyName !== undefined) {\n\t\t\tparams.push(opts.companyName);\n\t\t\tsets.push(`company_name = $${params.length}`);\n\t\t}\n\t\tif (opts.avatarUrl !== undefined) {\n\t\t\tparams.push(opts.avatarUrl);\n\t\t\tsets.push(`avatar_url = $${params.length}`);\n\t\t}\n\t\tif (opts.locale !== undefined) {\n\t\t\tparams.push(opts.locale);\n\t\t\tsets.push(`locale = $${params.length}`);\n\t\t}\n\t\tconst resolvedCode = opts.referenceCode ?? autoCode;\n\t\tif (resolvedCode !== undefined) {\n\t\t\tparams.push(resolvedCode);\n\t\t\tsets.push(`reference_code = $${params.length}`);\n\t\t}\n\n\t\tconst [row] = await this.store.query<ICustomer>(\n\t\t\t`UPDATE fonderie_customers\n\t\t\t SET ${sets.join(', ')}\n\t\t\t WHERE id = $1 AND workspace_id = $2\n\t\t\t RETURNING ${SELECT_CUSTOMER}`,\n\t\t\tparams,\n\t\t);\n\t\treturn row ?? null;\n\t}\n\n\tasync delete(id: string, workspaceId: string): Promise<void> {\n\t\t// Delete all sub-resources in parallel, then the customer.\n\t\t// Relationships: remove both sides (owner and target) — detach strategy,\n\t\t// related customers are left intact.\n\t\t// Addresses: delete the underlying fonderie_addresses rows directly; their\n\t\t// ON DELETE CASCADE removes the customer_addresses link automatically.\n\t\t// The remaining DB cascades on the customer row act as a safety net.\n\t\tawait Promise.all([\n\t\t\tthis.store.query(`DELETE FROM fonderie_customer_emails WHERE customer_id = $1`, [id]),\n\t\t\tthis.store.query(`DELETE FROM fonderie_customer_phones WHERE customer_id = $1`, [id]),\n\t\t\tthis.store.query(\n\t\t\t\t`DELETE FROM fonderie_addresses WHERE id IN (\n\t\t\t\t\tSELECT addr_id FROM fonderie_customer_addresses WHERE customer_id = $1\n\t\t\t\t)`,\n\t\t\t\t[id],\n\t\t\t),\n\t\t\tthis.store.query(`DELETE FROM fonderie_customer_notes WHERE customer_id = $1`, [id]),\n\t\t\tthis.store.query(`DELETE FROM fonderie_customer_tags WHERE customer_id = $1`, [id]),\n\t\t\tthis.store.query(\n\t\t\t\t`DELETE FROM fonderie_customer_relationships WHERE customer_id = $1 OR related_id = $1`,\n\t\t\t\t[id],\n\t\t\t),\n\t\t]);\n\t\tawait this.store.query(`DELETE FROM fonderie_customers WHERE id = $1 AND workspace_id = $2`, [\n\t\t\tid,\n\t\t\tworkspaceId,\n\t\t]);\n\t}\n\n\tasync blacklist(id: string, workspaceId: string, reason?: string | null): Promise<void> {\n\t\tawait this.store.query(\n\t\t\t`UPDATE fonderie_customers\n\t\t\t SET is_blacklisted = true, blacklist_reason = $3, updated_at = now()\n\t\t\t WHERE id = $1 AND workspace_id = $2`,\n\t\t\t[id, workspaceId, reason ?? null],\n\t\t);\n\t}\n\n\tasync unblacklist(id: string, workspaceId: string): Promise<void> {\n\t\tawait this.store.query(\n\t\t\t`UPDATE fonderie_customers\n\t\t\t SET is_blacklisted = false, blacklist_reason = null, updated_at = now()\n\t\t\t WHERE id = $1 AND workspace_id = $2`,\n\t\t\t[id, workspaceId],\n\t\t);\n\t}\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { ICustomerRelationship } from '../types';\n\nexport interface AddRelationshipOpts {\n\tworkspaceId: string;\n\tcustomerId: string;\n\trelatedId: string;\n\trelationship: string;\n\tisPrimary?: boolean;\n}\n\nexport class CustomerRelationshipModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tasync list(customerId: string): Promise<ICustomerRelationship[]> {\n\t\treturn this.store.query<ICustomerRelationship>(\n\t\t\t`SELECT id,\n\t\t\t workspace_id AS \"workspaceId\",\n\t\t\t customer_id AS \"customerId\",\n\t\t\t related_id AS \"relatedId\",\n\t\t\t relationship,\n\t\t\t is_primary AS \"isPrimary\",\n\t\t\t created_at AS \"createdAt\"\n\t\t\t FROM fonderie_customer_relationships\n\t\t\t WHERE customer_id = $1\n\t\t\t ORDER BY is_primary DESC, created_at ASC`,\n\t\t\t[customerId],\n\t\t);\n\t}\n\n\tasync add(opts: AddRelationshipOpts): Promise<ICustomerRelationship> {\n\t\treturn this.store.transaction(async (tx) => {\n\t\t\tif (opts.isPrimary) {\n\t\t\t\tawait tx.query(\n\t\t\t\t\t`UPDATE fonderie_customer_relationships\n\t\t\t\t\t SET is_primary = false\n\t\t\t\t\t WHERE customer_id = $1`,\n\t\t\t\t\t[opts.customerId],\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst [row] = await tx.query<ICustomerRelationship>(\n\t\t\t\t`INSERT INTO fonderie_customer_relationships\n\t\t\t\t (workspace_id, customer_id, related_id, relationship, is_primary)\n\t\t\t\t VALUES ($1, $2, $3, $4, $5)\n\t\t\t\t ON CONFLICT (customer_id, related_id) DO UPDATE\n\t\t\t\t SET relationship = EXCLUDED.relationship,\n\t\t\t\t is_primary = EXCLUDED.is_primary\n\t\t\t\t RETURNING\n\t\t\t\t id,\n\t\t\t\t workspace_id AS \"workspaceId\",\n\t\t\t\t customer_id AS \"customerId\",\n\t\t\t\t related_id AS \"relatedId\",\n\t\t\t\t relationship,\n\t\t\t\t is_primary AS \"isPrimary\",\n\t\t\t\t created_at AS \"createdAt\"`,\n\t\t\t\t[opts.workspaceId, opts.customerId, opts.relatedId, opts.relationship, opts.isPrimary ?? false],\n\t\t\t);\n\t\t\tif (!row) throw new Error('Failed to create relationship');\n\t\t\treturn row;\n\t\t});\n\t}\n\n\tasync setPrimary(customerId: string, relatedId: string): Promise<void> {\n\t\tawait this.store.transaction(async (tx) => {\n\t\t\tawait tx.query(\n\t\t\t\t`UPDATE fonderie_customer_relationships SET is_primary = false WHERE customer_id = $1`,\n\t\t\t\t[customerId],\n\t\t\t);\n\t\t\tawait tx.query(\n\t\t\t\t`UPDATE fonderie_customer_relationships\n\t\t\t\t SET is_primary = true\n\t\t\t\t WHERE customer_id = $1 AND related_id = $2`,\n\t\t\t\t[customerId, relatedId],\n\t\t\t);\n\t\t});\n\t}\n\n\tasync remove(customerId: string, relatedId: string): Promise<void> {\n\t\tawait this.store.query(\n\t\t\t`DELETE FROM fonderie_customer_relationships\n\t\t\t WHERE customer_id = $1 AND related_id = $2`,\n\t\t\t[customerId, relatedId],\n\t\t);\n\t}\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { ICustomerAddress } from '../types';\n\nconst SELECT_CUSTOMER_ADDRESS = `\n\tca.addr_id AS \"addrId\",\n\tca.customer_id AS \"customerId\",\n\tca.label_id AS \"labelId\",\n\t(SELECT value FROM fonderie_customer_labels WHERE id = ca.label_id) AS label,\n\tca.is_primary AS \"isPrimary\",\n\tjsonb_build_object(\n\t\t'id', a.id,\n\t\t'countryIso', a.country_iso,\n\t\t'subdivision1Iso', a.subdivision1_iso,\n\t\t'subdivision2Iso', a.subdivision2_iso,\n\t\t'zipPostalCode', a.zip_postal_code,\n\t\t'unit', a.unit,\n\t\t'line1', a.line1,\n\t\t'line2', a.line2\n\t) AS address\n`;\n\nexport class CustomerAddressModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tlist(customerId: string): Promise<ICustomerAddress[]> {\n\t\treturn this.store.query<ICustomerAddress>(\n\t\t\t`SELECT ${SELECT_CUSTOMER_ADDRESS}\n\t\t\t FROM fonderie_customer_addresses ca\n\t\t\t JOIN fonderie_addresses a ON a.id = ca.addr_id\n\t\t\t WHERE ca.customer_id = $1\n\t\t\t ORDER BY ca.is_primary DESC`,\n\t\t\t[customerId],\n\t\t);\n\t}\n\n\tasync add(opts: {\n\t\tcustomerId: string;\n\t\tcountryIso: string;\n\t\tsubdivision1Iso?: string | null;\n\t\tsubdivision2Iso?: string | null;\n\t\tzipPostalCode: string;\n\t\tunit?: string | null;\n\t\tline1?: string | null;\n\t\tline2?: string | null;\n\t\tlabelId: string;\n\t\tisPrimary?: boolean;\n\t}): Promise<ICustomerAddress> {\n\t\tconst [existing] = await this.store.query<{ addrId: string }>(\n\t\t\t`SELECT ca.addr_id AS \"addrId\"\n\t\t\t FROM fonderie_customer_addresses ca\n\t\t\t JOIN fonderie_addresses a ON a.id = ca.addr_id\n\t\t\t WHERE ca.customer_id = $1\n\t\t\t AND a.country_iso = $2\n\t\t\t AND a.zip_postal_code = $3\n\t\t\t AND a.subdivision1_iso IS NOT DISTINCT FROM $4\n\t\t\t AND a.subdivision2_iso IS NOT DISTINCT FROM $5\n\t\t\t AND a.unit IS NOT DISTINCT FROM $6\n\t\t\t AND a.line1 IS NOT DISTINCT FROM $7\n\t\t\t AND a.line2 IS NOT DISTINCT FROM $8\n\t\t\t LIMIT 1`,\n\t\t\t[\n\t\t\t\topts.customerId,\n\t\t\t\topts.countryIso,\n\t\t\t\topts.zipPostalCode,\n\t\t\t\topts.subdivision1Iso ?? null,\n\t\t\t\topts.subdivision2Iso ?? null,\n\t\t\t\topts.unit ?? null,\n\t\t\t\topts.line1 ?? null,\n\t\t\t\topts.line2 ?? null,\n\t\t\t],\n\t\t);\n\t\tif (existing) {\n\t\t\tthrow Object.assign(new Error('Duplicate address for this customer'), { code: 'DUPLICATE_ADDRESS' });\n\t\t}\n\n\t\treturn this.store.transaction(async (tx) => {\n\t\t\tif (opts.isPrimary) {\n\t\t\t\tawait tx.query(\n\t\t\t\t\t`UPDATE fonderie_customer_addresses\n\t\t\t\t\t SET is_primary = false\n\t\t\t\t\t WHERE customer_id = $1`,\n\t\t\t\t\t[opts.customerId],\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst [addr] = await tx.query<{ id: string }>(\n\t\t\t\t`INSERT INTO fonderie_addresses\n\t\t\t\t (id, country_iso, subdivision1_iso, subdivision2_iso, zip_postal_code, unit, line1, line2)\n\t\t\t\t VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7)\n\t\t\t\t RETURNING id`,\n\t\t\t\t[\n\t\t\t\t\topts.countryIso,\n\t\t\t\t\topts.subdivision1Iso ?? null,\n\t\t\t\t\topts.subdivision2Iso ?? null,\n\t\t\t\t\topts.zipPostalCode,\n\t\t\t\t\topts.unit ?? null,\n\t\t\t\t\topts.line1 ?? null,\n\t\t\t\t\topts.line2 ?? null,\n\t\t\t\t],\n\t\t\t);\n\t\t\tif (!addr) throw new Error('Failed to create address');\n\n\t\t\tconst [row] = await tx.query<ICustomerAddress>(\n\t\t\t\t`INSERT INTO fonderie_customer_addresses (addr_id, customer_id, label_id, is_primary)\n\t\t\t\t VALUES ($1, $2, $3, $4)\n\t\t\t\t RETURNING\n\t\t\t\t addr_id AS \"addrId\",\n\t\t\t\t customer_id AS \"customerId\",\n\t\t\t\t label_id AS \"labelId\",\n\t\t\t\t NULL::text AS label,\n\t\t\t\t is_primary AS \"isPrimary\",\n\t\t\t\t NULL::jsonb AS address`,\n\t\t\t\t[addr.id, opts.customerId, opts.labelId, opts.isPrimary ?? false],\n\t\t\t);\n\t\t\tif (!row) throw new Error('Failed to link address');\n\n\t\t\tconst [full] = await tx.query<ICustomerAddress>(\n\t\t\t\t`SELECT ${SELECT_CUSTOMER_ADDRESS}\n\t\t\t\t FROM fonderie_customer_addresses ca\n\t\t\t\t JOIN fonderie_addresses a ON a.id = ca.addr_id\n\t\t\t\t WHERE ca.addr_id = $1 AND ca.customer_id = $2`,\n\t\t\t\t[addr.id, opts.customerId],\n\t\t\t);\n\t\t\tif (!full) throw new Error('Failed to fetch created address');\n\t\t\treturn full;\n\t\t});\n\t}\n\n\tasync updateLabel(addrId: string, customerId: string, labelId: string): Promise<ICustomerAddress> {\n\t\tconst [row] = await this.store.query<ICustomerAddress>(\n\t\t\t`UPDATE fonderie_customer_addresses\n\t\t\t SET label_id = $3\n\t\t\t WHERE addr_id = $1 AND customer_id = $2\n\t\t\t RETURNING addr_id AS \"addrId\", customer_id AS \"customerId\", label_id AS \"labelId\", NULL::text AS label, is_primary AS \"isPrimary\", NULL::jsonb AS address`,\n\t\t\t[addrId, customerId, labelId],\n\t\t);\n\t\tif (!row) throw Object.assign(new Error('Address not found'), { code: 'NOT_FOUND' });\n\n\t\tconst [full] = await this.store.query<ICustomerAddress>(\n\t\t\t`SELECT ${SELECT_CUSTOMER_ADDRESS}\n\t\t\t FROM fonderie_customer_addresses ca\n\t\t\t JOIN fonderie_addresses a ON a.id = ca.addr_id\n\t\t\t WHERE ca.addr_id = $1 AND ca.customer_id = $2`,\n\t\t\t[addrId, customerId],\n\t\t);\n\t\tif (!full) throw new Error('Failed to fetch updated address');\n\t\treturn full;\n\t}\n\n\tasync setPrimary(addrId: string, customerId: string): Promise<void> {\n\t\tawait this.store.transaction(async (tx) => {\n\t\t\tawait tx.query(\n\t\t\t\t`UPDATE fonderie_customer_addresses SET is_primary = false WHERE customer_id = $1`,\n\t\t\t\t[customerId],\n\t\t\t);\n\t\t\tawait tx.query(\n\t\t\t\t`UPDATE fonderie_customer_addresses SET is_primary = true WHERE addr_id = $1 AND customer_id = $2`,\n\t\t\t\t[addrId, customerId],\n\t\t\t);\n\t\t});\n\t}\n\n\tasync remove(addrId: string, customerId: string): Promise<void> {\n\t\tawait this.store.transaction(async (tx) => {\n\t\t\tconst [deleted] = await tx.query<{ isPrimary: boolean }>(\n\t\t\t\t`DELETE FROM fonderie_customer_addresses\n\t\t\t\t WHERE addr_id = $1 AND customer_id = $2\n\t\t\t\t RETURNING is_primary AS \"isPrimary\"`,\n\t\t\t\t[addrId, customerId],\n\t\t\t);\n\t\t\tawait tx.query(`DELETE FROM fonderie_addresses WHERE id = $1`, [addrId]);\n\t\t\tif (deleted?.isPrimary) {\n\t\t\t\tawait tx.query(\n\t\t\t\t\t`UPDATE fonderie_customer_addresses\n\t\t\t\t\t SET is_primary = true\n\t\t\t\t\t WHERE addr_id = (\n\t\t\t\t\t SELECT addr_id FROM fonderie_customer_addresses\n\t\t\t\t\t WHERE customer_id = $1\n\t\t\t\t\t ORDER BY addr_id ASC\n\t\t\t\t\t LIMIT 1\n\t\t\t\t\t )`,\n\t\t\t\t\t[customerId],\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t}\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { ICustomerEmail } from '../types';\n\nconst SELECT_EMAIL = `\n\tid,\n\tcustomer_id AS \"customerId\",\n\temail,\n\tlabel_id AS \"labelId\",\n\t(SELECT value FROM fonderie_customer_labels WHERE id = label_id) AS label,\n\tis_primary AS \"isPrimary\",\n\tcreated_at AS \"createdAt\"\n`;\n\nexport class CustomerEmailModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tlist(customerId: string): Promise<ICustomerEmail[]> {\n\t\treturn this.store.query<ICustomerEmail>(\n\t\t\t`SELECT ${SELECT_EMAIL}\n\t\t\t FROM fonderie_customer_emails\n\t\t\t WHERE customer_id = $1\n\t\t\t ORDER BY is_primary DESC, created_at ASC`,\n\t\t\t[customerId],\n\t\t);\n\t}\n\n\tasync add(opts: {\n\t\tcustomerId: string;\n\t\temail: string;\n\t\tlabelId: string;\n\t\tisPrimary?: boolean;\n\t}): Promise<ICustomerEmail> {\n\t\treturn this.store.transaction(async (tx) => {\n\t\t\tif (opts.isPrimary) {\n\t\t\t\tawait tx.query(\n\t\t\t\t\t`UPDATE fonderie_customer_emails SET is_primary = false WHERE customer_id = $1`,\n\t\t\t\t\t[opts.customerId],\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst [row] = await tx.query<ICustomerEmail>(\n\t\t\t\t`INSERT INTO fonderie_customer_emails (id, customer_id, email, label_id, is_primary)\n\t\t\t\t VALUES (gen_random_uuid(), $1, $2, $3, $4)\n\t\t\t\t RETURNING ${SELECT_EMAIL}`,\n\t\t\t\t[opts.customerId, opts.email, opts.labelId, opts.isPrimary ?? false],\n\t\t\t);\n\t\t\tif (!row) throw new Error('Failed to add customer email');\n\t\t\treturn row;\n\t\t});\n\t}\n\n\tasync updateLabel(emailId: string, customerId: string, labelId: string): Promise<ICustomerEmail> {\n\t\tconst [row] = await this.store.query<ICustomerEmail>(\n\t\t\t`UPDATE fonderie_customer_emails\n\t\t\t SET label_id = $3\n\t\t\t WHERE id = $1 AND customer_id = $2\n\t\t\t RETURNING ${SELECT_EMAIL}`,\n\t\t\t[emailId, customerId, labelId],\n\t\t);\n\t\tif (!row) throw Object.assign(new Error('Email not found'), { code: 'NOT_FOUND' });\n\t\treturn row;\n\t}\n\n\tasync setPrimary(emailId: string, customerId: string): Promise<void> {\n\t\tawait this.store.transaction(async (tx) => {\n\t\t\tawait tx.query(\n\t\t\t\t`UPDATE fonderie_customer_emails SET is_primary = false WHERE customer_id = $1`,\n\t\t\t\t[customerId],\n\t\t\t);\n\t\t\tawait tx.query(\n\t\t\t\t`UPDATE fonderie_customer_emails SET is_primary = true WHERE id = $1 AND customer_id = $2`,\n\t\t\t\t[emailId, customerId],\n\t\t\t);\n\t\t});\n\t}\n\n\tasync remove(emailId: string, customerId: string): Promise<void> {\n\t\tawait this.store.transaction(async (tx) => {\n\t\t\tconst [deleted] = await tx.query<{ isPrimary: boolean }>(\n\t\t\t\t`DELETE FROM fonderie_customer_emails\n\t\t\t\t WHERE id = $1 AND customer_id = $2\n\t\t\t\t RETURNING is_primary AS \"isPrimary\"`,\n\t\t\t\t[emailId, customerId],\n\t\t\t);\n\t\t\tif (deleted?.isPrimary) {\n\t\t\t\tawait tx.query(\n\t\t\t\t\t`UPDATE fonderie_customer_emails\n\t\t\t\t\t SET is_primary = true\n\t\t\t\t\t WHERE id = (\n\t\t\t\t\t SELECT id FROM fonderie_customer_emails\n\t\t\t\t\t WHERE customer_id = $1\n\t\t\t\t\t ORDER BY created_at ASC\n\t\t\t\t\t LIMIT 1\n\t\t\t\t\t )`,\n\t\t\t\t\t[customerId],\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t}\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { CustomerLabelType, ICustomerLabel } from '../types';\n\nexport class CustomerLabelModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tlist(type: CustomerLabelType): Promise<ICustomerLabel[]> {\n\t\treturn this.store.query<ICustomerLabel>(\n\t\t\t`SELECT id, type, value, created_at AS \"createdAt\"\n\t\t\t FROM fonderie_customer_labels\n\t\t\t WHERE type = $1\n\t\t\t ORDER BY value ASC`,\n\t\t\t[type],\n\t\t);\n\t}\n\n\tremove(id: string): Promise<void> {\n\t\treturn this.store.query(\n\t\t\t`DELETE FROM fonderie_customer_labels WHERE id = $1`,\n\t\t\t[id],\n\t\t).then(() => undefined);\n\t}\n\n\tasync findOrCreate(type: CustomerLabelType, raw: string): Promise<ICustomerLabel> {\n\t\tconst value = raw.trim().toLowerCase();\n\t\tconst [row] = await this.store.query<ICustomerLabel>(\n\t\t\t`INSERT INTO fonderie_customer_labels (type, value)\n\t\t\t VALUES ($1, $2)\n\t\t\t ON CONFLICT (type, value) DO UPDATE SET value = EXCLUDED.value\n\t\t\t RETURNING id, type, value, created_at AS \"createdAt\"`,\n\t\t\t[type, value],\n\t\t);\n\t\tif (!row) throw new Error('Failed to find or create label');\n\t\treturn row;\n\t}\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { ICustomerNote } from '../types';\n\nconst SELECT_NOTE = `\n\tid,\n\tcustomer_id AS \"customerId\",\n\tauthor_id AS \"authorId\",\n\tbody,\n\tcreated_at AS \"createdAt\",\n\tupdated_at AS \"updatedAt\"\n`;\n\nexport class CustomerNoteModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tlist(customerId: string): Promise<ICustomerNote[]> {\n\t\treturn this.store.query<ICustomerNote>(\n\t\t\t`SELECT ${SELECT_NOTE}\n\t\t\t FROM fonderie_customer_notes\n\t\t\t WHERE customer_id = $1\n\t\t\t ORDER BY created_at DESC`,\n\t\t\t[customerId],\n\t\t);\n\t}\n\n\tasync create(opts: {\n\t\tcustomerId: string;\n\t\tauthorId?: string | null;\n\t\tbody: string;\n\t}): Promise<ICustomerNote> {\n\t\tconst [row] = await this.store.query<ICustomerNote>(\n\t\t\t`INSERT INTO fonderie_customer_notes (id, customer_id, author_id, body)\n\t\t\t VALUES (gen_random_uuid(), $1, $2, $3)\n\t\t\t RETURNING ${SELECT_NOTE}`,\n\t\t\t[opts.customerId, opts.authorId ?? null, opts.body],\n\t\t);\n\t\tif (!row) throw new Error('Failed to create note');\n\t\treturn row;\n\t}\n\n\tasync update(noteId: string, customerId: string, body: string): Promise<ICustomerNote | null> {\n\t\tconst [row] = await this.store.query<ICustomerNote>(\n\t\t\t`UPDATE fonderie_customer_notes\n\t\t\t SET body = $1, updated_at = now()\n\t\t\t WHERE id = $2 AND customer_id = $3\n\t\t\t RETURNING ${SELECT_NOTE}`,\n\t\t\t[body, noteId, customerId],\n\t\t);\n\t\treturn row ?? null;\n\t}\n\n\tasync delete(noteId: string, customerId: string): Promise<void> {\n\t\tawait this.store.query(\n\t\t\t`DELETE FROM fonderie_customer_notes\n\t\t\t WHERE id = $1 AND customer_id = $2`,\n\t\t\t[noteId, customerId],\n\t\t);\n\t}\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { ICustomerPhone } from '../types';\n\nconst SELECT_PHONE = `\n\tid,\n\tcustomer_id AS \"customerId\",\n\tphone,\n\tlabel_id AS \"labelId\",\n\t(SELECT value FROM fonderie_customer_labels WHERE id = label_id) AS label,\n\tis_primary AS \"isPrimary\",\n\tcreated_at AS \"createdAt\"\n`;\n\nexport class CustomerPhoneModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tlist(customerId: string): Promise<ICustomerPhone[]> {\n\t\treturn this.store.query<ICustomerPhone>(\n\t\t\t`SELECT ${SELECT_PHONE}\n\t\t\t FROM fonderie_customer_phones\n\t\t\t WHERE customer_id = $1\n\t\t\t ORDER BY is_primary DESC, created_at ASC`,\n\t\t\t[customerId],\n\t\t);\n\t}\n\n\tasync add(opts: {\n\t\tcustomerId: string;\n\t\tphone: string;\n\t\tlabelId: string;\n\t\tisPrimary?: boolean;\n\t}): Promise<ICustomerPhone> {\n\t\treturn this.store.transaction(async (tx) => {\n\t\t\tif (opts.isPrimary) {\n\t\t\t\tawait tx.query(\n\t\t\t\t\t`UPDATE fonderie_customer_phones SET is_primary = false WHERE customer_id = $1`,\n\t\t\t\t\t[opts.customerId],\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst [row] = await tx.query<ICustomerPhone>(\n\t\t\t\t`INSERT INTO fonderie_customer_phones (id, customer_id, phone, label_id, is_primary)\n\t\t\t\t VALUES (gen_random_uuid(), $1, $2, $3, $4)\n\t\t\t\t RETURNING ${SELECT_PHONE}`,\n\t\t\t\t[opts.customerId, opts.phone, opts.labelId, opts.isPrimary ?? false],\n\t\t\t);\n\t\t\tif (!row) throw new Error('Failed to add customer phone');\n\t\t\treturn row;\n\t\t});\n\t}\n\n\tasync updateLabel(phoneId: string, customerId: string, labelId: string): Promise<ICustomerPhone> {\n\t\tconst [row] = await this.store.query<ICustomerPhone>(\n\t\t\t`UPDATE fonderie_customer_phones\n\t\t\t SET label_id = $3\n\t\t\t WHERE id = $1 AND customer_id = $2\n\t\t\t RETURNING ${SELECT_PHONE}`,\n\t\t\t[phoneId, customerId, labelId],\n\t\t);\n\t\tif (!row) throw Object.assign(new Error('Phone not found'), { code: 'NOT_FOUND' });\n\t\treturn row;\n\t}\n\n\tasync setPrimary(phoneId: string, customerId: string): Promise<void> {\n\t\tawait this.store.transaction(async (tx) => {\n\t\t\tawait tx.query(\n\t\t\t\t`UPDATE fonderie_customer_phones SET is_primary = false WHERE customer_id = $1`,\n\t\t\t\t[customerId],\n\t\t\t);\n\t\t\tawait tx.query(\n\t\t\t\t`UPDATE fonderie_customer_phones SET is_primary = true WHERE id = $1 AND customer_id = $2`,\n\t\t\t\t[phoneId, customerId],\n\t\t\t);\n\t\t});\n\t}\n\n\tasync remove(phoneId: string, customerId: string): Promise<void> {\n\t\tawait this.store.transaction(async (tx) => {\n\t\t\tconst [deleted] = await tx.query<{ isPrimary: boolean }>(\n\t\t\t\t`DELETE FROM fonderie_customer_phones\n\t\t\t\t WHERE id = $1 AND customer_id = $2\n\t\t\t\t RETURNING is_primary AS \"isPrimary\"`,\n\t\t\t\t[phoneId, customerId],\n\t\t\t);\n\t\t\tif (deleted?.isPrimary) {\n\t\t\t\tawait tx.query(\n\t\t\t\t\t`UPDATE fonderie_customer_phones\n\t\t\t\t\t SET is_primary = true\n\t\t\t\t\t WHERE id = (\n\t\t\t\t\t SELECT id FROM fonderie_customer_phones\n\t\t\t\t\t WHERE customer_id = $1\n\t\t\t\t\t ORDER BY created_at ASC\n\t\t\t\t\t LIMIT 1\n\t\t\t\t\t )`,\n\t\t\t\t\t[customerId],\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t}\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nexport class CustomerTagModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tasync list(customerId: string): Promise<string[]> {\n\t\tconst rows = await this.store.query<{ tag: string }>(\n\t\t\t`SELECT tag FROM fonderie_customer_tags\n\t\t\t WHERE customer_id = $1\n\t\t\t ORDER BY tag ASC`,\n\t\t\t[customerId],\n\t\t);\n\t\treturn rows.map((r) => r.tag);\n\t}\n\n\tasync add(customerId: string, tag: string): Promise<void> {\n\t\tawait this.store.query(\n\t\t\t`INSERT INTO fonderie_customer_tags (customer_id, tag)\n\t\t\t VALUES ($1, $2)\n\t\t\t ON CONFLICT (customer_id, tag) DO NOTHING`,\n\t\t\t[customerId, tag],\n\t\t);\n\t}\n\n\tasync remove(customerId: string, tag: string): Promise<void> {\n\t\tawait this.store.query(\n\t\t\t`DELETE FROM fonderie_customer_tags\n\t\t\t WHERE customer_id = $1 AND tag = $2`,\n\t\t\t[customerId, tag],\n\t\t);\n\t}\n}\n","import type { Middleware } from '@fonderie/core';\nimport { requireAuth } from '@fonderie/core/middlewares';\nimport type { EventBus } from '@fonderie/events';\nimport type { IStoreAdapter } from '@fonderie/store';\nimport { withWorkspace } from '@fonderie/workspaces';\n\nimport type { ICustomersConfig } from './config';\nimport { customerController } from './controllers/customer.controller';\nimport { customerAddressController } from './controllers/customer-address.controller';\nimport { customerEmailController } from './controllers/customer-email.controller';\nimport { customerNoteController } from './controllers/customer-note.controller';\nimport { customerPhoneController } from './controllers/customer-phone.controller';\nimport { customerTagController } from './controllers/customer-tag.controller';\nimport { customerLabelController } from './controllers/customer-label.controller';\nimport { customerRelationshipController } from './controllers/customer-relationship.controller';\n\ntype RouteDefinition = [string, string, ...Middleware[]];\n\nexport function buildCustomerRoutes(\n\tstore: IStoreAdapter,\n\tconfig: ICustomersConfig,\n\tbus?: EventBus,\n): RouteDefinition[] {\n\tconst wsCtx = withWorkspace(store);\n\n\tconst customer = customerController(store, config, bus);\n\tconst email = customerEmailController(store);\n\tconst phone = customerPhoneController(store);\n\tconst address = customerAddressController(store);\n\tconst note = customerNoteController(store);\n\tconst tag = customerTagController(store);\n\tconst relationship = customerRelationshipController(store);\n\tconst label = customerLabelController(store);\n\n\treturn [\n\t\t// ── Labels ───────────────────────────────────────────────────────\n\t\t['GET', '/customers/labels', requireAuth, wsCtx, label.list],\n\t\t['DELETE', '/customers/labels/:labelId', requireAuth, wsCtx, label.remove],\n\n\t\t// ── Core customer CRUD ───────────────────────────────────────────\n\t\t['GET', '/customers', requireAuth, wsCtx, customer.list],\n\t\t['POST', '/customers', requireAuth, wsCtx, customer.create],\n\t\t['GET', '/customers/:customerId', requireAuth, wsCtx, customer.get],\n\t\t['PUT', '/customers/:customerId', requireAuth, wsCtx, customer.update],\n\t\t['DELETE', '/customers/:customerId', requireAuth, wsCtx, customer.delete],\n\t\t['POST', '/customers/:customerId/blacklist', requireAuth, wsCtx, customer.blacklist],\n\t\t['POST', '/customers/:customerId/unblacklist', requireAuth, wsCtx, customer.unblacklist],\n\n\t\t// ── Emails ───────────────────────────────────────────────────────\n\t\t['GET', '/customers/:customerId/emails', requireAuth, wsCtx, email.list],\n\t\t['POST', '/customers/:customerId/emails', requireAuth, wsCtx, email.add],\n\t\t['PATCH', '/customers/:customerId/emails/:emailId', requireAuth, wsCtx, email.update],\n\t\t['PUT', '/customers/:customerId/emails/:emailId/primary', requireAuth, wsCtx, email.setPrimary],\n\t\t['DELETE', '/customers/:customerId/emails/:emailId', requireAuth, wsCtx, email.remove],\n\n\t\t// ── Phones ───────────────────────────────────────────────────────\n\t\t['GET', '/customers/:customerId/phones', requireAuth, wsCtx, phone.list],\n\t\t['POST', '/customers/:customerId/phones', requireAuth, wsCtx, phone.add],\n\t\t['PATCH', '/customers/:customerId/phones/:phoneId', requireAuth, wsCtx, phone.update],\n\t\t['PUT', '/customers/:customerId/phones/:phoneId/primary', requireAuth, wsCtx, phone.setPrimary],\n\t\t['DELETE', '/customers/:customerId/phones/:phoneId', requireAuth, wsCtx, phone.remove],\n\n\t\t// ── Addresses ────────────────────────────────────────────────────\n\t\t['GET', '/customers/:customerId/addresses', requireAuth, wsCtx, address.list],\n\t\t['POST', '/customers/:customerId/addresses', requireAuth, wsCtx, address.add],\n\t\t['PATCH', '/customers/:customerId/addresses/:addrId', requireAuth, wsCtx, address.update],\n\t\t['PUT', '/customers/:customerId/addresses/:addrId/primary', requireAuth, wsCtx, address.setPrimary],\n\t\t['DELETE', '/customers/:customerId/addresses/:addrId', requireAuth, wsCtx, address.remove],\n\n\t\t// ── Notes ────────────────────────────────────────────────────────\n\t\t['GET', '/customers/:customerId/notes', requireAuth, wsCtx, note.list],\n\t\t['POST', '/customers/:customerId/notes', requireAuth, wsCtx, note.create],\n\t\t['PUT', '/customers/:customerId/notes/:noteId', requireAuth, wsCtx, note.update],\n\t\t['DELETE', '/customers/:customerId/notes/:noteId', requireAuth, wsCtx, note.delete],\n\n\t\t// ── Tags ─────────────────────────────────────────────────────────\n\t\t['GET', '/customers/:customerId/tags', requireAuth, wsCtx, tag.list],\n\t\t['POST', '/customers/:customerId/tags', requireAuth, wsCtx, tag.add],\n\t\t['DELETE', '/customers/:customerId/tags/:tag', requireAuth, wsCtx, tag.remove],\n\n\t\t// ── Relationships ────────────────────────────────────────────────────\n\t\t['GET', '/customers/:customerId/relationships', requireAuth, wsCtx, relationship.list],\n\t\t['POST', '/customers/:customerId/relationships', requireAuth, wsCtx, relationship.add],\n\t\t['PUT', '/customers/:customerId/relationships/:relatedId/primary', requireAuth, wsCtx, relationship.setPrimary],\n\t\t['DELETE', '/customers/:customerId/relationships/:relatedId', requireAuth, wsCtx, relationship.remove],\n\t];\n}\n","import type { IFonderieContext } from '@fonderie/core';\nimport { HTTP, setApiResponse } from '@fonderie/core';\nimport type { EventBus } from '@fonderie/events';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { DEFAULT_REFERENCE_CODE_PREFIX, EVENT_KEYS, type ICustomersConfig } from '../config';\nimport { toCustomerDetailD2DTO, toCustomerDetailDTO, toCustomerDTO } from '../dtos/customer';\nimport type { ICustomerDetailD2 } from '../types';\nimport { CustomerModel } from '../models/customer.model';\nimport { isUuid } from '../utils';\n\nexport function customerController(store: IStoreAdapter, config: ICustomersConfig = {}, bus?: EventBus) {\n\tconst customers = new CustomerModel(store);\n\tconst prefix = (config.referenceCodePrefix ?? DEFAULT_REFERENCE_CODE_PREFIX).toUpperCase();\n\n\treturn {\n\t\tasync list(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst workspaceId = ctx.workspace?.id;\n\t\t\tif (!workspaceId) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.BAD_REQUEST,\n\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t'Workspace context is required',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst query = ctx.request.url ? new URL(ctx.request.url).searchParams : null;\n\t\t\tconst search = query?.get('search') ?? undefined;\n\t\t\tconst archived = query?.get('blacklisted');\n\t\t\tconst limit = Number(query?.get('limit') ?? 50);\n\t\t\tconst offset = Number(query?.get('offset') ?? 0);\n\n\t\t\tconst listOpts: Parameters<typeof customers.list>[0] = {\n\t\t\t\tworkspaceId,\n\t\t\t\tlimit: Number.isFinite(limit) ? limit : 50,\n\t\t\t\toffset: Number.isFinite(offset) ? offset : 0,\n\t\t\t};\n\n\t\t\tif (search !== undefined) {\n\t\t\t\tlistOpts.search = search;\n\t\t\t}\n\n\t\t\tif (archived !== null && archived !== undefined) {\n\t\t\t\tlistOpts.blacklisted = archived === 'true' || archived === '1';\n\t\t\t}\n\n\t\t\tconst list = await customers.list(listOpts);\n\n\t\t\treturn setApiResponse(HTTP.OK, 'CUSTOMERS_FETCHED', 'Customers retrieved successfully.', {\n\t\t\t\tcustomers: list.map(toCustomerDTO),\n\t\t\t});\n\t\t},\n\n\t\tasync get(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst workspaceId = ctx.workspace?.id;\n\t\t\tif (!workspaceId) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.BAD_REQUEST,\n\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t'Workspace context is required',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst id = params?.['customerId'];\n\t\t\tif (!isUuid(id)) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'customerId must be a valid UUID');\n\t\t\t}\n\n\t\t\tconst query = ctx.request.url ? new URL(ctx.request.url).searchParams : null;\n\t\t\tconst depth = query?.get('depth') === '1' ? 1 : 2;\n\n\t\t\tif (depth === 2) {\n\t\t\t\tconst customer = await customers.findDetail(id, workspaceId, 2);\n\t\t\t\tif (!customer) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Customer not found');\n\t\t\t\treturn setApiResponse(HTTP.OK, 'CUSTOMER_FETCHED', 'Customer retrieved successfully.', toCustomerDetailD2DTO(customer));\n\t\t\t}\n\n\t\t\tconst customer = await customers.findDetail(id, workspaceId);\n\t\t\tif (!customer) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Customer not found');\n\t\t\t}\n\n\t\t\treturn setApiResponse(HTTP.OK, 'CUSTOMER_FETCHED', 'Customer retrieved successfully.', toCustomerDetailDTO(customer));\n\t\t},\n\n\t\tasync create(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst workspaceId = ctx.workspace?.id;\n\t\t\tif (!workspaceId) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.BAD_REQUEST,\n\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t'Workspace context is required',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst type = body?.['type'];\n\t\t\tconst sex = body?.['sex'];\n\t\t\tconst firstName = body?.['firstName'];\n\t\t\tconst lastName = body?.['lastName'];\n\t\t\tconst companyName = body?.['companyName'];\n\n\t\t\tconst avatarUrl = body?.['avatarUrl'];\n\t\t\tconst locale = body?.['locale'];\n\t\t\tconst referenceCode = body?.['referenceCode'];\n\n\t\t\tif (type !== undefined && type !== 'individual' && type !== 'business') {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t'INVALID_PARAMETER',\n\t\t\t\t\t'type must be individual or business',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (sex !== undefined && sex !== 'UNKNOWN' && sex !== 'MALE' && sex !== 'FEMALE') {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t'INVALID_PARAMETER',\n\t\t\t\t\t'sex must be UNKNOWN, MALE, or FEMALE',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tlet customer;\n\t\t\ttry {\n\t\t\t\tcustomer = await customers.create({\n\t\t\t\t\tworkspaceId,\n\t\t\t\t\ttype: typeof type === 'string' ? type : 'individual',\n\t\t\t\t\tsex: typeof sex === 'string' ? sex : 'UNKNOWN',\n\t\t\t\t\tfirstName: typeof firstName === 'string' ? firstName : null,\n\t\t\t\t\tlastName: typeof lastName === 'string' ? lastName : null,\n\t\t\t\t\tcompanyName: typeof companyName === 'string' ? companyName : null,\n\t\n\t\t\t\t\tavatarUrl: typeof avatarUrl === 'string' ? avatarUrl : null,\n\t\t\t\t\tlocale: typeof locale === 'string' ? locale : 'en-US',\n\t\t\t\t\treferenceCode: typeof referenceCode === 'string' ? referenceCode.toUpperCase() : undefined,\n\t\t\t\t\treferenceCodePrefix: prefix,\n\t\t\t\t\tcreatedBy: ctx.user?.id ?? null,\n\t\t\t\t});\n\t\t\t} catch (err: unknown) {\n\t\t\t\tif (err instanceof Error && err.message.includes('idx_fc_reference_code')) {\n\t\t\t\t\treturn setApiResponse(HTTP.CONFLICT, 'DUPLICATE_REFERENCE_CODE', 'A customer with this reference code already exists');\n\t\t\t\t}\n\t\t\t\tthrow err;\n\t\t\t}\n\n\t\t\tbus\n\t\t\t\t?.emit(EVENT_KEYS.customerCreated, {\n\t\t\t\t\tcustomerId: customer.id,\n\t\t\t\t\tworkspaceId: customer.workspaceId,\n\t\t\t\t})\n\t\t\t\t.catch(() => {});\n\n\t\t\treturn setApiResponse(HTTP.CREATED, 'CUSTOMER_CREATED', 'Customer created successfully.', {\n\t\t\t\tcustomer: toCustomerDTO(customer),\n\t\t\t});\n\t\t},\n\n\t\tasync update(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst workspaceId = ctx.workspace?.id;\n\t\t\tif (!workspaceId) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.BAD_REQUEST,\n\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t'Workspace context is required',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst id = params?.['customerId'];\n\t\t\tif (!isUuid(id)) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'customerId must be a valid UUID');\n\t\t\t}\n\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst opts: Parameters<typeof customers.update>[2] = {};\n\n\t\t\tif (body?.['type'] !== undefined) {\n\t\t\t\tconst t = body['type'];\n\t\t\t\tif (t !== 'individual' && t !== 'business') {\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'INVALID_PARAMETER',\n\t\t\t\t\t\t'type must be individual or business',\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\topts.type = t;\n\t\t\t}\n\n\t\t\tif (body?.['sex'] !== undefined) {\n\t\t\t\tconst s = body['sex'];\n\t\t\t\tif (s !== 'UNKNOWN' && s !== 'MALE' && s !== 'FEMALE') {\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'INVALID_PARAMETER',\n\t\t\t\t\t\t'sex must be UNKNOWN, MALE, or FEMALE',\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\topts.sex = s;\n\t\t\t}\n\n\t\t\tif (body?.['firstName'] !== undefined) {\n\t\t\t\topts.firstName = typeof body['firstName'] === 'string' ? body['firstName'] : null;\n\t\t\t}\n\n\t\t\tif (body?.['lastName'] !== undefined) {\n\t\t\t\topts.lastName = typeof body['lastName'] === 'string' ? body['lastName'] : null;\n\t\t\t}\n\n\t\t\tif (body?.['companyName'] !== undefined) {\n\t\t\t\topts.companyName = typeof body['companyName'] === 'string' ? body['companyName'] : null;\n\t\t\t}\n\n\t\t\tif (body?.['avatarUrl'] !== undefined) {\n\t\t\t\topts.avatarUrl = typeof body['avatarUrl'] === 'string' ? body['avatarUrl'] : null;\n\t\t\t}\n\n\t\t\tif (body?.['locale'] !== undefined && typeof body['locale'] === 'string') {\n\t\t\t\topts.locale = body['locale'];\n\t\t\t}\n\n\t\t\tif (body?.['referenceCode'] !== undefined && typeof body['referenceCode'] === 'string') {\n\t\t\t\topts.referenceCode = body['referenceCode'].toUpperCase();\n\t\t\t}\n\n\t\t\tconst customer = await customers.update(id, workspaceId, opts, prefix);\n\t\t\tif (!customer) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Customer not found');\n\t\t\t}\n\n\t\t\tbus\n\t\t\t\t?.emit(EVENT_KEYS.customerUpdated, {\n\t\t\t\t\tcustomerId: customer.id,\n\t\t\t\t\tworkspaceId: customer.workspaceId,\n\t\t\t\t})\n\t\t\t\t.catch(() => {});\n\n\t\t\treturn setApiResponse(HTTP.OK, 'CUSTOMER_UPDATED', 'Customer updated successfully.', {\n\t\t\t\tcustomer: toCustomerDTO(customer),\n\t\t\t});\n\t\t},\n\n\t\tasync delete(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst workspaceId = ctx.workspace?.id;\n\t\t\tif (!workspaceId) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.BAD_REQUEST,\n\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t'Workspace context is required',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst id = params?.['customerId'];\n\t\t\tif (!isUuid(id)) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'customerId must be a valid UUID');\n\t\t\t}\n\n\t\t\tconst existing = await customers.findById(id, workspaceId);\n\t\t\tif (!existing) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Customer not found');\n\t\t\t}\n\n\t\t\tawait customers.delete(id, workspaceId);\n\n\t\t\tbus\n\t\t\t\t?.emit(EVENT_KEYS.customerDeleted, {\n\t\t\t\t\tcustomerId: id,\n\t\t\t\t\tworkspaceId,\n\t\t\t\t})\n\t\t\t\t.catch(() => {});\n\n\t\t\treturn setApiResponse(HTTP.OK, 'CUSTOMER_DELETED', 'Customer deleted successfully.');\n\t\t},\n\n\t\tasync blacklist(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst workspaceId = ctx.workspace?.id;\n\t\t\tif (!workspaceId) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.BAD_REQUEST,\n\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t'Workspace context is required',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst id = params?.['customerId'];\n\t\t\tif (!isUuid(id)) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'customerId must be a valid UUID');\n\t\t\t}\n\n\t\t\tconst existing = await customers.findById(id, workspaceId);\n\t\t\tif (!existing) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Customer not found');\n\t\t\t}\n\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst reason = typeof body?.['reason'] === 'string' ? body['reason'].trim() || null : null;\n\n\t\t\tawait customers.blacklist(id, workspaceId, reason);\n\n\t\t\tbus\n\t\t\t\t?.emit(EVENT_KEYS.customerBlacklisted, {\n\t\t\t\t\tcustomerId: id,\n\t\t\t\t\tworkspaceId,\n\t\t\t\t})\n\t\t\t\t.catch(() => {});\n\n\t\t\treturn setApiResponse(HTTP.OK, 'CUSTOMER_BLACKLISTED', 'Customer blacklisted successfully.');\n\t\t},\n\n\t\tasync unblacklist(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst workspaceId = ctx.workspace?.id;\n\t\t\tif (!workspaceId) {\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.BAD_REQUEST,\n\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t'Workspace context is required',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst id = params?.['customerId'];\n\t\t\tif (!isUuid(id)) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'customerId must be a valid UUID');\n\t\t\t}\n\n\t\t\tconst existing = await customers.findById(id, workspaceId);\n\t\t\tif (!existing) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Customer not found');\n\t\t\t}\n\n\t\t\tawait customers.unblacklist(id, workspaceId);\n\n\t\t\tbus\n\t\t\t\t?.emit(EVENT_KEYS.customerUnblacklisted, {\n\t\t\t\t\tcustomerId: id,\n\t\t\t\t\tworkspaceId,\n\t\t\t\t})\n\t\t\t\t.catch(() => {});\n\n\t\t\treturn setApiResponse(HTTP.OK, 'CUSTOMER_UNBLACKLISTED', 'Customer removed from blacklist successfully.');\n\t\t},\n\t};\n}\n","const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nexport function isUuid(value: unknown): value is string {\n\treturn typeof value === 'string' && UUID_RE.test(value);\n}\n","import type { IFonderieContext } from '@fonderie/core';\nimport { HTTP, setApiResponse } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\nimport { toCustomerAddressDTO } from '../dtos/customer';\nimport { CustomerModel } from '../models/customer.model';\nimport { CustomerAddressModel } from '../models/customer-address.model';\nimport { CustomerLabelModel } from '../models/customer-label.model';\nimport { isUuid } from '../utils';\n\nexport function customerAddressController(store: IStoreAdapter) {\n\tconst customers = new CustomerModel(store);\n\tconst addresses = new CustomerAddressModel(store);\n\tconst labels = new CustomerLabelModel(store);\n\n\tasync function resolveCustomer(ctx: IFonderieContext) {\n\t\tconst workspaceId = ctx.workspace?.id;\n\t\tif (!workspaceId) {\n\t\t\treturn {\n\t\t\t\terror: setApiResponse(\n\t\t\t\t\tHTTP.BAD_REQUEST,\n\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t'Workspace context is required',\n\t\t\t\t),\n\t\t\t};\n\t\t}\n\n\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\tconst id = params?.['customerId'];\n\t\tif (!isUuid(id)) {\n\t\t\treturn {error: setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'customerId must be a valid UUID')};\n\t\t}\n\n\t\tconst customer = await customers.findById(id, workspaceId);\n\t\tif (!customer) {\n\t\t\treturn {error: setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Customer not found')};\n\t\t}\n\n\t\treturn { customer, workspaceId };\n\t}\n\n\treturn {\n\t\tasync list(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) {\n\t\t\t\treturn r.error;\n\t\t\t}\n\n\t\t\tconst list = await addresses.list(r.customer.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'ADDRESSES_FETCHED', 'Addresses retrieved successfully.', {\n\t\t\t\taddresses: list.map(toCustomerAddressDTO),\n\t\t\t});\n\t\t},\n\n\t\tasync add(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) {\n\t\t\t\treturn r.error;\n\t\t\t}\n\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst countryIso = body?.['countryIso'];\n\t\t\tconst zipPostalCode = body?.['zipPostalCode'];\n\n\t\t\tif (typeof countryIso !== 'string' || countryIso.trim().length === 0) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'countryIso is required');\n\t\t\t}\n\n\t\t\tif (typeof zipPostalCode !== 'string' || zipPostalCode.trim().length === 0) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'zipPostalCode is required');\n\t\t\t}\n\n\t\t\tconst rawLabel = typeof body?.['label'] === 'string' ? body['label'] : 'service';\n\t\t\tconst { id: labelId } = await labels.findOrCreate('address', rawLabel);\n\n\t\t\tlet created;\n\t\t\ttry {\n\t\t\t\tcreated = await addresses.add({\n\t\t\t\t\tcustomerId: r.customer.id,\n\t\t\t\t\tcountryIso: countryIso.trim(),\n\t\t\t\t\tzipPostalCode: zipPostalCode.trim(),\n\t\t\t\t\tsubdivision1Iso: typeof body?.['subdivision1Iso'] === 'string' ? body['subdivision1Iso'] : null,\n\t\t\t\t\tsubdivision2Iso: typeof body?.['subdivision2Iso'] === 'string' ? body['subdivision2Iso'] : null,\n\t\t\t\t\tunit: typeof body?.['unit'] === 'string' ? body['unit'] : null,\n\t\t\t\t\tline1: typeof body?.['line1'] === 'string' ? body['line1'] : null,\n\t\t\t\t\tline2: typeof body?.['line2'] === 'string' ? body['line2'] : null,\n\t\t\t\t\tlabelId,\n\t\t\t\t\tisPrimary: body?.['isPrimary'] === true,\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tif (err instanceof Error && (err as any).code === 'DUPLICATE_ADDRESS') {\n\t\t\t\t\treturn setApiResponse(HTTP.CONFLICT, 'DUPLICATE_ADDRESS', 'This address is already linked to this customer');\n\t\t\t\t}\n\t\t\t\tthrow err;\n\t\t\t}\n\n\t\t\treturn setApiResponse(HTTP.CREATED, 'ADDRESS_ADDED', 'Address added successfully.', {\n\t\t\t\taddress: toCustomerAddressDTO(created),\n\t\t\t});\n\t\t},\n\n\t\tasync update(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) return r.error;\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst addrId = params?.['addrId'];\n\t\t\tif (!isUuid(addrId)) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'addrId must be a valid UUID');\n\t\t\t}\n\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tif (typeof body?.['label'] !== 'string' || body['label'].trim().length === 0) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'label is required');\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst { id: labelId } = await labels.findOrCreate('address', body['label'].trim());\n\t\t\t\tconst updated = await addresses.updateLabel(addrId, r.customer.id, labelId);\n\t\t\t\treturn setApiResponse(HTTP.OK, 'ADDRESS_UPDATED', 'Address updated successfully.', {\n\t\t\t\t\taddress: toCustomerAddressDTO(updated),\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tif (err instanceof Error && (err as any).code === 'NOT_FOUND') {\n\t\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Address not found');\n\t\t\t\t}\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t},\n\n\t\tasync setPrimary(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) {\n\t\t\t\treturn r.error;\n\t\t\t}\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst addrId = params?.['addrId'];\n\t\t\tif (!isUuid(addrId)) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'addrId must be a valid UUID');\n\t\t\t}\n\n\t\t\tawait addresses.setPrimary(addrId, r.customer.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'ADDRESS_PRIMARY_SET', 'Primary address updated successfully.');\n\t\t},\n\n\t\tasync remove(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) {\n\t\t\t\treturn r.error;\n\t\t\t}\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst addrId = params?.['addrId'];\n\t\t\tif (!isUuid(addrId)) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'addrId must be a valid UUID');\n\t\t\t}\n\n\t\t\tawait addresses.remove(addrId, r.customer.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'ADDRESS_REMOVED', 'Address removed successfully.');\n\t\t},\n\t};\n}\n","import type { IFonderieContext } from '@fonderie/core';\nimport { HTTP, setApiResponse } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\nimport { toCustomerEmailDTO } from '../dtos/customer';\nimport { CustomerModel } from '../models/customer.model';\nimport { CustomerEmailModel } from '../models/customer-email.model';\nimport { CustomerLabelModel } from '../models/customer-label.model';\nimport { isUuid } from '../utils';\n\nexport function customerEmailController(store: IStoreAdapter) {\n\tconst customers = new CustomerModel(store);\n\tconst emails = new CustomerEmailModel(store);\n\tconst labels = new CustomerLabelModel(store);\n\n\tasync function resolveCustomer(ctx: IFonderieContext) {\n\t\tconst workspaceId = ctx.workspace?.id;\n\t\tif (!workspaceId) {\n\t\t\treturn {\n\t\t\t\terror: setApiResponse(\n\t\t\t\t\tHTTP.BAD_REQUEST,\n\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t'Workspace context is required',\n\t\t\t\t),\n\t\t\t};\n\t\t}\n\n\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\tconst id = params?.['customerId'];\n\t\tif (!isUuid(id)) {\n\t\t\treturn {error: setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'customerId must be a valid UUID')};\n\t\t}\n\n\t\tconst customer = await customers.findById(id, workspaceId);\n\t\tif (!customer) {\n\t\t\treturn {error: setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Customer not found')};\n\t\t}\n\n\t\treturn { customer, workspaceId };\n\t}\n\n\treturn {\n\t\tasync list(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) {\n\t\t\t\treturn r.error;\n\t\t\t}\n\n\t\t\tconst list = await emails.list(r.customer.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'EMAILS_FETCHED', 'Emails retrieved successfully.', {\n\t\t\t\temails: list.map(toCustomerEmailDTO),\n\t\t\t});\n\t\t},\n\n\t\tasync add(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) {\n\t\t\t\treturn r.error;\n\t\t\t}\n\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst email = body?.['email'];\n\t\t\tif (typeof email !== 'string' || email.trim().length === 0) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'email is required');\n\t\t\t}\n\n\t\t\tconst rawLabel = typeof body?.['label'] === 'string' ? body['label'] : 'work';\n\t\t\tconst isPrimary = body?.['isPrimary'] === true;\n\t\t\tconst { id: labelId } = await labels.findOrCreate('email', rawLabel);\n\n\t\t\tlet created;\n\t\t\ttry {\n\t\t\t\tcreated = await emails.add({\n\t\t\t\t\tcustomerId: r.customer.id,\n\t\t\t\t\temail: email.trim(),\n\t\t\t\t\tlabelId,\n\t\t\t\t\tisPrimary,\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tif (err instanceof Error && err.message.includes('idx_fce_unique_email')) {\n\t\t\t\t\treturn setApiResponse(HTTP.CONFLICT, 'DUPLICATE_EMAIL', 'This email is already linked to this customer');\n\t\t\t\t}\n\t\t\t\tthrow err;\n\t\t\t}\n\n\t\t\treturn setApiResponse(HTTP.CREATED, 'EMAIL_ADDED', 'Email added successfully.', {\n\t\t\t\temail: toCustomerEmailDTO(created),\n\t\t\t});\n\t\t},\n\n\t\tasync update(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) return r.error;\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst emailId = params?.['emailId'];\n\t\t\tif (!isUuid(emailId)) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'emailId must be a valid UUID');\n\t\t\t}\n\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tif (typeof body?.['label'] !== 'string' || body['label'].trim().length === 0) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'label is required');\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst { id: labelId } = await labels.findOrCreate('email', body['label'].trim());\n\t\t\t\tconst updated = await emails.updateLabel(emailId, r.customer.id, labelId);\n\t\t\t\treturn setApiResponse(HTTP.OK, 'EMAIL_UPDATED', 'Email updated successfully.', {\n\t\t\t\t\temail: toCustomerEmailDTO(updated),\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tif (err instanceof Error && (err as any).code === 'NOT_FOUND') {\n\t\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Email not found');\n\t\t\t\t}\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t},\n\n\t\tasync setPrimary(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) {\n\t\t\t\treturn r.error;\n\t\t\t}\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst emailId = params?.['emailId'];\n\t\t\tif (!isUuid(emailId)) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'emailId must be a valid UUID');\n\t\t\t}\n\n\t\t\tawait emails.setPrimary(emailId, r.customer.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'EMAIL_PRIMARY_SET', 'Primary email updated successfully.');\n\t\t},\n\n\t\tasync remove(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) {\n\t\t\t\treturn r.error;\n\t\t\t}\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst emailId = params?.['emailId'];\n\t\t\tif (!isUuid(emailId)) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'emailId must be a valid UUID');\n\t\t\t}\n\n\t\t\tawait emails.remove(emailId, r.customer.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'EMAIL_REMOVED', 'Email removed successfully.');\n\t\t},\n\t};\n}\n","import type { IFonderieContext } from '@fonderie/core';\nimport { HTTP, setApiResponse } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\nimport { toCustomerNoteDTO } from '../dtos/customer';\nimport { CustomerModel } from '../models/customer.model';\nimport { CustomerNoteModel } from '../models/customer-note.model';\nimport { isUuid } from '../utils';\n\nexport function customerNoteController(store: IStoreAdapter) {\n\tconst customers = new CustomerModel(store);\n\tconst notes = new CustomerNoteModel(store);\n\n\tasync function resolveCustomer(ctx: IFonderieContext) {\n\t\tconst workspaceId = ctx.workspace?.id;\n\t\tif (!workspaceId)\n\t\t\treturn {\n\t\t\t\terror: setApiResponse(\n\t\t\t\t\tHTTP.BAD_REQUEST,\n\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t'Workspace context is required',\n\t\t\t\t),\n\t\t\t};\n\n\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\tconst id = params?.['customerId'];\n\t\tif (!isUuid(id))\n\t\t\treturn { error: setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'customerId must be a valid UUID') };\n\n\t\tconst customer = await customers.findById(id, workspaceId);\n\t\tif (!customer)\n\t\t\treturn { error: setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Customer not found') };\n\n\t\treturn { customer, workspaceId };\n\t}\n\n\treturn {\n\t\tasync list(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) return r.error;\n\n\t\t\tconst list = await notes.list(r.customer.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'NOTES_FETCHED', 'Notes retrieved successfully.', {\n\t\t\t\tnotes: list.map(toCustomerNoteDTO),\n\t\t\t});\n\t\t},\n\n\t\tasync create(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) return r.error;\n\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst bodyText = body?.['body'];\n\t\t\tif (typeof bodyText !== 'string' || bodyText.trim().length === 0) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'body is required');\n\t\t\t}\n\n\t\t\tconst note = await notes.create({\n\t\t\t\tcustomerId: r.customer.id,\n\t\t\t\tauthorId: ctx.user?.id ?? null,\n\t\t\t\tbody: bodyText.trim(),\n\t\t\t});\n\n\t\t\treturn setApiResponse(HTTP.CREATED, 'NOTE_CREATED', 'Note created successfully.', {\n\t\t\t\tnote: toCustomerNoteDTO(note),\n\t\t\t});\n\t\t},\n\n\t\tasync update(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) return r.error;\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst noteId = params?.['noteId'];\n\t\t\tif (!isUuid(noteId)) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'noteId must be a valid UUID');\n\t\t\t}\n\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst bodyText = body?.['body'];\n\t\t\tif (typeof bodyText !== 'string' || bodyText.trim().length === 0) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'body is required');\n\t\t\t}\n\n\t\t\tconst note = await notes.update(noteId, r.customer.id, bodyText.trim());\n\t\t\tif (!note) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Note not found');\n\t\t\t}\n\n\t\t\treturn setApiResponse(HTTP.OK, 'NOTE_UPDATED', 'Note updated successfully.', {\n\t\t\t\tnote: toCustomerNoteDTO(note),\n\t\t\t});\n\t\t},\n\n\t\tasync delete(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) return r.error;\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst noteId = params?.['noteId'];\n\t\t\tif (!isUuid(noteId)) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'noteId must be a valid UUID');\n\t\t\t}\n\n\t\t\tawait notes.delete(noteId, r.customer.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'NOTE_DELETED', 'Note deleted successfully.');\n\t\t},\n\t};\n}\n","import type { IFonderieContext } from '@fonderie/core';\nimport { HTTP, setApiResponse } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\nimport { toCustomerPhoneDTO } from '../dtos/customer';\nimport { CustomerModel } from '../models/customer.model';\nimport { CustomerPhoneModel } from '../models/customer-phone.model';\nimport { CustomerLabelModel } from '../models/customer-label.model';\nimport { isUuid } from '../utils';\n\nexport function customerPhoneController(store: IStoreAdapter) {\n\tconst customers = new CustomerModel(store);\n\tconst phones = new CustomerPhoneModel(store);\n\tconst labels = new CustomerLabelModel(store);\n\n\tasync function resolveCustomer(ctx: IFonderieContext) {\n\t\tconst workspaceId = ctx.workspace?.id;\n\t\tif (!workspaceId) {\n\t\t\treturn {\n\t\t\t\terror: setApiResponse(\n\t\t\t\t\tHTTP.BAD_REQUEST,\n\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t'Workspace context is required',\n\t\t\t\t),\n\t\t\t};\n\t\t}\n\n\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\tconst id = params?.['customerId'];\n\t\tif (!isUuid(id)) {\n\t\t\treturn {error: setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'customerId must be a valid UUID')};\n\t\t}\n\n\t\tconst customer = await customers.findById(id, workspaceId);\n\t\tif (!customer) {\n\t\t\treturn {error: setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Customer not found')};\n\t\t}\n\n\t\treturn { customer, workspaceId };\n\t}\n\n\treturn {\n\t\tasync list(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) {\n\t\t\t\treturn r.error;\n\t\t\t}\n\n\t\t\tconst list = await phones.list(r.customer.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'PHONES_FETCHED', 'Phones retrieved successfully.', {\n\t\t\t\tphones: list.map(toCustomerPhoneDTO),\n\t\t\t});\n\t\t},\n\n\t\tasync add(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) {\n\t\t\t\treturn r.error;\n\t\t\t}\n\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst phone = body?.['phone'];\n\t\t\tif (typeof phone !== 'string' || phone.trim().length === 0) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'phone is required');\n\t\t\t}\n\n\t\t\tconst rawLabel = typeof body?.['label'] === 'string' ? body['label'] : 'mobile';\n\t\t\tconst isPrimary = body?.['isPrimary'] === true;\n\t\t\tconst { id: labelId } = await labels.findOrCreate('phone', rawLabel);\n\n\t\t\tlet created;\n\t\t\ttry {\n\t\t\t\tcreated = await phones.add({\n\t\t\t\t\tcustomerId: r.customer.id,\n\t\t\t\t\tphone: phone.trim(),\n\t\t\t\t\tlabelId,\n\t\t\t\t\tisPrimary,\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tif (err instanceof Error && err.message.includes('idx_fcp_unique_phone')) {\n\t\t\t\t\treturn setApiResponse(HTTP.CONFLICT, 'DUPLICATE_PHONE', 'This phone is already linked to this customer');\n\t\t\t\t}\n\t\t\t\tthrow err;\n\t\t\t}\n\n\t\t\treturn setApiResponse(HTTP.CREATED, 'PHONE_ADDED', 'Phone added successfully.', {\n\t\t\t\tphone: toCustomerPhoneDTO(created),\n\t\t\t});\n\t\t},\n\n\t\tasync update(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) return r.error;\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst phoneId = params?.['phoneId'];\n\t\t\tif (!isUuid(phoneId)) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'phoneId must be a valid UUID');\n\t\t\t}\n\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tif (typeof body?.['label'] !== 'string' || body['label'].trim().length === 0) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'label is required');\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst { id: labelId } = await labels.findOrCreate('phone', body['label'].trim());\n\t\t\t\tconst updated = await phones.updateLabel(phoneId, r.customer.id, labelId);\n\t\t\t\treturn setApiResponse(HTTP.OK, 'PHONE_UPDATED', 'Phone updated successfully.', {\n\t\t\t\t\tphone: toCustomerPhoneDTO(updated),\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tif (err instanceof Error && (err as any).code === 'NOT_FOUND') {\n\t\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Phone not found');\n\t\t\t\t}\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t},\n\n\t\tasync setPrimary(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) {\n\t\t\t\treturn r.error;\n\t\t\t}\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst phoneId = params?.['phoneId'];\n\t\t\tif (!isUuid(phoneId)) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'phoneId must be a valid UUID');\n\t\t\t}\n\n\t\t\tawait phones.setPrimary(phoneId, r.customer.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'PHONE_PRIMARY_SET', 'Primary phone updated successfully.');\n\t\t},\n\n\t\tasync remove(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) {\n\t\t\t\treturn r.error;\n\t\t\t}\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst phoneId = params?.['phoneId'];\n\t\t\tif (!isUuid(phoneId)) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'phoneId must be a valid UUID');\n\t\t\t}\n\n\t\t\tawait phones.remove(phoneId, r.customer.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'PHONE_REMOVED', 'Phone removed successfully.');\n\t\t},\n\t};\n}\n","import type { IFonderieContext } from '@fonderie/core';\nimport { HTTP, setApiResponse } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { CustomerModel } from '../models/customer.model';\nimport { CustomerTagModel } from '../models/customer-tag.model';\nimport { isUuid } from '../utils';\n\nexport function customerTagController(store: IStoreAdapter) {\n\tconst customers = new CustomerModel(store);\n\tconst tags = new CustomerTagModel(store);\n\n\tasync function resolveCustomer(ctx: IFonderieContext) {\n\t\tconst workspaceId = ctx.workspace?.id;\n\t\tif (!workspaceId)\n\t\t\treturn {\n\t\t\t\terror: setApiResponse(\n\t\t\t\t\tHTTP.BAD_REQUEST,\n\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t'Workspace context is required',\n\t\t\t\t),\n\t\t\t};\n\n\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\tconst id = params?.['customerId'];\n\t\tif (!isUuid(id))\n\t\t\treturn { error: setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'customerId must be a valid UUID') };\n\n\t\tconst customer = await customers.findById(id, workspaceId);\n\t\tif (!customer)\n\t\t\treturn { error: setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Customer not found') };\n\n\t\treturn { customer, workspaceId };\n\t}\n\n\treturn {\n\t\tasync list(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) return r.error;\n\n\t\t\tconst list = await tags.list(r.customer.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'TAGS_FETCHED', 'Tags retrieved successfully.', {\n\t\t\t\ttags: list,\n\t\t\t});\n\t\t},\n\n\t\tasync add(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) return r.error;\n\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst tag = body?.['tag'];\n\t\t\tif (typeof tag !== 'string' || tag.trim().length === 0) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'tag is required');\n\t\t\t}\n\n\t\t\tawait tags.add(r.customer.id, tag.trim());\n\t\t\treturn setApiResponse(HTTP.CREATED, 'TAG_ADDED', 'Tag added successfully.');\n\t\t},\n\n\t\tasync remove(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) return r.error;\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst tag = params?.['tag'];\n\t\t\tif (!tag) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'tag is required');\n\t\t\t}\n\n\t\t\tawait tags.remove(r.customer.id, tag);\n\t\t\treturn setApiResponse(HTTP.OK, 'TAG_REMOVED', 'Tag removed successfully.');\n\t\t},\n\t};\n}\n","import type { IFonderieContext } from '@fonderie/core';\nimport { HTTP, setApiResponse } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\nimport { CustomerLabelModel } from '../models/customer-label.model';\nimport type { CustomerLabelType } from '../types';\nimport { isUuid } from '../utils';\n\nconst VALID_TYPES: CustomerLabelType[] = ['phone', 'email', 'address'];\n\nexport function customerLabelController(store: IStoreAdapter) {\n\tconst labels = new CustomerLabelModel(store);\n\n\treturn {\n\t\tasync list(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst query = ctx.request.url ? new URL(ctx.request.url).searchParams : null;\n\t\t\tconst type = query?.get('type') as CustomerLabelType | null;\n\n\t\t\tif (!type || !VALID_TYPES.includes(type)) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'type must be phone, email, or address');\n\t\t\t}\n\n\t\t\tconst rows = await labels.list(type);\n\t\t\treturn setApiResponse(HTTP.OK, 'LABELS_FETCHED', 'Labels retrieved successfully.', { labels: rows });\n\t\t},\n\n\t\tasync remove(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst labelId = params?.['labelId'];\n\n\t\t\tif (!isUuid(labelId)) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'labelId must be a valid UUID');\n\t\t\t}\n\n\t\t\tawait labels.remove(labelId);\n\t\t\treturn setApiResponse(HTTP.OK, 'LABEL_DELETED', 'Label deleted successfully.');\n\t\t},\n\t};\n}\n","import type { IFonderieContext } from '@fonderie/core';\nimport { HTTP, setApiResponse } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { toCustomerRelationshipDTO } from '../dtos/customer';\nimport { CustomerModel } from '../models/customer.model';\nimport { CustomerRelationshipModel } from '../models/customer-relationship.model';\nimport { isUuid } from '../utils';\n\nexport function customerRelationshipController(store: IStoreAdapter) {\n\tconst customers = new CustomerModel(store);\n\tconst relationships = new CustomerRelationshipModel(store);\n\n\tasync function resolveCustomer(ctx: IFonderieContext) {\n\t\tconst workspaceId = ctx.workspace?.id;\n\t\tif (!workspaceId) {\n\t\t\treturn {\n\t\t\t\terror: setApiResponse(HTTP.BAD_REQUEST, 'MISSING_WORKSPACE', 'Workspace context is required'),\n\t\t\t};\n\t\t}\n\n\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\tconst id = params?.['customerId'];\n\t\tif (!isUuid(id)) {\n\t\t\treturn { error: setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'customerId must be a valid UUID') };\n\t\t}\n\n\t\tconst customer = await customers.findById(id, workspaceId);\n\t\tif (!customer) {\n\t\t\treturn { error: setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Customer not found') };\n\t\t}\n\n\t\treturn { customer, workspaceId };\n\t}\n\n\treturn {\n\t\tasync list(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) return r.error;\n\n\t\t\tconst list = await relationships.list(r.customer.id);\n\t\t\treturn setApiResponse(HTTP.OK, 'RELATIONSHIPS_FETCHED', 'Relationships retrieved successfully.', {\n\t\t\t\trelationships: list.map(toCustomerRelationshipDTO),\n\t\t\t});\n\t\t},\n\n\t\tasync add(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) return r.error;\n\n\t\t\tconst body = ctx.meta['body'] as Record<string, unknown> | undefined;\n\t\t\tconst relatedId = body?.['relatedId'];\n\t\t\tconst relationship = body?.['relationship'];\n\n\t\t\tif (!isUuid(relatedId)) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'relatedId must be a valid UUID');\n\t\t\t}\n\t\t\tif (typeof relationship !== 'string' || relationship.trim().length === 0) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'relationship is required');\n\t\t\t}\n\t\t\tif (relatedId === r.customer.id) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'A customer cannot be related to itself');\n\t\t\t}\n\n\t\t\tconst related = await customers.findById(relatedId, r.workspaceId);\n\t\t\tif (!related) {\n\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Related customer not found');\n\t\t\t}\n\n\t\t\tconst created = await relationships.add({\n\t\t\t\tworkspaceId: r.workspaceId,\n\t\t\t\tcustomerId: r.customer.id,\n\t\t\t\trelatedId,\n\t\t\t\trelationship: relationship.trim(),\n\t\t\t\tisPrimary: body?.['isPrimary'] === true,\n\t\t\t});\n\n\t\t\treturn setApiResponse(HTTP.CREATED, 'RELATIONSHIP_ADDED', 'Relationship added successfully.', {\n\t\t\t\trelationship: toCustomerRelationshipDTO(created),\n\t\t\t});\n\t\t},\n\n\t\tasync setPrimary(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) return r.error;\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst relatedId = params?.['relatedId'];\n\t\t\tif (!isUuid(relatedId)) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'relatedId must be a valid UUID');\n\t\t\t}\n\n\t\t\tawait relationships.setPrimary(r.customer.id, relatedId);\n\t\t\treturn setApiResponse(HTTP.OK, 'RELATIONSHIP_PRIMARY_SET', 'Primary relationship updated successfully.');\n\t\t},\n\n\t\tasync remove(ctx: IFonderieContext): Promise<Response> {\n\t\t\tconst r = await resolveCustomer(ctx);\n\t\t\tif ('error' in r) return r.error;\n\n\t\t\tconst params = ctx.meta['params'] as Record<string, string> | undefined;\n\t\t\tconst relatedId = params?.['relatedId'];\n\t\t\tif (!isUuid(relatedId)) {\n\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'INVALID_PARAMETER', 'relatedId must be a valid UUID');\n\t\t\t}\n\n\t\t\tawait relationships.remove(r.customer.id, relatedId);\n\t\t\treturn setApiResponse(HTTP.OK, 'RELATIONSHIP_REMOVED', 'Relationship removed successfully.');\n\t\t},\n\t};\n}\n","import type { IFonderieApp, IFonderieModule } from '@fonderie/core';\nimport type { EventBus } from '@fonderie/events';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport type { ICustomersConfig } from './config';\nimport { buildCustomerRoutes } from './routes';\n\nexport class CustomersModule implements IFonderieModule {\n\treadonly name = '@fonderie/customers';\n\treadonly deps = ['@fonderie/workspaces'];\n\n\tconstructor(\n\t\tprivate store: IStoreAdapter,\n\t\tprivate config: ICustomersConfig = {},\n\t\tprivate bus?: EventBus,\n\t) {}\n\n\tinstall(app: IFonderieApp): void {\n\t\tconst routes = buildCustomerRoutes(this.store, this.config, this.bus);\n\t\tfor (const [method, path, ...handlers] of routes) {\n\t\t\tapp.addRoute(method, path, ...handlers);\n\t\t}\n\t}\n}\n"],"mappings":"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","names":["customer","HTTP","setApiResponse","setApiResponse","HTTP","HTTP","setApiResponse","setApiResponse","HTTP","HTTP","setApiResponse","setApiResponse","HTTP","HTTP","setApiResponse","setApiResponse","HTTP","HTTP","setApiResponse","setApiResponse","HTTP","HTTP","setApiResponse","setApiResponse","HTTP","HTTP","setApiResponse","setApiResponse","HTTP"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/migrations/index.ts"],"sourcesContent":["import { createMigrationsPath } from '@fonderie/store';\n\nexport const getMigrationsPath = (): string => createMigrationsPath(import.meta.url);\n"],"mappings":";AAAA,SAAS,4BAA4B;AAE9B,IAAM,oBAAoB,MAAc,qBAAqB,YAAY,GAAG;","names":[]}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
-- fonderie_customers
|
|
2
|
+
CREATE TABLE IF NOT EXISTS fonderie_customers (
|
|
3
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
4
|
+
workspace_id UUID NOT NULL,
|
|
5
|
+
type TEXT NOT NULL DEFAULT 'individual',
|
|
6
|
+
first_name TEXT,
|
|
7
|
+
last_name TEXT,
|
|
8
|
+
company_name TEXT,
|
|
9
|
+
job_title TEXT,
|
|
10
|
+
avatar_url TEXT,
|
|
11
|
+
locale TEXT NOT NULL DEFAULT 'en-US',
|
|
12
|
+
reference_code TEXT,
|
|
13
|
+
is_blacklisted BOOLEAN NOT NULL DEFAULT false,
|
|
14
|
+
created_by UUID,
|
|
15
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
16
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
CREATE INDEX IF NOT EXISTS idx_fc_workspace
|
|
20
|
+
ON fonderie_customers (workspace_id);
|
|
21
|
+
|
|
22
|
+
CREATE INDEX IF NOT EXISTS idx_fc_workspace_blacklisted
|
|
23
|
+
ON fonderie_customers (workspace_id, is_blacklisted);
|
|
24
|
+
|
|
25
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_fc_reference_code
|
|
26
|
+
ON fonderie_customers (workspace_id, reference_code)
|
|
27
|
+
WHERE reference_code IS NOT NULL;
|
|
28
|
+
|
|
29
|
+
-- fonderie_addresses (shared; created IF NOT EXISTS so another package can also define it)
|
|
30
|
+
CREATE TABLE IF NOT EXISTS fonderie_addresses (
|
|
31
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
32
|
+
country_iso TEXT NOT NULL,
|
|
33
|
+
subdivision1_iso TEXT,
|
|
34
|
+
subdivision2_iso TEXT,
|
|
35
|
+
zip_postal_code TEXT NOT NULL,
|
|
36
|
+
line1 TEXT,
|
|
37
|
+
line2 TEXT
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
-- fonderie_customer_emails
|
|
41
|
+
CREATE TABLE IF NOT EXISTS fonderie_customer_emails (
|
|
42
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
43
|
+
customer_id UUID NOT NULL REFERENCES fonderie_customers(id) ON DELETE CASCADE,
|
|
44
|
+
email TEXT NOT NULL,
|
|
45
|
+
label TEXT NOT NULL DEFAULT 'work',
|
|
46
|
+
is_primary BOOLEAN NOT NULL DEFAULT false,
|
|
47
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
CREATE INDEX IF NOT EXISTS idx_fce_customer
|
|
51
|
+
ON fonderie_customer_emails (customer_id);
|
|
52
|
+
|
|
53
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_fce_primary
|
|
54
|
+
ON fonderie_customer_emails (customer_id)
|
|
55
|
+
WHERE is_primary = true;
|
|
56
|
+
|
|
57
|
+
-- fonderie_customer_phones
|
|
58
|
+
CREATE TABLE IF NOT EXISTS fonderie_customer_phones (
|
|
59
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
60
|
+
customer_id UUID NOT NULL REFERENCES fonderie_customers(id) ON DELETE CASCADE,
|
|
61
|
+
phone TEXT NOT NULL,
|
|
62
|
+
label TEXT NOT NULL DEFAULT 'mobile',
|
|
63
|
+
is_primary BOOLEAN NOT NULL DEFAULT false,
|
|
64
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
CREATE INDEX IF NOT EXISTS idx_fcp_customer
|
|
68
|
+
ON fonderie_customer_phones (customer_id);
|
|
69
|
+
|
|
70
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_fcp_primary
|
|
71
|
+
ON fonderie_customer_phones (customer_id)
|
|
72
|
+
WHERE is_primary = true;
|
|
73
|
+
|
|
74
|
+
-- fonderie_customer_addresses (junction)
|
|
75
|
+
CREATE TABLE IF NOT EXISTS fonderie_customer_addresses (
|
|
76
|
+
addr_id UUID NOT NULL REFERENCES fonderie_addresses(id) ON DELETE CASCADE,
|
|
77
|
+
customer_id UUID NOT NULL REFERENCES fonderie_customers(id) ON DELETE CASCADE,
|
|
78
|
+
label TEXT NOT NULL DEFAULT 'service',
|
|
79
|
+
is_primary BOOLEAN NOT NULL DEFAULT false,
|
|
80
|
+
PRIMARY KEY (addr_id, customer_id)
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
CREATE INDEX IF NOT EXISTS idx_fca_customer
|
|
84
|
+
ON fonderie_customer_addresses (customer_id);
|
|
85
|
+
|
|
86
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_fca_primary
|
|
87
|
+
ON fonderie_customer_addresses (customer_id)
|
|
88
|
+
WHERE is_primary = true;
|
|
89
|
+
|
|
90
|
+
-- fonderie_customer_notes
|
|
91
|
+
CREATE TABLE IF NOT EXISTS fonderie_customer_notes (
|
|
92
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
93
|
+
customer_id UUID NOT NULL REFERENCES fonderie_customers(id) ON DELETE CASCADE,
|
|
94
|
+
author_id UUID,
|
|
95
|
+
body TEXT NOT NULL,
|
|
96
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
97
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
CREATE INDEX IF NOT EXISTS idx_fcn_customer
|
|
101
|
+
ON fonderie_customer_notes (customer_id);
|
|
102
|
+
|
|
103
|
+
-- fonderie_customer_tags
|
|
104
|
+
CREATE TABLE IF NOT EXISTS fonderie_customer_tags (
|
|
105
|
+
customer_id UUID NOT NULL REFERENCES fonderie_customers(id) ON DELETE CASCADE,
|
|
106
|
+
tag TEXT NOT NULL,
|
|
107
|
+
PRIMARY KEY (customer_id, tag)
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
CREATE INDEX IF NOT EXISTS idx_fct_tag
|
|
111
|
+
ON fonderie_customer_tags (tag);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
-- No-op: sequence table stays (workspace_id, prefix) — per-prefix counters are intentional.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE fonderie_customers DROP COLUMN IF EXISTS job_title;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
-- Remove duplicate emails per customer, keeping the primary or the oldest row.
|
|
2
|
+
DELETE FROM fonderie_customer_emails
|
|
3
|
+
WHERE id NOT IN (
|
|
4
|
+
SELECT DISTINCT ON (customer_id, email)
|
|
5
|
+
id
|
|
6
|
+
FROM fonderie_customer_emails
|
|
7
|
+
ORDER BY customer_id, email, is_primary DESC, created_at ASC
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_fce_unique_email
|
|
11
|
+
ON fonderie_customer_emails (customer_id, email);
|
|
12
|
+
|
|
13
|
+
-- Remove duplicate phones per customer, keeping the primary or the oldest row.
|
|
14
|
+
DELETE FROM fonderie_customer_phones
|
|
15
|
+
WHERE id NOT IN (
|
|
16
|
+
SELECT DISTINCT ON (customer_id, phone)
|
|
17
|
+
id
|
|
18
|
+
FROM fonderie_customer_phones
|
|
19
|
+
ORDER BY customer_id, phone, is_primary DESC, created_at ASC
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_fcp_unique_phone
|
|
23
|
+
ON fonderie_customer_phones (customer_id, phone);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
DO $$
|
|
2
|
+
BEGIN
|
|
3
|
+
IF EXISTS (
|
|
4
|
+
SELECT 1 FROM information_schema.columns
|
|
5
|
+
WHERE table_name = 'fonderie_customers' AND column_name = 'is_archived'
|
|
6
|
+
) THEN
|
|
7
|
+
ALTER TABLE fonderie_customers RENAME COLUMN is_archived TO is_blacklisted;
|
|
8
|
+
END IF;
|
|
9
|
+
END $$;
|
|
10
|
+
|
|
11
|
+
DROP INDEX IF EXISTS idx_fc_workspace_archived;
|
|
12
|
+
CREATE INDEX IF NOT EXISTS idx_fc_workspace_blacklisted
|
|
13
|
+
ON fonderie_customers (workspace_id, is_blacklisted);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
-- Unit number on shared addresses table
|
|
2
|
+
ALTER TABLE fonderie_addresses
|
|
3
|
+
ADD COLUMN IF NOT EXISTS unit TEXT;
|
|
4
|
+
|
|
5
|
+
-- Links two customers with a typed, directional relationship
|
|
6
|
+
CREATE TABLE IF NOT EXISTS fonderie_customer_relationships (
|
|
7
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
8
|
+
workspace_id UUID NOT NULL,
|
|
9
|
+
customer_id UUID NOT NULL REFERENCES fonderie_customers(id) ON DELETE CASCADE,
|
|
10
|
+
related_id UUID NOT NULL REFERENCES fonderie_customers(id) ON DELETE CASCADE,
|
|
11
|
+
relationship TEXT NOT NULL,
|
|
12
|
+
is_primary BOOLEAN NOT NULL DEFAULT false,
|
|
13
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
14
|
+
UNIQUE (customer_id, related_id)
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
CREATE INDEX IF NOT EXISTS idx_fcrel_customer
|
|
18
|
+
ON fonderie_customer_relationships (customer_id);
|
|
19
|
+
|
|
20
|
+
CREATE INDEX IF NOT EXISTS idx_fcrel_related
|
|
21
|
+
ON fonderie_customer_relationships (related_id);
|
|
22
|
+
|
|
23
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_fcrel_primary
|
|
24
|
+
ON fonderie_customer_relationships (customer_id)
|
|
25
|
+
WHERE is_primary = true;
|