diff options
| author | Aldrik Ramaekers <aldrikboy@gmail.com> | 2025-08-16 11:10:29 +0200 |
|---|---|---|
| committer | Aldrik Ramaekers <aldrikboy@gmail.com> | 2025-08-16 11:10:29 +0200 |
| commit | 05bc81cd42c5aeff7cfb6cf6b18f88792e7c16c9 (patch) | |
| tree | 7bc9c0439507b4531ed78578e441b12cba4dda4e /src/ui | |
| parent | f67e92f55b6223f2806c3d5ef1cbe2a638920562 (diff) | |
invoice form work
Diffstat (limited to 'src/ui')
| -rw-r--r-- | src/ui/ui_invoices.cpp | 263 | ||||
| -rw-r--r-- | src/ui/ui_main.cpp | 4 |
2 files changed, 251 insertions, 16 deletions
diff --git a/src/ui/ui_invoices.cpp b/src/ui/ui_invoices.cpp index 2b5a834..036d85b 100644 --- a/src/ui/ui_invoices.cpp +++ b/src/ui/ui_invoices.cpp @@ -16,21 +16,136 @@ void ui_draw_address_form(address* buffer); static view_state current_view_state = view_state::LIST; static invoice active_invoice = {0}; +cost_center* cost_center_list_buffer = 0; +country_tax_bracket* tax_bracket_list_buffer = 0; + void draw_contact_form_ex(contact* buffer, bool viewing_only = false, bool with_autocomplete = false, bool* on_autocomplete = 0); void ui_setup_invoices() { current_view_state = view_state::LIST; active_invoice = administration_create_empty_invoice(); + + u32 costcenter_count = administration_get_cost_center_count(); + cost_center_list_buffer = (cost_center*) malloc(sizeof(cost_center) * costcenter_count); // @leak + + u32 tax_bracket_count = administration_get_tax_bracket_count(); + tax_bracket_list_buffer = (country_tax_bracket*) malloc(sizeof(country_tax_bracket) * tax_bracket_count); // @leak +} + +// TODO move custom ui functions to helpers.cpp + +void draw_tax_bracket_selector(char* tax_bracket_id) +{ + country_tax_bracket* selected_tax_bracket = NULL; + + country_tax_bracket* buffer = tax_bracket_list_buffer; + u32 tax_bracket_count = administration_get_tax_brackets_for_country(buffer, administration_get_company_info().address.country_code); + + // Select tax bracket by given id. + if (strlen(tax_bracket_id) > 0) + { + for (u32 i = 0; i < tax_bracket_count; i++) + { + if (strcmp(buffer[i].id, tax_bracket_id) == 0) + { + selected_tax_bracket = &buffer[i]; + break; + } + } + } + + int selected_tax_bracket_index = -1; + char rate_str_buf[20]; + rate_str_buf[0] = 0; + if (selected_tax_bracket) + { + if (strcmp(selected_tax_bracket->country_code, "00") == 0) snprintf(rate_str_buf, 20, "%s", localize(selected_tax_bracket->description)); + else snprintf(rate_str_buf, 20, "%s/%.1f%%", selected_tax_bracket->country_code, selected_tax_bracket->rate); + } + + if (ImGui::BeginCombo("##Tax Bracket", rate_str_buf)) + { + for (u32 n = 0; n < tax_bracket_count; n++) + { + bool is_selected = selected_tax_bracket && strcmp(selected_tax_bracket->id, buffer[n].id) == 0; + + if (strcmp(buffer[n].country_code, "00") == 0) snprintf(rate_str_buf, 20, "%s", localize(buffer[n].description)); + else snprintf(rate_str_buf, 20, "%s/%.1f%%", buffer[n].country_code, buffer[n].rate); + + if (ImGui::Selectable(rate_str_buf, is_selected)) { + selected_tax_bracket_index = n; + } + } + ImGui::EndCombo(); + } + + if (selected_tax_bracket_index != -1) { + strops_copy(tax_bracket_id, buffer[selected_tax_bracket_index].id, 16); + } +} + +bool draw_currency_selector(char* currency) +{ + int currentCurrency = 0; + bool result = false; + + // Top 15 most traded currencies + all EU official currencies + const char* currencies[] = { + // Top 15 + "EUR", "USD", "JPY", "GBP", "AUD", "CAD", "CHF", + "CNY", "HKD", "NZD", "SEK", "KRW", "SGD", "NOK", "MXN", + + // Additional EU currencies + "BGN", // Bulgarian Lev + "CZK", // Czech Koruna + "DKK", // Danish Krone + "HUF", // Hungarian Forint + "PLN", // Polish Zloty + "RON", // Romanian Leu + // "HRK", // Croatian Kuna (legacy, replaced by EUR in 2023) + }; + int currency_count = sizeof(currencies) / sizeof(char*); + + if (strlen(currency) > 0) + { + for (int i = 0; i < currency_count; i++) + { + if (strcmp(currencies[i], currency) == 0) + { + currentCurrency = i; + break; + } + } + } + + ImGui::SetNextItemWidth(100.0f); + if (ImGui::BeginCombo("##currency", currencies[currentCurrency])) + { + for (int n = 0; n < IM_ARRAYSIZE(currencies); n++) + { + bool isSelected = (currentCurrency == n); + if (ImGui::Selectable(currencies[n], isSelected)) + { + result = true; + strops_copy(currency, currencies[n], CURRENCY_LENGTH); + } + + if (isSelected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + + return result; } void draw_costcenter_selector(char* costcenter_id) { cost_center* selected_costcenter = NULL; - u32 costcenter_count = administration_get_cost_center_count(); - cost_center* buffer = (cost_center*) malloc(sizeof(cost_center) * costcenter_count); - costcenter_count = administration_get_cost_centers(buffer); + cost_center* buffer = cost_center_list_buffer; + u32 costcenter_count = administration_get_cost_centers(buffer); // Select cost center by given id. if (strlen(costcenter_id) > 0) @@ -61,8 +176,6 @@ void draw_costcenter_selector(char* costcenter_id) if (selected_costcenter_index != -1) { strops_copy(costcenter_id, buffer[selected_costcenter_index].id, 16); } - - free(buffer); } void draw_project_selector(char* project_id) @@ -106,9 +219,105 @@ void draw_project_selector(char* project_id) free(buffer); } +static void draw_invoice_items_form(invoice* invoice) +{ + u32 invoice_items = administration_get_billing_items_count(invoice); + billing_item* buffer = (billing_item*)malloc(sizeof(billing_item) * invoice_items); + administration_get_all_billing_items_for_invoice(invoice, buffer); + + if (ImGui::BeginTable("TableBillingItems", 8, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + + ImGui::TableSetupColumn("Amount", ImGuiTableColumnFlags_WidthFixed, 80); + ImGui::TableSetupColumn("Description"); + ImGui::TableSetupColumn("Price", ImGuiTableColumnFlags_WidthFixed, 100); + ImGui::TableSetupColumn("Discount", ImGuiTableColumnFlags_WidthFixed, 100); + ImGui::TableSetupColumn("Net", ImGuiTableColumnFlags_WidthFixed, 100); + ImGui::TableSetupColumn("Tax %", ImGuiTableColumnFlags_WidthFixed, 170); + ImGui::TableSetupColumn("Tax", ImGuiTableColumnFlags_WidthFixed, 100); + ImGui::TableSetupColumn("Total", ImGuiTableColumnFlags_WidthFixed, 100); + + //ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 100); + ImGui::TableHeadersRow(); + + for (u32 i = 0; i < invoice_items; i++) + { + billing_item item = buffer[i]; + + ImGui::TableNextRow(); + + ImGui::PushID(i); + + ImGui::TableSetColumnIndex(0); + ImGui::InputFloat("##amount", &item.amount, 0.0f, 0.0f, "%.0f"); + ImGui::SameLine(); + + // Toggle between X and % + { + const char* items[] = { "X", "%" }; + if (ImGui::BeginCombo("Mode", items[item.amount_is_percentage])) { + for (int n = 0; n < 2; n++) { + bool is_selected = (n == (int)item.amount_is_percentage); + if (ImGui::Selectable(items[n], is_selected)) { + item.amount_is_percentage = n; + } + if (is_selected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + } + + ImGui::TableSetColumnIndex(1); + ImGui::InputText("##desc", item.description, IM_ARRAYSIZE(item.description)); + + ImGui::TableSetColumnIndex(2); + ImGui::InputFloat("##price", &item.net_per_item, 0.0f, 0.0f, "%.2f"); + + ImGui::TableSetColumnIndex(3); + ImGui::InputFloat("##discount", &item.discount, 0.0f, 0.0f, "%.0f"); + ImGui::SameLine(); + + // Toggle between currency and % + { + const char* items[] = { item.currency, "%" }; + if (ImGui::BeginCombo("Mode##discountMode", items[item.discount_is_percentage])) { + for (int n = 0; n < 2; n++) { + bool is_selected = (n == (int)item.discount_is_percentage); + if (ImGui::Selectable(items[n], is_selected)) { + item.discount_is_percentage = n; + } + if (is_selected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + } + + ImGui::TableSetColumnIndex(4); + ImGui::Text("%.2f %s", item.net, item.currency); + + ImGui::TableSetColumnIndex(5); + draw_tax_bracket_selector(item.tax_bracket_id); + + ImGui::TableSetColumnIndex(6); + ImGui::Text("%.2f %s", item.tax, item.currency); + + ImGui::TableSetColumnIndex(7); + ImGui::Text("%.2f %s", item.total, item.currency); + + ImGui::PopID(); + + administration_update_billing_item_of_invoice(invoice, item); + } + + ImGui::EndTable(); + } +} + void draw_invoice_form(invoice* buffer, bool viewing_only = false) { - //float widthAvailable = ImGui::GetContentRegionAvail().x; ImGui::BeginDisabled(); // 1. Identifier @@ -122,7 +331,7 @@ void draw_invoice_form(invoice* buffer, bool viewing_only = false) // 3. Supplier (you) ImGui::Text("Supplier: %s", buffer->supplier.name); - // 6. Invoice issued at + // 4. Invoice issued at ImGui::BeginDisabled(); tm issued_at_date = *gmtime(&buffer->issued_at); if (ImGui::DatePicker("##issuedAt", issued_at_date)) @@ -133,7 +342,7 @@ void draw_invoice_form(invoice* buffer, bool viewing_only = false) ImGui::Text("Invoice issued at"); ImGui::EndDisabled(); - // 7. Invoice expires at + // 5. Invoice expires at ImGui::BeginDisabled(); tm expires_at_date = *gmtime(&buffer->expires_at); if (ImGui::DatePicker("##expiresAt", expires_at_date)) @@ -144,7 +353,7 @@ void draw_invoice_form(invoice* buffer, bool viewing_only = false) ImGui::Text("Invoice expires at"); ImGui::EndDisabled(); - // 8. Product/service delivered at + // 6. Product/service delivered at tm delivered_at_date = *gmtime(&buffer->delivered_at); if (ImGui::DatePicker("##deliveredAt", delivered_at_date)) { @@ -155,7 +364,7 @@ void draw_invoice_form(invoice* buffer, bool viewing_only = false) ImGui::Separator(); - // 4. Customer information + // 7. Customer information ImGui::Text("Billing information"); bool on_autocomplete; draw_contact_form_ex(&buffer->customer, false, true, &on_autocomplete); @@ -164,7 +373,7 @@ void draw_invoice_form(invoice* buffer, bool viewing_only = false) strops_copy(buffer->customer_id, buffer->customer.id, sizeof(buffer->customer_id)); } - // 5. (optional) shipping address. + // 8. (optional) shipping address. static bool shipping_is_billing_addr = true; ImGui::Checkbox("Shipping information is billing information", &shipping_is_billing_addr); if (!shipping_is_billing_addr) { @@ -180,9 +389,33 @@ void draw_invoice_form(invoice* buffer, bool viewing_only = false) // 10. Cost center selection draw_costcenter_selector(buffer->cost_center_id); - //ImGui::SetNextItemWidth(widthAvailable*0.5f); - //ImGui::InputTextWithHint("Invoice number", "Invoice number", buffer->sequential_number, IM_ARRAYSIZE(buffer->sequential_number)); - //ImGui::SameLine();ui_helper_draw_required_tag(); + ImGui::Separator(); + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::Spacing(); + + // 11. New billing item button. + if (ImGui::Button(localize("+ Billing item"))) + { + billing_item item = administration_create_empty_billing_item(); + administration_add_billing_item_to_invoice(buffer, item); + } + + // 12. Dropdown for invoice currency. + ImGui::SameLine(); + ImGui::Text("| Currency: "); + ImGui::SameLine(); + if (draw_currency_selector(buffer->currency)) + { + administration_invoice_set_currency(buffer, buffer->currency); + } + + // 13. Invoice items form + draw_invoice_items_form(buffer); + + ImGui::Separator(); + + // 14. Totals overview. if (viewing_only) ImGui::EndDisabled(); } @@ -193,7 +426,7 @@ void draw_invoices_list() if (ImGui::Button(localize("form.create"))) { current_view_state = view_state::CREATE; - active_invoice = administration_create_empty_invoice(); + active_invoice = administration_create_empty_invoice(); // @leak active_invoice.supplier = administration_get_company_info(); strops_copy(active_invoice.supplier_id, active_invoice.supplier.id, sizeof(active_invoice.supplier_id)); } diff --git a/src/ui/ui_main.cpp b/src/ui/ui_main.cpp index 05a98b4..b4617d1 100644 --- a/src/ui/ui_main.cpp +++ b/src/ui/ui_main.cpp @@ -16,7 +16,7 @@ typedef enum END } dashboard_view_state; -static dashboard_view_state dashboard_state = dashboard_view_state::INVOICES; +static dashboard_view_state dashboard_state = dashboard_view_state::END; void (*drawcalls[dashboard_view_state::END])(void) = { ui_draw_invoices, 0, @@ -45,6 +45,8 @@ static void set_dashboard_state(dashboard_view_state state) void ui_draw_main() { + if (dashboard_state == dashboard_view_state::END) set_dashboard_state(dashboard_view_state::INVOICES); + // @localize if (ImGui::BeginMainMenuBar()) { |
