summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAldrik Ramaekers <aldrikboy@gmail.com>2025-09-07 08:39:01 +0200
committerAldrik Ramaekers <aldrikboy@gmail.com>2025-09-07 08:39:01 +0200
commit026e38982f5388cede0cd7ebad2ea7571d1d57ed (patch)
tree5d164c41bec4f0589e991740c1e0050fe746501a
parent17e035839a19a8b10d329c28ccaa44ff608d3a33 (diff)
income statement generation
-rw-r--r--docs/CHANGES.rst4
-rw-r--r--include/administration.hpp53
-rw-r--r--include/ui.hpp3
-rw-r--r--src/administration.cpp266
-rw-r--r--src/administration_writer.cpp6
-rw-r--r--src/locales/en.cpp2
-rw-r--r--src/ui/ui_earnings.cpp151
-rw-r--r--src/ui/ui_expenses.cpp2
-rw-r--r--src/ui/ui_invoices.cpp11
-rw-r--r--src/ui/ui_main.cpp8
10 files changed, 470 insertions, 36 deletions
diff --git a/docs/CHANGES.rst b/docs/CHANGES.rst
index 0ac0aca..41a2f11 100644
--- a/docs/CHANGES.rst
+++ b/docs/CHANGES.rst
@@ -11,7 +11,6 @@ TODO:
- View invoice history for projects
- Create monthly and quarterly results (per project).
- Create quarterly tax reports for NL.
-- Store invoices in Peppol BIS 3.0 format.
- Make sure invoices/expenses cannot be added when company info is empty/invalid.
- net negative billing items should not also have discounts.
- net negative billing items should set tax to 0.
@@ -27,4 +26,5 @@ v0.1 (master)
- Create/read/update VAT rates
- Create/read/update cost centers
- Generate default VAT rates
-- Generate default cost centers \ No newline at end of file
+- Generate default cost centers
+- Archive invoices in Peppol BIS 3.0 format. \ No newline at end of file
diff --git a/include/administration.hpp b/include/administration.hpp
index 015b1c3..45effee 100644
--- a/include/administration.hpp
+++ b/include/administration.hpp
@@ -20,6 +20,9 @@
#define MAX_LEN_BUSINESSID 32
#define MAX_LEN_TAX_SECTION 16
+#define MAX_LEN_INCOME_STATEMENT_REPORT_QUARTERS 400
+#define MAX_LEN_QUARTERLY_REPORT_PROJECTS 200
+#define MAX_LEN_PROJECT_REPORT_COSTCENTERS 50
#define MAX_BILLING_ITEMS 50
typedef struct
@@ -237,7 +240,7 @@ typedef struct
time_t expires_at;
time_t delivered_at;
char document[MAX_LEN_PATH]; // path to copy of document for incomming invoice.
- char project_id[MAX_LEN_ID]; // For incomming invoices. optional.
+ char project_id[MAX_LEN_ID]; // Optional.
char cost_center_id[MAX_LEN_ID]; // For incomming invoices. optional.
list_t billing_items;
@@ -265,6 +268,43 @@ typedef struct
typedef struct
{
+ char cost_center_id[MAX_LEN_ID];
+ char description[MAX_LEN_LONG_DESC];
+ float total;
+} project_expense;
+
+typedef struct
+{
+ char project_id[MAX_LEN_ID];
+ char description[MAX_LEN_LONG_DESC];
+ u32 expense_count;
+ project_expense expenses[MAX_LEN_PROJECT_REPORT_COSTCENTERS];
+ float expenses_total; // Sum of all expenses. (incl uncategorized)
+ float revenue;
+ float taxes;
+} project_report;
+
+typedef struct
+{
+ float uncategorized_expenses; // Sum of expenses without project.
+ float uncategorized_revenue;
+ float uncategorized_taxes;
+
+ u32 report_count;
+ project_report reports[MAX_LEN_QUARTERLY_REPORT_PROJECTS];
+ u16 year; // 00-99
+ u8 quarter; // 0-3
+ char quarter_str[MAX_LEN_SHORT_DESC];
+} quarterly_report;
+
+typedef struct
+{
+ u32 quarter_count;
+ quarterly_report quarters[MAX_LEN_INCOME_STATEMENT_REPORT_QUARTERS];
+} income_statement;
+
+typedef struct
+{
contact company_info; // Company info used for invoices. User cannot create invoices when this is empty/invalid.
s32 next_id; // Id increment shared across all objects that have an ID.
s32 next_sequence_number; // Sequence number for generating invoice numbers.
@@ -286,12 +326,13 @@ typedef struct
void administration_create();
void administration_destroy();
-// General functions.
+// Other functions.
// =======================
-administration* administation_get();
+administration* administration_get();
char* administration_file_path_get();
contact administration_company_info_get();
void administration_company_info_set(contact data);
+void administration_create_income_statement(income_statement* statement);
// Contact functions.
// =======================
@@ -324,6 +365,7 @@ bool administration_project_is_valid(project data);
char* administration_project_get_status_string(project data);
u32 administration_project_get_all(project* buffer);
u32 administration_project_get_partial_list(u32 page_index, u32 page_size, project* buffer);
+bool administration_project_get_by_id(project* buffer, char* id);
// Tax bracket functions.
// =======================
@@ -345,12 +387,13 @@ bool administration_cost_center_verify_code(char* code);
bool administration_cost_center_verify_description(char* text);
u32 administration_cost_center_get_all(cost_center* buffer);
+bool administration_cost_center_get_by_id(cost_center* buffer, char* id);
// Invoice functions.
// =======================
u32 administration_invoice_count();
-u32 administation_invoice_get_incomming_count();
-u32 administation_invoice_get_outgoing_count();
+u32 administration_invoice_get_incomming_count();
+u32 administration_invoice_get_outgoing_count();
invoice administration_invoice_create_empty();
bool administration_invoice_add(invoice* invoice);
bool administration_invoice_update(invoice* invoice);
diff --git a/include/ui.hpp b/include/ui.hpp
index c0bdcf8..044eff5 100644
--- a/include/ui.hpp
+++ b/include/ui.hpp
@@ -41,16 +41,19 @@ void ui_draw_projects();
void ui_draw_invoices();
void ui_draw_settings();
void ui_draw_expenses();
+void ui_draw_earnings();
void ui_setup_invoices();
void ui_setup_contacts();
void ui_setup_projects();
void ui_setup_settings();
void ui_setup_expenses();
+void ui_setup_earnings();
void ui_destroy_invoices();
void ui_destroy_settings();
void ui_destroy_expenses();
+void ui_destroy_earnings();
// Custom imgui widgets.
int TextInputWithAutocomplete(const char* label, const char* hint, char* buffer, size_t buf_size, char** suggestions, int suggestion_count); \ No newline at end of file
diff --git a/src/administration.cpp b/src/administration.cpp
index e6c9821..0cca2b7 100644
--- a/src/administration.cpp
+++ b/src/administration.cpp
@@ -1,3 +1,5 @@
+#define _CRT_SECURE_NO_WARNINGS
+
#include <string.h>
#include <stdlib.h>
#include <assert.h>
@@ -33,12 +35,12 @@ static int compare_tax_countries(const void *a, const void *b)
return strcmp(objA->country_code, objB->country_code);
}
-static invoice_status administation_get_random_invoice_status()
+static invoice_status administration_get_random_invoice_status()
{
return (invoice_status)(rand() % invoice_status::INVOICE_END);
}
-static void administation_get_random_project(char* project_id)
+static void administration_get_random_project(char* project_id)
{
int size = list_size(&g_administration.projects);
if (size > 0) {
@@ -48,7 +50,7 @@ static void administation_get_random_project(char* project_id)
}
}
-static void administation_get_random_cost_center(char* cost_center_id)
+static void administration_get_random_cost_center(char* cost_center_id)
{
int size = list_size(&g_administration.cost_centers);
if (size > 0) {
@@ -58,7 +60,7 @@ static void administation_get_random_cost_center(char* cost_center_id)
}
}
-static contact* administation_get_random_contact()
+static contact* administration_get_random_contact()
{
int size = list_size(&g_administration.contacts);
if (size > 0) {
@@ -76,7 +78,7 @@ static time_t administration_get_default_invoice_expire_duration()
static void administration_recalculate_billing_item_totals(billing_item* item);
-static void administation_get_random_billing_items(invoice* inv)
+static void administration_get_random_billing_items(invoice* inv)
{
int amount = 1 + rand() % 20;
@@ -364,22 +366,22 @@ static void administration_create_debug_data()
#define ADD_INVOICE(_outgoing)\
{\
invoice inv = administration_invoice_create_empty();\
- if (_outgoing) inv.supplier = administration_company_info_get(); else inv.supplier = *administation_get_random_contact();\
- if (_outgoing) inv.customer = *administation_get_random_contact(); else inv.customer = administration_company_info_get();\
- administation_get_random_project(inv.project_id);\
- administation_get_random_cost_center(inv.cost_center_id);\
+ if (_outgoing) inv.supplier = administration_company_info_get(); else inv.supplier = *administration_get_random_contact();\
+ if (_outgoing) inv.customer = *administration_get_random_contact(); else inv.customer = administration_company_info_get();\
+ administration_get_random_project(inv.project_id);\
+ if (!_outgoing) administration_get_random_cost_center(inv.cost_center_id);\
inv.is_outgoing = _outgoing;\
- inv.status = administation_get_random_invoice_status();\
+ inv.status = administration_get_random_invoice_status();\
inv.issued_at = time(NULL) - (86400 * (rand() % 720));\
inv.delivered_at = inv.issued_at;\
inv.expires_at = inv.issued_at + administration_get_default_invoice_expire_duration();\
- administation_get_random_billing_items(&inv);\
+ administration_get_random_billing_items(&inv);\
administration_invoice_add(&inv);\
}
// Create about 2 years of random data.
- for (int i = 0; i < 1; i++) { ADD_INVOICE(1); }
- for (int i = 0; i < 1; i++) { ADD_INVOICE(0); }
+ for (int i = 0; i < 200; i++) { ADD_INVOICE(1); }
+ for (int i = 0; i < 200; i++) { ADD_INVOICE(0); }
}
static s32 administration_create_sequence_number()
@@ -407,7 +409,9 @@ void administration_create()
administration_create_debug_data();
- administration_writer_save_all_async();
+ //administration_writer_save_all_async();
+ income_statement* statement = (income_statement*)malloc(sizeof(income_statement));
+ administration_create_income_statement(statement);
}
static void administration_destroy_list(list_t *list)
@@ -430,8 +434,194 @@ void administration_destroy()
administration_destroy_list(&g_administration.cost_centers);
}
-// General functions.
+// Other functions.
// =======================
+static void time_t_to_quarter(time_t time, u16* year, u8* quarter)
+{
+ struct tm *lt = localtime(&time);
+ //sprinf(buffer, "%dQ%d", lt->tm_mon / 3, lt->tm_year / 100);
+ *year = (u16)((1900+lt->tm_year) % 100);
+ *quarter = (u8)(lt->tm_mon / 3);
+}
+
+static void administration_debug_print_income_statement(income_statement* statement)
+{
+ for (u32 i = 0; i < statement->quarter_count; i++)
+ {
+ quarterly_report report = statement->quarters[i];
+
+ printf("=== %dQ%d ===\n", report.quarter+1, report.year);
+ printf("general revenue: %.2f\n", report.uncategorized_revenue);
+ printf("general taxes: %.2f\n", report.uncategorized_taxes);
+ printf("general expenses: %.2f\n", report.uncategorized_expenses);
+
+ for (u32 x = 0; x < report.report_count; x++)
+ {
+ project_report pr = report.reports[x];
+
+ project proj;
+ administration_project_get_by_id(&proj, pr.project_id);
+ printf("PROJECT: %s\n", proj.description);
+ printf(" revenue: %.2f\n", pr.revenue);
+ printf(" taxes: %.2f\n", pr.taxes);
+ printf(" expenses: %.2f\n", pr.expenses_total);
+
+ for (u32 y = 0; y < pr.expense_count; y++)
+ {
+ project_expense expense = pr.expenses[y];
+
+ cost_center costcenter;
+ administration_cost_center_get_by_id(&costcenter, expense.cost_center_id);
+ printf(" %s: %.2f\n", costcenter.code, expense.total);
+ }
+ }
+
+ printf("\n");
+ }
+}
+
+void administration_create_income_statement(income_statement* statement)
+{
+ assert(statement);
+ statement->quarter_count = 0;
+
+ u32 invoice_count = administration_invoice_count();
+ invoice* invoice_buffer = (invoice*)malloc(sizeof(invoice)*invoice_count);
+ invoice_count = administration_invoice_get_all(invoice_buffer);
+
+ // Find oldest and youngest invoice.
+ time_t oldest = INT64_MAX;
+ time_t youngest = 0;
+ for (u32 i = 0; i < invoice_count; i++)
+ {
+ if (invoice_buffer[i].delivered_at < oldest) oldest = invoice_buffer[i].delivered_at;
+ if (invoice_buffer[i].delivered_at > youngest) youngest = invoice_buffer[i].delivered_at;
+ }
+
+ u16 oldest_year;
+ u8 oldest_quarter;
+ time_t_to_quarter(oldest, &oldest_year, &oldest_quarter);
+
+ u16 youngest_year;
+ u8 youngest_quarter;
+ time_t_to_quarter(youngest, &youngest_year, &youngest_quarter);
+
+ u32 num_quarters = (youngest_quarter+1 + (youngest_year*4)) - (oldest_quarter + (oldest_year*4));
+ assert(num_quarters <= MAX_LEN_INCOME_STATEMENT_REPORT_QUARTERS);
+
+ // Generate quarters.
+ for (u32 i = 0; i < num_quarters; i++)
+ {
+ quarterly_report quarter;
+ quarter.year = oldest_year + (u16)((oldest_quarter+i) / 4);
+ quarter.quarter = (u8)((oldest_quarter + (i)) % 4);
+ quarter.uncategorized_expenses = 0.0f;
+ quarter.uncategorized_revenue = 0.0f;
+ quarter.uncategorized_taxes = 0.0f;
+ quarter.report_count = 0;
+ snprintf(quarter.quarter_str, MAX_LEN_SHORT_DESC, "%dQ%d", quarter.quarter+1, quarter.year);
+
+ u32 project_count = administration_project_count();
+ project* project_buffer = (project*)malloc(sizeof(project)*project_count);
+ project_count = administration_project_get_all(project_buffer);
+ assert(project_count <= MAX_LEN_QUARTERLY_REPORT_PROJECTS);
+
+ u32 costcenter_count = administration_cost_center_count();
+ cost_center* costcenter_buffer = (cost_center*)malloc(sizeof(cost_center)*costcenter_count);
+ costcenter_count = administration_cost_center_get_all(costcenter_buffer);
+ assert(costcenter_count <= MAX_LEN_PROJECT_REPORT_COSTCENTERS);
+
+ for (u32 x = 0; x < project_count; x++)
+ {
+ project_report report;
+ report.expenses_total = 0.0f;
+ report.revenue = 0.0f;
+ report.taxes = 0.0f;
+ report.expense_count = 0;
+ strops_copy(report.project_id, project_buffer[x].id, MAX_LEN_ID);
+ strops_copy(report.description, project_buffer[x].description, MAX_LEN_LONG_DESC);
+
+ for (u32 y = 0; y < costcenter_count; y++)
+ {
+ project_expense expense;
+ strops_copy(expense.cost_center_id, costcenter_buffer[y].id, MAX_LEN_ID);
+ strops_copy(expense.description, costcenter_buffer[y].description, MAX_LEN_LONG_DESC);
+ expense.total = 0.0f;
+ report.expenses[report.expense_count++] = expense;
+ }
+
+ quarter.reports[quarter.report_count++] = report;
+ }
+
+ statement->quarters[statement->quarter_count++] = quarter;
+ }
+
+ // Fill quarters.
+ for (u32 i = 0; i < invoice_count; i++)
+ {
+ invoice* inv = &invoice_buffer[i];
+
+ u16 yy;
+ u8 qq;
+ time_t_to_quarter(inv->delivered_at, &yy, &qq);
+
+ u32 report_index = (qq + (yy*4)) - (oldest_quarter + (oldest_year*4));
+
+ quarterly_report* quarter = &statement->quarters[report_index];
+ assert(yy == quarter->year && qq == quarter->quarter);
+
+ if (strcmp(inv->project_id, "") == 0) {
+ if (inv->is_outgoing) {
+ quarter->uncategorized_revenue += inv->total;
+ quarter->uncategorized_taxes += inv->tax;
+ }
+ else {
+ quarter->uncategorized_expenses += inv->total;
+ }
+ }
+ else {
+ int project_report_index = -1;
+ for (u32 x = 0; x < quarter->report_count; x++)
+ {
+ if (strcmp(quarter->reports[x].project_id, inv->project_id) == 0) {
+ project_report_index = x;
+ break;
+ }
+ }
+ assert(project_report_index != -1);
+
+ project_report* report = &quarter->reports[project_report_index];
+
+ if (inv->is_outgoing) {
+ report->revenue += inv->total;
+ report->taxes += inv->tax;
+ }
+ else {
+ report->expenses_total += inv->total;
+
+ if (strcmp(inv->cost_center_id, "") != 0) {
+ int expense_report_index = -1;
+ for (u32 x = 0; x < report->expense_count; x++)
+ {
+ if (strcmp(report->expenses[x].cost_center_id, inv->cost_center_id) == 0) {
+ expense_report_index = x;
+ break;
+ }
+ }
+ assert(expense_report_index != -1);
+
+ project_expense* expense = &report->expenses[expense_report_index];
+ expense->total += inv->total;
+ }
+ }
+ }
+ }
+
+ //administration_debug_print_income_statement(statement);
+
+ free(invoice_buffer);
+}
+
char* administration_file_path_get()
{
return g_administration.path;
@@ -447,7 +637,7 @@ void administration_company_info_set(contact data)
g_administration.company_info = data;
}
-administration* administation_get()
+administration* administration_get()
{
return &g_administration;
}
@@ -635,6 +825,25 @@ u32 administration_project_count()
return list_size(&g_administration.projects);
}
+bool administration_project_get_by_id(project* buffer, char* id)
+{
+ assert(buffer);
+
+ list_iterator_start(&g_administration.projects);
+ while (list_iterator_hasnext(&g_administration.projects)) {
+ project c = *(project *)list_iterator_next(&g_administration.projects);
+ if (strcmp(c.id, id) == 0)
+ {
+ *buffer = c;
+ list_iterator_stop(&g_administration.projects);
+ return true;
+ }
+ }
+ list_iterator_stop(&g_administration.projects);
+
+ return false;
+}
+
u32 administration_project_get_all(project* buffer)
{
u32 write_cursor = 0;
@@ -853,6 +1062,25 @@ u32 administration_cost_center_count()
return list_size(&g_administration.cost_centers);
}
+bool administration_cost_center_get_by_id(cost_center* buffer, char* id)
+{
+ assert(buffer);
+
+ list_iterator_start(&g_administration.cost_centers);
+ while (list_iterator_hasnext(&g_administration.cost_centers)) {
+ cost_center c = *(cost_center *)list_iterator_next(&g_administration.cost_centers);
+ if (strcmp(c.id, id) == 0)
+ {
+ *buffer = c;
+ list_iterator_stop(&g_administration.cost_centers);
+ return true;
+ }
+ }
+ list_iterator_stop(&g_administration.cost_centers);
+
+ return false;
+}
+
u32 administration_cost_center_get_all(cost_center* buffer)
{
assert(buffer);
@@ -1059,7 +1287,7 @@ bool administration_invoice_remove(invoice* inv)
static void administration_invoice_set_refs(invoice* inv)
{
// This function makes sure that contact info referenced in the IDs actually
- // matches the contact info stored in administation, if not we make new contacts.
+ // matches the contact info stored in administration, if not we make new contacts.
// Check if supplier info is equal to contact stored in supplier id, in case we autocomplete and edit data after,
// this should be handled as a new contact.
@@ -1203,12 +1431,12 @@ u32 administration_invoice_count()
return list_size(&g_administration.invoices);
}
-u32 administation_invoice_get_incomming_count()
+u32 administration_invoice_get_incomming_count()
{
return g_administration.expense_count;
}
-u32 administation_invoice_get_outgoing_count()
+u32 administration_invoice_get_outgoing_count()
{
return g_administration.invoice_count;
}
diff --git a/src/administration_writer.cpp b/src/administration_writer.cpp
index 0176d97..d9c9c31 100644
--- a/src/administration_writer.cpp
+++ b/src/administration_writer.cpp
@@ -527,9 +527,9 @@ bool administration_writer_save_all_administration_info_blocking()
int buf_length = 0;
char* file_content = administration_writer_copy_template(administration_save_template, &buf_length);
- strops_replace_int32(file_content, buf_length, "{{NEXT_ID}}", administation_get()->next_id);
- strops_replace_int32(file_content, buf_length, "{{NEXT_SEQUENCE_NUMBER}}", administation_get()->next_sequence_number);
- strops_replace(file_content, buf_length, "{{PROGRAM_VERSION}}", administation_get()->program_version);
+ strops_replace_int32(file_content, buf_length, "{{NEXT_ID}}", administration_get()->next_id);
+ strops_replace_int32(file_content, buf_length, "{{NEXT_SEQUENCE_NUMBER}}", administration_get()->next_sequence_number);
+ strops_replace(file_content, buf_length, "{{PROGRAM_VERSION}}", administration_get()->program_version);
//// Write to Disk.
int final_length = (int)strlen(file_content);
diff --git a/src/locales/en.cpp b/src/locales/en.cpp
index 03e5940..6013f84 100644
--- a/src/locales/en.cpp
+++ b/src/locales/en.cpp
@@ -79,7 +79,7 @@ locale_entry en_locales[] = {
{"nav.expenses", "Expenses"},
{"nav.contacts", "Contacts"},
{"nav.reports", "Reports"},
- {"nav.reports.results", "Results"},
+ {"nav.reports.results", "Earnings"},
{"nav.reports.tax", "Tax return"},
{"nav.projects", "Projects"},
{"nav.settings", "Settings"},
diff --git a/src/ui/ui_earnings.cpp b/src/ui/ui_earnings.cpp
new file mode 100644
index 0000000..81e31b5
--- /dev/null
+++ b/src/ui/ui_earnings.cpp
@@ -0,0 +1,151 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "ui.hpp"
+#include "imgui.h"
+#include "administration.hpp"
+#include "locales.hpp"
+
+income_statement* statement = 0;
+
+void ui_setup_earnings()
+{
+ statement = (income_statement*)malloc(sizeof(income_statement));
+ administration_create_income_statement(statement);
+}
+
+void ui_destroy_earnings()
+{
+ free(statement);
+}
+
+void ui_draw_earnings()
+{
+ if (ImGui::BeginTable("QuarterlyResultsTable", 5,
+ ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit))
+ {
+ ImGui::TableSetupColumn("##names");
+
+ int quarter_start = 0;
+ int quarter_count = 4;
+
+ // Table header
+ for (int q = 0; q < quarter_count; q++)
+ {
+ ImGui::TableSetupColumn(statement->quarters[q].quarter_str, ImGuiTableColumnFlags_WidthStretch);
+ }
+
+ ImGui::TableHeadersRow();
+
+ // Uncategorized income and expenses
+ ImGui::TableNextRow();
+ ImGui::TableSetColumnIndex(0); ImGui::Text("(Uncategorized)");
+ ImGui::TableSetColumnIndex(1); ImGui::Text("");
+ ImGui::TableSetColumnIndex(2); ImGui::Text("");
+ ImGui::TableSetColumnIndex(3); ImGui::Text("");
+ ImGui::TableSetColumnIndex(4); ImGui::Text("");
+
+ ImGui::TableNextRow();
+ ImGui::TableSetColumnIndex(0); ImGui::TextUnformatted(" Revenue");
+ ImGui::TableSetColumnIndex(1); ImGui::Text("%.2f", statement->quarters[quarter_start + 0].uncategorized_revenue);
+ ImGui::TableSetColumnIndex(2); ImGui::Text("%.2f", statement->quarters[quarter_start + 1].uncategorized_revenue);
+ ImGui::TableSetColumnIndex(3); ImGui::Text("%.2f", statement->quarters[quarter_start + 2].uncategorized_revenue);
+ ImGui::TableSetColumnIndex(4); ImGui::Text("%.2f", statement->quarters[quarter_start + 3].uncategorized_revenue);
+
+ ImGui::TableNextRow();
+ ImGui::TableSetColumnIndex(0); ImGui::TextUnformatted(" Tax");
+ ImGui::TableSetColumnIndex(1); ImGui::Text("%.2f", statement->quarters[quarter_start + 0].uncategorized_taxes);
+ ImGui::TableSetColumnIndex(2); ImGui::Text("%.2f", statement->quarters[quarter_start + 1].uncategorized_taxes);
+ ImGui::TableSetColumnIndex(3); ImGui::Text("%.2f", statement->quarters[quarter_start + 2].uncategorized_taxes);
+ ImGui::TableSetColumnIndex(4); ImGui::Text("%.2f", statement->quarters[quarter_start + 3].uncategorized_taxes);
+
+ ImGui::TableNextRow();
+ ImGui::TableSetColumnIndex(0); ImGui::TextUnformatted(" Expenses");
+ ImGui::TableSetColumnIndex(1); ImGui::Text("%.2f", statement->quarters[quarter_start + 0].uncategorized_expenses);
+ ImGui::TableSetColumnIndex(2); ImGui::Text("%.2f", statement->quarters[quarter_start + 1].uncategorized_expenses);
+ ImGui::TableSetColumnIndex(3); ImGui::Text("%.2f", statement->quarters[quarter_start + 2].uncategorized_expenses);
+ ImGui::TableSetColumnIndex(4); ImGui::Text("%.2f", statement->quarters[quarter_start + 3].uncategorized_expenses);
+
+ quarterly_report default_quater = statement->quarters[0];
+ for (u32 p = 0; p < default_quater.report_count; p++)
+ {
+ ImGui::TableNextRow();
+ project_report* report = &default_quater.reports[p];
+ ImGui::TableSetColumnIndex(0); ImGui::Text("(%s)", report->description);
+ ImGui::TableSetColumnIndex(1); ImGui::Text("");
+ ImGui::TableSetColumnIndex(2); ImGui::Text("");
+ ImGui::TableSetColumnIndex(3); ImGui::Text("");
+ ImGui::TableSetColumnIndex(4); ImGui::Text("");
+
+ ImGui::TableNextRow();
+ ImGui::TableSetColumnIndex(0); ImGui::TextUnformatted(" Revenue");
+ ImGui::TableSetColumnIndex(1); ImGui::Text("%.2f", statement->quarters[quarter_start + 0].reports[p].revenue);
+ ImGui::TableSetColumnIndex(2); ImGui::Text("%.2f", statement->quarters[quarter_start + 1].reports[p].revenue);
+ ImGui::TableSetColumnIndex(3); ImGui::Text("%.2f", statement->quarters[quarter_start + 2].reports[p].revenue);
+ ImGui::TableSetColumnIndex(4); ImGui::Text("%.2f", statement->quarters[quarter_start + 3].reports[p].revenue);
+
+ ImGui::TableNextRow();
+ ImGui::TableSetColumnIndex(0); ImGui::TextUnformatted(" Tax");
+ ImGui::TableSetColumnIndex(1); ImGui::Text("%.2f", statement->quarters[quarter_start + 0].reports[p].taxes);
+ ImGui::TableSetColumnIndex(2); ImGui::Text("%.2f", statement->quarters[quarter_start + 1].reports[p].taxes);
+ ImGui::TableSetColumnIndex(3); ImGui::Text("%.2f", statement->quarters[quarter_start + 2].reports[p].taxes);
+ ImGui::TableSetColumnIndex(4); ImGui::Text("%.2f", statement->quarters[quarter_start + 3].reports[p].taxes);
+
+ ImGui::TableNextRow();
+ ImGui::TableSetColumnIndex(0); ImGui::TextUnformatted(" Expenses");
+ ImGui::TableSetColumnIndex(1); ImGui::Text("%.2f", statement->quarters[quarter_start + 0].reports[p].expenses_total);
+ ImGui::TableSetColumnIndex(2); ImGui::Text("%.2f", statement->quarters[quarter_start + 1].reports[p].expenses_total);
+ ImGui::TableSetColumnIndex(3); ImGui::Text("%.2f", statement->quarters[quarter_start + 2].reports[p].expenses_total);
+ ImGui::TableSetColumnIndex(4); ImGui::Text("%.2f", statement->quarters[quarter_start + 3].reports[p].expenses_total);
+
+ for (u32 e = 0; e < report->expense_count; e++)
+ {
+ ImGui::TableNextRow();
+ project_expense* expense = &report->expenses[e];
+ ImGui::TableSetColumnIndex(0); ImGui::Text(" %s", localize(expense->description));
+ ImGui::TableSetColumnIndex(1); ImGui::Text("%.2f", statement->quarters[quarter_start + 0].reports[p].expenses[e].total);
+ ImGui::TableSetColumnIndex(2); ImGui::Text("%.2f", statement->quarters[quarter_start + 1].reports[p].expenses[e].total);
+ ImGui::TableSetColumnIndex(3); ImGui::Text("%.2f", statement->quarters[quarter_start + 2].reports[p].expenses[e].total);
+ ImGui::TableSetColumnIndex(4); ImGui::Text("%.2f", statement->quarters[quarter_start + 3].reports[p].expenses[e].total);
+
+
+ }
+ }
+
+ /*
+ for (int i = 0; i < 1; i++)
+ {
+ quarterly_report quarter = statement->quarters[quarter_start + quarter_index];
+
+ for (u32 p = 0; p < quarter.report_count; p++)
+ {
+ ImGui::TableNextRow();
+ project_report* report = &quarter.reports[p];
+ ImGui::TableSetColumnIndex(0); ImGui::TextUnformatted(report->description);
+ ImGui::TableSetColumnIndex(1); ImGui::Text("");
+ ImGui::TableSetColumnIndex(2); ImGui::Text("");
+ ImGui::TableSetColumnIndex(3); ImGui::Text("");
+ ImGui::TableSetColumnIndex(4); ImGui::Text("");
+
+
+ // for (u32 e = 0; e < report->expense_count; e++)
+ // {
+ // project_expense* expense = &report->expenses[e];
+ // ImGui::TableNextColumn(); ImGui::TextUnformatted(expense->description);
+ // }
+ }
+ }*/
+
+ // // Rows
+ // for (int i = 0; i < results_count; i++)
+ // {
+ // ImGui::TableNextRow();
+ // ImGui::TableSetColumnIndex(0); ImGui::TextUnformatted(results[i].quarter);
+ // ImGui::TableSetColumnIndex(1); ImGui::TextUnformatted(results[i].revenue);
+ // ImGui::TableSetColumnIndex(2); ImGui::TextUnformatted(results[i].profit);
+ // ImGui::TableSetColumnIndex(3); ImGui::TextUnformatted(results[i].expenses);
+ // }
+
+ ImGui::EndTable();
+ }
+} \ No newline at end of file
diff --git a/src/ui/ui_expenses.cpp b/src/ui/ui_expenses.cpp
index 6d1b36c..ab47e26 100644
--- a/src/ui/ui_expenses.cpp
+++ b/src/ui/ui_expenses.cpp
@@ -343,7 +343,7 @@ static void ui_draw_expenses_list()
invoice invoice_list[items_per_page];
u32 invoice_count = administration_invoice_get_partial_list_incomming(current_page, items_per_page, invoice_list);
- u32 total_invoice_count = administation_invoice_get_incomming_count();
+ u32 total_invoice_count = administration_invoice_get_incomming_count();
s32 max_page = (total_invoice_count + items_per_page - 1) / items_per_page;
if (max_page == 0) max_page = 1;
diff --git a/src/ui/ui_invoices.cpp b/src/ui/ui_invoices.cpp
index 8cdf959..9d6f8a2 100644
--- a/src/ui/ui_invoices.cpp
+++ b/src/ui/ui_invoices.cpp
@@ -18,14 +18,17 @@ static invoice selected_for_removal = {0};
static country_tax_bracket* tax_bracket_list_buffer = 0;
static billing_item* invoice_items_buffer = 0;
+static project* project_list_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_project_selector(char* project_id, project* buffer);
void ui_destroy_invoices()
{
free(tax_bracket_list_buffer);
free(invoice_items_buffer);
+ free(project_list_buffer);
}
void ui_setup_invoices()
@@ -38,6 +41,9 @@ void ui_setup_invoices()
u32 invoice_items_count = MAX_BILLING_ITEMS;
invoice_items_buffer = (billing_item*)malloc(sizeof(billing_item) * invoice_items_count);
+
+ u32 project_count = administration_project_count();
+ project_list_buffer = (project*) malloc(sizeof(project) * project_count);
}
void draw_tax_bracket_selector(char* tax_bracket_id, country_tax_bracket* buffer, char* country_code)
@@ -328,6 +334,9 @@ static void draw_invoice_form(invoice* buffer, bool viewing_only = false)
}
ImGui::Separator();
+ // 9. Project selection
+ draw_project_selector(buffer->project_id, project_list_buffer);
+
ImGui::Spacing();
ImGui::Spacing();
ImGui::Spacing();
@@ -364,7 +373,7 @@ static void ui_draw_invoices_list()
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 = administation_invoice_get_outgoing_count();
+ 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;
diff --git a/src/ui/ui_main.cpp b/src/ui/ui_main.cpp
index 33747f1..9ff965d 100644
--- a/src/ui/ui_main.cpp
+++ b/src/ui/ui_main.cpp
@@ -21,7 +21,7 @@ void (*drawcalls[dashboard_view_state::END])(void) = {
ui_draw_invoices,
ui_draw_expenses,
ui_draw_contacts,
- 0,
+ ui_draw_earnings,
0,
ui_draw_projects,
ui_draw_settings,
@@ -31,7 +31,7 @@ void (*setupcalls[dashboard_view_state::END])(void) = {
ui_setup_invoices,
ui_setup_expenses,
ui_setup_contacts,
- 0,
+ ui_setup_earnings,
0,
ui_setup_projects,
ui_setup_settings,
@@ -41,7 +41,7 @@ void (*destroycalls[dashboard_view_state::END])(void) = {
ui_destroy_invoices,
ui_destroy_expenses,
0,
- 0,
+ ui_destroy_earnings,
0,
0,
ui_destroy_settings,
@@ -94,7 +94,7 @@ void ui_draw_main()
if (ImGui::Button(localize("nav.expenses"), ImVec2(buttonWidth, 24))) set_dashboard_state(dashboard_view_state::EXPENSES);
if (ImGui::Button(localize("nav.contacts"), ImVec2(buttonWidth, 24))) set_dashboard_state(dashboard_view_state::CONTACTS);
- static bool reports_opened = false;
+ static bool reports_opened = true;
if (ImGui::Button(localize("nav.reports"), ImVec2(buttonWidth, 24))) reports_opened = !reports_opened;
if (reports_opened)
{