summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAldrik Ramaekers <aldrikboy@gmail.com>2025-09-27 09:47:26 +0200
committerAldrik Ramaekers <aldrikboy@gmail.com>2025-09-27 09:47:26 +0200
commit58edcf619ee9d589dd7b54b8a9cbd4271740c13b (patch)
treefccd23132f96eb3f5da3b530b78ad826f534687f
parentbd3f2b84742067d0b9049b9f42f2266f94dbb0f9 (diff)
fix shipping info r/w issue. fix date r/w timezone issue.
-rw-r--r--docs/CHANGES.rst1
-rw-r--r--include/administration.hpp9
-rw-r--r--libs/xml.c/src/xml.c5
-rw-r--r--src/administration.cpp43
-rw-r--r--src/administration_reader.cpp4
-rw-r--r--src/administration_writer.cpp15
-rw-r--r--src/ui/ui_contacts.cpp20
-rw-r--r--src/ui/ui_expenses.cpp3
-rw-r--r--src/ui/ui_invoices.cpp3
-rw-r--r--tests/administration_rw_tests.cpp51
10 files changed, 126 insertions, 28 deletions
diff --git a/docs/CHANGES.rst b/docs/CHANGES.rst
index ad9359b..39a011c 100644
--- a/docs/CHANGES.rst
+++ b/docs/CHANGES.rst
@@ -5,7 +5,6 @@ TODO:
- write tests that check error handling for corrupt files. (e.g. references to tax rates, project and cost center that failed to load)
- it is possible a referenced tax rate is loaded after an invoice is loaded. This means all invoices need to be recalculated after file load. (try to write a test for this).
-- project start and end date should be stored as YYYY-MM-DD
- Send invoice by email
- create invoice from PDF file
- create invoice from image of receipt
diff --git a/include/administration.hpp b/include/administration.hpp
index a91a4e3..ad01193 100644
--- a/include/administration.hpp
+++ b/include/administration.hpp
@@ -68,6 +68,12 @@ typedef struct
char country_code[MAX_LEN_COUNTRY_CODE]; // 2 letter country code
} address;
+typedef struct
+{
+ char name[MAX_LEN_LONG_DESC];
+ address address;
+} delivery_info;
+
typedef enum
{
CONTACT_BUSINESS,
@@ -269,7 +275,7 @@ typedef struct
contact supplier;
contact customer;
- contact addressee;
+ delivery_info addressee;
} invoice;
typedef struct
@@ -414,6 +420,7 @@ a_err administration_contact_add(contact data);
a_err administration_contact_update(contact data);
a_err administration_contact_remove(contact data);
+a_err administration_addressee_is_valid(delivery_info data);
a_err administration_contact_is_valid(contact data);
bool administration_contact_can_be_deleted(contact data);
bool administration_contact_equals(contact c1, contact c2);
diff --git a/libs/xml.c/src/xml.c b/libs/xml.c/src/xml.c
index e88ba93..3601aa6 100644
--- a/libs/xml.c/src/xml.c
+++ b/libs/xml.c/src/xml.c
@@ -1315,5 +1315,8 @@ time_t xml_get_date_x(struct xml_node* root, char* child_name, ...)
tm_info.tm_min = 0;
tm_info.tm_sec = 0;
- return mktime(&tm_info) + 86400; // Hack
+ time_t result = mktime(&tm_info);
+
+ if (result == -1) return 0;
+ else return result - _timezone;
} \ No newline at end of file
diff --git a/src/administration.cpp b/src/administration.cpp
index b6dfcbb..af276f3 100644
--- a/src/administration.cpp
+++ b/src/administration.cpp
@@ -871,6 +871,18 @@ int administration_contact_get_autocompletions(contact* buffer, int buf_size, ch
return write_cursor;
}
+a_err administration_addressee_is_valid(delivery_info data)
+{
+ a_err result = A_ERR_SUCCESS;
+ if (strlen(data.name) == 0) result |= A_ERR_MISSING_NAME;
+ if (strlen(data.address.city) == 0) result |= A_ERR_MISSING_CITY;
+ if (strlen(data.address.postal) == 0) result |= A_ERR_MISSING_POSTAL;
+ if (strlen(data.address.address1) == 0) result |= A_ERR_MISSING_ADDRESS1;
+ if (strlen(data.address.country_code) == 0) result |= A_ERR_MISSING_COUNTRYCODE;
+
+ return result;
+}
+
a_err administration_contact_is_valid(contact data)
{
a_err result = A_ERR_SUCCESS;
@@ -879,7 +891,6 @@ a_err administration_contact_is_valid(contact data)
if (strlen(data.address.city) == 0) result |= A_ERR_MISSING_CITY;
if (strlen(data.address.postal) == 0) result |= A_ERR_MISSING_POSTAL;
if (strlen(data.address.address1) == 0) result |= A_ERR_MISSING_ADDRESS1;
- //if (strlen(data.address.address2) == 0) return 0;
if (strlen(data.address.country_code) == 0) result |= A_ERR_MISSING_COUNTRYCODE;
if (data.type == contact_type::CONTACT_BUSINESS)
@@ -1080,6 +1091,7 @@ project administration_project_create_empty()
memset(&result, 0, sizeof(project));
result.state = project_state::PROJECT_RUNNING;
result.start_date = time(NULL);
+ result.start_date -= (result.start_date % 86400);
result.end_date = 0;
snprintf(result.id, sizeof(result.id), "P/%d", administration_create_id());
return result;
@@ -1461,7 +1473,7 @@ a_err administration_invoice_is_valid(invoice* invoice)
{
a_err result = A_ERR_SUCCESS;
if (list_size(&invoice->billing_items) == 0) result |= A_ERR_MISSING_BILLING_ITEMS;
- if (invoice->is_triangulation && administration_contact_is_valid(invoice->addressee) != A_ERR_SUCCESS) result |= A_ERR_INVALID_ADDRESSEE;
+ if (invoice->is_triangulation && administration_addressee_is_valid(invoice->addressee) != A_ERR_SUCCESS) result |= A_ERR_INVALID_ADDRESSEE;
if (administration_contact_is_valid(invoice->customer) != A_ERR_SUCCESS) result |= A_ERR_INVALID_CUSTOMER;
if (administration_contact_is_valid(invoice->supplier) != A_ERR_SUCCESS) result |= A_ERR_INVALID_SUPPLIER;
@@ -1500,16 +1512,24 @@ a_err administration_invoice_remove(invoice* inv)
return A_ERR_NOT_FOUND;
}
+static void _administration_invoice_set_addressee(invoice* inv, contact contact)
+{
+ memcpy(&inv->addressee.name, &contact.name, sizeof(inv->addressee.name));
+ memcpy(&inv->addressee.address.address1, &contact.address.address1, sizeof(inv->addressee.address.address1));
+ memcpy(&inv->addressee.address.address2, &contact.address.address2, sizeof(inv->addressee.address.address2));
+ memcpy(&inv->addressee.address.city, &contact.address.city, sizeof(inv->addressee.address.city));
+ memcpy(&inv->addressee.address.postal, &contact.address.postal, sizeof(inv->addressee.address.postal));
+ memcpy(&inv->addressee.address.region, &contact.address.region, sizeof(inv->addressee.address.region));
+ memcpy(&inv->addressee.address.country_code, &contact.address.country_code, sizeof(inv->addressee.address.country_code));
+}
+
a_err administration_invoice_update(invoice* inv)
{
a_err result = administration_invoice_is_valid(inv);
if (result != A_ERR_SUCCESS) return result;
// Addressee is same as customer.
- if (!inv->is_triangulation)
- {
- memcpy(&inv->addressee, &inv->customer, sizeof(contact));
- }
+ if (!inv->is_triangulation) _administration_invoice_set_addressee(inv, inv->customer);
list_iterator_start(&g_administration.invoices);
while (list_iterator_hasnext(&g_administration.invoices)) {
@@ -1535,10 +1555,7 @@ a_err administration_invoice_import(invoice* inv)
a_err result = administration_invoice_is_valid(inv);
if (result != A_ERR_SUCCESS) return result;
- if (!inv->is_triangulation)
- {
- memcpy(&inv->addressee, &inv->customer, sizeof(contact));
- }
+ inv->is_triangulation = !(memcmp(&inv->addressee.address, &inv->customer.address, sizeof(address)) == 0);
inv->issued_at -= (inv->issued_at % 86400);
inv->delivered_at -= (inv->delivered_at % 86400);
@@ -1563,11 +1580,7 @@ a_err administration_invoice_add(invoice* inv)
a_err result = administration_invoice_is_valid(inv);
if (result != A_ERR_SUCCESS) return result;
- // Addressee is same as customer.
- if (!inv->is_triangulation)
- {
- memcpy(&inv->addressee, &inv->customer, sizeof(contact));
- }
+ if (!inv->is_triangulation) _administration_invoice_set_addressee(inv, inv->customer);
inv->issued_at -= (inv->issued_at % 86400);
inv->delivered_at -= (inv->delivered_at % 86400);
diff --git a/src/administration_reader.cpp b/src/administration_reader.cpp
index 09a4a0a..bf8f8db 100644
--- a/src/administration_reader.cpp
+++ b/src/administration_reader.cpp
@@ -302,8 +302,8 @@ bool administration_reader_import_project(char* buffer, size_t buffer_size)
xml_get_str(root, data.id, MAX_LEN_ID, "Id");
xml_get_str(root, data.description, MAX_LEN_LONG_DESC, "Description");
data.state = (project_state)xml_get_s32(root, "State");
- data.start_date = xml_get_s64(root, "StartDate");
- data.end_date = xml_get_s64(root, "EndDate");
+ data.start_date = xml_get_date_x(root, "StartDate", 0);
+ data.end_date = xml_get_date_x(root, "EndDate", 0);
a_err result = administration_project_import(data);
if (result == A_ERR_SUCCESS) {
diff --git a/src/administration_writer.cpp b/src/administration_writer.cpp
index c27b3e3..b08723b 100644
--- a/src/administration_writer.cpp
+++ b/src/administration_writer.cpp
@@ -522,11 +522,22 @@ bool administration_writer_save_project_blocking(project project)
int buf_length = 0;
char* file_content = administration_writer_copy_template(project_save_template, &buf_length);
+ struct tm *tm_info = 0;
+ char date_buffer[11]; // "YYYY-MM-DD" + null terminator
+
+
+
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);
+
+ tm_info = gmtime(&project.start_date);
+ strftime(date_buffer, sizeof(date_buffer), "%Y-%m-%d", tm_info);
+ strops_replace(file_content, buf_length, "{{PROJECT_STARTDATE}}", date_buffer);
+
+ tm_info = gmtime(&project.end_date);
+ strftime(date_buffer, sizeof(date_buffer), "%Y-%m-%d", tm_info);
+ strops_replace(file_content, buf_length, "{{PROJECT_ENDDATE}}", date_buffer);
//// Write to Disk.
char final_path[50];
diff --git a/src/ui/ui_contacts.cpp b/src/ui/ui_contacts.cpp
index 549273d..fc03e84 100644
--- a/src/ui/ui_contacts.cpp
+++ b/src/ui/ui_contacts.cpp
@@ -48,6 +48,25 @@ void ui_setup_contacts()
memset(&selected_for_removal, 0, sizeof(contact));
}
+void draw_addressee_form_ex(delivery_info* buffer, bool viewing_only = false)
+{
+ a_err last_err = administration_addressee_is_valid(*buffer);
+
+ ImGui::PushID(buffer);
+
+ ImGui::Spacing();
+ ImGui::BeginDisabled();
+
+ if (!viewing_only) ImGui::EndDisabled();
+
+ ImGui::FormInputTextWithErrorHint(localize("contact.form.fullname"), buffer->name, IM_ARRAYSIZE(buffer->name), last_err & A_ERR_MISSING_NAME);
+
+ ui_draw_address_form(&buffer->address, last_err);
+
+ if (viewing_only) ImGui::EndDisabled();
+ ImGui::PopID();
+}
+
void draw_contact_form_ex(contact* buffer, bool viewing_only = false, bool with_autocomplete = false)
{
a_err last_err = administration_contact_is_valid(*buffer);
@@ -86,7 +105,6 @@ void draw_contact_form(contact* buffer, bool viewing_only = false)
draw_contact_form_ex(buffer, viewing_only, false);
}
-
static void draw_contact_list()
{
const u32 items_per_page = 50;
diff --git a/src/ui/ui_expenses.cpp b/src/ui/ui_expenses.cpp
index 7dd5add..9094d30 100644
--- a/src/ui/ui_expenses.cpp
+++ b/src/ui/ui_expenses.cpp
@@ -34,6 +34,7 @@ static invoice selected_for_removal = {0};
static billing_item* invoice_items_buffer = 0;
+void draw_addressee_form_ex(delivery_info* buffer, bool viewing_only = false);
void draw_contact_form_ex(contact* buffer, bool viewing_only = false, bool with_autocomplete = false);
void draw_invoice_items_form(invoice* invoice);
@@ -91,7 +92,7 @@ static void draw_expense_form(invoice* buffer, bool viewing_only = false)
if (buffer->is_triangulation) {
ImGui::Spacing();
ImGui::Text(localize("invoice.form.shippinginformation"));
- draw_contact_form_ex(&buffer->addressee, 0,0);
+ draw_addressee_form_ex(&buffer->addressee, 0);
}
ImGui::Separator();
diff --git a/src/ui/ui_invoices.cpp b/src/ui/ui_invoices.cpp
index 6c3e229..0ef3fb2 100644
--- a/src/ui/ui_invoices.cpp
+++ b/src/ui/ui_invoices.cpp
@@ -36,6 +36,7 @@ static invoice selected_for_removal = {0};
static billing_item* invoice_items_buffer = 0;
void draw_contact_form_ex(contact* buffer, bool viewing_only = false, bool with_autocomplete = false);
+void draw_addressee_form_ex(delivery_info* buffer, bool viewing_only = false);
void ui_destroy_invoices()
{
@@ -212,7 +213,7 @@ static void draw_invoice_form(invoice* buffer, bool viewing_only = false)
if (buffer->is_triangulation) {
ImGui::Spacing();
ImGui::Text(localize("invoice.form.shippinginformation"));
- draw_contact_form_ex(&buffer->addressee, 0,0);
+ draw_addressee_form_ex(&buffer->addressee, 0);
}
ImGui::Separator();
diff --git a/tests/administration_rw_tests.cpp b/tests/administration_rw_tests.cpp
index 9cb199a..e1582f7 100644
--- a/tests/administration_rw_tests.cpp
+++ b/tests/administration_rw_tests.cpp
@@ -209,9 +209,9 @@ TEST _assert_invoices_are_equal(invoice inv, invoice invr)
ASSERT_EQ(invr.status, inv.status);
ASSERT_EQ(invr.is_outgoing, inv.is_outgoing);
ASSERT_MEM_EQ(&invr.payment_means, &inv.payment_means, sizeof(payment_information));
- ASSERT_MEM_EQ(&invr.supplier, &inv.supplier, sizeof(contact));
- ASSERT_MEM_EQ(&invr.customer, &inv.customer, sizeof(contact));
- ASSERT_MEM_EQ(&invr.addressee, &inv.addressee, sizeof(contact));
+ ASSERT_MEM_EQ(&invr.supplier, &inv.supplier, sizeof(delivery_info));
+ ASSERT_MEM_EQ(&invr.customer, &inv.customer, sizeof(delivery_info));
+ ASSERT_MEM_EQ(&invr.addressee, &inv.addressee, sizeof(delivery_info));
PASS();
}
@@ -302,6 +302,50 @@ TEST _administration_rw_b2c_invoice(void)
PASS();
}
+
+TEST _administration_rw_b2b2c_invoice(void)
+{
+ u32 count;
+ invoice inv;
+ invoice invr;
+
+ administration_writer_create();
+
+ administration_create_default(test_file_path);
+ {
+ inv = administration_invoice_create_empty();
+ inv.supplier = _create_nl_business();
+ inv.customer = _create_nl_business();
+ strops_copy(inv.addressee.name, "Piet Pinda", sizeof(inv.addressee.name));
+ 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;
+
+ administration_billing_item_add_to_invoice(&inv, _create_bi3(&inv));
+ administration_billing_item_add_to_invoice(&inv, _create_bi4(&inv));
+ administration_billing_item_add_to_invoice(&inv, _create_bi5(&inv));
+ administration_billing_item_add_to_invoice(&inv, _create_bi6(&inv));
+
+ count = administration_invoice_count();
+ ASSERT_EQ(administration_invoice_add(&inv), A_ERR_SUCCESS);
+ ASSERT_EQ(count+1, administration_invoice_count());
+ }
+
+ administration_reader_open_existing(test_file_path);
+ {
+ ASSERT_EQ(count+1, administration_invoice_count());
+ ASSERT_EQ(A_ERR_SUCCESS, administration_invoice_get_by_id(&invr, inv.id));
+
+ CHECK_CALL(_assert_invoices_are_equal(inv, invr));
+ }
+
+ PASS();
+}
+
SUITE(administration_rw) {
SET_SETUP(setup_cb, NULL);
SET_TEARDOWN(teardown_cb, NULL);
@@ -313,4 +357,5 @@ SUITE(administration_rw) {
RUN_TEST(_administration_rw_info);
RUN_TEST(_administration_rw_b2b_invoice);
RUN_TEST(_administration_rw_b2c_invoice);
+ RUN_TEST(_administration_rw_b2b2c_invoice);
} \ No newline at end of file