summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NOTES.md18
-rw-r--r--include/administration.hpp76
-rw-r--r--run.bat2
-rw-r--r--src/administration.cpp132
-rw-r--r--src/locales.cpp2
-rw-r--r--src/locales/en.cpp16
-rw-r--r--src/ui/ui_contacts.cpp13
-rw-r--r--src/ui/ui_projects.cpp2
-rw-r--r--src/ui/ui_settings.cpp113
9 files changed, 315 insertions, 59 deletions
diff --git a/NOTES.md b/NOTES.md
index 8cb6521..9c1b419 100644
--- a/NOTES.md
+++ b/NOTES.md
@@ -77,9 +77,8 @@ administration.money/
| expires at `required` | Date when invoice expires |
| document `required` | Document associated with invoice, stored as filename |
| billing items `required` `list` | List of billed items |
-| project `required` | Project, stored as reference `P/[id]` |
-| cost center id `required` | Cost center, stored as reference `E/[id]` (incomming invoices only) |
-| cost unit id `required` | Cost unit, stored as reference `U/[id]` (incomming invoices only) |
+| project | Project, stored as reference `P/[id]` |
+| cost center id | Cost center, stored as reference `E/[id]` (incomming invoices only) |
| total `required` | Total amount billed |
| tax `required` | Total tax billed |
| net `required` | Total amount excl. tax |
@@ -100,8 +99,9 @@ administration.money/
| description `required` | Description of billed item |
| tax bracket `required` | Tax bracket (e.g. 0%, 21%, exempt, reversed) |
| tax section `required` | Tax section (e.g. 1a upto 4b in The Netherlands) |
-| amount `required` | Amount of items |
-| net per item `required` | Price per item excl. tax |
+| amount `required` | Amount of items, or a percentage |
+| amount is percentage `required` | Amount of items, or a percentage |
+| net per item `required` | is amount a percentage |
| net `required` | Total amount excl. tax |
| discount `required` | Total discount |
| tax `required` | Total tax billed |
@@ -121,11 +121,5 @@ administration.money/
| Cost center||
|-|-|
| id `auto` | reference id `E/[id]` |
-| code `required` | Code of cost center |
-| description `required` | Description of cost center |
-
-| Cost unit||
-|-|-|
-| id `auto` | reference id `U/[id]` |
-| code `required` | Code of cost center |
+| code `required` | Internal code of cost center |
| description `required` | Description of cost center | \ No newline at end of file
diff --git a/include/administration.hpp b/include/administration.hpp
index aca0131..196e674 100644
--- a/include/administration.hpp
+++ b/include/administration.hpp
@@ -6,12 +6,19 @@
typedef struct
{
char country_code[3];
- float rate;
+ float rate; // 0-100
char description[32];
} country_tax_bracket;
typedef struct
{
+ char id[16];
+ char code[5];
+ char description[64];
+} cost_center;
+
+typedef struct
+{
char address1[128];
char address2[128];
char country[128];
@@ -31,9 +38,9 @@ typedef struct
typedef enum
{
- RUNNING,
- PAUSED,
- CANCELLED,
+ PROJECT_RUNNING,
+ PROJECT_PAUSED,
+ PROJECT_CANCELLED,
} project_state;
typedef struct
@@ -61,7 +68,8 @@ typedef struct
char description[128];
country_tax_bracket tax_bracket;
char tax_section[16];
- s32 amount;
+ float amount;
+ bool amount_is_percentage;
s32 net_per_item;
s32 net;
s32 discount;
@@ -85,7 +93,6 @@ typedef struct
list_t billing_items;
char project_id[16];
char cost_center_id[16];
- char cost_unit_id[16];
s32 total;
s32 tax;
s32 net;
@@ -109,34 +116,39 @@ typedef struct
list_t projects;
list_t invoices;
list_t tax_brackets;
- char ai_service[16];
- char ai_key[32];
- char email_service[16];
- char email_key[32];
+ list_t cost_centers;
} administration;
-void administration_create();
-void administration_destroy();
+void administration_create();
+void administration_destroy();
-char* administration_get_file_path();
-s32 administration_create_id();
+char* administration_get_file_path();
+s32 administration_create_id();
contact administration_get_company_info();
-void administration_set_company_info(contact data);
-
-bool administration_remove_contact(contact data);
-bool administration_create_contact(contact data);
-bool administration_update_contact(contact data);
-u32 administration_get_contact_count();
-u32 administration_get_contacts(u32 page_index, u32 page_size, contact* buffer);
-
-void administration_cancel_project(project data);
-bool administration_remove_project(project data);
-bool administration_create_project(project data);
-bool administration_update_project(project data);
-char* administration_project_get_status_string(project data);
-u32 administration_get_project_count();
-u32 administration_get_projects(u32 page_index, u32 page_size, project* buffer);
-
-u32 administration_get_tax_bracket_count();
-u32 administration_get_tax_brackets(country_tax_bracket* buffer); \ No newline at end of file
+void administration_set_company_info(contact data);
+
+bool administration_can_contact_be_deleted(contact data);
+bool administration_remove_contact(contact data);
+bool administration_create_contact(contact data);
+bool administration_update_contact(contact data);
+u32 administration_get_contact_count();
+u32 administration_get_contacts(u32 page_index, u32 page_size, contact* buffer);
+
+void administration_cancel_project(project data);
+bool administration_remove_project(project data);
+bool administration_create_project(project data);
+bool administration_update_project(project data);
+char* administration_project_get_status_string(project data);
+u32 administration_get_project_count();
+u32 administration_get_projects(u32 page_index, u32 page_size, project* buffer);
+
+u32 administration_get_tax_bracket_count();
+u32 administration_get_tax_brackets(country_tax_bracket* buffer);
+
+u32 administration_get_cost_center_count();
+u32 administration_get_cost_centers(cost_center* buffer);
+bool administration_verify_cost_center_code(char* code);
+bool administration_verify_cost_center_description(char* text);
+bool administration_add_cost_center(cost_center data);
+bool administration_update_cost_center(cost_center data); \ No newline at end of file
diff --git a/run.bat b/run.bat
index eb9e517..7197b94 100644
--- a/run.bat
+++ b/run.bat
@@ -3,8 +3,6 @@ REM Find the latest Visual Studio installation path
for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -property installationPath`) do (
set VSPath=%%i
)
-
-REM Check if found
if not defined VSPath (
echo Visual Studio not found.
exit /b 1
diff --git a/src/administration.cpp b/src/administration.cpp
index 0353276..e41942e 100644
--- a/src/administration.cpp
+++ b/src/administration.cpp
@@ -2,6 +2,7 @@
#include <stdlib.h>
#include <assert.h>
#include <time.h>
+#include <stdio.h>
#include "strops.hpp"
#include "administration.hpp"
@@ -156,14 +157,41 @@ static void administration_create_default_tax_brackets()
ADD_BRACKET("SE", 12.0f, "tax.reduced");
}
+static void administration_create_default_cost_centers()
+{
+ #define ADD_COSTCENTER(_description, _code)\
+ {\
+ cost_center* tb = (cost_center*)malloc(sizeof(cost_center));\
+ snprintf(tb->id, sizeof(tb->id), "E/%d", administration_create_id());\
+ memcpy(tb->description, _description, sizeof(tb->description));\
+ memcpy(tb->code, _code, sizeof(tb->code));\
+ list_append(&g_administration.cost_centers, tb);\
+ g_administration.next_id++;\
+ }
+
+ ADD_COSTCENTER("costcenter.general_expenses", "GENE");
+ ADD_COSTCENTER("costcenter.administration_general_management", "ADMN");
+ ADD_COSTCENTER("costcenter.finance_accounting", "FINC");
+ ADD_COSTCENTER("costcenter.information_technology", "INFO");
+ ADD_COSTCENTER("costcenter.sales_marketing", "SALE");
+ ADD_COSTCENTER("costcenter.operations_production", "OPER");
+ ADD_COSTCENTER("costcenter.supply_chain_logistics", "SUPP");
+ ADD_COSTCENTER("costcenter.research_development", "RDEV");
+ ADD_COSTCENTER("costcenter.facilities_maintenance", "FACL");
+ ADD_COSTCENTER("costcenter.customer_service_support", "CUST");
+ ADD_COSTCENTER("costcenter.other_specialized", "OTHR");
+}
+
void administration_create()
{
list_init(&g_administration.contacts);
list_init(&g_administration.projects);
list_init(&g_administration.tax_brackets);
+ list_init(&g_administration.cost_centers);
strops_copy(g_administration.path, "", sizeof(g_administration.path));
administration_create_default_tax_brackets();
+ administration_create_default_cost_centers();
}
void administration_destroy()
@@ -171,6 +199,7 @@ void administration_destroy()
list_destroy(&g_administration.contacts);
list_destroy(&g_administration.projects);
list_destroy(&g_administration.tax_brackets);
+ list_destroy(&g_administration.cost_centers);
}
bool administration_create_contact(contact data)
@@ -184,6 +213,13 @@ bool administration_create_contact(contact data)
return true;
}
+bool administration_can_contact_be_deleted(contact data)
+{
+ (void)data;
+ // TODO
+ return true;
+}
+
bool administration_update_contact(contact data)
{
list_iterator_start(&g_administration.contacts);
@@ -285,7 +321,7 @@ u32 administration_get_projects(u32 page_index, u32 page_size, project* buffer)
void administration_cancel_project(project data)
{
data.end_date = time(NULL);
- data.state = project_state::CANCELLED;
+ data.state = project_state::PROJECT_CANCELLED;
administration_update_project(data);
}
@@ -293,9 +329,9 @@ char* administration_project_get_status_string(project data)
{
switch(data.state)
{
- case project_state::RUNNING: return "project.state.running";
- case project_state::PAUSED: return "project.state.paused";
- case project_state::CANCELLED: return "project.state.cancelled";
+ case project_state::PROJECT_RUNNING: return "project.state.running";
+ case project_state::PROJECT_PAUSED: return "project.state.paused";
+ case project_state::PROJECT_CANCELLED: return "project.state.cancelled";
default: assert(0); break;
}
return "";
@@ -303,7 +339,7 @@ char* administration_project_get_status_string(project data)
bool administration_create_project(project data)
{
- data.state = project_state::RUNNING;
+ data.state = project_state::PROJECT_RUNNING;
data.start_date = time(NULL);
data.end_date = 0;
project* new_project = (project*)malloc(sizeof(project));
@@ -378,4 +414,90 @@ u32 administration_get_tax_brackets(country_tax_bracket* buffer)
list_iterator_stop(&g_administration.tax_brackets);
return write_cursor;
+}
+
+u32 administration_get_cost_center_count()
+{
+ return list_size(&g_administration.cost_centers);
+}
+
+u32 administration_get_cost_centers(cost_center* buffer)
+{
+ assert(buffer);
+
+ u32 write_cursor = 0;
+
+ 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);
+ buffer[write_cursor++] = c;
+ }
+ list_iterator_stop(&g_administration.cost_centers);
+
+ return write_cursor;
+}
+
+static bool administration_get_cost_center_by_code(char* code, cost_center* buffer)
+{
+ bool result = false;
+ 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);
+ *buffer = c;
+ if (strcmp(code, c.code) == 0) {
+ result = true;
+ break;
+ }
+ }
+ list_iterator_stop(&g_administration.cost_centers);
+
+ return result;
+}
+
+bool administration_verify_cost_center_description(char* text)
+{
+ return strlen(text) != 0;
+}
+
+bool administration_verify_cost_center_code(char* code)
+{
+ if (strlen(code) == 0) return false;
+
+ cost_center cost_center;
+ bool found = administration_get_cost_center_by_code(code, &cost_center);
+ return !found;
+}
+
+bool administration_add_cost_center(cost_center data)
+{
+ cost_center cs;
+ bool found = administration_get_cost_center_by_code(data.code, &cs);
+ if (found) return false;
+
+ cost_center* tb = (cost_center*)malloc(sizeof(cost_center));
+ snprintf(tb->id, sizeof(tb->id), "E/%d", administration_create_id());
+ memcpy(tb->description, data.description, sizeof(tb->description));
+ memcpy(tb->code, data.code, sizeof(tb->code));
+ list_append(&g_administration.cost_centers, tb);
+
+ g_administration.next_id++;
+
+ return true;
+}
+
+bool administration_update_cost_center(cost_center data)
+{
+ 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, data.id) == 0) {
+ memcpy(c, &data, sizeof(data));
+ list_iterator_stop(&g_administration.cost_centers);
+ return true;
+ }
+ }
+ list_iterator_stop(&g_administration.cost_centers);
+
+ return false;
} \ No newline at end of file
diff --git a/src/locales.cpp b/src/locales.cpp
index 363d3c9..89c8499 100644
--- a/src/locales.cpp
+++ b/src/locales.cpp
@@ -28,5 +28,5 @@ const char* localize(const char* key)
return g_locale.entries[i].value;
}
}
- return "[!MISSING!]";
+ return key;
} \ No newline at end of file
diff --git a/src/locales/en.cpp b/src/locales/en.cpp
index 3c208ef..3c6bafd 100644
--- a/src/locales/en.cpp
+++ b/src/locales/en.cpp
@@ -19,6 +19,19 @@ locale_entry en_locales[] = {
{"form.confirmCancelProject", "Are you sure you want to cancel this Project?"},
{"form.required", "required"},
+ // Default cost centers
+ {"costcenter.general_expenses", "General Expenses"},
+ {"costcenter.administration_general_management", "Administration & General Management"},
+ {"costcenter.finance_accounting", "Finance & Accounting"},
+ {"costcenter.information_technology", "Information Technology (IT)"},
+ {"costcenter.sales_marketing", "Sales & Marketing"},
+ {"costcenter.operations_production", "Operations / Production"},
+ {"costcenter.supply_chain_logistics", "Supply Chain & Logistics"},
+ {"costcenter.research_development", "Research & Development"},
+ {"costcenter.facilities_maintenance", "Facilities & Maintenance"},
+ {"costcenter.customer_service_support", "Customer Service & Support"},
+ {"costcenter.other_specialized", "Other / Specialized"},
+
// Tax strings.
{"tax.reverse_charge", "Reverse tax"},
{"tax.exempt", "Tax exempt"},
@@ -93,8 +106,11 @@ locale_entry en_locales[] = {
// Settings strings.
{"settings.table.company", "Company"},
{"settings.table.vatrates", "VAT Rates"},
+ {"settings.table.costcenters", "Cost Centers"},
{"settings.vat.table.country", "Country"},
{"settings.vat.table.rates", "Rates"},
+ {"settings.costcenters.table.code", "Code"},
+ {"settings.costcenters.table.description", "Description"},
};
const int en_locale_count = sizeof(en_locales) / sizeof(en_locales[0]); \ No newline at end of file
diff --git a/src/ui/ui_contacts.cpp b/src/ui/ui_contacts.cpp
index 24f45f7..e00fb78 100644
--- a/src/ui/ui_contacts.cpp
+++ b/src/ui/ui_contacts.cpp
@@ -196,12 +196,13 @@ static void draw_contact_list()
}
ImGui::SameLine();
-
- // TODO check to make sure no invoices are connected to this contact.
- snprintf(btn_name, sizeof(btn_name), "%s##%d", localize("form.delete"), i);
- if (ImGui::Button(btn_name)) {
- selected_for_removal = c;
- ImGui::OpenPopup("ConfirmDeletePopup");
+ if (administration_can_contact_be_deleted(c))
+ {
+ snprintf(btn_name, sizeof(btn_name), "%s##%d", localize("form.delete"), i);
+ if (ImGui::Button(btn_name)) {
+ selected_for_removal = c;
+ ImGui::OpenPopup("ConfirmDeletePopup");
+ }
}
}
diff --git a/src/ui/ui_projects.cpp b/src/ui/ui_projects.cpp
index a831f60..377155e 100644
--- a/src/ui/ui_projects.cpp
+++ b/src/ui/ui_projects.cpp
@@ -130,7 +130,7 @@ static void draw_project_list()
current_view_state = view_state::VIEW;
}
- if (c.state == project_state::RUNNING)
+ if (c.state == project_state::PROJECT_RUNNING)
{
ImGui::SameLine();
snprintf(btn_name, sizeof(btn_name), "%s##%d", localize("form.change"), i);
diff --git a/src/ui/ui_settings.cpp b/src/ui/ui_settings.cpp
index 97e0503..7854be3 100644
--- a/src/ui/ui_settings.cpp
+++ b/src/ui/ui_settings.cpp
@@ -10,9 +10,19 @@
extern bool draw_contact_form(contact* buffer, bool back_button_enabled = true, bool viewing_only = false);
static contact company_info;
+
country_tax_bracket* tax_brackets;
u32 tax_bracket_count;
+cost_center* cost_centers;
+u32 cost_center_count;
+
+void ui_destroy_settings()
+{
+ free(tax_brackets);
+ free(cost_centers);
+}
+
void ui_setup_settings()
{
company_info = administration_get_company_info();
@@ -20,6 +30,10 @@ void ui_setup_settings()
tax_bracket_count = administration_get_tax_bracket_count();
tax_brackets = (country_tax_bracket*)malloc(tax_bracket_count * sizeof(country_tax_bracket));
administration_get_tax_brackets(tax_brackets);
+
+ cost_center_count = administration_get_cost_center_count();
+ cost_centers = (cost_center*)malloc(cost_center_count * sizeof(cost_center));
+ administration_get_cost_centers(cost_centers);
}
static void ui_draw_vat_rates()
@@ -66,6 +80,100 @@ static void ui_draw_vat_rates()
}
}
+static void ui_draw_cost_centers()
+{
+ static bool is_adding_item = false;
+ static cost_center new_cost_center;
+
+ static bool is_editing_item = false;
+ static u32 editing_item_index = 0;
+
+ if (ImGui::BeginTable("TableCostCenters", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
+
+ ImGui::TableSetupColumn(localize("settings.costcenters.table.code"), ImGuiTableColumnFlags_WidthFixed, 140);
+ ImGui::TableSetupColumn(localize("settings.costcenters.table.description"));
+
+ for (u32 i = 0; i < cost_center_count; i++) {
+ cost_center c = cost_centers[i];
+
+ ImGui::TableNextRow();
+ ImGui::TableSetColumnIndex(0); ImGui::Text(c.code);
+ ImGui::TableSetColumnIndex(1);
+
+ if (is_editing_item && editing_item_index == i)
+ {
+ ImGui::InputText("##Description", new_cost_center.description, IM_ARRAYSIZE(new_cost_center.description));
+
+ bool is_desc_valid = administration_verify_cost_center_description(new_cost_center.description);
+
+ if (!is_desc_valid) ImGui::BeginDisabled();
+ ImGui::SameLine();
+ if (ImGui::Button(localize("form.save"))) {
+ is_editing_item = false;
+ is_adding_item = false;
+
+ administration_update_cost_center(new_cost_center);
+
+ ui_destroy_settings();
+ ui_setup_settings();
+ }
+ if (!is_desc_valid) ImGui::EndDisabled();
+ }
+ else
+ {
+ ImGui::Text(localize(c.description));
+ }
+
+ if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
+ {
+ is_editing_item = true;
+ is_adding_item = false;
+ editing_item_index = i;
+ new_cost_center = c;
+ }
+ }
+
+ if (is_adding_item)
+ {
+ ImGui::TableNextRow();
+
+ bool is_code_valid = administration_verify_cost_center_code(new_cost_center.code);
+ if (!is_code_valid) ImGui::PushStyleColor(ImGuiCol_FrameBg, IM_COL32(255, 0, 0, 64));
+ ImGui::TableSetColumnIndex(0); ImGui::InputText("##Code", new_cost_center.code, IM_ARRAYSIZE(new_cost_center.code));
+ if (!is_code_valid) ImGui::PopStyleColor();
+
+ bool is_desc_valid = administration_verify_cost_center_description(new_cost_center.description);
+ if (!is_desc_valid) ImGui::PushStyleColor(ImGuiCol_FrameBg, IM_COL32(255, 0, 0, 64));
+ ImGui::TableSetColumnIndex(1); ImGui::InputText("##Description", new_cost_center.description, IM_ARRAYSIZE(new_cost_center.description));
+ if (!is_desc_valid) ImGui::PopStyleColor();
+
+ bool can_save = is_code_valid && is_desc_valid;
+
+ if (!can_save) ImGui::BeginDisabled();
+ ImGui::SameLine();
+ if (ImGui::Button(localize("form.create")))
+ {
+ is_adding_item = false;
+ is_editing_item = false;
+ administration_add_cost_center(new_cost_center);
+
+ ui_destroy_settings();
+ ui_setup_settings();
+ }
+ if (!can_save) ImGui::EndDisabled();
+ }
+
+ ImGui::EndTable();
+ }
+
+ if (!is_adding_item && ImGui::Button(localize("form.create")))
+ {
+ is_adding_item = true;
+ is_editing_item = false;
+ memset(&new_cost_center, 0, sizeof(new_cost_center));
+ }
+}
+
void ui_draw_settings()
{
if (ImGui::BeginTabBar("SettingsTabBar"))
@@ -84,6 +192,11 @@ void ui_draw_settings()
ui_draw_vat_rates();
ImGui::EndTabItem();
}
+ if (ImGui::BeginTabItem(localize("settings.table.costcenters")))
+ {
+ ui_draw_cost_centers();
+ ImGui::EndTabItem();
+ }
ImGui::EndTabBar();
}
} \ No newline at end of file