feat(notes): add YsterbaardNote type
This commit is contained in:
commit
f3e297b9c8
1 changed files with 583 additions and 0 deletions
583
Workflow/lib/types.ts
Normal file
583
Workflow/lib/types.ts
Normal file
|
|
@ -0,0 +1,583 @@
|
|||
// lib/types.ts
|
||||
// Mirrors the two PocketBase collections used by the app. Field names
|
||||
// match the live PB schema exactly (case-sensitive). Optional fields
|
||||
// are nullable on the wire; we narrow them to `T | null` so callers
|
||||
// have to handle empty / missing cases.
|
||||
|
||||
export interface YsterbaardDirector {
|
||||
[key: string]: unknown;
|
||||
id: string;
|
||||
collectionId: string;
|
||||
collectionName: string;
|
||||
/** 13-digit SA ID number, e.g. "8001015009087". */
|
||||
id_no: string;
|
||||
/** SARS income-tax reference number for the director (added 2026-09-16). */
|
||||
tax_reference_no: number | null;
|
||||
surname: string;
|
||||
first_names: string;
|
||||
/** True if Ysterbaard / SJ is the appointed accountant for this director. */
|
||||
is_client: boolean;
|
||||
/** Free-text from CIPC: "Active", "Disqualified", etc. */
|
||||
disqualification_status: string | null;
|
||||
/** SA-format phone or empty. Pattern ^[0-9 +()\-]{0,30}$ enforced in PB. */
|
||||
telephone: string | null;
|
||||
email: string | null;
|
||||
total_companies: number;
|
||||
active_companies: number;
|
||||
inactive_companies: number;
|
||||
first_seen: string | null; // ISO date
|
||||
last_checked: string | null; // ISO date
|
||||
notes: string | null;
|
||||
// ─── Structured address fields (added 2026-08-18 from onboarding form) ───
|
||||
/** Flat address — back-compat. Use line_1 + line_2 + town_city + province + postal_code. */
|
||||
residential_address: string | null;
|
||||
residential_address_line_1: string | null;
|
||||
residential_address_line_2: string | null;
|
||||
residential_town_city: string | null;
|
||||
residential_province: string | null;
|
||||
residential_postal_code: string | null;
|
||||
postal_address: string | null;
|
||||
postal_address_line_1: string | null;
|
||||
postal_address_line_2: string | null;
|
||||
postal_town_city: string | null;
|
||||
postal_province: string | null;
|
||||
postal_postal_code: string | null;
|
||||
/** ISO date string YYYY-MM-DD. */
|
||||
date_of_birth: string | null;
|
||||
created: string;
|
||||
updated: string;
|
||||
}
|
||||
|
||||
export interface YsterbaardGroup {
|
||||
[key: string]: unknown;
|
||||
id: string;
|
||||
collectionId: string;
|
||||
collectionName: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
created: string;
|
||||
updated: string;
|
||||
}
|
||||
|
||||
export interface YsterbaardCompany {
|
||||
[key: string]: unknown;
|
||||
id: string;
|
||||
collectionId: string;
|
||||
collectionName: string;
|
||||
/** Unique CIPC enterprise number, e.g. "K2020123456". */
|
||||
enterprise_no: string;
|
||||
enterprise_name: string;
|
||||
/** e.g. "In Business", "Deregistered", "In Process". */
|
||||
enterprise_status: string | null;
|
||||
/** Per-director status: "ACTIVE", "INACTIVE", "DISQUALIFIED", etc. */
|
||||
director_status: string | null;
|
||||
is_active: boolean;
|
||||
/** True if Ysterbaard / SJ is the appointed accountant for this company. */
|
||||
is_client: boolean;
|
||||
/** Relation -> ysterbaard_directors. Expanded manually below. */
|
||||
director: string;
|
||||
/** Relation -> groups (multiple). Expanded manually below. */
|
||||
groups: string[];
|
||||
/** Expanded groups payload (when `?expand=groups` is used). */
|
||||
expand?: {
|
||||
director?: Pick<
|
||||
YsterbaardDirector,
|
||||
"id" | "id_no" | "surname" | "first_names" | "is_client"
|
||||
>;
|
||||
groups?: YsterbaardGroup[];
|
||||
/** Tax representative payload (when `?expand=tax_representative` is
|
||||
* used). Only the fields shown in the Companies tab — added 2026-09-16. */
|
||||
tax_representative?: Pick<
|
||||
YsterbaardDirector,
|
||||
"id" | "id_no" | "surname" | "first_names" | "tax_reference_no"
|
||||
>;
|
||||
};
|
||||
source: string | null;
|
||||
notes: string | null;
|
||||
enterprise_type: string | null;
|
||||
registration_date: string | null;
|
||||
physical_address: string | null;
|
||||
postal_address: string | null;
|
||||
physical_address_normalized: string | null;
|
||||
postal_address_normalized: string | null;
|
||||
company_email: string | null;
|
||||
// ─── Structured address fields (added 2026-08-18 from onboarding form) ───
|
||||
physical_address_line_1: string | null;
|
||||
physical_address_line_2: string | null;
|
||||
physical_town_city: string | null;
|
||||
physical_province: string | null;
|
||||
physical_postal_code: string | null;
|
||||
postal_address_line_1: string | null;
|
||||
postal_address_line_2: string | null;
|
||||
postal_town_city: string | null;
|
||||
postal_province: string | null;
|
||||
postal_postal_code: string | null;
|
||||
// ─── Company-level fields (added 2026-08-18 from onboarding form) ───
|
||||
/** 1-12, month when the company financial year ends. */
|
||||
financial_year_end_month: number | null;
|
||||
/** Total number of authorised shares (e.g. 100 or 1000). */
|
||||
authorised_shares: number | null;
|
||||
/** Company cell phone (different from director mobile). */
|
||||
company_cell_phone: string | null;
|
||||
/** SARS "Public Officer" — typically the accountant / responsible person. */
|
||||
public_officer_name: string | null;
|
||||
/** Company secretary name. */
|
||||
company_secretary_name: string | null;
|
||||
/** Client relationship status (added 2026-08-20). */
|
||||
client_status: "active_client" | "dormant_client" | "deregistered" | "non_client" | null;
|
||||
sars_tax_number: string | null;
|
||||
uif_registration_number: string | null;
|
||||
compensation_fund_registration_number: string | null;
|
||||
/** SARS eFiling username */
|
||||
sars_login_username: string | null;
|
||||
/** SARS eFiling password */
|
||||
sars_login_password: string | null;
|
||||
/** SARS PAYE reference number */
|
||||
sars_paye_no: string | null;
|
||||
/** SARS VAT registration number */
|
||||
sars_vat_no: string | null;
|
||||
/** Relation -> ysterbaard_directors (single). The company's tax
|
||||
* representative — a director who is also the SARS tax rep on this
|
||||
* entity. Created 2026-09-16. */
|
||||
tax_representative: string;
|
||||
/** SARS sends an SMS one-time PIN on every login as this account
|
||||
* (added 2026-09-11). When true, the SARS login script pauses for
|
||||
* the agent to inject the PIN via CDP instead of waiting the full
|
||||
* dashboard timeout. */
|
||||
otp: boolean;
|
||||
compliance_notice: string | null;
|
||||
bbbee_status: string | null;
|
||||
bbbee_certificate_applied: boolean;
|
||||
info_regulator_registered: boolean;
|
||||
ar_outstanding: boolean;
|
||||
ar_outstanding_details: string | null;
|
||||
last_5_ars: unknown[] | Record<string, unknown> | null;
|
||||
enterprise_history: unknown[] | Record<string, unknown> | null;
|
||||
first_seen: string | null;
|
||||
last_checked: string | null;
|
||||
created: string;
|
||||
updated: string;
|
||||
}
|
||||
|
||||
/** PocketBase paginated response envelope. */
|
||||
export interface PBList<T> {
|
||||
page: number;
|
||||
perPage: number;
|
||||
totalItems: number;
|
||||
totalPages: number;
|
||||
items: T[];
|
||||
}
|
||||
|
||||
/** Standard query params we forward to the proxy on every list call. */
|
||||
export interface ListParams {
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
sort?: string;
|
||||
filter?: string;
|
||||
expand?: string;
|
||||
fields?: string;
|
||||
}
|
||||
|
||||
export type CompanyStatusFilter = "all" | "active" | "inactive" | "in_process" | "deregistered";
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Onboarding intake — paperless replacement for the Annexure 1/2/3 pack.
|
||||
// A single submission holds company + directors + shareholders as JSON
|
||||
// blobs, with uploaded documents attached. Once processed into the live
|
||||
// `ysterbaard_directors` records, the row stays around for audit until
|
||||
// archived (status='rejected') and deleted by hand.
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface OnboardingDirector {
|
||||
/** Generated client-side, never persisted as-is — just a UI key. */
|
||||
ui_key: string;
|
||||
first_names: string;
|
||||
surname: string;
|
||||
id_no: string;
|
||||
date_of_birth: string; // YYYY-MM-DD
|
||||
residential_address: string;
|
||||
residential_address_line_1: string;
|
||||
residential_address_line_2: string;
|
||||
residential_town_city: string;
|
||||
residential_province: string;
|
||||
residential_postal_code: string;
|
||||
postal_address: string;
|
||||
postal_address_line_1: string;
|
||||
postal_address_line_2: string;
|
||||
postal_town_city: string;
|
||||
postal_province: string;
|
||||
postal_postal_code: string;
|
||||
cell_phone: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface OnboardingShareholder {
|
||||
ui_key: string;
|
||||
/** "person" or "entity" — toggles whether the next field validates as ID or company number. */
|
||||
kind: "person" | "entity";
|
||||
name: string;
|
||||
id_or_company_no: string;
|
||||
share_percent: number;
|
||||
}
|
||||
|
||||
export interface OnboardingCompany {
|
||||
proposed_names: [string, string, string]; // up to 3 in priority order
|
||||
financial_year_end_month: number; // 1-12
|
||||
authorised_shares: "100" | "1000";
|
||||
physical_address: string;
|
||||
physical_address_line_1: string;
|
||||
physical_address_line_2: string;
|
||||
physical_town_city: string;
|
||||
physical_province: string;
|
||||
physical_postal_code: string;
|
||||
postal_address: string;
|
||||
postal_address_line_1: string;
|
||||
postal_address_line_2: string;
|
||||
postal_town_city: string;
|
||||
postal_province: string;
|
||||
postal_postal_code: string;
|
||||
company_cell_phone: string;
|
||||
company_email: string;
|
||||
public_officer_name: string;
|
||||
company_secretary_name: string;
|
||||
}
|
||||
|
||||
export type OnboardingDirectorPayload = Omit<OnboardingDirector, "ui_key">;
|
||||
export type OnboardingShareholderPayload = Omit<OnboardingShareholder, "ui_key">;
|
||||
|
||||
export interface OnboardingSubmissionPayload {
|
||||
/** Client may omit this — server mints a fresh reference_no on submit. */
|
||||
reference_no?: string;
|
||||
client_name: string;
|
||||
client_email: string;
|
||||
client_telephone: string;
|
||||
/** Free-form notes from the client (may have been captured via voice). */
|
||||
client_notes?: string;
|
||||
company: OnboardingCompany;
|
||||
directors: OnboardingDirectorPayload[];
|
||||
shareholders: OnboardingShareholderPayload[];
|
||||
}
|
||||
|
||||
export type OnboardingStatus =
|
||||
| "draft"
|
||||
| "submitted"
|
||||
| "processing"
|
||||
| "processed"
|
||||
| "rejected";
|
||||
|
||||
export interface OnboardingSubmission {
|
||||
[key: string]: unknown;
|
||||
id: string;
|
||||
collectionId: string;
|
||||
collectionName: string;
|
||||
created: string;
|
||||
updated: string;
|
||||
reference_no: string;
|
||||
access_token_hash: string;
|
||||
client_name: string;
|
||||
client_email: string;
|
||||
client_telephone: string | null;
|
||||
company_data: OnboardingCompany;
|
||||
directors_data: OnboardingDirector[];
|
||||
shareholders_data: OnboardingShareholder[] | null;
|
||||
processed_director_ids: string[] | null;
|
||||
documents: string[];
|
||||
submitted_at: string | null;
|
||||
processed_at: string | null;
|
||||
internal_notes: string | null;
|
||||
status: OnboardingStatus;
|
||||
}
|
||||
|
||||
export type ResolutionType =
|
||||
| "company_registration"
|
||||
| "director_resignation"
|
||||
| "director_appointment_acceptance"
|
||||
| "resolution_appointment_resignation_directors"
|
||||
| "general_resolution";
|
||||
|
||||
export type ResolutionStatus = "draft" | "pending" | "completed" | "archived";
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Document management — types and document types taxonomy.
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
export type DocumentContext = "director" | "company";
|
||||
|
||||
export interface YsterbaardDocumentType {
|
||||
[key: string]: unknown;
|
||||
id: string;
|
||||
collectionId: string;
|
||||
collectionName: string;
|
||||
name: string;
|
||||
/** "director" | "company" */
|
||||
context: DocumentContext;
|
||||
description: string | null;
|
||||
created: string;
|
||||
updated: string;
|
||||
}
|
||||
|
||||
export interface YsterbaardDirectorDocument {
|
||||
[key: string]: unknown;
|
||||
id: string;
|
||||
collectionId: string;
|
||||
collectionName: string;
|
||||
director: string;
|
||||
document_type: string;
|
||||
file: string;
|
||||
original_name: string;
|
||||
notes: string | null;
|
||||
uploaded_at: string;
|
||||
created: string;
|
||||
updated: string;
|
||||
expand?: {
|
||||
document_type?: Pick<YsterbaardDocumentType, "id" | "name" | "description">;
|
||||
};
|
||||
}
|
||||
|
||||
export interface YsterbaardCompanyDocument {
|
||||
[key: string]: unknown;
|
||||
id: string;
|
||||
collectionId: string;
|
||||
collectionName: string;
|
||||
company: string;
|
||||
document_type: string;
|
||||
file: string;
|
||||
original_name: string;
|
||||
notes: string | null;
|
||||
uploaded_at: string;
|
||||
created: string;
|
||||
updated: string;
|
||||
expand?: {
|
||||
document_type?: Pick<YsterbaardDocumentType, "id" | "name" | "description">;
|
||||
};
|
||||
}
|
||||
|
||||
export interface YsterbaardResolution {
|
||||
[key: string]: unknown;
|
||||
id: string;
|
||||
collectionId: string;
|
||||
collectionName: string;
|
||||
/** Resolution type drives which tab it appears under. */
|
||||
type: ResolutionType;
|
||||
/** Workflow state of this resolution. */
|
||||
status: ResolutionStatus;
|
||||
/** Short human-readable label, e.g. "Onboarding — M Smith". */
|
||||
title: string | null;
|
||||
/** Related company (optional — not all resolutions are company-specific). */
|
||||
company: string | null;
|
||||
/** Related director (optional). */
|
||||
director: string | null;
|
||||
/** For onboarding: the client's full name. */
|
||||
client_name: string | null;
|
||||
/** For onboarding: SA ID number. */
|
||||
client_id_no: string | null;
|
||||
/** For onboarding: client's email address. */
|
||||
client_email: string | null;
|
||||
/** Free-form notes, e.g. "CIPC returned — resubmit". */
|
||||
notes: string | null;
|
||||
/** ISO date string of the resolution itself. */
|
||||
resolution_date: string | null;
|
||||
created: string;
|
||||
updated: string;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Combined Resolution of Directors (Phase 4+) — appointment + resignation in
|
||||
// one minute. Mirrors PB collection `ysterbaard_resolutions` (id: pbc_266170881).
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
export type NewResolutionStatus =
|
||||
| "draft"
|
||||
| "pending_signatures"
|
||||
| "signed"
|
||||
| "cancelled";
|
||||
|
||||
export type ResolutionPath = "letters_received" | "letter_generated";
|
||||
|
||||
export interface YsterbaardNewResolution {
|
||||
[key: string]: unknown;
|
||||
id: string;
|
||||
collectionId: string;
|
||||
collectionName: string;
|
||||
/** Relation -> ysterbaard_companies (single, required). */
|
||||
company: string;
|
||||
/** ISO date YYYY-MM-DD — meeting date. */
|
||||
meeting_date: string;
|
||||
/** Workflow state. */
|
||||
status: NewResolutionStatus;
|
||||
/** How the resolution letter is sourced. */
|
||||
path: ResolutionPath;
|
||||
/** Path B only: PDF generated from the master template. */
|
||||
generated_letter: string | null;
|
||||
/** Final assembled PDF with all signatures embedded. */
|
||||
signed_letter: string | null;
|
||||
/** Relation[] -> directors who are resigning. */
|
||||
resigned_directors: string[];
|
||||
/** Relation[] -> directors who are being appointed. */
|
||||
appointed_directors: string[];
|
||||
/** Free-text who created this (e.g. "sj@wfsales.co.za"). */
|
||||
created_by: string | null;
|
||||
/** Internal notes. */
|
||||
notes: string | null;
|
||||
/** Relation[] -> resolution attendees. */
|
||||
attendees: string[];
|
||||
created: string;
|
||||
updated: string;
|
||||
/** Client-side hydration (assembled in the API route). */
|
||||
expand?: {
|
||||
company?: Pick<
|
||||
YsterbaardCompany,
|
||||
"id" | "enterprise_name" | "enterprise_no"
|
||||
>;
|
||||
resigned_directors?: Pick<
|
||||
YsterbaardDirector,
|
||||
"id" | "first_names" | "surname"
|
||||
>[];
|
||||
appointed_directors?: Pick<
|
||||
YsterbaardDirector,
|
||||
"id" | "first_names" | "surname"
|
||||
>[];
|
||||
attendees?: Array<
|
||||
Pick<YsterbaardDirector, "id" | "first_names" | "surname"> & {
|
||||
must_sign?: boolean;
|
||||
}
|
||||
>;
|
||||
};
|
||||
/** Signature progress (assembled in API route, not stored in PB). */
|
||||
signature_progress?: {
|
||||
total: number;
|
||||
signed: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface YsterbaardDirectorAppointment {
|
||||
[key: string]: unknown;
|
||||
id: string;
|
||||
collectionId: string;
|
||||
collectionName: string;
|
||||
director: string;
|
||||
company: string;
|
||||
status: "pending" | "mail_sent" | "signature_received" | "mail_sent_to_all";
|
||||
letter: string | null;
|
||||
signature: string | null;
|
||||
sent_to: string | null;
|
||||
sent_at: string | null;
|
||||
sign_token: string | null;
|
||||
expand?: {
|
||||
director?: Pick<
|
||||
YsterbaardDirector,
|
||||
"id" | "first_names" | "surname" | "email" | "cell" | "id_no"
|
||||
>;
|
||||
company?: Pick<
|
||||
YsterbaardCompany,
|
||||
"id" | "enterprise_name" | "enterprise_no"
|
||||
>;
|
||||
};
|
||||
created: string;
|
||||
updated: string;
|
||||
}
|
||||
|
||||
|
||||
export interface YsterbaardDirectorResignation {
|
||||
[key: string]: unknown;
|
||||
id: string;
|
||||
collectionId: string;
|
||||
collectionName: string;
|
||||
director: string;
|
||||
company: string;
|
||||
status: "pending" | "mail_sent" | "signature_received" | "mail_sent_to_all";
|
||||
letter: string | null;
|
||||
signature: string | null;
|
||||
sent_to: string | null;
|
||||
sent_at: string | null;
|
||||
sign_token: string | null;
|
||||
expand?: {
|
||||
director?: Pick<
|
||||
YsterbaardDirector,
|
||||
"id" | "first_names" | "surname" | "email" | "cell" | "id_no"
|
||||
>;
|
||||
company?: Pick<
|
||||
YsterbaardCompany,
|
||||
"id" | "enterprise_name" | "enterprise_no"
|
||||
>;
|
||||
};
|
||||
created: string;
|
||||
updated: string;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// SARS PAYE EMP201 monthly submissions — SARS Employee's Tax declaration.
|
||||
//
|
||||
// `Return_Filed` is the source of truth for whether the period was filed.
|
||||
// Number fields (paye_amount, sdl_amount, uif_amount, total_payable) are
|
||||
// zero-coerced by PB when Return_Filed is false, so don't rely on them
|
||||
// alone — check `Return_Filed` first.
|
||||
//
|
||||
// `company` is a real PocketBase relation to ysterbaard_companies; the
|
||||
// record stores the related record's ID as a string, so the API route can
|
||||
// filter with `company = '<id>'` directly.
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface YsterbaardSarsEmp201Paye {
|
||||
[key: string]: unknown;
|
||||
id: string;
|
||||
collectionId: string;
|
||||
collectionName: string;
|
||||
/** Company ID (relation -> ysterbaard_companies). */
|
||||
company: string;
|
||||
/** Period in YYYYMM format, e.g. "202609" = September 2026. */
|
||||
period: string;
|
||||
/** PAYE amount paid. */
|
||||
paye_amount: number;
|
||||
/** SDL amount paid. */
|
||||
sdl_amount: number;
|
||||
/** UIF amount paid. */
|
||||
uif_amount: number;
|
||||
/** Total paid (paye + sdl + uif + interest + penalties). */
|
||||
total_payable: number;
|
||||
/** Interest charged. */
|
||||
interest_d: number;
|
||||
/** Penalties charged. */
|
||||
penalties_d: number;
|
||||
/** Workflow status, e.g. "submitted" | "draft". */
|
||||
status: string;
|
||||
/** ISO date when the return was submitted to SARS. Empty if not submitted. */
|
||||
date_submitted: string;
|
||||
/** ISO date when the payment was made. Empty if not paid. */
|
||||
date_paid: string;
|
||||
/** ISO date when the return was due (day 7 of next month per SJ's rule). */
|
||||
due_date: string;
|
||||
/** SARS payment reference number. */
|
||||
payment_reference: string;
|
||||
/** Composite key, e.g. "<companyId>_202608". */
|
||||
company_period_key: string;
|
||||
/** Free-text notes. */
|
||||
notes: string;
|
||||
/** Whether this row is currently active / in use. */
|
||||
is_active: boolean;
|
||||
/** Source of truth for whether the return was actually filed to SARS. */
|
||||
Return_Filed: boolean;
|
||||
/** Last time this row was verified against SARS (set by push-to-PB script). */
|
||||
last_updated: string;
|
||||
created: string;
|
||||
updated: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* PocketBase collection: ysterbaard_notes
|
||||
* Plain-text personal checklist items. No relations to companies / directors
|
||||
* in v1. `done_at` is set server-side when `is_done` flips to true, and
|
||||
* cleared when it flips back to false.
|
||||
*/
|
||||
export interface YsterbaardNote {
|
||||
[key: string]: unknown;
|
||||
id: string;
|
||||
collectionId: string;
|
||||
collectionName: string;
|
||||
title: string;
|
||||
content: string | null;
|
||||
is_done: boolean;
|
||||
done_at: string | null;
|
||||
created: string;
|
||||
updated: string;
|
||||
}
|
||||
Loading…
Reference in a new issue