summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAldrik Ramaekers <aldrikboy@gmail.com>2025-09-06 11:58:12 +0200
committerAldrik Ramaekers <aldrikboy@gmail.com>2025-09-06 11:58:12 +0200
commit68d0ef1586a90ddef6d0cef7ebd593828282c76a (patch)
tree6166037ca8a5d5ad168badc7ff5b76510bf05df4
parentb970907ef53f1b8367285cbe7c2dc2a7fa47967f (diff)
working on invoice templates
-rw-r--r--docs/CHANGES.rst1
-rw-r--r--include/administration.hpp117
-rw-r--r--include/file_templates.hpp79
-rw-r--r--include/strops.hpp6
-rw-r--r--src/administration.cpp61
-rw-r--r--src/administration_writer.cpp195
-rw-r--r--src/locales/en.cpp6
-rw-r--r--src/strops.cpp31
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, &copy, 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