summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAldrik Ramaekers <aldrikboy@gmail.com>2025-08-31 12:49:59 +0200
committerAldrik Ramaekers <aldrikboy@gmail.com>2025-08-31 12:49:59 +0200
commitb970907ef53f1b8367285cbe7c2dc2a7fa47967f (patch)
tree73ec8747346393263e21ea72cffb6aa27d921c4a /src
parent9fcfc3215a4caaa32872abc9cfc6deeb86b9765c (diff)
invoice templateing work
Diffstat (limited to 'src')
-rw-r--r--src/administration.cpp117
-rw-r--r--src/administration_writer.cpp152
-rw-r--r--src/locales/en.cpp3
-rw-r--r--src/strops.cpp25
-rw-r--r--src/ui/ui_contacts.cpp15
5 files changed, 265 insertions, 47 deletions
diff --git a/src/administration.cpp b/src/administration.cpp
index 21b70f3..d75aff7 100644
--- a/src/administration.cpp
+++ b/src/administration.cpp
@@ -274,23 +274,25 @@ static void administration_create_debug_data()
{
srand((unsigned) time(NULL));
- #define ADD_CONSUMER(_name, _addr1, _addr2, _cc)\
+ #define ADD_CONSUMER(_name, _addr1, _cc, _city, _postal)\
{contact _c = administration_contact_create_empty();\
strops_copy(_c.name, _name, sizeof(_c.name));\
strops_copy(_c.address.address1, _addr1, sizeof(_c.address.address1));\
- strops_copy(_c.address.address2, _addr2, sizeof(_c.address.address2));\
strops_copy(_c.address.country_code, _cc, sizeof(_c.address.country_code));\
+ strops_copy(_c.address.city, _city, sizeof(_c.address.city));\
+ strops_copy(_c.address.postal, _postal, sizeof(_c.address.postal));\
_c.type = contact_type::CONTACT_CONSUMER;\
administration_contact_add(_c);};
- #define ADD_BUSINESS(_name, _addr1, _addr2, _cc, _tc, _bc)\
+ #define ADD_BUSINESS(_name, _addr1, _cc, _tc, _bc, _city, _postal)\
{contact _c = administration_contact_create_empty();\
strops_copy(_c.name, _name, sizeof(_c.name));\
strops_copy(_c.address.address1, _addr1, sizeof(_c.address.address1));\
- strops_copy(_c.address.address2, _addr2, sizeof(_c.address.address2));\
strops_copy(_c.address.country_code, _cc, sizeof(_c.address.country_code));\
strops_copy(_c.taxid, _tc, sizeof(_c.taxid));\
strops_copy(_c.businessid, _bc, sizeof(_c.businessid));\
+ strops_copy(_c.address.city, _city, sizeof(_c.address.city));\
+ strops_copy(_c.address.postal, _postal, sizeof(_c.address.postal));\
_c.type = contact_type::CONTACT_BUSINESS;\
administration_contact_add(_c);};
@@ -301,37 +303,37 @@ static void administration_create_debug_data()
strops_copy(g_administration.path, "C:\\Users\\aldri\\Downloads\\test.openbooks", sizeof(g_administration.path));
- ADD_CONSUMER("Emma Müller", "Hauptstraße 12", "10115 Berlin", "DE");
- ADD_CONSUMER("Luca Rossi", "Via Roma 45", "00184 Roma", "IT");
- ADD_CONSUMER("Sofia Garcia", "Calle Mayor 7", "28013 Madrid", "ES");
- ADD_CONSUMER("Jean Dupont", "10 Rue de la Paix", "75002 Paris", "FR");
- ADD_CONSUMER("Anna Nowak", "ul. Kwiatowa 3", "00-001 Warszawa", "PL");
- ADD_CONSUMER("Mikkel Jensen", "Østergade 8", "8000 Aarhus", "DK");
- ADD_CONSUMER("Maria Svensson", "Kungsgatan 15", "111 22 Stockholm", "SE");
- ADD_CONSUMER("Péter Kovács", "Fő utca 25", "1051 Budapest", "HU");
- ADD_CONSUMER("Lucas Silva", "Rua Augusta 100", "1250-001 Lisboa", "PT");
- ADD_CONSUMER("Isabelle Lefevre", "5 Place Stanislas", "54000 Nancy", "FR");
-
- ADD_BUSINESS("Schmidt & Co GmbH", "Friedrichstraße 45", "10117 Berlin", "DE", "DE123456789", "HRB123456");
- ADD_BUSINESS("Bianchi Srl", "Corso Venezia 12", "20121 Milano", "IT", "IT987654321", "MI1234567");
- ADD_BUSINESS("Fernández y Asociados", "Gran Vía 20", "28013 Madrid", "ES", "ES456789123", "CIFB123456");
- ADD_BUSINESS("Martin & Partners", "12 Avenue Victor Hugo", "75016 Paris", "FR", "FR321654987", "SIRET123456");
- ADD_BUSINESS("Zielińska Consulting", "ul. Marszałkowska 10", "00-590 Warszawa", "PL", "PL789123456", "REGON123456");
- ADD_BUSINESS("Sørensen ApS", "Strøget 3", "1460 København", "DK", "DK654321789", "CVR12345678");
- ADD_BUSINESS("Johansson AB", "Drottninggatan 22", "111 51 Stockholm", "SE", "SE987654321", "OrgNr1234567");
- ADD_BUSINESS("Nagy Kft.", "Andrássy út 60", "1062 Budapest", "HU", "HU123987654", "Cégjegyzékszám123");
- ADD_BUSINESS("Santos Lda.", "Avenida da Liberdade 50", "1250-142 Lisboa", "PT", "PT321789654", "NIPC123456789");
- ADD_BUSINESS("Dupuis SARL", "8 Rue Saint-Denis", "75001 Paris", "FR", "FR456123789", "SIREN123456");
- ADD_BUSINESS("Müller & Söhne GmbH", "Leipziger Platz 8", "10117 Berlin", "DE", "DE654987321", "HRB987654");
- ADD_BUSINESS("Romano Srl", "Via Garibaldi 14", "16124 Genova", "IT", "IT321654987", "GE1239876");
- ADD_BUSINESS("López Asociados", "Plaza del Pilar 6", "50003 Zaragoza", "ES", "ES789321654", "CIFC654321");
- ADD_BUSINESS("Laurent & Fils", "15 Boulevard Haussmann", "75009 Paris", "FR", "FR987321654", "SIRET654987");
- ADD_BUSINESS("Kowalczyk Sp. z o.o.", "ul. Piotrkowska 55", "90-001 Łódź", "PL", "PL123456789", "REGON654321");
- ADD_BUSINESS("Nielsen ApS", "Nørregade 12", "1165 København", "DK", "DK789456123", "CVR87654321");
- ADD_BUSINESS("Lindberg AB", "Vasagatan 18", "111 20 Stockholm", "SE", "SE456789123", "OrgNr7654321");
- ADD_BUSINESS("Szabó Kft.", "Kossuth Lajos tér 1", "1055 Budapest", "HU", "HU987123654", "Cégjegyzékszám654321");
- ADD_BUSINESS("Costa Lda.", "Rua do Ouro 24", "1100-063 Lisboa", "PT", "PT654123987", "NIPC987654321");
- ADD_BUSINESS("Moreau SARL", "3 Place de la République", "75011 Paris", "FR", "FR321456987", "SIREN789123");
+ ADD_CONSUMER("Emma Müller", "Hauptstraße 12", "DE", "Hamburg", "20095");
+ ADD_CONSUMER("Luca Rossi", "Via Roma 45", "IT", "Torino", "10121");
+ ADD_CONSUMER("Sofia Garcia", "Calle Mayor 7", "ES", "Valencia", "46001");
+ ADD_CONSUMER("Jean Dupont", "10 Rue de la Paix", "FR", "Lyon", "69001");
+ ADD_CONSUMER("Anna Nowak", "ul. Kwiatowa 3", "PL", "Kraków", "30-001");
+ ADD_CONSUMER("Mikkel Jensen", "Østergade 8", "DK", "Odense", "5000");
+ ADD_CONSUMER("Maria Svensson", "Kungsgatan 15", "SE", "Gothenburg", "411 01");
+ ADD_CONSUMER("Péter Kovács", "Fő utca 25", "HU", "Debrecen", "4025");
+ ADD_CONSUMER("Lucas Silva", "Rua Augusta 100", "PT", "Porto", "4000-123");
+ ADD_CONSUMER("Isabelle Lefevre", "5 Place Stanislas", "FR", "Marseille", "13001");
+
+ ADD_BUSINESS("Schmidt & Co GmbH", "Friedrichstraße 45", "DE", "DE123456789", "HRB123456", "Stuttgart", "70173");
+ ADD_BUSINESS("Bianchi Srl", "Corso Venezia 12", "IT", "IT987654321", "MI1234567", "Napoli", "80100");
+ ADD_BUSINESS("Fernández y Asociados", "Gran Vía 20", "ES", "ES456789123", "CIFB123456", "Sevilla", "41001");
+ ADD_BUSINESS("Martin & Partners", "12 Avenue Victor Hugo", "FR", "FR321654987", "SIRET123456", "Nice", "06000");
+ ADD_BUSINESS("Zielińska Consulting", "ul. Marszałkowska 10", "PL", "PL789123456", "REGON123456", "Gdańsk", "80-001");
+ ADD_BUSINESS("Sørensen ApS", "Strøget 3", "DK", "DK654321789", "CVR12345678", "Aalborg", "9000");
+ ADD_BUSINESS("Johansson AB", "Drottninggatan 22", "SE", "SE987654321", "OrgNr1234567", "Malmö", "211 01");
+ ADD_BUSINESS("Nagy Kft.", "Andrássy út 60", "HU", "HU123987654", "Cégjegyzékszám123", "Szeged", "6720");
+ ADD_BUSINESS("Santos Lda.", "Avenida da Liberdade 50", "PT", "PT321789654", "NIPC123456789", "Coimbra", "3000-132");
+ ADD_BUSINESS("Dupuis SARL", "8 Rue Saint-Denis", "FR", "FR456123789", "SIREN123456", "Toulouse", "31000");
+ ADD_BUSINESS("Müller & Söhne GmbH", "Leipziger Platz 8", "DE", "DE654987321", "HRB987654", "Dresden", "01067");
+ ADD_BUSINESS("Romano Srl", "Via Garibaldi 14", "IT", "IT321654987", "GE1239876", "Firenze", "50123");
+ ADD_BUSINESS("López Asociados", "Plaza del Pilar 6", "ES", "ES789321654", "CIFC654321", "Bilbao", "48001");
+ ADD_BUSINESS("Laurent & Fils", "15 Boulevard Haussmann", "FR", "FR987321654", "SIRET654987", "Bordeaux", "33000");
+ ADD_BUSINESS("Kowalczyk Sp. z o.o.", "ul. Piotrkowska 55", "PL", "PL123456789", "REGON654321", "Poznań", "60-101");
+ ADD_BUSINESS("Nielsen ApS", "Nørregade 12", "DK", "DK789456123", "CVR87654321", "Esbjerg", "6700");
+ ADD_BUSINESS("Lindberg AB", "Vasagatan 18", "SE", "SE456789123", "OrgNr7654321", "Uppsala", "753 10");
+ ADD_BUSINESS("Szabó Kft.", "Kossuth Lajos tér 1", "HU", "HU987123654", "Cégjegyzékszám654321", "Pécs", "7621");
+ ADD_BUSINESS("Costa Lda.", "Rua do Ouro 24", "PT", "PT654123987", "NIPC987654321", "Braga", "4700-001");
+ ADD_BUSINESS("Moreau SARL", "3 Place de la République", "FR", "FR321456987", "SIREN789123", "Lille", "59000");
ADD_PROJECT("eCommerce");
ADD_PROJECT("Retail store #1");
@@ -342,8 +344,10 @@ static void administration_create_debug_data()
snprintf(g_administration.company_info.id, sizeof(g_administration.company_info.id), "C/%d", administration_create_id());
strops_copy(g_administration.company_info.name, "Aldrik Ramaekers", sizeof(g_administration.company_info.name));
strops_copy(g_administration.company_info.address.address1, "Keerderstraat 81", sizeof(g_administration.company_info.address.address1));
- strops_copy(g_administration.company_info.address.address2, "6226XW Maastricht", sizeof(g_administration.company_info.address.address2));
- strops_copy(g_administration.company_info.address.country_code, "NL", sizeof(g_administration.company_info.address.country_code));
+ strops_copy(g_administration.company_info.address.address2, "", sizeof(g_administration.company_info.address.address2));
+ strops_copy(g_administration.company_info.address.city, "Maastricht", sizeof(g_administration.company_info.address.city));
+ strops_copy(g_administration.company_info.address.postal, "6226XW", sizeof(g_administration.company_info.address.postal));
+ strops_copy(g_administration.company_info.address.country_code, "NL", sizeof(g_administration.company_info.address.country_code));
strops_copy(g_administration.company_info.taxid, "123", sizeof(g_administration.company_info.taxid));
strops_copy(g_administration.company_info.businessid, "123", sizeof(g_administration.company_info.businessid));
g_administration.next_id++;
@@ -366,8 +370,8 @@ static void administration_create_debug_data()
}
// Create about 2 years of random data.
- for (int i = 0; i < 410; i++) ADD_INVOICE(1);
- for (int i = 0; i < 700; i++) ADD_INVOICE(0);
+ for (int i = 0; i < 1; i++) { ADD_INVOICE(1); }
+ for (int i = 0; i < 1; i++) { ADD_INVOICE(0); }
}
static s32 administration_create_sequence_number()
@@ -587,17 +591,20 @@ int administration_contact_get_autocompletions(contact* buffer, int buf_size, ch
bool administration_contact_is_valid(contact data)
{
- if (data.type == contact_type::CONTACT_CONSUMER)
- {
- return strlen(data.name) > 0 && strlen(data.address.address1) > 0 && strlen(data.address.address2) > 0 && strlen(data.address.country_code) > 0;
- }
- else if (data.type == contact_type::CONTACT_BUSINESS)
+ if (strlen(data.name) == 0) return 0;
+ if (strlen(data.address.city) == 0) return 0;
+ if (strlen(data.address.postal) == 0) return 0;
+ if (strlen(data.address.address1) == 0) return 0;
+ //if (strlen(data.address.address2) == 0) return 0;
+ if (strlen(data.address.country_code) == 0) return 0;
+
+ if (data.type == contact_type::CONTACT_BUSINESS)
{
- return strlen(data.name) > 0 && strlen(data.address.address1) > 0 && strlen(data.address.address2) > 0 && strlen(data.address.country_code) > 0
- && strlen(data.taxid) > 0 && strlen(data.businessid);
+ if (strlen(data.taxid) == 0) return 0;
+ if (strlen(data.businessid) == 0) return 0;
}
- return false;
+ return true;
}
contact administration_contact_create_empty()
@@ -1184,6 +1191,22 @@ u32 administation_invoice_get_outgoing_count()
return g_administration.invoice_count;
}
+u32 administration_invoice_get_all(invoice* buffer)
+{
+ assert(buffer);
+
+ u32 write_cursor = 0;
+
+ list_iterator_start(&g_administration.invoices);
+ while (list_iterator_hasnext(&g_administration.invoices)) {
+ invoice c = *(invoice *)list_iterator_next(&g_administration.invoices);
+ buffer[write_cursor++] = c;
+ }
+ list_iterator_stop(&g_administration.invoices);
+
+ 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 00543af..40fcd25 100644
--- a/src/administration_writer.cpp
+++ b/src/administration_writer.cpp
@@ -6,7 +6,9 @@
#include <threads.h>
#include "ui.hpp"
+#include "strops.hpp"
#include "administration_writer.hpp"
+#include "file_templates.hpp"
mtx_t _save_file_mutex;
@@ -80,6 +82,155 @@ static bool administration_writer_write_to_zip(char* entry_to_replace, char* ori
}
/////////////////////////////
+//// Invoices
+/////////////////////////////
+static char* administration_writer_get_eas_id_for_contact(contact contact)
+{
+ // https://docs.peppol.eu/poacc/billing/3.0/codelist/eas/
+ char* country_code = contact.address.country_code;
+
+ // Countries using tax identification numbers.
+ if (strcmp(country_code, "AT") == 0) return contact.taxid; // Austria
+ if (strcmp(country_code, "BE") == 0) return contact.taxid; // Belgium
+ if (strcmp(country_code, "BG") == 0) return contact.taxid; // Bulgaria
+ if (strcmp(country_code, "CY") == 0) return contact.taxid; // Cyprus
+ if (strcmp(country_code, "CZ") == 0) return contact.taxid; // Czech Republic
+ if (strcmp(country_code, "DE") == 0) return contact.taxid; // Germany
+ if (strcmp(country_code, "EE") == 0) return contact.taxid; // Estonia
+ if (strcmp(country_code, "FR") == 0) return contact.taxid; // France
+ if (strcmp(country_code, "GR") == 0) return contact.taxid; // Greece
+ if (strcmp(country_code, "HR") == 0) return contact.taxid; // Croatia
+ if (strcmp(country_code, "HU") == 0) return contact.taxid; // Hungary
+ if (strcmp(country_code, "IE") == 0) return contact.taxid; // Ireland
+ if (strcmp(country_code, "LU") == 0) return contact.taxid; // Luxembourg
+ if (strcmp(country_code, "LV") == 0) return contact.taxid; // Latvia
+ if (strcmp(country_code, "MT") == 0) return contact.taxid; // Malta
+ if (strcmp(country_code, "NL") == 0) return contact.taxid; // Netherlands
+ if (strcmp(country_code, "PL") == 0) return contact.taxid; // Poland
+ if (strcmp(country_code, "PT") == 0) return contact.taxid; // Portugal
+ if (strcmp(country_code, "RO") == 0) return contact.taxid; // Romania
+ if (strcmp(country_code, "SI") == 0) return contact.taxid; // Slovenia
+ if (strcmp(country_code, "SK") == 0) return contact.taxid; // Slovakia
+ if (strcmp(country_code, "ES") == 0) return contact.taxid; // Spain
+
+ // Countries using business identification numbers.
+ if (strcmp(country_code, "SE") == 0) return contact.businessid; // Sweden
+ if (strcmp(country_code, "LT") == 0) return contact.businessid; // Lithuania
+ if (strcmp(country_code, "IT") == 0) return contact.businessid; // Italy
+ if (strcmp(country_code, "FI") == 0) return contact.businessid; // Finland
+ if (strcmp(country_code, "DK") == 0) return contact.businessid; // Denmark
+ return NULL; // Unknown country code
+}
+
+static char* administration_writer_get_eas_scheme_for_address(address addr)
+{
+ // https://docs.peppol.eu/poacc/billing/3.0/codelist/eas/
+ char* country_code = addr.country_code;
+ if (strcmp(country_code, "AT") == 0) return "9914"; // Austria
+ if (strcmp(country_code, "BE") == 0) return "9925"; // Belgium
+ if (strcmp(country_code, "BG") == 0) return "9926"; // Bulgaria
+ if (strcmp(country_code, "CY") == 0) return "9928"; // Cyprus
+ if (strcmp(country_code, "CZ") == 0) return "9929"; // Czech Republic
+ if (strcmp(country_code, "DE") == 0) return "9930"; // Germany
+ if (strcmp(country_code, "DK") == 0) return "0096"; // Denmark
+ if (strcmp(country_code, "EE") == 0) return "9931"; // Estonia
+ if (strcmp(country_code, "FR") == 0) return "9957"; // France
+ if (strcmp(country_code, "GR") == 0) return "9933"; // Greece
+ if (strcmp(country_code, "HR") == 0) return "9934"; // Croatia
+ if (strcmp(country_code, "HU") == 0) return "9910"; // Hungary
+ if (strcmp(country_code, "IE") == 0) return "9935"; // Ireland
+ if (strcmp(country_code, "FI") == 0) return "0212"; // Finland
+ if (strcmp(country_code, "IT") == 0) return "0208"; // Italy
+ if (strcmp(country_code, "LT") == 0) return "0200"; // Lithuania
+ if (strcmp(country_code, "LU") == 0) return "9938"; // Luxembourg
+ if (strcmp(country_code, "LV") == 0) return "9939"; // Latvia
+ if (strcmp(country_code, "MT") == 0) return "9943"; // Malta
+ if (strcmp(country_code, "NL") == 0) return "9944"; // Netherlands
+ if (strcmp(country_code, "PL") == 0) return "9945"; // Poland
+ if (strcmp(country_code, "PT") == 0) return "9946"; // Portugal
+ if (strcmp(country_code, "RO") == 0) return "9947"; // Romania
+ if (strcmp(country_code, "SE") == 0) return "0007"; // Sweden
+ if (strcmp(country_code, "SI") == 0) return "9949"; // Slovenia
+ if (strcmp(country_code, "SK") == 0) return "9950"; // Slovakia
+ if (strcmp(country_code, "ES") == 0) return "9920"; // Spain
+ return NULL; // Unknown country code
+}
+
+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);
+ memset(file_content, 0, buf_length);
+ memcpy(file_content, peppol_invoice_template, strlen(peppol_invoice_template));
+
+ struct tm *tm_info = 0;
+ char date_buffer[11]; // "YYYY-MM-DD" + null terminator
+
+ strops_replace(file_content, buf_length, "{{INVOICE_ID}}", inv.sequential_number);
+ strops_replace(file_content, buf_length, "{{CURRENCY}}", inv.currency);
+
+ // Supplier data
+ strops_replace(file_content, buf_length, "{{SUPPLIER_ENDPOINT_SCHEME}}", administration_writer_get_eas_scheme_for_address(inv.supplier.address));
+ strops_replace(file_content, buf_length, "{{SUPPLIER_ENDPOINT_ID}}", administration_writer_get_eas_id_for_contact(inv.supplier));
+ strops_replace(file_content, buf_length, "{{SUPPLIER_NAME}}", inv.supplier.name);
+ strops_replace(file_content, buf_length, "{{SUPPLIER_STREET}}", inv.supplier.address.address1);
+ strops_replace(file_content, buf_length, "{{SUPPLIER_STREET2}}", inv.supplier.address.address2);
+ strops_replace(file_content, buf_length, "{{SUPPLIER_CITY}}", inv.supplier.address.city);
+ strops_replace(file_content, buf_length, "{{SUPPLIER_POSTAL}}", inv.supplier.address.postal);
+ strops_replace(file_content, buf_length, "{{SUPPLIER_REGION}}", inv.supplier.address.region);
+ strops_replace(file_content, buf_length, "{{SUPPLIER_COUNTRY}}", inv.supplier.address.country_code);
+ strops_replace(file_content, buf_length, "{{SUPPLIER_VAT_ID}}", inv.supplier.taxid);
+
+ // Customer data
+ strops_replace(file_content, buf_length, "{{CUSTOMER_ENDPOINT_SCHEME}}", administration_writer_get_eas_scheme_for_address(inv.customer.address));
+ strops_replace(file_content, buf_length, "{{CUSTOMER_ENDPOINT_ID}}", administration_writer_get_eas_id_for_contact(inv.customer));
+ strops_replace(file_content, buf_length, "{{CUSTOMER_NAME}}", inv.customer.name);
+ strops_replace(file_content, buf_length, "{{CUSTOMER_STREET}}", inv.customer.address.address1);
+ strops_replace(file_content, buf_length, "{{CUSTOMER_STREET2}}", inv.customer.address.address2);
+ strops_replace(file_content, buf_length, "{{CUSTOMER_CITY}}", inv.customer.address.city);
+ strops_replace(file_content, buf_length, "{{CUSTOMER_POSTAL}}", inv.customer.address.postal);
+ strops_replace(file_content, buf_length, "{{CUSTOMER_REGION}}", inv.customer.address.region);
+ 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);
+
+ // Dates
+ tm_info = localtime(&inv.issued_at);
+ strftime(date_buffer, sizeof(date_buffer), "%Y-%m-%d", tm_info);
+ strops_replace(file_content, buf_length, "{{ISSUE_DATE}}", date_buffer);
+
+ //// Write to Disk.
+ char final_path[50];
+ snprintf(final_path, 50, "%s.xml", inv.id);
+
+ 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;
+}
+
+static bool administration_writer_save_all_invoices_blocking()
+{
+ bool result = 1;
+ u32 num_invoices = administration_invoice_count();
+ u32 buffer_size = sizeof(invoice) * num_invoices;
+ invoice* invoice_buffer = (invoice*)malloc(buffer_size);
+ num_invoices = administration_invoice_get_all(invoice_buffer);
+
+ for (u32 i = 0; i < num_invoices; i++) {
+ invoice c = invoice_buffer[i];
+ if (!administration_writer_save_invoice_blocking(c)) result = 0;
+ }
+
+ free(invoice_buffer);
+ return result;
+}
+
+/////////////////////////////
//// Projects
/////////////////////////////
bool administration_writer_save_project_blocking(project project)
@@ -326,6 +477,7 @@ static int administration_writer_write_all_t(void *arg) {
mtx_lock(&_save_file_mutex);
+ if (!administration_writer_save_all_invoices_blocking()) result = 0;
if (!administration_writer_save_all_projects_blocking()) result = 0;
if (!administration_writer_save_all_administration_info_blocking()) result = 0;
if (!administration_writer_save_all_tax_brackets_blocking()) result = 0;
diff --git a/src/locales/en.cpp b/src/locales/en.cpp
index c965edd..767a07d 100644
--- a/src/locales/en.cpp
+++ b/src/locales/en.cpp
@@ -99,6 +99,9 @@ locale_entry en_locales[] = {
{"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 116eec4..3389eb1 100644
--- a/src/strops.cpp
+++ b/src/strops.cpp
@@ -32,4 +32,29 @@ char* strops_stristr(char* haystack, char* needle)
}
} while (*haystack++);
return 0;
+}
+
+void strops_replace(char *buf, size_t buf_size, const char *search, const char *replace)
+{
+ size_t search_len = strlen(search);
+ size_t replace_len = strlen(replace);
+
+ char *r = buf; // read pointer
+ char *w = buf; // write pointer
+
+ while (*r && (w - buf) < (signed int)(buf_size - 1)) {
+ if (strncmp(r, search, search_len) == 0) {
+ // Ensure space
+ size_t remaining = buf_size - (w - buf) - 1;
+ size_t copy_len = (replace_len < remaining) ? replace_len : remaining;
+
+ memcpy(w, replace, copy_len);
+ w += copy_len;
+ r += search_len;
+ } else {
+ *w++ = *r++;
+ }
+ }
+
+ *w = '\0'; // terminate
} \ No newline at end of file
diff --git a/src/ui/ui_contacts.cpp b/src/ui/ui_contacts.cpp
index d680ae8..ca73ac3 100644
--- a/src/ui/ui_contacts.cpp
+++ b/src/ui/ui_contacts.cpp
@@ -30,6 +30,21 @@ void ui_draw_address_form(address* buffer)
ImGui::InputTextWithHint(id, localize("contact.form.address2"), buffer->address2, IM_ARRAYSIZE(buffer->address2));
ImGui::SameLine();ui_helper_draw_required_tag();
+ ImGui::SetNextItemWidth(widthAvailable*0.5f);
+ snprintf(id, sizeof(id), "%s##%p", localize("contact.form.city"), buffer);
+ ImGui::InputTextWithHint(id, localize("contact.form.city"), buffer->city, IM_ARRAYSIZE(buffer->city));
+ ImGui::SameLine();ui_helper_draw_required_tag();
+
+ ImGui::SetNextItemWidth(widthAvailable*0.5f);
+ snprintf(id, sizeof(id), "%s##%p", localize("contact.form.postal"), buffer);
+ ImGui::InputTextWithHint(id, localize("contact.form.postal"), buffer->postal, IM_ARRAYSIZE(buffer->postal));
+ ImGui::SameLine();ui_helper_draw_required_tag();
+
+ ImGui::SetNextItemWidth(widthAvailable*0.5f);
+ snprintf(id, sizeof(id), "%s##%p", localize("contact.form.region"), buffer);
+ ImGui::InputTextWithHint(id, localize("contact.form.region"), buffer->region, IM_ARRAYSIZE(buffer->region));
+ ImGui::SameLine();ui_helper_draw_required_tag();
+
// 5. Country dropdown.
ImGui::SetNextItemWidth(widthAvailable*0.5f);
const char* countries[] = { localize("country.AT"),localize("country.BE"),localize("country.BG"),localize("country.HR"),localize("country.CY"),localize("country.CZ"),localize("country.DK"),localize("country.EE"),localize("country.FI"),localize("country.FR"),localize("country.DE"),localize("country.GR"),localize("country.HU"),localize("country.IE"),localize("country.IT"),localize("country.LV"),localize("country.LT"),localize("country.LU"),localize("country.MT"),localize("country.NL"),localize("country.PL"),localize("country.PT"),localize("country.RO"),localize("country.SK"),localize("country.SI"),localize("country.ES"),localize("country.SE") };