diff options
| -rw-r--r-- | docs/CHANGES.rst | 1 | ||||
| -rw-r--r-- | include/administration.hpp | 9 | ||||
| -rw-r--r-- | libs/xml.c/src/xml.c | 5 | ||||
| -rw-r--r-- | src/administration.cpp | 43 | ||||
| -rw-r--r-- | src/administration_reader.cpp | 4 | ||||
| -rw-r--r-- | src/administration_writer.cpp | 15 | ||||
| -rw-r--r-- | src/ui/ui_contacts.cpp | 20 | ||||
| -rw-r--r-- | src/ui/ui_expenses.cpp | 3 | ||||
| -rw-r--r-- | src/ui/ui_invoices.cpp | 3 | ||||
| -rw-r--r-- | tests/administration_rw_tests.cpp | 51 |
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 |
