@accounter/server
Version:
Accounter GraphQL server
155 lines (154 loc) • 6.27 kB
TypeScript
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>;