summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAldrik Ramaekers <aldrik@mailbox.org>2026-01-03 11:03:50 +0100
committerAldrik Ramaekers <aldrik@mailbox.org>2026-01-03 11:03:50 +0100
commit83a9739b3aff75cf767db687bd531fa5283e0e72 (patch)
treeadf130cb7728a4358062a040f6322ecc236d0e6b
parent2218ef68056ebc5a3a416e2dd7e8e020fba60a4f (diff)
move invoice status to invoice extras struct. implement r/w
-rw-r--r--include/administration.hpp10
-rw-r--r--include/administration_reader.hpp4
-rw-r--r--include/file_templates.hpp13
-rw-r--r--src/administration.cpp3
-rw-r--r--src/administration_reader.cpp40
-rw-r--r--src/administration_writer.cpp38
-rw-r--r--src/importer.cpp2
-rw-r--r--src/ui/ui_expenses.cpp4
-rw-r--r--src/ui/ui_invoices.cpp7
-rw-r--r--tests/administration_rw_tests.cpp4
-rw-r--r--tests/peppol_write_tests.cpp2
-rw-r--r--tests/test_helper.cpp2
12 files changed, 100 insertions, 29 deletions
diff --git a/include/administration.hpp b/include/administration.hpp
index fede22a..d899987 100644
--- a/include/administration.hpp
+++ b/include/administration.hpp
@@ -280,6 +280,11 @@ typedef struct
typedef struct
{
+ invoice_status status;
+} invoice_extras;
+
+typedef struct
+{
char id[MAX_LEN_ID]; // I/[id]
char sequential_number[MAX_LEN_SEQ_NUM]; // INV0000000000 - INV9999999999
time_t issued_at;
@@ -298,13 +303,14 @@ typedef struct
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_extras extras; // Stored outside of invoice file.
} invoice;
typedef struct
diff --git a/include/administration_reader.hpp b/include/administration_reader.hpp
index b2311af..bb9ba29 100644
--- a/include/administration_reader.hpp
+++ b/include/administration_reader.hpp
@@ -16,6 +16,8 @@
#pragma once
+#include <zip.h>
+
namespace administration_reader {
bool open_new();
@@ -26,7 +28,7 @@ namespace administration_reader {
bool import_cost_center(char* buffer, size_t buffer_size);
bool import_project(char* buffer, size_t buffer_size);
bool import_contact(char* buffer, size_t buffer_size);
- bool import_invoice(char* buffer, size_t buffer_size);
+ bool import_invoice(zip_t* zip, char* buffer, size_t buffer_size);
bool read_invoice_from_xml(invoice* result, char* buffer, size_t buffer_size);
} \ No newline at end of file
diff --git a/include/file_templates.hpp b/include/file_templates.hpp
index c5e0191..5404270 100644
--- a/include/file_templates.hpp
+++ b/include/file_templates.hpp
@@ -126,6 +126,11 @@ namespace file_template {
" </cac:Price>\n"
" </cac:InvoiceLine>\n";
+ static const char *invoice_extras_template =
+ "<InvoiceExtras>\n"
+ " <Status>{{INVOICE_STATUS}}</Status>\n"
+ "</InvoiceExtras>\n";
+
static 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"
@@ -141,10 +146,10 @@ namespace file_template {
" <cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>\n"
" <cbc:DocumentCurrencyCode>{{CURRENCY}}</cbc:DocumentCurrencyCode>\n"
"\n"
- " <cac:DespatchDocumentReference>\n"
- " <cbc:ID>{{INVOICE_STATUS}}</cbc:ID>\n"
- " </cac:DespatchDocumentReference>\n"
- "\n"
+ // " <cac:DespatchDocumentReference>\n"
+ // " <cbc:ID>{{INVOICE_STATUS}}</cbc:ID>\n"
+ // " </cac:DespatchDocumentReference>\n"
+ // "\n"
" <cac:AdditionalDocumentReference>\n"
" <cbc:ID>{{INVOICE_DOCUMENT_COPY}}</cbc:ID>\n"
" <cbc:DocumentDescription>{{INVOICE_DOCUMENT_ORIG}}</cbc:DocumentDescription>\n"
diff --git a/src/administration.cpp b/src/administration.cpp
index 77e8754..6e7d2f1 100644
--- a/src/administration.cpp
+++ b/src/administration.cpp
@@ -1397,6 +1397,7 @@ invoice administration::invoice_create_empty()
strops::format(result.sequential_number, sizeof(result.id), "INV%010d", create_sequence_number());
result.issued_at = time(NULL);
result.issued_at -= (result.issued_at % 86400);
+ result.extras.status = invoice_status::INVOICE_CONCEPT;
result.delivered_at = result.issued_at;
result.expires_at = result.issued_at + administration::get_default_invoice_expire_duration();
@@ -1752,7 +1753,7 @@ u32 administration::invoice_get_partial_list_incomming(u32 page_index, u32 page_
char* administration::invoice_get_status_string(invoice* invoice)
{
- switch(invoice->status)
+ switch(invoice->extras.status)
{
case invoice_status::INVOICE_CONCEPT: return "invoice.status.concept";
case invoice_status::INVOICE_SENT: return "invoice.status.sent";
diff --git a/src/administration_reader.cpp b/src/administration_reader.cpp
index d790b91..3706e72 100644
--- a/src/administration_reader.cpp
+++ b/src/administration_reader.cpp
@@ -92,7 +92,7 @@ bool administration_reader::open_existing(char* file_path)
}
else if (strops::is_prefixed("I/", name))
{
- administration_reader::import_invoice(buffer, (size_t)size);
+ administration_reader::import_invoice(zip, buffer, (size_t)size);
}
memops::unalloc(buffer);
}
@@ -123,7 +123,7 @@ bool administration_reader::read_invoice_from_xml(invoice* result, char* buffer,
xml_get_str(root, data.id, MAX_LEN_ID, "cbc:ID");
xml_get_str_x(root, data.sequential_number, MAX_LEN_ID, "cac:OrderReference", "cbc:ID", 0);
xml_get_str(root, data.currency, MAX_LEN_CURRENCY, "cbc:DocumentCurrencyCode");
- data.status = (invoice_status)xml_get_s32_x(root, "cac:DespatchDocumentReference", "cbc:ID", 0);
+ //data.status = (invoice_status)xml_get_s32_x(root, "cac:DespatchDocumentReference", "cbc:ID", 0);
// Dates
data.issued_at = xml_get_date_x(root, "cbc:IssueDate", 0);
@@ -232,12 +232,46 @@ bool administration_reader::read_invoice_from_xml(invoice* result, char* buffer,
return true;
}
-bool administration_reader::import_invoice(char* buffer, size_t buffer_size)
+static bool _import_invoice_extras(zip_t* zip, invoice* data)
+{
+ STOPWATCH_START;
+
+ char final_path[50];
+ strops::format(final_path, 50, "EXTRAS/%s.xml", data->sequential_number);
+ if (zip_entry_open(zip, final_path) < 0) {
+ logger::error("ERROR loading invoice extras for '%s', file '%s' does not exist.", data->sequential_number, final_path);
+ return false;
+ }
+
+ // Load into buffer.
+ char* buffer;
+ unsigned long long size;
+ zip_entry_read(zip, (void**)&buffer, (size_t*)&size);
+
+ xml_document* document = xml_parse_document((uint8_t *)buffer, size);
+ if (!document) return false;
+
+ struct xml_node* root = xml_document_root(document);
+ if (!root) {
+ xml_document_free(document, false);
+ return false;
+ }
+
+ data->extras.status = (invoice_status)xml_get_s32_x(root, "Status", 0);
+
+ logger::info("Loaded invoice extras '%s' in %.3fms.", final_path, STOPWATCH_TIME);
+
+ return true;
+}
+
+bool administration_reader::import_invoice(zip_t* zip, char* buffer, size_t buffer_size)
{
STOPWATCH_START;
invoice data;
if (!administration_reader::read_invoice_from_xml(&data, buffer, buffer_size)) return false;
+ if (!_import_invoice_extras(zip, &data)) return false;
+ printf("%d\n", data.extras.status);
a_err result = administration::invoice_import(&data);
if (result == A_ERR_SUCCESS) {
diff --git a/src/administration_writer.cpp b/src/administration_writer.cpp
index 8304e5c..3191be7 100644
--- a/src/administration_writer.cpp
+++ b/src/administration_writer.cpp
@@ -240,7 +240,7 @@ void administration_writer::destroy()
static char* copy_template(const char* template_str, int* buf_size)
{
size_t template_size = strops::length(template_str);
- size_t buf_length = template_size*5; // Ballpark file content size.
+ size_t buf_length = template_size*5; // Ballpark file content size. // @TODO
char* file_content = (char*)memops::alloc(buf_length);
memops::zero(file_content, buf_length);
memops::copy(file_content, template_str, template_size);
@@ -504,12 +504,40 @@ static void _add_document_to_zip(invoice* inv)
}
}
+bool _save_invoice_extras(invoice inv)
+{
+ STOPWATCH_START;
+
+ bool result = 1;
+ int buf_length = 0;
+ char* file_content = copy_template(file_template::invoice_extras_template, &buf_length);
+
+ strops::replace_int32(file_content, buf_length, "{{INVOICE_STATUS}}", (s32)inv.extras.status);
+
+ //// Write to Disk.
+ char final_path[50];
+ strops::format(final_path, 50, "EXTRAS/%s.xml", inv.sequential_number);
+
+ int final_length = (int)strops::length(file_content);
+ if (!xml_string_is_valid((uint8_t*)file_content, final_length)) result = 0;
+ else if (!write_to_zip(final_path, file_content, final_length)) result = 0;
+
+ memops::unalloc(file_content);
+
+ if (result) logger::info("Saved invoice extras for '%s' in %.3fms.", inv.sequential_number, STOPWATCH_TIME);
+ else logger::error("Failed to save invoice extras for '%s'.", inv.sequential_number);
+
+ return result;
+}
+
bool administration_writer::save_invoice_blocking(invoice inv)
{
+ _save_invoice_extras(inv);
+
STOPWATCH_START;
bool result = 1;
- int buf_length = 150000; // Ballpark file content size.
+ int buf_length = 150000; // Ballpark file content size. // @TODO
char* file_content = (char*)memops::alloc(buf_length);
memops::zero(file_content, buf_length);
memops::copy(file_content, file_template::peppol_invoice_template, strops::length(file_template::peppol_invoice_template));
@@ -526,7 +554,7 @@ bool administration_writer::save_invoice_blocking(invoice inv)
strops::replace(file_content, buf_length, "{{COST_CENTER_ID}}", inv.cost_center_id);
strops::replace(file_content, buf_length, "{{INVOICE_DOCUMENT_COPY}}", inv.document.copy_path);
strops::replace(file_content, buf_length, "{{INVOICE_DOCUMENT_ORIG}}", inv.document.original_path);
- strops::replace_int32(file_content, buf_length, "{{INVOICE_STATUS}}", (s32)inv.status);
+ //strops::replace_int32(file_content, buf_length, "{{INVOICE_STATUS}}", (s32)inv.status);
// Supplier data
strops::replace(file_content, buf_length, "{{SUPPLIER_ENDPOINT_SCHEME}}", get_eas_scheme_for_contact(inv.supplier));
@@ -587,7 +615,7 @@ bool administration_writer::save_invoice_blocking(invoice inv)
tax_rate* tax_rate_buffer = (tax_rate*)memops::alloc(sizeof(tax_rate)*administration::billing_item_count(&inv));
u32 tax_rate_count = administration::invoice_get_tax_rates(&inv, tax_rate_buffer);
- u32 tax_subtotal_list_buffer_size = (u32)strops::length(file_template::peppol_invoice_tax_subtotal_template) * 2 * tax_rate_count; // Ballpark list size.
+ u32 tax_subtotal_list_buffer_size = (u32)strops::length(file_template::peppol_invoice_tax_subtotal_template) * 2 * tax_rate_count; // Ballpark list size. // @TODO
char* tax_subtotal_list_buffer = (char*)memops::alloc(tax_subtotal_list_buffer_size);
memops::zero(tax_subtotal_list_buffer, tax_subtotal_list_buffer_size);
u32 tax_subtotal_list_buffer_cursor = 0;
@@ -631,7 +659,7 @@ bool administration_writer::save_invoice_blocking(invoice inv)
billing_item* billing_item_buffer = (billing_item*)memops::alloc(sizeof(billing_item) * administration::billing_item_count(&inv));
u32 billing_item_count = administration::billing_item_get_all_for_invoice(&inv, billing_item_buffer);
- u32 billing_item_list_buffer_size = (u32)strops::length(file_template::peppol_invoice_line_template) * 2 * billing_item_count; // Ballpark list size.
+ u32 billing_item_list_buffer_size = (u32)strops::length(file_template::peppol_invoice_line_template) * 2 * billing_item_count; // Ballpark list size. // @TODO
char* billing_item_list_buffer = (char*)memops::alloc(billing_item_list_buffer_size);
memops::zero(billing_item_list_buffer, billing_item_list_buffer_size);
u32 billing_item_list_buffer_cursor = 0;
diff --git a/src/importer.cpp b/src/importer.cpp
index 3c56062..65726bc 100644
--- a/src/importer.cpp
+++ b/src/importer.cpp
@@ -255,7 +255,7 @@ static int _ai_document_to_invoice_t(void *arg)
return 0;
}
- inv.status = invoice_status::INVOICE_RECEIVED;
+ inv.extras.status = invoice_status::INVOICE_RECEIVED;
inv.is_triangulation = !memops::equals(&inv.addressee.address, &inv.customer.address, sizeof(address));
diff --git a/src/ui/ui_expenses.cpp b/src/ui/ui_expenses.cpp
index 4227e08..7f8d1cc 100644
--- a/src/ui/ui_expenses.cpp
+++ b/src/ui/ui_expenses.cpp
@@ -179,7 +179,7 @@ static void draw_expenses_list()
active_invoice = administration::invoice_create_empty();
active_invoice.customer = administration::company_info_get();
active_invoice.is_outgoing = 0;
- active_invoice.status = invoice_status::INVOICE_RECEIVED;
+ active_invoice.extras.status = invoice_status::INVOICE_RECEIVED;
}
char import_file_path[MAX_LEN_PATH] = {0};
@@ -189,7 +189,7 @@ static void draw_expenses_list()
active_invoice = administration::invoice_create_empty();
active_invoice.customer = administration::company_info_get();
active_invoice.is_outgoing = 0;
- active_invoice.status = invoice_status::INVOICE_RECEIVED;
+ active_invoice.extras.status = invoice_status::INVOICE_RECEIVED;
active_import_request = importer::ai_document_to_invoice(import_file_path);
}
diff --git a/src/ui/ui_invoices.cpp b/src/ui/ui_invoices.cpp
index a5fbd3c..519544b 100644
--- a/src/ui/ui_invoices.cpp
+++ b/src/ui/ui_invoices.cpp
@@ -287,7 +287,6 @@ static void draw_invoices_list()
_set_active_invoice(administration::invoice_create_empty());
active_invoice.supplier = administration::company_info_get();
active_invoice.is_outgoing = 1;
- active_invoice.status = invoice_status::INVOICE_CONCEPT;
}
if (current_page >= max_page-1) current_page = max_page-1;
@@ -478,7 +477,11 @@ static void draw_send_options()
active_request = exporter::send_email("test@test-vz9dlemj2564kj50.mlsender.net", "aldrikboy@gmail.com", "test", "test 123",
[](e_err status) {
if (status == E_ERR_SUCCESS) {
- administration::activity_add(ACTIVITY_USER, active_invoice.id, "Sent email", 0);
+ active_invoice.extras.status = invoice_status::INVOICE_SENT;
+ administration::invoice_update(&active_invoice);
+ administration::activity_add(ACTIVITY_USER, active_invoice.id, "Invoice status changed", 0); // @locale
+ administration::activity_add(ACTIVITY_USER, active_invoice.id, "Sent email", 0); // @locale
+
_reload_activities();
}
});
diff --git a/tests/administration_rw_tests.cpp b/tests/administration_rw_tests.cpp
index be52f5f..a33173f 100644
--- a/tests/administration_rw_tests.cpp
+++ b/tests/administration_rw_tests.cpp
@@ -241,7 +241,6 @@ TEST _administration_rw_b2b_invoice(void)
inv.supplier = pw1;
inv.customer = pw2;
inv.is_outgoing = 1;
- inv.status = invoice_status::INVOICE_CONCEPT;
inv.issued_at = time(NULL);
inv.delivered_at = inv.issued_at;
inv.expires_at = inv.issued_at + 1000;
@@ -285,7 +284,6 @@ TEST _administration_rw_b2c_invoice(void)
inv.supplier = pw1;
inv.customer = pw2;
inv.is_outgoing = 1;
- inv.status = invoice_status::INVOICE_CONCEPT;
inv.issued_at = time(NULL);
inv.delivered_at = inv.issued_at;
inv.expires_at = inv.issued_at + 1000;
@@ -329,7 +327,6 @@ TEST _administration_rw_b2b2c_invoice(void)
inv.addressee.address = _create_nl_consumer().address;
inv.is_triangulation = 1;
inv.is_outgoing = 1;
- inv.status = invoice_status::INVOICE_CONCEPT;
inv.issued_at = time(NULL);
inv.delivered_at = inv.issued_at;
inv.expires_at = inv.issued_at + 1000;
@@ -373,7 +370,6 @@ TEST _administration_rw_b2c_2currency_invoice(void)
inv.supplier = pw1;
inv.customer = pw2;
inv.is_outgoing = 1;
- inv.status = invoice_status::INVOICE_CONCEPT;
inv.issued_at = time(NULL);
inv.delivered_at = inv.issued_at;
inv.expires_at = inv.issued_at + 1000;
diff --git a/tests/peppol_write_tests.cpp b/tests/peppol_write_tests.cpp
index 35cf940..a967477 100644
--- a/tests/peppol_write_tests.cpp
+++ b/tests/peppol_write_tests.cpp
@@ -12,7 +12,6 @@ TEST _peppol_write_nl2nl_b2b(void)
inv.supplier = _create_nl_business();
inv.customer = _create_nl_business();
inv.is_outgoing = 1;
- inv.status = invoice_status::INVOICE_CONCEPT;
inv.issued_at = time(NULL);
inv.delivered_at = inv.issued_at;
inv.expires_at = inv.issued_at + 1000;
@@ -40,7 +39,6 @@ TEST _peppol_write_nl2nl_b2c(void)
inv.supplier = _create_nl_business();
inv.customer = _create_nl_consumer();
inv.is_outgoing = 1;
- inv.status = invoice_status::INVOICE_CONCEPT;
inv.issued_at = time(NULL);
inv.delivered_at = inv.issued_at;
inv.expires_at = inv.issued_at + 1000;
diff --git a/tests/test_helper.cpp b/tests/test_helper.cpp
index c99bdd0..7b1bf96 100644
--- a/tests/test_helper.cpp
+++ b/tests/test_helper.cpp
@@ -152,7 +152,6 @@ static invoice _create_nl_b2b_inv_outgoing()
inv.supplier = administration::company_info_get();
inv.customer = _create_nl_business();
inv.is_outgoing = 1;
- inv.status = invoice_status::INVOICE_CONCEPT;
inv.issued_at = time(NULL);
inv.delivered_at = inv.issued_at;
inv.expires_at = inv.issued_at + 1000;
@@ -165,7 +164,6 @@ static invoice _create_nl_b2b_inv_incomming()
inv.supplier = _create_nl_business();
inv.customer = administration::company_info_get();
inv.is_outgoing = 0;
- inv.status = invoice_status::INVOICE_CONCEPT;
inv.issued_at = time(NULL);
inv.delivered_at = inv.issued_at;
inv.expires_at = inv.issued_at + 1000;