diff options
Diffstat (limited to 'src/ui/ui_expenses.cpp')
| -rw-r--r-- | src/ui/ui_expenses.cpp | 531 |
1 files changed, 531 insertions, 0 deletions
diff --git a/src/ui/ui_expenses.cpp b/src/ui/ui_expenses.cpp new file mode 100644 index 0000000..3df0994 --- /dev/null +++ b/src/ui/ui_expenses.cpp @@ -0,0 +1,531 @@ +#define _CRT_SECURE_NO_WARNINGS +#include <stdio.h> +#include <time.h> + +#include "ImGuiDatePicker/ImGuiDatePicker.hpp" +//#include "tinyfiledialogs/tinyfiledialogs.h" + +#include "strops.hpp" +#include "ui.hpp" +#include "imgui.h" +#include "administration.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 cost_center* cost_center_list_buffer = 0; +static country_tax_bracket* tax_bracket_list_buffer = 0; +static project* project_list_buffer = 0; +static billing_item* invoice_items_buffer = 0; + +void ui_draw_address_form(address* buffer); +void draw_contact_form_ex(contact* buffer, bool viewing_only = false, bool with_autocomplete = false, bool* on_autocomplete = 0); +void draw_tax_bracket_selector(char* tax_bracket_id, country_tax_bracket* buffer, char* country_code); +bool draw_currency_selector(char* currency); + +void draw_costcenter_selector(char* costcenter_id, cost_center* buffer) +{ + cost_center* selected_costcenter = NULL; + u32 costcenter_count = administration_cost_center_get_all(buffer); + + // Select cost center by given id. + if (strlen(costcenter_id) > 0) + { + for (u32 i = 0; i < costcenter_count; i++) + { + if (strcmp(buffer[i].id, costcenter_id) == 0) + { + selected_costcenter = &buffer[i]; + break; + } + } + } + + int selected_costcenter_index = -1; + if (ImGui::BeginCombo(localize("invoice.form.costcenter"), selected_costcenter == NULL ? NULL : localize(selected_costcenter->description))) + { + for (u32 n = 0; n < costcenter_count; n++) + { + bool is_selected = selected_costcenter && strcmp(selected_costcenter->id, buffer[n].id) == 0; + if (ImGui::Selectable(localize(buffer[n].description), is_selected)) { + selected_costcenter_index = n; + } + } + ImGui::EndCombo(); + } + + if (selected_costcenter_index != -1) { + strops_copy(costcenter_id, buffer[selected_costcenter_index].id, MAX_LEN_ID); + } +} + +void draw_project_selector(char* project_id, project* buffer) +{ + project* selected_project = NULL; + u32 project_count = administration_project_get_all(buffer); + + // Select project by given id. + if (strlen(project_id) > 0) + { + for (u32 i = 0; i < project_count; i++) + { + if (strcmp(buffer[i].id, project_id) == 0) + { + selected_project = &buffer[i]; + break; + } + } + } + + int selected_project_index = -1; + if (ImGui::BeginCombo(localize("invoice.form.project"), selected_project == NULL ? NULL : selected_project->description)) + { + for (u32 n = 0; n < project_count; n++) + { + bool is_selected = selected_project && strcmp(selected_project->id, buffer[n].id) == 0; + if (ImGui::Selectable(buffer[n].description, is_selected)) { + selected_project_index = n; + } + } + ImGui::EndCombo(); + } + + if (selected_project_index != -1) { + strops_copy(project_id, buffer[selected_project_index].id, MAX_LEN_ID); + } +} + +void ui_destroy_expenses() +{ + free(cost_center_list_buffer); + free(tax_bracket_list_buffer); + free(project_list_buffer); + free(invoice_items_buffer); +} + +void ui_setup_expenses() +{ + current_view_state = view_state::LIST; + active_invoice = administration_invoice_create_empty(); + + u32 costcenter_count = administration_cost_center_count(); + cost_center_list_buffer = (cost_center*) malloc(sizeof(cost_center) * costcenter_count); + + u32 tax_bracket_count = administration_tax_bracket_count(); + tax_bracket_list_buffer = (country_tax_bracket*) malloc(sizeof(country_tax_bracket) * tax_bracket_count); + + u32 project_count = administration_project_count(); + project_list_buffer = (project*) malloc(sizeof(project) * project_count); + + u32 invoice_items_count = MAX_BILLING_ITEMS; + invoice_items_buffer = (billing_item*)malloc(sizeof(billing_item) * invoice_items_count); +} + +static void draw_expense_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]; + + 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(); + + // 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(2); + ImGui::PushItemWidth(-1); + ImGui::InputText("##desc", item.description, IM_ARRAYSIZE(item.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, "%.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(5); + ImGui::Text("%.2f %s", item.net, item.currency); + + ImGui::TableSetColumnIndex(6); + ImGui::PushItemWidth(-1); + // TODO: should be country of invoice issuer + draw_tax_bracket_selector(item.tax_bracket_id, tax_bracket_list_buffer, administration_company_info_get().address.country_code); + 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->net, invoice->currency); + + ImGui::TableSetColumnIndex(7); + ImGui::Text("%.2f %s", invoice->tax, invoice->currency); + + ImGui::TableSetColumnIndex(8); + ImGui::Text("%.2f %s", invoice->total, invoice->currency); + + ImGui::EndTable(); + } +} + +static void draw_expense_form(invoice* buffer, bool viewing_only = false) +{ + // 1. Identifier + //ImGui::SetNextItemWidth(widthAvailable*0.2f); + //ImGui::InputText(localize("contact.form.identifier"), buffer->id, IM_ARRAYSIZE(buffer->id)); + + // 2. Sequential number + ImGui::Text("%s: %s", localize("invoice.form.invoicenumber"), buffer->sequential_number); + + // 3. Billed to (you) + ImGui::Text("%s: %s", localize("invoice.form.billinginformation"), buffer->customer.name); + + // 4. Invoice issued at + 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")); + + // 5. Invoice expires at + 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")); + + // 6. Product/service delivered at + 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(); + + // 7. Supplier information + ImGui::Text(localize("invoice.form.supplier")); + bool on_autocomplete; + draw_contact_form_ex(&buffer->supplier, false, true, &on_autocomplete); + + if (on_autocomplete) { + strops_copy(buffer->supplier_id, buffer->supplier.id, sizeof(buffer->supplier_id)); + } + + // Check if contact info is equal to contact stored in customer id, in case we select from dropdown and edit data after, + // this should be handled as a new contact and supplier_id should be set to "" so we create a new contact. + contact lookup_buffer; + if (administration_contact_get_by_id(&lookup_buffer, buffer->supplier_id)) + { + if (!administration_contact_equals(lookup_buffer, buffer->supplier)) + { + buffer->supplier_id[0] = '\0'; + } + } + + // 8. (optional) shipping address. + ImGui::Checkbox(localize("invoice.form.triangulation"), &buffer->is_triangulation); + if (buffer->is_triangulation) { + ImGui::Spacing(); + ImGui::Text(localize("invoice.form.shippinginformation")); + draw_contact_form_ex(&buffer->addressee, 0,0,0); + } + ImGui::Separator(); + + // 9. Project selection + draw_project_selector(buffer->project_id, project_list_buffer); + + // 10. Cost center selection + draw_costcenter_selector(buffer->cost_center_id, cost_center_list_buffer); + ImGui::Separator(); + + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::Spacing(); + + // 11. New billing item button. + 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(); + + // 12. Dropdown for invoice currency. + ImGui::SameLine(); + ImGui::Text("| %s: ", localize("invoice.form.currency")); + ImGui::SameLine(); + if (draw_currency_selector(buffer->currency)) + { + administration_invoice_set_currency(buffer, buffer->currency); + } + + // 13. Invoice items form + draw_expense_items_form(buffer); +} + +static void ui_draw_expenses_list() +{ + const u32 items_per_page = 50; + static s32 current_page = 0; + s32 max_page = (administration_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.customer = administration_company_info_get(); + strops_copy(active_invoice.customer_id, active_invoice.customer.id, sizeof(active_invoice.customer_id)); + } + + 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("<< 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("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.sender")); + ImGui::TableSetupColumn(localize("invoice.table.customer")); + ImGui::TableSetupColumn(localize("invoice.table.issuedat")); + ImGui::TableSetupColumn(localize("invoice.table.total")); + ImGui::TableSetupColumn(localize("invoice.table.status")); + ImGui::TableSetupColumn(""); + ImGui::TableHeadersRow(); + + invoice invoice_list[items_per_page]; + u32 invoice_count = administration_invoice_get_partial_list(current_page, items_per_page, invoice_list); + + 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.supplier.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_expense_update() +{ + if (ImGui::Button(localize("form.back"))) { + current_view_state = view_state::LIST; + } + + draw_expense_form(&active_invoice); + + bool can_save = administration_invoice_is_valid(&active_invoice); + 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_expense_create() +{ + if (ImGui::Button(localize("form.back"))) { + current_view_state = view_state::LIST; + } + + draw_expense_form(&active_invoice); + + bool can_save = administration_invoice_is_valid(&active_invoice); + 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_expense_view() +{ + if (ImGui::Button(localize("form.back"))) { + current_view_state = view_state::LIST; + } + + draw_expense_form(&active_invoice, true); +} + +void ui_draw_expenses() +{ + switch(current_view_state) + { + case view_state::LIST: ui_draw_expenses_list(); break; + case view_state::CREATE: ui_draw_expense_create(); break; + case view_state::EDIT: ui_draw_expense_update(); break; + case view_state::VIEW: ui_draw_expense_view(); break; + } +}
\ No newline at end of file |
