summaryrefslogtreecommitdiff
path: root/src/administration.cpp
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 /src/administration.cpp
parent17e035839a19a8b10d329c28ccaa44ff608d3a33 (diff)
income statement generation
Diffstat (limited to 'src/administration.cpp')
-rw-r--r--src/administration.cpp266
1 files changed, 247 insertions, 19 deletions
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;
}