summaryrefslogtreecommitdiff
path: root/src/ui/ui_expenses.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/ui_expenses.cpp')
-rw-r--r--src/ui/ui_expenses.cpp531
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