diff options
| author | Aldrik Ramaekers <aldrikboy@gmail.com> | 2025-09-20 20:25:14 +0200 |
|---|---|---|
| committer | Aldrik Ramaekers <aldrikboy@gmail.com> | 2025-09-20 20:25:14 +0200 |
| commit | 946a0c939c0cb7c28c9af9b7e4e2b20c45dd3702 (patch) | |
| tree | c0d33801ecff3edb15f2e369e7aeeb2514dd73eb | |
| parent | 9a16fd2cf0bca13d8a3015da89833db2230b391f (diff) | |
billing item validation
| -rw-r--r-- | docs/CHANGES.rst | 3 | ||||
| -rw-r--r-- | include/administration.hpp | 5 | ||||
| -rw-r--r-- | include/ui.hpp | 3 | ||||
| -rw-r--r-- | src/administration.cpp | 17 | ||||
| -rw-r--r-- | src/administration_writer.cpp | 1 | ||||
| -rw-r--r-- | src/ui/imgui_extensions.cpp | 30 | ||||
| -rw-r--r-- | src/ui/ui_expenses.cpp | 2 | ||||
| -rw-r--r-- | src/ui/ui_invoices.cpp | 7 |
8 files changed, 56 insertions, 12 deletions
diff --git a/docs/CHANGES.rst b/docs/CHANGES.rst index 8701d77..2bb8399 100644 --- a/docs/CHANGES.rst +++ b/docs/CHANGES.rst @@ -1,9 +1,8 @@ .. _changes: TODO: -- get rid of billing item properties: invoice_id and tax_rate_id. replace tax_rate_id with copy of tax rate +- write tests that check error handling for corrupt files. (e.g. references to tax rates, project and cost center that failed to load) - get rid of customer_id and supplier_id. Just check for existing contact entries when finishing invoice and create new contact if necessary. -- validate billing items in invoice add/update - project start and end date should be stored as YYYY-MM-DD - Send invoice by email - create invoice from PDF file diff --git a/include/administration.hpp b/include/administration.hpp index 0d15195..e2ff80d 100644 --- a/include/administration.hpp +++ b/include/administration.hpp @@ -121,7 +121,6 @@ typedef enum typedef struct { char id[MAX_LEN_ID]; // B/[id] - char invoice_id[MAX_LEN_ID]; // I/[id] float amount; bool amount_is_percentage; char description[MAX_LEN_LONG_DESC]; @@ -368,6 +367,8 @@ typedef struct #define A_ERR_MAX_ITEMS_REACHED (1ULL << 15) #define A_ERR_MISSING_CODE (1ULL << 16) #define A_ERR_MISSING_EMAIL (1ULL << 17) +#define A_ERR_MISSING_TAX_RATE (1ULL << 18) +#define A_ERR_INVALID_BILLING_ITEM (1ULL << 19) typedef uint32_t a_err; @@ -497,4 +498,6 @@ a_err administration_billing_item_add_to_invoice(invoice* invoice, billing_ite a_err administration_billing_item_update_in_invoice(invoice* invoice, billing_item item); a_err administration_billing_item_remove_from_invoice(invoice* invoice, billing_item item); +a_err administration_billing_item_is_valid(billing_item item); + u32 administration_billing_item_get_all_for_invoice(invoice* invoice, billing_item* buffer);
\ No newline at end of file diff --git a/include/ui.hpp b/include/ui.hpp index 3a8ce66..ebfa210 100644 --- a/include/ui.hpp +++ b/include/ui.hpp @@ -95,6 +95,7 @@ void ui_destroy_earnings(); // Custom imgui widgets. namespace ImGui { + void InputTextWithError(const char* text, char* buffer, size_t buf_size, bool has_error); int TextInputWithAutocomplete(const char* hint, char* buffer, size_t buf_size, char** suggestions, int suggestion_count, bool has_error); void FormContactAutocomplete(contact* buffer, bool has_error); @@ -103,7 +104,7 @@ namespace ImGui void FormContactTypeCombo(contact_type* type); void FormCostCenterCombo(char* costcenter_id); void FormProjectCombo(char* project_id); - void FormTaxRateCombo(char* tax_rate_id, char* orig_country, char* dest_country); + void FormTaxRateCombo(char* tax_rate_id, char* orig_country, char* dest_country, bool has_error); bool FormCurrencyCombo(char* currency); void FormToggleCombo(bool *buffer, char* option1, char* option2); }
\ No newline at end of file diff --git a/src/administration.cpp b/src/administration.cpp index 09bb560..690fcd9 100644 --- a/src/administration.cpp +++ b/src/administration.cpp @@ -1633,6 +1633,13 @@ a_err administration_invoice_is_valid(invoice* invoice) 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; + 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 (administration_billing_item_is_valid(*c) != A_ERR_SUCCESS) result |= A_ERR_INVALID_BILLING_ITEM; + } + list_iterator_stop(&invoice->billing_items); + return result; } @@ -2049,6 +2056,15 @@ a_err administration_billing_item_update_in_invoice(invoice* invoice, billing_it return A_ERR_NOT_FOUND; } +a_err administration_billing_item_is_valid(billing_item item) +{ + a_err result = A_ERR_SUCCESS; + if (strlen(item.description) == 0) result |= A_ERR_MISSING_DESCRIPTION; + if (strlen(item.tax_rate_id) == 0) result |= A_ERR_MISSING_TAX_RATE; + + return result; +} + a_err administration_billing_item_add_to_invoice(invoice* invoice, billing_item item) { if (administration_billing_item_count(invoice) >= MAX_BILLING_ITEMS) return A_ERR_MAX_ITEMS_REACHED; @@ -2058,7 +2074,6 @@ a_err administration_billing_item_add_to_invoice(invoice* invoice, billing_item memcpy(tb, &item, sizeof(billing_item)); snprintf(tb->id, sizeof(tb->id), "B/%d", administration_create_id()); - strops_copy(tb->invoice_id, invoice->id, sizeof(tb->invoice_id)); strops_copy(tb->currency, invoice->currency, MAX_LEN_CURRENCY); // Set billing item currency to invoice currency. administration_recalculate_billing_item_totals(tb); diff --git a/src/administration_writer.cpp b/src/administration_writer.cpp index e7d9e07..e02d476 100644 --- a/src/administration_writer.cpp +++ b/src/administration_writer.cpp @@ -308,7 +308,6 @@ bool administration_writer_save_invoice_blocking(invoice inv) // These can all be retrieved from existing contacts. // properties not stored from billing item: - // - invoice_id (not necessary) // - discount (can be recalculated from line_amount - (quantity * unit_price) ) // - tax (can be recalculated) // - total (can be recalculated) diff --git a/src/ui/imgui_extensions.cpp b/src/ui/imgui_extensions.cpp index 1875c22..8485dbf 100644 --- a/src/ui/imgui_extensions.cpp +++ b/src/ui/imgui_extensions.cpp @@ -9,6 +9,19 @@ namespace ImGui { + void InputTextWithError(const char* text, char* buffer, size_t buf_size, bool has_error) + { + if (has_error) { + ImGui::PushStyleColor(ImGuiCol_Border, COLOR_ERROR_OUTLINE); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.5f); + } + ImGui::InputText(text, buffer, buf_size); + if (has_error) { + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + } + } + void FormInputTextWithErrorHint(const char* hint, char* buffer, size_t buf_size, bool has_error) { float widthAvailable = ImGui::GetContentRegionAvail().x; @@ -272,7 +285,7 @@ namespace ImGui free(buffer); } - void FormTaxRateCombo(char* tax_rate_id, char* orig_country, char* dest_country) + void FormTaxRateCombo(char* tax_rate_id, char* orig_country, char* dest_country, bool has_error) { u32 tax_rate_count = administration_tax_rate_count(); tax_rate* buffer = (tax_rate*) malloc(sizeof(tax_rate) * tax_rate_count); @@ -307,6 +320,11 @@ namespace ImGui else snprintf(rate_str_buf, 40, "%s/%.1f%%", selected_tax_rate->country_code, selected_tax_rate->rate); } + if (has_error) { + ImGui::PushStyleColor(ImGuiCol_Border, COLOR_ERROR_OUTLINE); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.5f); + } + if (ImGui::BeginCombo("##Tax Bracket", rate_str_buf)) { for (u32 n = 0; n < tax_rate_count; n++) @@ -326,6 +344,11 @@ namespace ImGui } ImGui::EndCombo(); } + + if (has_error) { + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + } if (selected_tax_rate_index != -1) { strops_copy(tax_rate_id, buffer[selected_tax_rate_index].id, MAX_LEN_ID); @@ -392,7 +415,10 @@ namespace ImGui void FormToggleCombo(bool *buffer, char* option1, char* option2) { const char* items[] = { option1, option2 }; - if (ImGui::BeginCombo("Mode", items[*buffer])) { + + char ID[MAX_LEN_LONG_DESC]; + snprintf(ID, MAX_LEN_LONG_DESC, "Mode##%p", buffer); + if (ImGui::BeginCombo(ID, items[*buffer])) { for (int n = 0; n < 2; n++) { bool is_selected = (n == (int)*buffer); if (ImGui::Selectable(items[n], is_selected)) { diff --git a/src/ui/ui_expenses.cpp b/src/ui/ui_expenses.cpp index 7d94eaf..7dd5add 100644 --- a/src/ui/ui_expenses.cpp +++ b/src/ui/ui_expenses.cpp @@ -204,7 +204,7 @@ static void ui_draw_expenses_list() ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::Text(c.sequential_number); ImGui::TableSetColumnIndex(1); ImGui::Text(c.supplier.name); - ImGui::TableSetColumnIndex(2); ImGui::Text(c.addressee.name); + ImGui::TableSetColumnIndex(2); ImGui::Text(c.customer.name); struct tm *lt = localtime(&c.issued_at); char buf[80]; diff --git a/src/ui/ui_invoices.cpp b/src/ui/ui_invoices.cpp index f061e3f..6c3e229 100644 --- a/src/ui/ui_invoices.cpp +++ b/src/ui/ui_invoices.cpp @@ -73,6 +73,7 @@ void draw_invoice_items_form(invoice* invoice) for (u32 i = 0; i < invoice_items; i++) { billing_item item = buffer[i]; + a_err valid = administration_billing_item_is_valid(item); ImGui::TableNextRow(); @@ -92,7 +93,7 @@ void draw_invoice_items_form(invoice* invoice) ImGui::TableSetColumnIndex(2); ImGui::PushItemWidth(-1); - ImGui::InputText("##desc", item.description, IM_ARRAYSIZE(item.description)); + ImGui::InputTextWithError("##desc", item.description, IM_ARRAYSIZE(item.description), valid & A_ERR_MISSING_DESCRIPTION); ImGui::PopItemWidth(); ImGui::TableSetColumnIndex(3); @@ -104,14 +105,14 @@ void draw_invoice_items_form(invoice* invoice) ImGui::InputFloat("##discount", &item.discount, 0.0f, 0.0f, "%.2f"); ImGui::SameLine(); - ImGui::FormToggleCombo(&item.amount_is_percentage, item.currency, "%"); + ImGui::FormToggleCombo(&item.discount_is_percentage, item.currency, "%"); ImGui::TableSetColumnIndex(5); ImGui::Text("%.2f %s", item.net, item.currency); ImGui::TableSetColumnIndex(6); ImGui::PushItemWidth(-1); - ImGui::FormTaxRateCombo(item.tax_rate_id, invoice->customer.address.country_code, invoice->supplier.address.country_code); + ImGui::FormTaxRateCombo(item.tax_rate_id, invoice->customer.address.country_code, invoice->supplier.address.country_code, valid & A_ERR_MISSING_TAX_RATE); ImGui::PopItemWidth(); |
