/* * Copyright (c) 2025 Aldrik Ramaekers * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "config.hpp" #include "simclist.h" #define MAX_LEN_SEQ_NUM 16 #define MAX_LEN_ID 16 #define MAX_LEN_COUNTRY_CODE 3 #define MAX_LEN_CODE 5 #define MAX_LEN_ADDRESS 128 #define MAX_LEN_FILENAME 255 #define MAX_LEN_PATH 4096 #define MAX_LEN_CURRENCY 8 #define MAX_LEN_SHORT_DESC 64 #define MAX_LEN_LONG_DESC 256 #define MAX_LEN_EMAIL 64 #define MAX_LEN_PHONE 16 #define MAX_LEN_BANK 35 #define MAX_LEN_TAXID 32 #define MAX_LEN_BUSINESSID 32 #define MAX_LEN_TAX_SECTION 16 #define MAX_LEN_API_KEY 256 #define MAX_LEN_TAX_REPORT_LINES 50 #define MAX_LEN_TAX_REPORT_QUARTERS 400 #define MAX_LEN_INCOME_STATEMENT_REPORT_QUARTERS 400 #define MAX_LEN_QUARTERLY_REPORT_PROJECTS 200 #define MAX_LEN_PROJECT_REPORT_COSTCENTERS 50 #define MAX_BILLING_ITEMS 50 #define MY_COMPANY_ID "C/1" typedef struct { char id[MAX_LEN_ID]; // T/[id] char country_code[MAX_LEN_COUNTRY_CODE]; // 2 letter country code float rate; // 0-100% char category_code[MAX_LEN_CODE]; // Category code. https://docs.peppol.eu/poacc/billing/3.0/codelist/UNCL5305/ } tax_rate; typedef struct { char id[MAX_LEN_ID]; // E/[id] char code[MAX_LEN_CODE]; // Internal 4 letter code char description[MAX_LEN_LONG_DESC]; } cost_center; typedef struct { char address1[MAX_LEN_ADDRESS]; char address2[MAX_LEN_ADDRESS]; char city[MAX_LEN_ADDRESS]; char postal[MAX_LEN_ADDRESS]; char region[MAX_LEN_ADDRESS]; char country_code[MAX_LEN_COUNTRY_CODE]; // 2 letter country code } address; typedef struct { char name[MAX_LEN_LONG_DESC]; address address; } delivery_info; typedef enum { CONTACT_BUSINESS, CONTACT_CONSUMER, } contact_type; typedef struct { char id[MAX_LEN_ID]; // C/[id] char name[MAX_LEN_LONG_DESC]; address address; contact_type type; char taxid[MAX_LEN_TAXID]; // Required for business contact char businessid[MAX_LEN_BUSINESSID]; // Required for business contact char email[MAX_LEN_EMAIL]; char phone_number[MAX_LEN_PHONE]; char bank_account[MAX_LEN_BANK]; } contact; typedef enum { PROJECT_RUNNING, PROJECT_PAUSED, PROJECT_CANCELLED, } project_state; typedef struct { char id[MAX_LEN_ID]; // P/[id] char description[MAX_LEN_LONG_DESC]; project_state state; time_t start_date; time_t end_date; // Set when project is cancelled } project; typedef enum { INVOICE_CONCEPT, INVOICE_SENT, INVOICE_REMINDED, INVOICE_PAID, INVOICE_EXPIRED, INVOICE_CANCELLED, INVOICE_REFUNDED, INVOICE_CORRECTED, INVOICE_RECEIVED, INVOICE_END, } invoice_status; typedef struct { char id[MAX_LEN_ID]; // B/[id] float amount; bool amount_is_percentage; char description[MAX_LEN_LONG_DESC]; float net_per_item; // Net per item before discount. float discount; bool discount_is_percentage; float allowance; // Total discount. float net; // Total net, with discount. char currency[MAX_LEN_CURRENCY]; // 3 letter code, always matches invoice currency. char tax_rate_id[MAX_LEN_ID]; // T/[id] float tax; float total; } billing_item; /** * UN/CEFACT Payment Means Code (UNCL4461) * Source: https://docs.peppol.eu/poacc/billing/3.0/codelist/UNCL4461/ */ typedef enum { PAYMENT_METHOD_INSTRUMENT_NOT_DEFINED = 1, PAYMENT_METHOD_ACH_CREDIT = 2, PAYMENT_METHOD_ACH_DEBIT = 3, PAYMENT_METHOD_ACH_DEMAND_DEBIT_REVERSAL = 4, PAYMENT_METHOD_ACH_DEMAND_CREDIT_REVERSAL = 5, PAYMENT_METHOD_ACH_DEMAND_CREDIT = 6, PAYMENT_METHOD_ACH_DEMAND_DEBIT = 7, PAYMENT_METHOD_HOLD = 8, PAYMENT_METHOD_NATIONAL_REGIONAL_CLEARING = 9, PAYMENT_METHOD_CASH = 10, PAYMENT_METHOD_ACH_SAVINGS_CREDIT_REVERSAL = 11, PAYMENT_METHOD_ACH_SAVINGS_DEBIT_REVERSAL = 12, PAYMENT_METHOD_ACH_SAVINGS_CREDIT = 13, PAYMENT_METHOD_ACH_SAVINGS_DEBIT = 14, PAYMENT_METHOD_BOOKENTRY_CREDIT = 15, PAYMENT_METHOD_BOOKENTRY_DEBIT = 16, PAYMENT_METHOD_ACH_DEMAND_CCD_CREDIT = 17, PAYMENT_METHOD_ACH_DEMAND_CCD_DEBIT = 18, PAYMENT_METHOD_ACH_DEMAND_CTP_CREDIT = 19, PAYMENT_METHOD_CHEQUE = 20, PAYMENT_METHOD_BANKERS_DRAFT = 21, PAYMENT_METHOD_CERTIFIED_BANKERS_DRAFT = 22, PAYMENT_METHOD_BANK_CHEQUE = 23, PAYMENT_METHOD_BILL_OF_EXCHANGE_AWAITING_ACCEPTANCE = 24, PAYMENT_METHOD_CERTIFIED_CHEQUE = 25, PAYMENT_METHOD_LOCAL_CHEQUE = 26, PAYMENT_METHOD_ACH_DEMAND_CTP_DEBIT = 27, PAYMENT_METHOD_ACH_DEMAND_CTX_CREDIT = 28, PAYMENT_METHOD_ACH_DEMAND_CTX_DEBIT = 29, PAYMENT_METHOD_CREDIT_TRANSFER = 30, PAYMENT_METHOD_DEBIT_TRANSFER = 31, PAYMENT_METHOD_ACH_DEMAND_CCD_PLUS_CREDIT = 32, PAYMENT_METHOD_ACH_DEMAND_CCD_PLUS_DEBIT = 33, PAYMENT_METHOD_ACH_PPD = 34, PAYMENT_METHOD_ACH_SAVINGS_CCD_CREDIT = 35, PAYMENT_METHOD_ACH_SAVINGS_CCD_DEBIT = 36, PAYMENT_METHOD_ACH_SAVINGS_CTP_CREDIT = 37, PAYMENT_METHOD_ACH_SAVINGS_CTP_DEBIT = 38, PAYMENT_METHOD_ACH_SAVINGS_CTX_CREDIT = 39, PAYMENT_METHOD_ACH_SAVINGS_CTX_DEBIT = 40, PAYMENT_METHOD_ACH_SAVINGS_CCD_PLUS_CREDIT = 41, PAYMENT_METHOD_PAYMENT_TO_BANK_ACCOUNT = 42, PAYMENT_METHOD_ACH_SAVINGS_CCD_PLUS_DEBIT = 43, PAYMENT_METHOD_ACCEPTED_BILL_OF_EXCHANGE = 44, PAYMENT_METHOD_REFERENCED_HOME_BANKING_CREDIT_TRANSFER = 45, PAYMENT_METHOD_INTERBANK_DEBIT_TRANSFER = 46, PAYMENT_METHOD_HOME_BANKING_DEBIT_TRANSFER = 47, PAYMENT_METHOD_BANK_CARD = 48, PAYMENT_METHOD_DIRECT_DEBIT = 49, PAYMENT_METHOD_PAYMENT_BY_POSTGIRO = 50, PAYMENT_METHOD_FR_NORME_6_97_CFONB_OPTION_A = 51, PAYMENT_METHOD_URGENT_COMMERCIAL_PAYMENT = 52, PAYMENT_METHOD_URGENT_TREASURY_PAYMENT = 53, PAYMENT_METHOD_CREDIT_CARD = 54, PAYMENT_METHOD_DEBIT_CARD = 55, PAYMENT_METHOD_BANKGIRO = 56, PAYMENT_METHOD_STANDING_AGREEMENT = 57, PAYMENT_METHOD_SEPA_CREDIT_TRANSFER = 58, PAYMENT_METHOD_SEPA_DIRECT_DEBIT = 59, PAYMENT_METHOD_PROMISSORY_NOTE = 60, PAYMENT_METHOD_PROMISSORY_NOTE_SIGNED_BY_DEBTOR = 61, PAYMENT_METHOD_PROMISSORY_NOTE_SIGNED_BY_DEBTOR_AND_ENDORSED_BY_BANK = 62, PAYMENT_METHOD_PROMISSORY_NOTE_SIGNED_BY_DEBTOR_AND_ENDORSED_BY_THIRD_PARTY = 63, PAYMENT_METHOD_PROMISSORY_NOTE_SIGNED_BY_BANK = 64, PAYMENT_METHOD_PROMISSORY_NOTE_SIGNED_BY_BANK_AND_ENDORSED_BY_ANOTHER_BANK = 65, PAYMENT_METHOD_PROMISSORY_NOTE_SIGNED_BY_THIRD_PARTY = 66, PAYMENT_METHOD_PROMISSORY_NOTE_SIGNED_BY_THIRD_PARTY_AND_ENDORSED_BY_BANK = 67, PAYMENT_METHOD_ONLINE_PAYMENT_SERVICE = 68, PAYMENT_METHOD_TRANSFER_ADVICE = 69, PAYMENT_METHOD_BILL_DRAWN_BY_CREDITOR_ON_DEBTOR = 70, PAYMENT_METHOD_BILL_DRAWN_BY_CREDITOR_ON_BANK = 74, PAYMENT_METHOD_BILL_DRAWN_BY_CREDITOR_ENDORSED_BY_ANOTHER_BANK = 75, PAYMENT_METHOD_BILL_DRAWN_BY_CREDITOR_ON_BANK_ENDORSED_BY_THIRD_PARTY = 76, PAYMENT_METHOD_BILL_DRAWN_BY_CREDITOR_ON_THIRD_PARTY = 77, PAYMENT_METHOD_BILL_DRAWN_BY_CREDITOR_ON_THIRD_PARTY_ACCEPTED_AND_ENDORSED_BY_BANK = 78, PAYMENT_METHOD_NOT_TRANSFERABLE_BANKERS_DRAFT = 91, PAYMENT_METHOD_NOT_TRANSFERABLE_LOCAL_CHEQUE = 92, PAYMENT_METHOD_REFERENCE_GIRO = 93, PAYMENT_METHOD_URGENT_GIRO = 94, PAYMENT_METHOD_FREE_FORMAT_GIRO = 95, PAYMENT_METHOD_REQUESTED_METHOD_NOT_USED = 96, PAYMENT_METHOD_CLEARING_BETWEEN_PARTNERS = 97, PAYMENT_METHOD_JP_ELECTRONICALLY_RECORDED_MONETARY_CLAIMS = 98, PAYMENT_METHOD_MUTUALLY_DEFINED = -1 /** ZZZ */ } payment_method; typedef struct { char payee_bank_account[MAX_LEN_BANK]; // Recipient IBAN or BBAN account. char payee_account_name[MAX_LEN_LONG_DESC]; // Name of account where payment is made to. char service_provider_id[MAX_LEN_LONG_DESC]; // BIC or national clearing code. char payer_bank_account[MAX_LEN_BANK]; // Sender IBAN or BBAN account. payment_method payment_method; } payment_information; typedef struct { float total; float tax; float net; float allowance; } tax_subtotal; typedef struct { char original_path[MAX_LEN_PATH]; char copy_path[MAX_LEN_PATH]; } document; typedef struct { char id[MAX_LEN_ID]; // I/[id] char sequential_number[MAX_LEN_SEQ_NUM]; // INV0000000000 - INV9999999999 time_t issued_at; time_t expires_at; time_t delivered_at; document document; // path to copy of document for incomming invoice. char project_id[MAX_LEN_ID]; // Optional. char cost_center_id[MAX_LEN_ID]; // For incomming invoices. optional. list_t billing_items; // Calculated from billing items, represented in invoice currency. float orig_total, orig_tax, orig_net, orig_allowance; // Represented in main currency. float total, tax, net, allowance; char currency[MAX_LEN_CURRENCY]; // 3 letter code bool is_triangulation; // True if addressee != customer invoice_status status; bool is_outgoing; // Outgoing or incomming invoice. payment_information payment_means; contact supplier; contact customer; delivery_info addressee; } invoice; typedef struct { char cost_center_id[MAX_LEN_ID]; char description[MAX_LEN_LONG_DESC]; float total; bool expense_used_in_project; } project_expense; typedef struct { char project_id[MAX_LEN_ID]; char description[MAX_LEN_LONG_DESC]; u32 expense_count; project_expense expenses[MAX_LEN_PROJECT_REPORT_COSTCENTERS]; float expenses_total; // Sum of all expenses. (incl uncategorized) float revenue; float taxes; } project_report; typedef struct { float uncategorized_expenses; // Sum of expenses without project. float uncategorized_revenue; float uncategorized_taxes; float profit; u32 report_count; project_report reports[MAX_LEN_QUARTERLY_REPORT_PROJECTS]; u16 year; // 00-99 u8 quarter; // 0-3 bool is_empty; char quarter_str[MAX_LEN_SHORT_DESC]; } quarterly_report; typedef struct { u32 quarter_count; quarterly_report quarters[MAX_LEN_INCOME_STATEMENT_REPORT_QUARTERS]; } income_statement; typedef struct { char tax_category[MAX_LEN_SHORT_DESC]; float total_net; float total_tax; } tax_line; typedef struct { bool is_empty; u16 year; // 00-99 u8 quarter; // 0-3 char quarter_str[MAX_LEN_SHORT_DESC]; u32 line_count; tax_line lines[MAX_LEN_TAX_REPORT_LINES]; } tax_report; typedef struct { u32 quarter_count; tax_report reports[MAX_LEN_TAX_REPORT_QUARTERS]; } tax_statement; // Administration callback functions. // These are called when adding/updating/deleting entries. // These are NOT called when using import functions. typedef void (*data_changed_event)(); typedef void (*data_deleted_event)(char id[MAX_LEN_ID]); typedef void (*invoice_changed_event)(invoice* invoice); typedef void (*contact_changed_event)(contact* contact); typedef void (*taxrate_changed_event)(tax_rate* rate); typedef void (*costcenter_changed_event)(cost_center* cost_center); typedef void (*project_changed_event)(project* project); typedef enum { AI_PROVIDER_OPENAI = 0, AI_PROVIDER_DEEPSEEK = 1, AI_PROVIDER_END, } ai_provider; typedef struct { ai_provider provider; char api_key_public[MAX_LEN_API_KEY]; } ai_service; typedef struct { contact company_info; // Company info used for invoices. User cannot create invoices when this is empty/invalid. s32 next_id; // Id increment shared across all objects that have an ID. s32 next_sequence_number; // Sequence number for generating invoice numbers. char path[MAX_LEN_PATH]; // Full path to save file of current administration file. char program_version[10]; // Program version of exe that stored the save file. char default_currency[MAX_LEN_CURRENCY]; // Currency where invoice totals and reports are represented in, based on company location. list_t contacts; list_t projects; list_t tax_rates; list_t cost_centers; u32 invoice_count; u32 expense_count; list_t invoices; // Service providers. ai_service ai_service; } ledger; // Add/Update result codes. #define A_ERR_SUCCESS 0 #define A_ERR_GENERIC (1ULL << 0) #define A_ERR_NOT_FOUND (1ULL << 1) #define A_ERR_MISSING_DESCRIPTION (1ULL << 2) #define A_ERR_CODE_EXISTS (1ULL << 3) #define A_ERR_MISSING_BILLING_ITEMS (1ULL << 4) #define A_ERR_INVALID_ADDRESSEE (1ULL << 5) #define A_ERR_INVALID_CUSTOMER (1ULL << 6) #define A_ERR_INVALID_SUPPLIER (1ULL << 7) #define A_ERR_MISSING_NAME (1ULL << 8) #define A_ERR_MISSING_CITY (1ULL << 9) #define A_ERR_MISSING_POSTAL (1ULL << 10) #define A_ERR_MISSING_ADDRESS1 (1ULL << 11) #define A_ERR_MISSING_COUNTRYCODE (1ULL << 12) #define A_ERR_MISSING_TAXID (1ULL << 13) #define A_ERR_MISSING_BUSINESSID (1ULL << 14) #define A_ERR_MAX_ITEMS_REACHED (1ULL << 15) #define A_ERR_MISSING_CODE (1ULL << 16) #define A_ERR_MISSING_EMAIL (1ULL << 17) #define A_ERR_MISSING_TAX_RATE (1ULL << 18) #define A_ERR_INVALID_BILLING_ITEM (1ULL << 19) typedef uint32_t a_err; namespace administration { // Setup functions. // ======================= void create_empty(char* save_file); void create_default(char* save_file); void create_from_file(char* save_file); // Callback functions. // ======================= void set_data_changed_event_callback(data_changed_event ev); void set_data_deleted_event_callback(data_deleted_event ev); void set_invoice_changed_event_callback(invoice_changed_event ev); void set_contact_changed_event_callback(contact_changed_event ev); void set_taxrate_changed_event_callback(taxrate_changed_event ev); void set_costcenter_changed_event_callback(costcenter_changed_event ev); void set_project_changed_event_callback(project_changed_event ev); // Company info functions. contact company_info_get(); void company_info_import(contact data); void company_info_set(contact data); // Other functions. // ======================= char* get_file_path(); s32 get_next_id(); s32 get_next_sequence_number(); char* get_currency_symbol_for_currency(char* code); char* get_default_currency(); time_t get_default_invoice_expire_duration(); ai_service get_ai_service(); void set_file_path(char* path); void set_next_id(s32 nr); void set_next_sequence_number(s32 nr); void set_ai_service(ai_service provider); void create_income_statement(income_statement* statement); void create_tax_statement(tax_statement* statement); bool can_create_invoices(); // Contact functions. // ======================= u32 contact_count(); contact contact_create_empty(); a_err contact_import(contact data); a_err contact_add(contact data); a_err contact_update(contact data); a_err contact_remove(contact data); a_err addressee_is_valid(delivery_info data); a_err contact_is_valid(contact data); bool contact_equals(contact c1, contact c2); a_err contact_get_by_id(contact* buffer, char* id); int contact_get_autocompletions(contact* buffer, int buf_size, char* name); u32 contact_get_partial_list(u32 page_index, u32 page_size, contact* buffer); u32 contact_get_all(contact* buffer); // Project functions. // ======================= u32 project_count(); project project_create_empty(); a_err project_import(project data); a_err project_add(project data); a_err project_update(project data); a_err project_remove(project data); void project_cancel(project data); a_err project_is_valid(project data); char* project_get_status_string(project data); u32 project_get_all(project* buffer); u32 project_get_partial_list(u32 page_index, u32 page_size, project* buffer); a_err project_get_by_id(project* buffer, char* id); // Tax rate functions. // ======================= u32 tax_rate_count(); tax_rate tax_rate_create_empty(); a_err tax_rate_import(tax_rate data); a_err tax_rate_add(tax_rate data); a_err tax_rate_update(tax_rate data); a_err tax_rate_get_by_shorthandle(tax_rate* buffer, char* handle); a_err tax_rate_get_by_id(tax_rate* buffer, char* id); u32 tax_rate_get_all(tax_rate* buffer); u32 tax_rate_get_by_country(tax_rate* buffer, u32 code_count, char** country_codes); // Cost center functions. // ======================= u32 cost_center_count(); cost_center cost_center_create_empty(); a_err cost_center_import(cost_center data); a_err cost_center_add(cost_center data); a_err cost_center_update(cost_center data); a_err cost_center_is_valid(cost_center data); u32 cost_center_get_all(cost_center* buffer); a_err cost_center_get_by_id(cost_center* buffer, char* id); // Invoice functions. // ======================= u32 invoice_count(); u32 invoice_get_incomming_count(); u32 invoice_get_outgoing_count(); invoice invoice_create_empty(); a_err invoice_import(invoice* invoice); a_err invoice_add(invoice* invoice); a_err invoice_update(invoice* invoice); a_err invoice_remove(invoice* invoice); void invoice_set_currency(invoice* invoice, char* currency); invoice invoice_create_copy(invoice* invoice); a_err invoice_is_valid(invoice* invoice); char* invoice_get_status_string(invoice* invoice); u32 invoice_get_partial_list_outgoing(u32 page_index, u32 page_size, invoice* buffer); u32 invoice_get_partial_list_incomming(u32 page_index, u32 page_size, invoice* buffer); u32 invoice_get_all(invoice* buffer); a_err invoice_get_by_id(invoice* buffer, char* id); u32 invoice_get_tax_rates(invoice* invoice, tax_rate* buffer); bool invoice_get_subtotal_for_tax_rate(invoice* invoice, tax_rate rate, tax_subtotal* buffer); // Billing item functions. // ======================= u32 billing_item_count(invoice* invoice); billing_item billing_item_create_empty(); a_err billing_item_import_to_invoice(invoice* invoice, billing_item item); a_err billing_item_add_to_invoice(invoice* invoice, billing_item item); a_err billing_item_update_in_invoice(invoice* invoice, billing_item item); a_err billing_item_remove_from_invoice(invoice* invoice, billing_item item); a_err billing_item_is_valid(billing_item item); u32 billing_item_get_all_for_invoice(invoice* invoice, billing_item* buffer); }