@accounter/server
Version:
Accounter GraphQL server
164 lines (143 loc) • 5.05 kB
text/typescript
import { Injectable, Scope } from 'graphql-modules';
// eslint-disable-next-line no-restricted-imports
import type { PoolClient } from 'pg';
import { sql } from '@pgtyped/runtime';
import { UUID_REGEX } from '../../../shared/constants.js';
import { makeUUID } from '../../../shared/helpers/deterministic-uuid.js';
import { TenantAwareDBClient } from '../../app-providers/tenant-db-client.js';
import type {
IEnsureBusinessQuery,
IEnsureCountryQuery,
IEnsureFinancialEntityQuery,
IEnsureTaxCategoryQuery,
} from '../types.js';
export type FinancialEntityType = 'business' | 'tax_category' | 'tag';
const VALID_ENTITY_TYPES: readonly FinancialEntityType[] = ['business', 'tax_category', 'tag'];
export interface EnsureFinancialEntityParams {
id?: string;
name: string;
type: FinancialEntityType;
ownerId?: string;
}
export interface EnsureBusinessOptions {
ownerId?: string;
country?: string;
isDocumentsOptional?: boolean;
hebrewName?: string;
address?: string;
city?: string;
zipCode?: string;
email?: string;
website?: string;
phoneNumber?: string;
governmentId?: string;
exemptDealer?: boolean;
optionalVat?: boolean;
isReceiptEnough?: boolean;
}
export interface EnsureTaxCategoryOptions {
ownerId?: string;
hashavshevetName?: string;
taxExcluded?: boolean;
}
const ensureFinancialEntity = sql<IEnsureFinancialEntityQuery>`
INSERT INTO accounter_schema.financial_entities (id, name, type, owner_id)
VALUES ($id, $name, $type, $ownerId)
ON CONFLICT (id) DO NOTHING;
`;
const ensureBusiness = sql<IEnsureBusinessQuery>`
INSERT INTO accounter_schema.businesses (
id, hebrew_name, address, city, zip_code, email, website, phone_number,
vat_number, exempt_dealer, optional_vat, country,
can_settle_with_receipt, no_invoices_required, owner_id
)
VALUES (
$id, $hebrewName, $address, $city, $zipCode, $email, $website, $phoneNumber,
$governmentId, $exemptDealer, $optionalVat, $country,
$isReceiptEnough, $isDocumentsOptional, $ownerId
)
ON CONFLICT (id) DO NOTHING;
`;
const ensureTaxCategory = sql<IEnsureTaxCategoryQuery>`
INSERT INTO accounter_schema.tax_categories (id, hashavshevet_name, tax_excluded, owner_id)
VALUES ($id, $hashavshevetName, $taxExcluded, $ownerId)
ON CONFLICT (id) DO NOTHING;
`;
const ensureCountry = sql<IEnsureCountryQuery>`
INSERT INTO accounter_schema.countries (code, name)
VALUES ($code, $name)
ON CONFLICT (code) DO NOTHING;
`;
@Injectable({ scope: Scope.Operation, global: true })
export class EntityEnsureProvider {
constructor(private db: TenantAwareDBClient) {}
async ensureFinancialEntity(
params: EnsureFinancialEntityParams,
client?: PoolClient,
): Promise<{ id: string }> {
const { name, type, ownerId, id: originId } = params;
if (!name || name.trim() === '') {
throw new Error('ensureFinancialEntity: name is required');
}
if (!VALID_ENTITY_TYPES.includes(type)) {
throw new Error(`ensureFinancialEntity: invalid type "${type}"`);
}
const compositeKey = ownerId ? `${name}:owner=${ownerId}` : name;
const id = originId ?? makeUUID(type, compositeKey);
await ensureFinancialEntity.run(
{ id, name, type, ownerId: ownerId ?? null },
client ?? this.db,
);
return { id };
}
async ensureBusinessForEntity(
entityId: string,
options?: EnsureBusinessOptions,
client?: PoolClient,
): Promise<void> {
if (!UUID_REGEX.test(entityId)) {
throw new Error(`ensureBusinessForEntity: invalid entityId "${entityId}"`);
}
await ensureBusiness.run(
{
id: entityId,
hebrewName: options?.hebrewName ?? null,
address: options?.address ?? null,
city: options?.city ?? null,
zipCode: options?.zipCode ?? null,
email: options?.email ?? null,
website: options?.website ?? null,
phoneNumber: options?.phoneNumber ?? null,
governmentId: options?.governmentId ?? null,
exemptDealer: options?.exemptDealer ?? false,
optionalVat: options?.optionalVat ?? false,
country: options?.country ?? 'ISR',
isReceiptEnough: options?.isReceiptEnough ?? false,
isDocumentsOptional: options?.isDocumentsOptional ?? false,
ownerId: options?.ownerId ?? null,
},
client ?? this.db,
);
}
async ensureTaxCategoryForEntity(
entityId: string,
options?: EnsureTaxCategoryOptions,
client?: PoolClient,
): Promise<void> {
if (!UUID_REGEX.test(entityId)) {
throw new Error(`ensureTaxCategoryForEntity: invalid entityId "${entityId}"`);
}
await ensureTaxCategory.run(
{
id: entityId,
hashavshevetName: options?.hashavshevetName ?? null,
taxExcluded: options?.taxExcluded ?? false,
ownerId: options?.ownerId ?? null,
},
client ?? this.db,
);
}
async ensureCountry(code: string, name: string, client?: PoolClient): Promise<void> {
await ensureCountry.run({ code, name }, client ?? this.db);
}
}