summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/CHANGES.rst2
-rw-r--r--docs/STORAGE.rst1
-rw-r--r--include/administration.hpp50
-rw-r--r--src/administration.cpp195
-rw-r--r--src/ui/ui_invoices.cpp263
-rw-r--r--src/ui/ui_main.cpp4
6 files changed, 475 insertions, 40 deletions
diff --git a/docs/CHANGES.rst b/docs/CHANGES.rst
index 34f37fa..282a21a 100644
--- a/docs/CHANGES.rst
+++ b/docs/CHANGES.rst
@@ -12,6 +12,8 @@
- Create monthly and quarterly results (per project).
- Create quarterly tax reports for NL.
- Store invoices in Peppol BIS 3.0 format.
+- destroy ui views on change (currently we leak all memory)
+- Make sure invoices/expenses cannot be added when company info is empty/invalid.
v0.1 (master)
diff --git a/docs/STORAGE.rst b/docs/STORAGE.rst
index 29ad037..4af6398 100644
--- a/docs/STORAGE.rst
+++ b/docs/STORAGE.rst
@@ -101,6 +101,7 @@ Billing item
- net **required** — Total amount excl. tax
- discount **required** — Total discount
- tax **required** — Total tax billed
+- total **required** — Total amount billed
- is intra-community **required** — If applicable, note like “intra-Community supply of goods”
- is triangulation **required** — For EU triangulation: note scheme and buyer liable
- currency **required** — Currency used for billing
diff --git a/include/administration.hpp b/include/administration.hpp
index af9b29e..fd55978 100644
--- a/include/administration.hpp
+++ b/include/administration.hpp
@@ -69,22 +69,28 @@ typedef enum
INVOICE_CORRECTED,
} invoice_status;
+#define CURRENCY_LENGTH 8
+
typedef struct
{
char id[16];
char invoice_id[16];
- char description[128];
- country_tax_bracket tax_bracket;
- char tax_section[16];
float amount;
bool amount_is_percentage;
- s32 net_per_item;
- s32 net;
- s32 discount;
- s32 tax;
+ char description[128];
+ float net_per_item;
+ float discount;
+ bool discount_is_percentage;
+ float net;
+ char currency[CURRENCY_LENGTH];
+ char tax_bracket_id[16];
+
+ // todo
+ char tax_section[16];
+ float tax;
+ float total;
bool is_intra_community;
bool is_triangulation;
- char currency[16];
char internal_code[64];
} billing_item;
@@ -103,15 +109,15 @@ typedef struct
char cost_center_id[16];
list_t billing_items;
- s32 total;
- s32 tax;
- s32 net;
+ float total;
+ float tax;
+ float net;
invoice_status status;
- char currency[8];
- time_t keep_untill;
- time_t payment_on_account_date;
- char tax_representative[64];
- char corrected_sequential_number[16];
+ char currency[CURRENCY_LENGTH];
+
+ time_t payment_on_account_date; // TODO
+ char tax_representative[64]; // TODO
+ char corrected_sequential_number[16]; // TODO
// Not stored.
contact supplier;
@@ -120,7 +126,9 @@ typedef struct
typedef struct
{
+ // Company info used for invoices. User should not be able to create invoices when this is empty.
contact company_info;
+
s32 next_id; // Shared across all objects that have an ID.
s32 next_sequence_number; // Sequence number for generating invoice numbers.
char path[4096];
@@ -163,6 +171,7 @@ u32 administration_get_all_projects(project* buffer);
u32 administration_get_tax_bracket_count();
u32 administration_get_tax_brackets(country_tax_bracket* buffer);
+u32 administration_get_tax_brackets_for_country(country_tax_bracket* buffer, char* country_code);
bool administration_add_tax_bracket(country_tax_bracket data);
bool administration_update_tax_bracket(country_tax_bracket data);
@@ -173,4 +182,11 @@ bool administration_verify_cost_center_description(char* text);
bool administration_add_cost_center(cost_center data);
bool administration_update_cost_center(cost_center data);
-invoice administration_create_empty_invoice(); \ No newline at end of file
+invoice administration_create_empty_invoice();
+void administration_invoice_set_currency(invoice* invoice, char* currency);
+
+billing_item administration_create_empty_billing_item();
+u32 administration_get_all_billing_items_for_invoice(invoice* invoice, billing_item* buffer);
+u32 administration_get_billing_items_count(invoice* invoice);
+bool administration_add_billing_item_to_invoice(invoice* invoice, billing_item item);
+bool administration_update_billing_item_of_invoice(invoice* invoice, billing_item item); \ No newline at end of file
diff --git a/src/administration.cpp b/src/administration.cpp
index 56d8663..2e16775 100644
--- a/src/administration.cpp
+++ b/src/administration.cpp
@@ -262,6 +262,12 @@ static void administration_create_debug_data()
ADD_PROJECT("Retail store #1");
ADD_PROJECT("Retail store #2");
ADD_PROJECT("Kayak rental");
+
+ // Company info.
+ strops_copy(g_administration.company_info.name, "Aldrik Ramaekers", sizeof(g_administration.company_info.name));
+ strops_copy(g_administration.company_info.address.address1, "Keerderstraat 81", sizeof(g_administration.company_info.address.address1));
+ strops_copy(g_administration.company_info.address.address2, "6226XW Maastricht", sizeof(g_administration.company_info.address.address2));
+ strops_copy(g_administration.company_info.address.country_code, "NL", sizeof(g_administration.company_info.address.country_code));
}
void administration_create()
@@ -269,11 +275,6 @@ void administration_create()
g_administration.next_id = 1;
g_administration.next_sequence_number = 1;
- strops_copy(g_administration.company_info.name, "Aldrik Ramaekers", sizeof(g_administration.company_info.name));
- strops_copy(g_administration.company_info.address.address1, "Keerderstraat 81", sizeof(g_administration.company_info.address.address1));
- strops_copy(g_administration.company_info.address.address2, "6226XW Maastricht", sizeof(g_administration.company_info.address.address2));
- strops_copy(g_administration.company_info.address.country_code, "NL", sizeof(g_administration.company_info.address.country_code));
-
list_init(&g_administration.contacts);
list_init(&g_administration.projects);
list_init(&g_administration.tax_brackets);
@@ -408,6 +409,25 @@ u32 administration_get_project_count()
return list_size(&g_administration.projects);
}
+u32 administration_get_billing_items_count(invoice* invoice)
+{
+ return list_size(&invoice->billing_items);
+}
+
+u32 administration_get_all_billing_items_for_invoice(invoice* invoice, billing_item* buffer)
+{
+ u32 write_cursor = 0;
+
+ list_iterator_start(&invoice->billing_items);
+ while (list_iterator_hasnext(&invoice->billing_items)) {
+ billing_item c = *(billing_item *)list_iterator_next(&invoice->billing_items);
+ buffer[write_cursor++] = c;
+ }
+ list_iterator_stop(&invoice->billing_items);
+
+ return write_cursor;
+}
+
u32 administration_get_all_projects(project* buffer)
{
u32 write_cursor = 0;
@@ -543,6 +563,23 @@ bool administration_add_tax_bracket(country_tax_bracket data)
return true;
}
+u32 administration_get_tax_brackets_for_country(country_tax_bracket* buffer, char* country_code)
+{
+ assert(buffer);
+
+ u32 write_cursor = 0;
+
+ list_iterator_start(&g_administration.tax_brackets);
+ while (list_iterator_hasnext(&g_administration.tax_brackets)) {
+ country_tax_bracket c = *(country_tax_bracket *)list_iterator_next(&g_administration.tax_brackets);
+
+ if (strcmp(c.country_code, country_code) == 0 || strcmp(c.country_code, "00") == 0) buffer[write_cursor++] = c;
+ }
+ list_iterator_stop(&g_administration.tax_brackets);
+
+ return write_cursor;
+}
+
u32 administration_get_tax_brackets(country_tax_bracket* buffer)
{
assert(buffer);
@@ -576,8 +613,6 @@ bool administration_update_tax_bracket(country_tax_bracket data)
return false;
}
-
-
u32 administration_get_cost_center_count()
{
return list_size(&g_administration.cost_centers);
@@ -689,6 +724,68 @@ static time_t administration_get_default_invoice_expire_duration()
return (30 * 24 * 60 * 60); // 30 days
}
+billing_item administration_create_empty_billing_item()
+{
+ billing_item item;
+ memset(&item, 0, sizeof(billing_item));
+ item.amount = 1;
+ return item;
+}
+
+bool administration_add_billing_item_to_invoice(invoice* invoice, billing_item item)
+{
+ billing_item* tb = (billing_item*)malloc(sizeof(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));
+ list_append(&invoice->billing_items, tb);
+ strops_copy(tb->currency, invoice->currency, CURRENCY_LENGTH); // Set billing item currency to invoice currency.
+
+ g_administration.next_id++;
+
+ return true;
+}
+
+static char* administration_get_default_currency_for_country(char* country_code)
+{
+ if (country_code == NULL || strlen(country_code) != 2)
+ return "EUR"; // default
+
+ // Non-euro EU currencies
+ if (strcmp(country_code, "BG") == 0) return "BGN"; // Bulgaria
+ else if (strcmp(country_code, "CZ") == 0) return "CZK"; // Czechia
+ else if (strcmp(country_code, "DK") == 0) return "DKK"; // Denmark
+ else if (strcmp(country_code, "HU") == 0) return "HUF"; // Hungary
+ else if (strcmp(country_code, "PL") == 0) return "PLN"; // Poland
+ else if (strcmp(country_code, "RO") == 0) return "RON"; // Romania
+ else if (strcmp(country_code, "SE") == 0) return "SEK"; // Sweden
+
+ // Eurozone members
+ else if (strcmp(country_code, "AT") == 0) return "EUR"; // Austria
+ else if (strcmp(country_code, "BE") == 0) return "EUR"; // Belgium
+ else if (strcmp(country_code, "CY") == 0) return "EUR"; // Cyprus
+ else if (strcmp(country_code, "DE") == 0) return "EUR"; // Germany
+ else if (strcmp(country_code, "EE") == 0) return "EUR"; // Estonia
+ else if (strcmp(country_code, "ES") == 0) return "EUR"; // Spain
+ else if (strcmp(country_code, "FI") == 0) return "EUR"; // Finland
+ else if (strcmp(country_code, "FR") == 0) return "EUR"; // France
+ else if (strcmp(country_code, "GR") == 0) return "EUR"; // Greece
+ else if (strcmp(country_code, "HR") == 0) return "EUR"; // Croatia
+ else if (strcmp(country_code, "IE") == 0) return "EUR"; // Ireland
+ else if (strcmp(country_code, "IT") == 0) return "EUR"; // Italy
+ else if (strcmp(country_code, "LT") == 0) return "EUR"; // Lithuania
+ else if (strcmp(country_code, "LU") == 0) return "EUR"; // Luxembourg
+ else if (strcmp(country_code, "LV") == 0) return "EUR"; // Latvia
+ else if (strcmp(country_code, "MT") == 0) return "EUR"; // Malta
+ else if (strcmp(country_code, "NL") == 0) return "EUR"; // Netherlands
+ else if (strcmp(country_code, "PT") == 0) return "EUR"; // Portugal
+ else if (strcmp(country_code, "SI") == 0) return "EUR"; // Slovenia
+ else if (strcmp(country_code, "SK") == 0) return "EUR"; // Slovakia
+
+ // Default fallback
+ return "EUR";
+}
+
invoice administration_create_empty_invoice()
{
invoice result;
@@ -698,6 +795,8 @@ invoice administration_create_empty_invoice()
result.issued_at = time(NULL);
result.delivered_at = time(NULL);
result.expires_at = time(NULL) + administration_get_default_invoice_expire_duration();
+ list_init(&result.billing_items); // @leak
+ strops_copy(result.currency, administration_get_default_currency_for_country(g_administration.company_info.address.country_code), CURRENCY_LENGTH);
return result;
}
@@ -715,4 +814,86 @@ project administration_create_empty_project()
memset(&result, 0, sizeof(project));
snprintf(result.id, sizeof(result.id), "P/%d", administration_create_id());
return result;
+}
+
+static bool administration_get_tax_bracket_by_id(country_tax_bracket* buffer, char* id)
+{
+ assert(buffer);
+
+ list_iterator_start(&g_administration.tax_brackets);
+ while (list_iterator_hasnext(&g_administration.tax_brackets)) {
+ country_tax_bracket c = *(country_tax_bracket *)list_iterator_next(&g_administration.tax_brackets);
+ if (strcmp(c.id, id) == 0)
+ {
+ *buffer = c;
+ list_iterator_stop(&g_administration.tax_brackets);
+ return true;
+ }
+ }
+ list_iterator_stop(&g_administration.tax_brackets);
+
+ return false;
+}
+
+static void administration_recalculate_billing_item_totals(billing_item* item)
+{
+ if (item->amount_is_percentage)
+ {
+ item->net = item->net_per_item * (item->amount / 100.0f);
+ }
+ else
+ {
+ item->net = item->net_per_item * item->amount;
+ }
+
+ if (item->discount != 0)
+ {
+ if (item->discount_is_percentage)
+ {
+ item->net -= item->net * (item->discount / 100.0f);
+ }
+ else
+ {
+ item->net -= item->discount;
+ }
+ }
+
+ country_tax_bracket bracket;
+ if (administration_get_tax_bracket_by_id(&bracket, item->tax_bracket_id))
+ {
+ item->tax = item->net * (bracket.rate/100.0f);
+ }
+
+ item->total = item->net + item->tax;
+}
+
+bool administration_update_billing_item_of_invoice(invoice* invoice, billing_item item)
+{
+ 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 (strcmp(c->id, item.id) == 0) {
+ memcpy(c, &item, sizeof(billing_item));
+ administration_recalculate_billing_item_totals(c);
+ list_iterator_stop(&invoice->billing_items);
+ return true;
+ }
+ }
+ list_iterator_stop(&invoice->billing_items);
+
+ return false;
+}
+
+void administration_invoice_set_currency(invoice* invoice, char* currency)
+{
+ strops_copy(invoice->currency, currency, CURRENCY_LENGTH);
+
+ list_iterator_start(&invoice->billing_items);
+ while (list_iterator_hasnext(&invoice->billing_items)) {
+ billing_item* c = (billing_item *)list_iterator_next(&invoice->billing_items);
+
+ strops_copy(c->currency, currency, CURRENCY_LENGTH);
+ }
+ list_iterator_stop(&invoice->billing_items);
} \ No newline at end of file
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())
{