/* * Copyright (c) 2025 Aldrik Ramaekers * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #define _CRT_SECURE_NO_WARNINGS #include #include #include "ImGuiDatePicker/ImGuiDatePicker.hpp" //#include "tinyfiledialogs/tinyfiledialogs.h" #include "strops.hpp" #include "ui.hpp" #include "imgui.h" #include "administration.hpp" #include "administration_writer.hpp" #include "locales.hpp" static view_state current_view_state = view_state::LIST; static invoice active_invoice = {0}; 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() { free(invoice_items_buffer); } void ui_setup_invoices() { current_view_state = view_state::LIST; active_invoice = administration_invoice_create_empty(); u32 invoice_items_count = MAX_BILLING_ITEMS; invoice_items_buffer = (billing_item*)malloc(sizeof(billing_item) * invoice_items_count); } void draw_invoice_items_form(invoice* invoice) { billing_item* buffer = invoice_items_buffer; u32 invoice_items = administration_billing_item_get_all_for_invoice(invoice, buffer); if (ImGui::BeginTable("TableBillingItems", 9, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { ImGui::TableSetupColumn("##actions", ImGuiTableColumnFlags_WidthFixed, 20); ImGui::TableSetupColumn(localize("invoice.table.amount"), ImGuiTableColumnFlags_WidthFixed, 80); ImGui::TableSetupColumn(localize("invoice.table.description")); ImGui::TableSetupColumn(localize("invoice.table.price"), ImGuiTableColumnFlags_WidthFixed, 100); ImGui::TableSetupColumn(localize("invoice.table.discount"), ImGuiTableColumnFlags_WidthFixed, 100); ImGui::TableSetupColumn(localize("invoice.table.net"), ImGuiTableColumnFlags_WidthFixed, 100); ImGui::TableSetupColumn(localize("invoice.table.tax%"), ImGuiTableColumnFlags_WidthFixed, 120); ImGui::TableSetupColumn(localize("invoice.table.tax"), ImGuiTableColumnFlags_WidthFixed, 100); ImGui::TableSetupColumn(localize("invoice.table.total"), ImGuiTableColumnFlags_WidthFixed, 100); ImGui::TableHeadersRow(); for (u32 i = 0; i < invoice_items; i++) { billing_item item = buffer[i]; a_err valid = administration_billing_item_is_valid(item); ImGui::TableNextRow(); ImGui::PushID(i); ImGui::TableSetColumnIndex(0); if (ImGui::Button("X")) { administration_billing_item_remove_from_invoice(invoice, item); } ImGui::TableSetColumnIndex(1); ImGui::InputFloat("##amount", &item.amount, 0.0f, 0.0f, "%.0f"); ImGui::SameLine(); ImGui::FormToggleCombo(&item.amount_is_percentage, "X", "%"); ImGui::TableSetColumnIndex(2); ImGui::PushItemWidth(-1); ImGui::InputTextWithError("##desc", item.description, IM_ARRAYSIZE(item.description), valid & A_ERR_MISSING_DESCRIPTION); ImGui::PopItemWidth(); ImGui::TableSetColumnIndex(3); ImGui::PushItemWidth(-1); ImGui::InputFloat("##price", &item.net_per_item, 0.0f, 0.0f, "%.2f"); ImGui::PopItemWidth(); ImGui::TableSetColumnIndex(4); ImGui::InputFloat("##discount", &item.discount, 0.0f, 0.0f, "%.2f"); ImGui::SameLine(); 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, valid & A_ERR_MISSING_TAX_RATE); ImGui::PopItemWidth(); ImGui::TableSetColumnIndex(7); ImGui::Text("%.2f %s", item.tax, item.currency); ImGui::TableSetColumnIndex(8); ImGui::Text("%.2f %s", item.total, item.currency); ImGui::PopID(); administration_billing_item_update_in_invoice(invoice, item); } ImGui::TableNextRow(); ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, IM_COL32(70, 70, 70, 255)); ImGui::TableSetColumnIndex(5); ImGui::Text("%.2f %s", invoice->orig_net, invoice->currency); ImGui::TableSetColumnIndex(7); ImGui::Text("%.2f %s", invoice->orig_tax, invoice->currency); ImGui::TableSetColumnIndex(8); ImGui::Text("%.2f %s", invoice->orig_total, invoice->currency); if (strcmp(invoice->currency, administration_get_default_currency()) != 0) { ImGui::TableNextRow(); ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, IM_COL32(50, 50, 50, 255)); ImGui::TableSetColumnIndex(2); ImGui::Text("%s %s", localize("invoice.form.finalSettlement"), administration_get_default_currency()); ImGui::TableSetColumnIndex(4); ImGui::InputFloat("##final_allowance", &invoice->allowance, 0.0f, 0.0f, "%.2f"); ImGui::SameLine(); ImGui::Text("%s", administration_get_default_currency()); ImGui::TableSetColumnIndex(5); ImGui::InputFloat("##final_net", &invoice->net, 0.0f, 0.0f, "%.2f"); ImGui::SameLine(); ImGui::Text("%s", administration_get_default_currency()); ImGui::TableSetColumnIndex(7); ImGui::InputFloat("##final_tax", &invoice->tax, 0.0f, 0.0f, "%.2f"); ImGui::SameLine(); ImGui::Text("%s", administration_get_default_currency()); ImGui::TableSetColumnIndex(8); ImGui::InputFloat("##final_total", &invoice->total, 0.0f, 0.0f, "%.2f"); ImGui::SameLine(); ImGui::Text("%s", administration_get_default_currency()); } ImGui::EndTable(); } } static void draw_invoice_form(invoice* buffer, bool viewing_only = false) { ImGui::BeginDisabled(); ImGui::Text("%s: %s", localize("invoice.form.invoicenumber"), buffer->sequential_number); ImGui::Text("%s: %s", localize("invoice.form.supplier"), buffer->supplier.name); tm issued_at_date = *gmtime(&buffer->issued_at); if (ImGui::DatePicker("##issuedAt", issued_at_date)) { buffer->issued_at = mktime(&issued_at_date); } ImGui::SameLine(); ImGui::Text(localize("invoice.form.issuedat")); tm expires_at_date = *gmtime(&buffer->expires_at); if (ImGui::DatePicker("##expiresAt", expires_at_date)) { buffer->expires_at = mktime(&expires_at_date); } ImGui::SameLine(); ImGui::Text(localize("invoice.form.expiresat")); if (!viewing_only) ImGui::EndDisabled(); tm delivered_at_date = *gmtime(&buffer->delivered_at); if (ImGui::DatePicker("##deliveredAt", delivered_at_date)) { buffer->delivered_at = mktime(&delivered_at_date); } ImGui::SameLine(); ImGui::Text(localize("invoice.form.deliveredat")); ImGui::Separator(); if (ImGui::FormFileSelector(buffer->document.original_path)) { buffer->document.copy_path[0] = 0; } ImGui::Separator(); ImGui::Text(localize("invoice.form.billinginformation")); draw_contact_form_ex(&buffer->customer, false, true); ImGui::Checkbox(localize("invoice.form.triangulation"), &buffer->is_triangulation); if (buffer->is_triangulation) { ImGui::Spacing(); ImGui::Text(localize("invoice.form.shippinginformation")); draw_addressee_form_ex(&buffer->addressee, 0); } ImGui::Separator(); ImGui::FormProjectCombo(buffer->project_id); ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing(); bool max_items_reached = administration_billing_item_count(buffer) >= MAX_BILLING_ITEMS; if (max_items_reached) ImGui::BeginDisabled(); if (ImGui::Button(localize(localize("invoice.form.add")))) { billing_item item = administration_billing_item_create_empty(); administration_billing_item_add_to_invoice(buffer, item); } if (max_items_reached) ImGui::EndDisabled(); ImGui::SameLine(); ImGui::Text("| %s: ", localize("invoice.form.currency")); ImGui::SameLine(); if (ImGui::FormCurrencyCombo(buffer->currency)) { administration_invoice_set_currency(buffer, buffer->currency); } draw_invoice_items_form(buffer); if (viewing_only) ImGui::EndDisabled(); } static void ui_draw_invoices_list() { if (!administration_can_create_invoices()) { ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 102, 204, 255)); // blue ImGui::Text(localize("ui.invoiceRequirementP1")); ImGui::PopStyleColor(); if (ImGui::IsItemHovered()) { ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { ui_set_state(main_state::UI_SETTINGS); } } ImGui::SameLine(); ImGui::Text(localize("ui.invoiceRequirementP2")); return; } const u32 items_per_page = 50; static s32 current_page = 0; invoice invoice_list[items_per_page]; u32 invoice_count = administration_invoice_get_partial_list_outgoing(current_page, items_per_page, invoice_list); u32 total_invoice_count = administration_invoice_get_outgoing_count(); s32 max_page = (total_invoice_count + items_per_page - 1) / items_per_page; if (max_page == 0) max_page = 1; // Table header controls: create button and pagination. if (ImGui::Button(localize("form.create"))) { current_view_state = view_state::CREATE; active_invoice = administration_invoice_create_empty(); // @leak 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; if (current_page < 0) current_page = 0; // Navigate to prev page button. ImGui::SameLine(); bool enable_prev = current_page > 0; if (!enable_prev) ImGui::BeginDisabled(); if (ImGui::Button(localize("ui.prev")) && current_page > 0) current_page--; if (!enable_prev) ImGui::EndDisabled(); ImGui::SameLine(); ImGui::Text("(%d/%d)", current_page+1, max_page); // Navigate to next page button. ImGui::SameLine(); bool enable_next = current_page < max_page-1; if (!enable_next) ImGui::BeginDisabled(); if (ImGui::Button(localize("ui.next")) && current_page < max_page-1) current_page++; if (!enable_next) ImGui::EndDisabled(); ImGui::Spacing(); if (ImGui::BeginTable("TableInvoices", 7, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { ImGui::TableSetupColumn(localize("invoice.table.invoicenumber"), ImGuiTableColumnFlags_WidthFixed, 120); ImGui::TableSetupColumn(localize("invoice.table.customer")); ImGui::TableSetupColumn(localize("invoice.table.addressee")); ImGui::TableSetupColumn(localize("invoice.table.issuedat")); ImGui::TableSetupColumn(localize("invoice.table.total")); ImGui::TableSetupColumn(localize("invoice.table.status")); ImGui::TableSetupColumn(""); ImGui::TableHeadersRow(); for (u32 i = 0; i < invoice_count; i++) { invoice c = invoice_list[i]; ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::Text(c.sequential_number); ImGui::TableSetColumnIndex(1); ImGui::Text(c.customer.name); ImGui::TableSetColumnIndex(2); ImGui::Text(c.addressee.name); struct tm *lt = localtime(&c.issued_at); char buf[80]; strftime(buf, sizeof(buf), "%d-%m-%Y", lt); ImGui::TableSetColumnIndex(3); ImGui::Text(buf); ImGui::TableSetColumnIndex(4); ImGui::Text("%.2f %s", c.total, c.currency); ImGui::TableSetColumnIndex(5); ImGui::Text("%s", localize(administration_invoice_get_status_string(&c))); ImGui::TableSetColumnIndex(6); char btn_name[20]; snprintf(btn_name, sizeof(btn_name), "%s##%d", localize("form.view"), i); if (ImGui::Button(btn_name)) { active_invoice = c; current_view_state = view_state::VIEW; } ImGui::SameLine(); if (c.status == invoice_status::INVOICE_CONCEPT) { snprintf(btn_name, sizeof(btn_name), "%s##%d", localize("form.change"), i); if (ImGui::Button(btn_name)) { active_invoice = administration_invoice_create_copy(&c); // We create a copy because of billing item list pointers. current_view_state = view_state::EDIT; } ImGui::SameLine(); snprintf(btn_name, sizeof(btn_name), "%s##%d", localize("form.delete"), i); if (ImGui::Button(btn_name)) { selected_for_removal = c; ImGui::OpenPopup("ConfirmDeletePopup"); } } } // Confirmation popup before contact is deleted definitively. if (ImGui::BeginPopupModal("ConfirmDeletePopup", nullptr, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoTitleBar)) { ImGui::Text(localize("form.confirmDelete")); ImGui::Separator(); if (ImGui::Button(localize("form.yes"), ImVec2(120, 0))) { administration_invoice_remove(&selected_for_removal); ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button(localize("form.no"), ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } ImGui::EndTable(); } } static void ui_draw_invoice_update() { if (ImGui::Button(localize("form.back"))) { current_view_state = view_state::LIST; } draw_invoice_form(&active_invoice); bool can_save = administration_invoice_is_valid(&active_invoice) == A_ERR_SUCCESS; if (!can_save) ImGui::BeginDisabled(); ImGui::Spacing(); if (ImGui::Button(localize("form.save"))) { administration_invoice_update(&active_invoice); current_view_state = view_state::LIST; ui_destroy_invoices(); ui_setup_invoices(); } if (!can_save) ImGui::EndDisabled(); } static void ui_draw_invoice_create() { if (ImGui::Button(localize("form.back"))) { current_view_state = view_state::LIST; } draw_invoice_form(&active_invoice); bool can_save = administration_invoice_is_valid(&active_invoice) == A_ERR_SUCCESS; if (!can_save) ImGui::BeginDisabled(); ImGui::Spacing(); if (ImGui::Button(localize("form.save"))) { administration_invoice_add(&active_invoice); current_view_state = view_state::LIST; ui_destroy_invoices(); ui_setup_invoices(); } if (!can_save) ImGui::EndDisabled(); } static void ui_draw_invoice_view() { if (ImGui::Button(localize("form.back"))) { current_view_state = view_state::LIST; } draw_invoice_form(&active_invoice, true); } void ui_draw_invoices() { switch(current_view_state) { case view_state::LIST: ui_draw_invoices_list(); break; case view_state::CREATE: ui_draw_invoice_create(); break; case view_state::EDIT: ui_draw_invoice_update(); break; case view_state::VIEW: ui_draw_invoice_view(); break; } }