diff options
| author | Aldrik Ramaekers <aldrikboy@gmail.com> | 2025-09-06 11:58:12 +0200 |
|---|---|---|
| committer | Aldrik Ramaekers <aldrikboy@gmail.com> | 2025-09-06 11:58:12 +0200 |
| commit | 68d0ef1586a90ddef6d0cef7ebd593828282c76a (patch) | |
| tree | 6166037ca8a5d5ad168badc7ff5b76510bf05df4 | |
| parent | b970907ef53f1b8367285cbe7c2dc2a7fa47967f (diff) | |
working on invoice templates
| -rw-r--r-- | docs/CHANGES.rst | 1 | ||||
| -rw-r--r-- | include/administration.hpp | 117 | ||||
| -rw-r--r-- | include/file_templates.hpp | 79 | ||||
| -rw-r--r-- | include/strops.hpp | 6 | ||||
| -rw-r--r-- | src/administration.cpp | 61 | ||||
| -rw-r--r-- | src/administration_writer.cpp | 195 | ||||
| -rw-r--r-- | src/locales/en.cpp | 6 | ||||
| -rw-r--r-- | src/strops.cpp | 31 |
8 files changed, 387 insertions, 109 deletions
diff --git a/docs/CHANGES.rst b/docs/CHANGES.rst index 2397853..b363ba7 100644 --- a/docs/CHANGES.rst +++ b/docs/CHANGES.rst @@ -17,6 +17,7 @@ TODO: - net negative billing items should set tax to 0. - validate data within administration on save to make sure it is valid for transmissions. (e.g. rules of https://docs.peppol.eu/poacc/billing/3.0/syntax/ubl-invoice/cac-AccountingSupplierParty/cac-Party/cbc-EndpointID/) - View local business number / vat number naming on contact form. e.g. when country is austria: "Österreichische Umsatzsteuer-Identifikationsnummer" +- add tax category code to country_tax_bracket https://docs.peppol.eu/poacc/billing/3.0/codelist/UNCL5305/ v0.1 (master) diff --git a/include/administration.hpp b/include/administration.hpp index 774faf8..5e6f0c1 100644 --- a/include/administration.hpp +++ b/include/administration.hpp @@ -15,7 +15,7 @@ #define MAX_LEN_LONG_DESC 64 #define MAX_LEN_EMAIL 64 #define MAX_LEN_PHONE 16 -#define MAX_LEN_BANK 32 +#define MAX_LEN_BANK 35 #define MAX_LEN_TAXID 32 #define MAX_LEN_BUSINESSID 32 #define MAX_LEN_TAX_SECTION 16 @@ -27,7 +27,7 @@ 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 description[MAX_LEN_SHORT_DESC]; // Currently only used for global tax brackets + char description[MAX_LEN_SHORT_DESC]; } country_tax_bracket; typedef struct @@ -117,6 +117,113 @@ typedef struct char tax_section[MAX_LEN_TAX_SECTION]; } 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; +} tax_subtotal; + typedef struct { char id[MAX_LEN_ID]; // I/[id] @@ -138,9 +245,10 @@ typedef struct float net; char currency[MAX_LEN_CURRENCY]; // 3 letter code - bool is_triangulation; // True of addressee != customer + bool is_triangulation; // True if addressee != customer invoice_status status; bool is_outgoing; // Outgoing or incomming invoice. + payment_information payment_means; bool is_intra_community; // TODO uninplemented time_t payment_on_account_date; // TODO uninplemented @@ -253,6 +361,9 @@ u32 administration_invoice_get_partial_list_outgoing(u32 page_index, u32 pag u32 administration_invoice_get_partial_list_incomming(u32 page_index, u32 page_size, invoice* buffer); u32 administration_invoice_get_all(invoice* buffer); +u32 administration_invoice_get_tax_brackets(invoice* invoice, country_tax_bracket* buffer); +bool administration_invoice_get_subtotal_for_tax_bracket(invoice* invoice, country_tax_bracket bracket, tax_subtotal* buffer); + // Billing item functions. // ======================= u32 administration_billing_item_count(invoice* invoice); diff --git a/include/file_templates.hpp b/include/file_templates.hpp index f267055..3559fbe 100644 --- a/include/file_templates.hpp +++ b/include/file_templates.hpp @@ -1,3 +1,67 @@ +const char* project_save_template = +"<Project>\n" +" <Id>{{PROJECT_ID}}</Id>\n" +" <Description>{{PROJECT_DESCRIPTION}}</Description>\n" +" <State>{{PROJECT_STATE}}</State>\n" +" <StartDate>{{PROJECT_STARTDATE}}</StartDate>\n" +" <EndDate>{{PROJECT_ENDDATE}}</EndDate>\n" +"</Project>\n"; + +const char* costcenter_save_template = +"<CostCenter>\n" +" <Id>{{COSTCENTER_ID}}</Id>\n" +" <Code>{{COSTCENTER_CODE}}</Code>\n" +" <Description>{{COSTCENTER_DESCRIPTION}}</Description>\n" +"</CostCenter>\n"; + +const char* taxbracket_save_template = +"<CountryTaxBracket>\n" +" <Id>{{TAXBRACKET_ID}}</Id>\n" +" <CountryCode>{{TAXBRACKET_COUNTRY}}</CountryCode>\n" +" <Rate>{{TAXBRACKET_RATE}}</Rate>\n" +" <Description>{{TAXBRACKET_DESCRIPTION}}</Description>\n" +"</CountryTaxBracket>\n"; + +const char* contact_save_template = +"<Contact>\n" +" <Id>{{CONTACT_ID}}</Id>\n" +" <Name>{{CONTACT_NAME}}</Name>\n" +" <Type>{{CONTACT_TYPE}}</Type>\n" +" <TaxId>{{CONTACT_TAXID}}</TaxId>\n" +" <BusinessId>{{CONTACT_BUSINESSID}}</BusinessId>\n" +" <Email>{{CONTACT_EMAIL}}</Email>\n" +" <PhoneNumber>{{CONTACT_PHONENUMBER}}</PhoneNumber>\n" +" <BankAccount>{{CONTACT_BANKACCOUNT}}</BankAccount>\n" +" <Address>\n" +" <AddressLine1>{{CONTACT_ADDRESS1}}</AddressLine1>\n" +" <AddressLine2>{{CONTACT_ADDRESS2}}</AddressLine2>\n" +" <CountryCode>{{CONTACT_COUNTRY}}</CountryCode>\n" +" <City>{{CONTACT_CITY}}</City>\n" +" <Postal>{{CONTACT_POSTAL}}</Postal>\n" +" <Region>{{CONTACT_REGION}}</Region>\n" +" </Address>\n" +"</Contact>\n"; + +const char* administration_save_template = +"<Administration>\n" +" <NextId>{{NEXT_ID}}</NextId>\n" +" <NextSequenceNumber>{{NEXT_SEQUENCE_NUMBER}}</NextSequenceNumber>\n" +" <ProgramVersion>{{PROGRAM_VERSION}}</ProgramVersion>\n" +"</Administration>\n"; + +const char* peppol_invoice_tax_subtotal_template = +" <cac:TaxSubtotal>\n" +" <cbc:TaxableAmount currencyID=\"{{CURRENCY}}\">{{TAXABLE_AMOUNT}}</cbc:TaxableAmount>\n" +" <cbc:TaxAmount currencyID=\"{{CURRENCY}}\">{{TAX_AMOUNT}}</cbc:TaxAmount>\n" +" <cac:TaxCategory>\n" +" <cbc:ID>{{TAX_CATEGORY}}</cbc:ID>\n" +" <cbc:Percent>{{TAX_PERCENT}}</cbc:Percent>\n" +" <cac:TaxScheme>\n" +" <cbc:ID>VAT</cbc:ID>\n" +" </cac:TaxScheme>\n" +" </cac:TaxCategory>\n" +" </cac:TaxSubtotal>\n"; + const char *peppol_invoice_template = /*"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"*/ "<Invoice xmlns=\"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2\"\n" @@ -63,7 +127,8 @@ const char *peppol_invoice_template = " </cac:AccountingCustomerParty>\n" "\n" " <cac:PaymentMeans>\n" -" <cbc:PaymentMeansCode>31</cbc:PaymentMeansCode>\n" +" <cbc:PaymentMeansCode>{{PAYMENT_TYPE}}</cbc:PaymentMeansCode>\n" +" <cbc:PaymentID>{{INVOICE_ID}}</cbc:PaymentID>\n" " <cac:PayeeFinancialAccount>\n" " <cbc:ID>{{SUPPLIER_IBAN}}</cbc:ID>\n" " <cac:FinancialInstitutionBranch>\n" @@ -76,17 +141,7 @@ const char *peppol_invoice_template = "\n" " <cac:TaxTotal>\n" " <cbc:TaxAmount currencyID=\"{{CURRENCY}}\">{{TOTAL_TAX_AMOUNT}}</cbc:TaxAmount>\n" -" <cac:TaxSubtotal>\n" -" <cbc:TaxableAmount currencyID=\"{{CURRENCY}}\">{{TAXABLE_AMOUNT}}</cbc:TaxableAmount>\n" -" <cbc:TaxAmount currencyID=\"{{CURRENCY}}\">{{TAX_AMOUNT}}</cbc:TaxAmount>\n" -" <cac:TaxCategory>\n" -" <cbc:ID>{{TAX_CATEGORY}}</cbc:ID>\n" -" <cbc:Percent>{{TAX_PERCENT}}</cbc:Percent>\n" -" <cac:TaxScheme>\n" -" <cbc:ID>VAT</cbc:ID>\n" -" </cac:TaxScheme>\n" -" </cac:TaxCategory>\n" -" </cac:TaxSubtotal>\n" +" {{TAX_SUBTOTAL_LIST}}" " </cac:TaxTotal>\n" "\n" " <cac:LegalMonetaryTotal>\n" diff --git a/include/strops.hpp b/include/strops.hpp index 4570a15..2874c4d 100644 --- a/include/strops.hpp +++ b/include/strops.hpp @@ -1,5 +1,9 @@ #pragma once +#include <stdint.h> size_t strops_copy(char *dst, const char *src, size_t size); char* strops_stristr(char* a, char* b); -void strops_replace(char *buf, size_t buf_size, const char *search, const char *replace);
\ No newline at end of file +void strops_replace(char *buf, size_t buf_size, const char *search, const char *replace); +void strops_replace_int32(char *buf, size_t buf_size, const char *search, int32_t number); +void strops_replace_int64(char *buf, size_t buf_size, const char *search, int64_t number); +void strops_replace_float(char *buf, size_t buf_size, const char *search, float number, int decimals);
\ No newline at end of file diff --git a/src/administration.cpp b/src/administration.cpp index d75aff7..b938271 100644 --- a/src/administration.cpp +++ b/src/administration.cpp @@ -1144,6 +1144,12 @@ bool administration_invoice_add(invoice* inv) invoice* new_inv = (invoice*)malloc(sizeof(invoice)); memcpy(new_inv, ©, sizeof(invoice)); + new_inv->payment_means.payment_method = PAYMENT_METHOD_DEBIT_TRANSFER; + strops_copy(new_inv->payment_means.payee_bank_account, inv->customer.bank_account, sizeof(new_inv->payment_means.payee_bank_account)); + strops_copy(new_inv->payment_means.payee_account_name, inv->customer.name, sizeof(new_inv->payment_means.payee_account_name)); + strops_copy(new_inv->payment_means.service_provider_id, "", sizeof(new_inv->payment_means.service_provider_id)); + strops_copy(new_inv->payment_means.payer_bank_account, inv->supplier.bank_account, sizeof(new_inv->payment_means.payer_bank_account)); + list_append(&g_administration.invoices, new_inv); g_administration.next_id++; @@ -1207,6 +1213,61 @@ u32 administration_invoice_get_all(invoice* buffer) return write_cursor; } +bool administration_invoice_get_subtotal_for_tax_bracket(invoice* invoice, country_tax_bracket bracket, tax_subtotal* buffer) +{ + bool result = false; + + buffer->tax = 0.0f; + buffer->total = 0.0f; + buffer->net = 0.0f; + + list_iterator_start(&invoice->billing_items); + while (list_iterator_hasnext(&invoice->billing_items)) { + billing_item* c = (billing_item *)list_iterator_next(&invoice->billing_items); + + if (strcmp(c->tax_bracket_id, bracket.id) == 0) + { + result = true; + buffer->tax += c->tax; + buffer->total += c->total; + buffer->net += c->net; + } + } + list_iterator_stop(&invoice->billing_items); + + return result; +} + +u32 administration_invoice_get_tax_brackets(invoice* invoice, country_tax_bracket* buffer) +{ + u32 write_cursor = 0; + + list_iterator_start(&invoice->billing_items); + while (list_iterator_hasnext(&invoice->billing_items)) { + billing_item c = *(billing_item *)list_iterator_next(&invoice->billing_items); + + country_tax_bracket bracket; + bool found = administration_get_tax_bracket_by_id(&bracket, c.tax_bracket_id); + if (found) { + + bool exists = false; + for (u32 i = 0; i < write_cursor; i++) { + if (strcmp(buffer[i].id, c.tax_bracket_id) == 0) { + exists = true; + break; + } + } + + if (!exists) { + buffer[write_cursor++] = bracket; + } + } + } + list_iterator_stop(&invoice->billing_items); + + return write_cursor; +} + static u32 administration_invoice_get_partial_list(u32 page_index, u32 page_size, invoice* buffer, bool want_outgoing) { assert(buffer); diff --git a/src/administration_writer.cpp b/src/administration_writer.cpp index 40fcd25..e00921d 100644 --- a/src/administration_writer.cpp +++ b/src/administration_writer.cpp @@ -22,6 +22,17 @@ void administration_writer_destroy() mtx_destroy(&_save_file_mutex); } +static char* administration_writer_copy_template(const char* template_str, int* buf_size) +{ + size_t template_size = strlen(template_str); + size_t buf_length = template_size*2; // Ballpark file content size. + char* file_content = (char*)malloc(buf_length); + memset(file_content, 0, buf_length); + memcpy(file_content, template_str, template_size); + *buf_size = (int)buf_length; + return file_content; +} + static bool administration_writer_entry_exists(char* entry) { bool entry_exists = false; @@ -158,8 +169,6 @@ static char* administration_writer_get_eas_scheme_for_address(address addr) bool administration_writer_save_invoice_blocking(invoice inv) { - #define APPEND(_txt, ...) file_content += sprintf(file_content, _txt, __VA_ARGS__); - bool result = 1; int buf_length = 15000; // Ballpark file content size. char* file_content = (char*)malloc(buf_length); @@ -196,6 +205,40 @@ bool administration_writer_save_invoice_blocking(invoice inv) strops_replace(file_content, buf_length, "{{CUSTOMER_COUNTRY}}", inv.customer.address.country_code); strops_replace(file_content, buf_length, "{{CUSTOMER_VAT_ID}}", inv.customer.taxid); + // Payment means + strops_replace_int32(file_content, buf_length, "{{PAYMENT_TYPE}}", inv.payment_means.payment_method); + strops_replace(file_content, buf_length, "{{SUPPLIER_IBAN}}", inv.payment_means.payee_bank_account); + strops_replace(file_content, buf_length, "{{SUPPLIER_BIC}}", inv.payment_means.service_provider_id); + + // Tax breakdown + strops_replace_float(file_content, buf_length, "{{TOTAL_TAX_AMOUNT}}", inv.total, 2); + + { // Create tax subtotal list. + country_tax_bracket* tax_bracket_buffer = (country_tax_bracket*)malloc(sizeof(country_tax_bracket)*administration_billing_item_count(&inv)); + u32 tax_bracket_count = administration_invoice_get_tax_brackets(&inv, tax_bracket_buffer); + //int tax_list_buf_length = 0; + //char* tax_list_file_content = administration_writer_copy_template(peppol_invoice_tax_subtotal_template, &tax_list_buf_length); + + for (u32 i = 0; i < tax_bracket_count; i++) + { + int tax_entry_buf_length = 0; + char* tax_entry_file_content = administration_writer_copy_template(peppol_invoice_tax_subtotal_template, &tax_entry_buf_length); + + tax_subtotal subtotal; + administration_invoice_get_subtotal_for_tax_bracket(&inv, tax_bracket_buffer[i], &subtotal); + + strops_replace(tax_entry_file_content, tax_entry_buf_length, "{{CURRENCY}}", inv.currency); + strops_replace_float(tax_entry_file_content, tax_entry_buf_length, "{{TAXABLE_AMOUNT}}", subtotal.net, 2); + strops_replace_float(tax_entry_file_content, tax_entry_buf_length, "{{TAX_AMOUNT}}", subtotal.tax, 2); + //strops_replace(tax_entry_file_content, tax_entry_buf_length, "{{TAX_CATEGORY}}", inv.currency); + strops_replace_float(tax_entry_file_content, tax_entry_buf_length, "{{TAX_PERCENT}}", tax_bracket_buffer[i].rate, 2); + + printf("%s\n", tax_entry_file_content); + } + + free(tax_bracket_buffer); + } + // Dates tm_info = localtime(&inv.issued_at); strftime(date_buffer, sizeof(date_buffer), "%Y-%m-%d", tm_info); @@ -236,29 +279,24 @@ static bool administration_writer_save_all_invoices_blocking() bool administration_writer_save_project_blocking(project project) { bool result = 1; - int buf_length = 1000; // Ballpark file content size. - char* file_content = (char*)malloc(buf_length); - memset(file_content, 0, buf_length); + int buf_length = 0; + char* file_content = administration_writer_copy_template(project_save_template, &buf_length); - char* orig_content = file_content; - - file_content += sprintf(file_content, "<project>\n"); - file_content += sprintf(file_content, "\t<id>%s</id>\n", project.id); - file_content += sprintf(file_content, "\t<description>%s</description>\n", project.description); - file_content += sprintf(file_content, "\t<state>%d</state>\n", project.state); - file_content += sprintf(file_content, "\t<start_date>%lld</start_date>\n", (long long)project.start_date); - file_content += sprintf(file_content, "\t<end_date>%lld</end_date>\n", (long long)project.end_date); - file_content += sprintf(file_content, "</project>\n"); + strops_replace(file_content, buf_length, "{{PROJECT_ID}}", project.id); + strops_replace(file_content, buf_length, "{{PROJECT_DESCRIPTION}}", project.description); + strops_replace_int32(file_content, buf_length, "{{PROJECT_STATE}}", project.state); + strops_replace_int64(file_content, buf_length, "{{PROJECT_STARTDATE}}", (long long)project.start_date); + strops_replace_int64(file_content, buf_length, "{{PROJECT_ENDDATE}}", (long long)project.end_date); //// Write to Disk. char final_path[50]; snprintf(final_path, 50, "%s.xml", project.id); - int final_length = (int)strlen(orig_content); - if (!xml_parse_document((uint8_t*)orig_content, final_length)) result = 0; - else if (!administration_writer_write_to_zip(final_path, orig_content, final_length)) result = 0; + int final_length = (int)strlen(file_content); + if (!xml_parse_document((uint8_t*)file_content, final_length)) result = 0; + else if (!administration_writer_write_to_zip(final_path, file_content, final_length)) result = 0; - free(orig_content); + free(file_content); return result; } @@ -285,27 +323,22 @@ static bool administration_writer_save_all_projects_blocking() bool administration_writer_save_cost_center_blocking(cost_center cost) { bool result = 1; - int buf_length = 1000; // Ballpark file content size. - char* file_content = (char*)malloc(buf_length); - memset(file_content, 0, buf_length); + int buf_length = 0; + char* file_content = administration_writer_copy_template(costcenter_save_template, &buf_length); - char* orig_content = file_content; - - file_content += sprintf(file_content, "<cost_center>\n"); - file_content += sprintf(file_content, "\t<id>%s</id>\n", cost.id); - file_content += sprintf(file_content, "\t<code>%s</code>\n", cost.code); - file_content += sprintf(file_content, "\t<description>%s</description>\n", cost.description); - file_content += sprintf(file_content, "</cost_center>\n"); + strops_replace(file_content, buf_length, "{{COSTCENTER_ID}}", cost.id); + strops_replace(file_content, buf_length, "{{COSTCENTER_CODE}}", cost.code); + strops_replace(file_content, buf_length, "{{COSTCENTER_DESCRIPTION}}", cost.description); //// Write to Disk. char final_path[50]; snprintf(final_path, 50, "%s.xml", cost.id); - int final_length = (int)strlen(orig_content); - if (!xml_parse_document((uint8_t*)orig_content, final_length)) result = 0; - else if (!administration_writer_write_to_zip(final_path, orig_content, final_length)) result = 0; - - free(orig_content); + int final_length = (int)strlen(file_content); + if (!xml_parse_document((uint8_t*)file_content, final_length)) result = 0; + else if (!administration_writer_write_to_zip(final_path, file_content, final_length)) result = 0; + + free(file_content); return result; } @@ -332,27 +365,23 @@ static bool administration_writer_save_all_cost_centers_blocking() bool administration_writer_save_tax_bracket_blocking(country_tax_bracket bracket) { bool result = 1; - int buf_length = 1000; // Ballpark file content size. - char* file_content = (char*)malloc(buf_length); - memset(file_content, 0, buf_length); - char* orig_content = file_content; + int buf_length = 0; + char* file_content = administration_writer_copy_template(taxbracket_save_template, &buf_length); - file_content += sprintf(file_content, "<country_tax_bracket>\n"); - file_content += sprintf(file_content, "\t<id>%s</id>\n", bracket.id); - file_content += sprintf(file_content, "\t<country_code>%s</country_code>\n", bracket.country_code); - file_content += sprintf(file_content, "\t<rate>%.2f</rate>\n", bracket.rate); - file_content += sprintf(file_content, "\t<description>%s</description>\n", bracket.description); - file_content += sprintf(file_content, "</country_tax_bracket>\n"); + strops_replace(file_content, buf_length, "{{TAXBRACKET_ID}}", bracket.id); + strops_replace(file_content, buf_length, "{{TAXBRACKET_COUNTRY}}", bracket.country_code); + strops_replace_float(file_content, buf_length, "{{TAXBRACKET_RATE}}", bracket.rate, 2); + strops_replace(file_content, buf_length, "{{TAXBRACKET_DESCRIPTION}}", bracket.description); //// Write to Disk. char final_path[50]; snprintf(final_path, 50, "%s.xml", bracket.id); - int final_length = (int)strlen(orig_content); - if (!xml_parse_document((uint8_t*)orig_content, final_length)) result = 0; - else if (!administration_writer_write_to_zip(final_path, orig_content, final_length)) result = 0; + int final_length = (int)strlen(file_content); + if (!xml_parse_document((uint8_t*)file_content, final_length)) result = 0; + else if (!administration_writer_write_to_zip(final_path, file_content, final_length)) result = 0; - free(orig_content); + free(file_content); return result; } @@ -381,39 +410,32 @@ static bool administration_writer_save_all_tax_brackets_blocking() bool administration_writer_save_contact_blocking(contact c) { bool result = 1; - int buf_length = 2000; // Ballpark contact content size. - char* file_content = (char*)malloc(buf_length); - memset(file_content, 0, buf_length); - - char* orig_content = file_content; - - file_content += sprintf(file_content, "<contact>\n"); - - file_content += sprintf(file_content, "\t<id>%s</id>\n", c.id); - file_content += sprintf(file_content, "\t<name>%s</name>\n", c.name); - file_content += sprintf(file_content, "\t<type>%d</type>\n", c.type); - file_content += sprintf(file_content, "\t<taxid>%s</taxid>\n", c.taxid); - file_content += sprintf(file_content, "\t<businessid>%s</businessid>\n", c.businessid); - file_content += sprintf(file_content, "\t<email>%s</email>\n", c.email); - file_content += sprintf(file_content, "\t<phone_number>%s</phone_number>\n", c.phone_number); - file_content += sprintf(file_content, "\t<bank_account>%s</bank_account>\n", c.bank_account); - - file_content += sprintf(file_content, "\t<address>\n"); - file_content += sprintf(file_content, "\t\t<address1>%s</address1>\n", c.address.address1); - file_content += sprintf(file_content, "\t\t<address2>%s</address2>\n", c.address.address2); - file_content += sprintf(file_content, "\t\t<country_code>%s</country_code>\n", c.address.country_code); - file_content += sprintf(file_content, "\t</address>\n"); - - file_content += sprintf(file_content, "</contact>\n"); + int buf_length = 0; + char* file_content = administration_writer_copy_template(contact_save_template, &buf_length); + + strops_replace(file_content, buf_length, "{{CONTACT_ID}}", c.id); + strops_replace(file_content, buf_length, "{{CONTACT_NAME}}", c.name); + strops_replace_int32(file_content, buf_length, "{{CONTACT_TYPE}}", c.type); + strops_replace(file_content, buf_length, "{{CONTACT_TAXID}}", c.taxid); + strops_replace(file_content, buf_length, "{{CONTACT_BUSINESSID}}", c.businessid); + strops_replace(file_content, buf_length, "{{CONTACT_EMAIL}}", c.email); + strops_replace(file_content, buf_length, "{{CONTACT_PHONENUMBER}}", c.phone_number); + strops_replace(file_content, buf_length, "{{CONTACT_BANKACCOUNT}}", c.bank_account); + strops_replace(file_content, buf_length, "{{CONTACT_ADDRESS1}}", c.address.address1); + strops_replace(file_content, buf_length, "{{CONTACT_ADDRESS2}}", c.address.address2); + strops_replace(file_content, buf_length, "{{CONTACT_COUNTRY}}", c.address.country_code); + strops_replace(file_content, buf_length, "{{CONTACT_CITY}}", c.address.city); + strops_replace(file_content, buf_length, "{{CONTACT_POSTAL}}", c.address.postal); + strops_replace(file_content, buf_length, "{{CONTACT_REGION}}", c.address.region); char final_path[50]; snprintf(final_path, 50, "%s.xml", c.id); - int final_length = (int)strlen(orig_content); - if (!xml_parse_document((uint8_t*)orig_content, final_length)) result = 0; - else if (!administration_writer_write_to_zip(final_path, orig_content, final_length)) result = 0; + int final_length = (int)strlen(file_content); + if (!xml_parse_document((uint8_t*)file_content, final_length)) result = 0; + else if (!administration_writer_write_to_zip(final_path, file_content, final_length)) result = 0; - free(orig_content); + free(file_content); return result; } @@ -446,24 +468,19 @@ static bool administration_writer_save_all_contacts_blocking() bool administration_writer_save_all_administration_info_blocking() { bool result = 1; - int buf_length = 1000; // Ballpark file content size. - char* file_content = (char*)malloc(buf_length); - memset(file_content, 0, buf_length); - char* orig_content = file_content; + int buf_length = 0; + char* file_content = administration_writer_copy_template(administration_save_template, &buf_length); - //// Cost centers. - file_content += sprintf(file_content, "<administration>\n"); - file_content += sprintf(file_content, "\t<next_id>%d</next_id>\n", administation_get()->next_id); - file_content += sprintf(file_content, "\t<next_sequence_number>%d</next_sequence_number>\n", administation_get()->next_sequence_number); - file_content += sprintf(file_content, "\t<program_version>%s</program_version>\n", administation_get()->program_version); - file_content += sprintf(file_content, "</administration>"); + strops_replace_int32(file_content, buf_length, "{{NEXT_ID}}", administation_get()->next_id); + strops_replace_int32(file_content, buf_length, "{{NEXT_SEQUENCE_NUMBER}}", administation_get()->next_sequence_number); + strops_replace(file_content, buf_length, "{{PROGRAM_VERSION}}", administation_get()->program_version); //// Write to Disk. - int final_length = (int)strlen(orig_content); - if (!xml_parse_document((uint8_t*)orig_content, final_length)) result = 0; - else if (!administration_writer_write_to_zip(ADMIN_FILE_INFO, orig_content, final_length)) result = 0; + int final_length = (int)strlen(file_content); + if (!xml_parse_document((uint8_t*)file_content, final_length)) result = 0; + else if (!administration_writer_write_to_zip(ADMIN_FILE_INFO, file_content, final_length)) result = 0; - free(orig_content); + free(file_content); return result; } diff --git a/src/locales/en.cpp b/src/locales/en.cpp index 767a07d..b528088 100644 --- a/src/locales/en.cpp +++ b/src/locales/en.cpp @@ -96,12 +96,12 @@ locale_entry en_locales[] = { {"contact.form.email", "Email address"}, {"contact.form.phonenumber", "Phone number"}, {"contact.form.bankaccount", "Bank account"}, + {"contact.form.city", "City"}, + {"contact.form.postal", "Postal"}, + {"contact.form.region", "Region"}, {"contact.table.identifier", "Identifier"}, {"contact.table.name", "Name"}, {"contact.table.address", "Address"}, - {"contact.table.city", "City"}, - {"contact.table.postal", "Postal"}, - {"contact.table.region", "Region"}, // Project strings. {"project.form.identifier", "Identifier"}, diff --git a/src/strops.cpp b/src/strops.cpp index 3389eb1..208aaed 100644 --- a/src/strops.cpp +++ b/src/strops.cpp @@ -1,5 +1,6 @@ #include <string.h> #include <ctype.h> +#include <stdio.h> #include "strops.hpp" @@ -47,14 +48,42 @@ void strops_replace(char *buf, size_t buf_size, const char *search, const char * // Ensure space size_t remaining = buf_size - (w - buf) - 1; size_t copy_len = (replace_len < remaining) ? replace_len : remaining; + size_t skip_size = search_len; + + if (replace_len > search_len) + { + memmove(w+replace_len, w+search_len, remaining - replace_len); + skip_size = replace_len; + } memcpy(w, replace, copy_len); w += copy_len; - r += search_len; + r += skip_size; } else { *w++ = *r++; } } *w = '\0'; // terminate +} + +void strops_replace_int32(char *buf, size_t buf_size, const char *search, int32_t number) +{ + char num_buf[200]; + snprintf(num_buf, 200, "%d", number); + strops_replace(buf, buf_size, search, num_buf); +} + +void strops_replace_int64(char *buf, size_t buf_size, const char *search, int64_t number) +{ + char num_buf[200]; + snprintf(num_buf, 200, "%lld", number); + strops_replace(buf, buf_size, search, num_buf); +} + +void strops_replace_float(char *buf, size_t buf_size, const char *search, float number, int decimals) +{ + char num_buf[200]; + snprintf(num_buf, 200, "%.*f", decimals, number); + strops_replace(buf, buf_size, search, num_buf); }
\ No newline at end of file |
