commit f3e297b9c8bc5bee08faad7cc5e520bf82209eb7 Author: sjcoetzeesj-coder Date: Wed Sep 16 14:26:52 2026 +0200 feat(notes): add YsterbaardNote type diff --git a/Workflow/lib/types.ts b/Workflow/lib/types.ts new file mode 100644 index 0000000..80d530c --- /dev/null +++ b/Workflow/lib/types.ts @@ -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 | null; + enterprise_history: unknown[] | Record | null; + first_seen: string | null; + last_checked: string | null; + created: string; + updated: string; +} + +/** PocketBase paginated response envelope. */ +export interface PBList { + 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; +export type OnboardingShareholderPayload = Omit; + +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; + }; +} + +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; + }; +} + +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 & { + 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 = ''` 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. "_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; +}