UNPKG

@accounter/server

Version:
155 lines (154 loc) 6.27 kB
import type { Client, PoolClient } from 'pg'; import type { FixtureBusinesses, FixtureTaxCategories } from './fixture-types.js'; /** * Valid financial entity types based on database schema */ export type FinancialEntityType = 'business' | 'tax_category' | 'tag'; export interface EnsureFinancialEntityParams { id?: string; name: string; type: FinancialEntityType; ownerId?: string; } export interface FinancialEntityResult { id: string; } /** * Ensure a financial entity exists in the database (idempotent) * * **Idempotency Behavior:** * - If an entity with matching (name, type, owner_id) exists, returns existing entity ID * - Does NOT update existing entities - preserves all existing field values * - If no match found, inserts new entity and returns new ID * - Safe to call multiple times with same parameters * * **NULL Handling:** * - `ownerId: undefined` and `ownerId: null` are treated identically (as NULL in DB) * - Correctly handles SQL NULL semantics in uniqueness check * * @param client - PostgreSQL client (must be within a transaction for rollback safety) * @param params - Entity parameters * @param params.name - Entity name (required, non-empty) * @param params.type - Entity type (must be 'business', 'tax_category', or 'tag') * @param params.ownerId - Optional owner entity ID (must exist in financial_entities if provided) * @returns Promise resolving to object containing entity id * @throws {EntityValidationError} If name is empty or type is invalid * @throws {SeedError} If database operation fails * * @example * ```typescript * // Create standalone business entity * const { id: adminId } = await ensureFinancialEntity(client, { * name: 'Admin Business', * type: 'business', * }); * * // Create owned tax category * const { id: taxId } = await ensureFinancialEntity(client, { * name: 'VAT Category', * type: 'tax_category', * ownerId: adminId, * }); * * // Calling again returns same ID (idempotent) * const { id: sameId } = await ensureFinancialEntity(client, { * name: 'Admin Business', * type: 'business', * }); * // sameId === adminId * ``` */ export declare function ensureFinancialEntity(client: PoolClient | Client, params: EnsureFinancialEntityParams): Promise<FinancialEntityResult>; export type EnsureBusinessForEntityOptions = Partial<Omit<FixtureBusinesses['businesses'][number], 'id'>>; /** * Ensure a business row exists for a given financial entity id (idempotent) * * **Idempotency Behavior:** * - If business already exists for the given entity ID, does nothing * - Does NOT update existing business configuration - preserves all existing values * - If business doesn't exist, creates new row with provided options * - Safe to call multiple times with same entityId * * **Foreign Key Requirement:** * - The entityId MUST correspond to an existing financial_entities record * - Will validate entity exists before attempting insert * * @param client - PostgreSQL client (must be within a transaction for rollback safety) * @param entityId - Financial entity ID to use as business ID (must exist in financial_entities) * @param options - Optional business configuration * @param options.noInvoicesRequired - Whether invoices are required (default: false) * @returns Promise resolving when complete * @throws {EntityValidationError} If entityId is invalid format * @throws {EntityNotFoundError} If financial entity doesn't exist * @throws {SeedError} If database operation fails * * @example * ```typescript * // First create financial entity * const { id: entityId } = await ensureFinancialEntity(client, { * name: 'My Business', * type: 'business', * }); * * // Then create business record * await ensureBusinessForEntity(client, entityId, { * noInvoicesRequired: true, * }); * * // Calling again is safe (no-op) * await ensureBusinessForEntity(client, entityId, { * noInvoicesRequired: false, // Ignored - existing value preserved * }); * ``` */ export declare function ensureBusinessForEntity(client: PoolClient | Client, entityId: string, options?: EnsureBusinessForEntityOptions): Promise<void>; export type EnsureTaxCategoryForEntityOptions = Partial<Omit<FixtureTaxCategories['taxCategories'][number], 'id'>> & { sortCode?: number; }; /** * Ensure a tax_categories row exists for a given financial entity id (idempotent) * * **Idempotency Behavior:** * - If tax category already exists for the given entity ID, does nothing * - Does NOT update existing tax category - preserves all existing values * - If tax category doesn't exist, creates new row * - Safe to call multiple times with same entityId * * **Foreign Key Requirement:** * - The entityId MUST correspond to an existing financial_entities record * - Will validate entity exists before attempting insert * * **Name Handling:** * - Tax category names live on the financial_entities table * - This table only ensures the linkage row exists * * @param client - PostgreSQL client (must be within a transaction for rollback safety) * @param entityId - Financial entity ID to use as tax category ID (must exist in financial_entities) * @param options - Optional tax category configuration * @param options.sortCode - Optional sort code for accounting systems * @returns Promise resolving when complete * @throws {EntityValidationError} If entityId is invalid format * @throws {EntityNotFoundError} If financial entity doesn't exist * @throws {SeedError} If database operation fails * * @example * ```typescript * // First create financial entity * const { id: entityId } = await ensureFinancialEntity(client, { * name: 'VAT Category', * type: 'tax_category', * }); * * // Then create tax category record * await ensureTaxCategoryForEntity(client, entityId, { * sortCode: 1000, * }); * * // Calling again is safe (no-op) * await ensureTaxCategoryForEntity(client, entityId, { * sortCode: 2000, // Ignored - existing value preserved * }); * ``` */ export declare function ensureTaxCategoryForEntity(client: PoolClient | Client, entityId: string, options?: EnsureTaxCategoryForEntityOptions): Promise<void>; export declare function setAdminEntity(client: PoolClient): Promise<string>;