summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAldrik Ramaekers <aldrikboy@gmail.com>2025-09-16 19:08:05 +0200
committerAldrik Ramaekers <aldrikboy@gmail.com>2025-09-16 19:08:05 +0200
commit9091a313b2bb2dafd2919fddb989745ddafa15d9 (patch)
treed3448a0e103fbbe0e14457b07e2e0a77b2f0f6da
parentbb55b2341c53174ed53a70855ef63bb20c8dd814 (diff)
error reporting for administration crud functions
-rw-r--r--docs/CHANGES.rst5
-rw-r--r--include/administration.hpp43
-rw-r--r--run.bat2
-rw-r--r--src/administration.cpp105
-rw-r--r--tests/administration_rw_tests.cpp40
-rw-r--r--tests/administration_validation_tests.cpp91
-rw-r--r--tests/main.cpp12
7 files changed, 230 insertions, 68 deletions
diff --git a/docs/CHANGES.rst b/docs/CHANGES.rst
index 90d8577..cb3c923 100644
--- a/docs/CHANGES.rst
+++ b/docs/CHANGES.rst
@@ -1,8 +1,8 @@
.. _changes:
TODO:
+- validate billing items in invoice add/update
- project start and end date should be stored as YYYY-MM-DD
-- write tests for writer and reader
- Send invoice by email
- create invoice from PDF file
- create invoice from image of receipt
@@ -12,13 +12,10 @@ TODO:
- View invoice history for contacts
- View invoice history for projects
- Create quarterly tax reports for NL.
-- 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.
- validate data within administration on save to make sure it is valid for transmissions. (e.g. rules of https://docs.peppol.eu/poacc/billing/3.0/syntax/ubl-invoice/cac-AccountingSupplierParty/cac-Party/cbc-EndpointID/)
- View local business number / vat number naming on contact form. e.g. when country is austria: "Österreichische Umsatzsteuer-Identifikationsnummer"
-- add allowances to invoice items
-- add accounting cost to invoice
- outgoing invoices should always have default currency
- if an incomming invoice currency != default currency, final amount should be manually set as the total is always represented in the default currency.
- ICP reports
diff --git a/include/administration.hpp b/include/administration.hpp
index e74b49c..6123df4 100644
--- a/include/administration.hpp
+++ b/include/administration.hpp
@@ -332,6 +332,29 @@ typedef struct
list_t invoices;
} administration;
+// Add/Update result codes.
+typedef enum {
+
+ A_ERR_GENERIC = 0,
+ A_ERR_SUCCESS = 1ULL << 0,
+ A_ERR_NOT_FOUND = 1ULL << 1,
+
+ A_ERR_MISSING_DESCRIPTION = 1ULL << 2,
+ A_ERR_CODE_EXISTS = 1ULL << 3,
+ A_ERR_MISSING_BILLING_ITEMS = 1ULL << 4,
+ A_ERR_INVALID_ADDRESSEE = 1ULL << 5,
+ A_ERR_INVALID_CUSTOMER = 1ULL << 6,
+ A_ERR_INVALID_SUPPLIER = 1ULL << 7,
+ A_ERR_MISSING_NAME = 1ULL << 8,
+ A_ERR_MISSING_CITY = 1ULL << 9,
+ A_ERR_MISSING_POSTAL = 1ULL << 10,
+ A_ERR_MISSING_ADDRESS1 = 1ULL << 11,
+ A_ERR_MISSING_COUNTRYCODE = 1ULL << 12,
+ A_ERR_MISSING_TAXID = 1ULL << 13,
+ A_ERR_MISSING_BUSINESSID = 1ULL << 14,
+ A_ERR_MAX_ITEMS_REACHED = 1ULL << 15,
+} a_err;
+
// Setup functions.
// =======================
void administration_create_empty(char* save_file);
@@ -389,28 +412,28 @@ u32 administration_contact_get_all(contact* buffer);
// =======================
u32 administration_project_count();
project administration_project_create_empty();
-bool administration_project_import(project data);
-bool administration_project_add(project data);
-bool administration_project_update(project data);
-bool administration_project_remove(project data);
+a_err administration_project_import(project data);
+a_err administration_project_add(project data);
+a_err administration_project_update(project data);
+a_err administration_project_remove(project data);
void administration_project_cancel(project data);
-bool administration_project_is_valid(project data);
+a_err 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);
+a_err administration_project_get_by_id(project* buffer, char* id);
// Tax bracket functions.
// =======================
u32 administration_tax_bracket_count();
country_tax_bracket administration_tax_bracket_create_empty();
-bool administration_tax_bracket_import(country_tax_bracket data);
-bool administration_tax_bracket_add(country_tax_bracket data);
-bool administration_tax_bracket_update(country_tax_bracket data);
+a_err administration_tax_bracket_import(country_tax_bracket data);
+a_err administration_tax_bracket_add(country_tax_bracket data);
+a_err administration_tax_bracket_update(country_tax_bracket data);
-bool administration_tax_bracket_get_by_id(country_tax_bracket* buffer, char* id);
+a_err administration_tax_bracket_get_by_id(country_tax_bracket* buffer, char* id);
u32 administration_tax_bracket_get_all(country_tax_bracket* buffer);
u32 administration_tax_bracket_get_by_country(country_tax_bracket* buffer, char* country_code);
diff --git a/run.bat b/run.bat
index 1087ffc..ae6e08d 100644
--- a/run.bat
+++ b/run.bat
@@ -31,7 +31,7 @@ set LIB_SOURCES=libs\imgui-1.92.1\backends\imgui_impl_dx11.cpp^
@set FLAGS=/nologo /Ob0 /MD /Oy- /Zi /FS /W4 /EHsc /utf-8
@set INCLUDE_DIRS=/I"libs/imgui-1.92.1" /I"libs/imgui-1.92.1/backends" /I"/" /I"libs/timer_lib" /I"libs/greatest" /I"libs/simclist-1.5" /I"libs/tinyfiledialogs" /I"libs/zip/src" /I"libs/xml.c/src" /I"libs/" /Iinclude
-if "%1"=="-t" @set SOURCES= tests\*.cpp src\administration.cpp src\administration_writer.cpp src\administration_reader.cpp src\strops.cpp src\log.cpp src\locales.cpp src\locales\*.cpp
+if "%1"=="-t" @set SOURCES= tests\main.cpp src\administration.cpp src\administration_writer.cpp src\administration_reader.cpp src\strops.cpp src\log.cpp src\locales.cpp src\locales\*.cpp
if "%1"=="-t" @set OUT_EXE=accounting_tests
cl %FLAGS% %INCLUDE_DIRS% %SOURCES% %LIB_SOURCES% /Fe%OUT_DIR%/%OUT_EXE%.exe /Fd%OUT_DIR%/vc140.pdb /Fo%OUT_DIR%/ /link %LIBS%
diff --git a/src/administration.cpp b/src/administration.cpp
index 90fc60a..955e377 100644
--- a/src/administration.cpp
+++ b/src/administration.cpp
@@ -1043,7 +1043,7 @@ u32 administration_project_count()
return list_size(&g_administration.projects);
}
-bool administration_project_get_by_id(project* buffer, char* id)
+a_err administration_project_get_by_id(project* buffer, char* id)
{
assert(buffer);
@@ -1054,12 +1054,12 @@ bool administration_project_get_by_id(project* buffer, char* id)
{
*buffer = c;
list_iterator_stop(&g_administration.projects);
- return true;
+ return a_err::A_ERR_SUCCESS;
}
}
list_iterator_stop(&g_administration.projects);
- return false;
+ return a_err::A_ERR_NOT_FOUND;
}
u32 administration_project_get_all(project* buffer)
@@ -1105,9 +1105,10 @@ void administration_project_cancel(project data)
administration_project_update(data);
}
-bool administration_project_is_valid(project data)
+a_err administration_project_is_valid(project data)
{
- return strlen(data.description) > 0;
+ if (strlen(data.description) == 0) return a_err::A_ERR_MISSING_DESCRIPTION;
+ return a_err::A_ERR_SUCCESS;
}
char* administration_project_get_status_string(project data)
@@ -1122,33 +1123,48 @@ char* administration_project_get_status_string(project data)
return "";
}
-bool administration_project_import(project data)
+a_err administration_project_import(project data)
{
+ a_err result = administration_project_is_valid(data);
+ if (result != a_err::A_ERR_SUCCESS) return result;
+
project* new_project = (project*)malloc(sizeof(project));
+ if (!new_project) return a_err::A_ERR_GENERIC;
+
memcpy((void*)new_project, (void*)&data, sizeof(project));
- list_append(&g_administration.projects, new_project);
+ if (!list_append(&g_administration.projects, new_project)) {
+ return a_err::A_ERR_GENERIC;
+ }
- return true;
+ return a_err::A_ERR_SUCCESS;
}
-bool administration_project_add(project data)
+a_err administration_project_add(project data)
{
- if (!administration_project_is_valid(data)) return false;
+ a_err result = administration_project_is_valid(data);
+ if (result != a_err::A_ERR_SUCCESS) return result;
+
project* new_project = (project*)malloc(sizeof(project));
+ if (!new_project) return a_err::A_ERR_GENERIC;
+
memcpy((void*)new_project, (void*)&data, sizeof(project));
- list_append(&g_administration.projects, new_project);
+
+ if (!list_append(&g_administration.projects, new_project)) {
+ return a_err::A_ERR_GENERIC;
+ }
g_administration.next_id++;
if (project_changed_event_callback) project_changed_event_callback(new_project);
if (data_changed_event_callback) data_changed_event_callback();
- return true;
+ return a_err::A_ERR_SUCCESS;
}
-bool administration_project_update(project data)
+a_err administration_project_update(project data)
{
- if (!administration_project_is_valid(data)) return false;
+ a_err result = administration_project_is_valid(data);
+ if (result != a_err::A_ERR_SUCCESS) return result;
list_iterator_start(&g_administration.projects);
while (list_iterator_hasnext(&g_administration.projects)) {
@@ -1161,15 +1177,15 @@ bool administration_project_update(project data)
if (project_changed_event_callback) project_changed_event_callback(c);
if (data_changed_event_callback) data_changed_event_callback();
- return true;
+ return a_err::A_ERR_SUCCESS;
}
}
list_iterator_stop(&g_administration.projects);
- return false;
+ return a_err::A_ERR_NOT_FOUND;
}
-bool administration_project_remove(project data)
+a_err administration_project_remove(project data)
{
list_iterator_start(&g_administration.projects);
while (list_iterator_hasnext(&g_administration.projects)) {
@@ -1177,17 +1193,17 @@ bool administration_project_remove(project data)
if (strcmp(c->id, data.id) == 0) {
list_iterator_stop(&g_administration.projects);
- list_delete(&g_administration.projects, c);
+ if (list_delete(&g_administration.projects, c) != 0) return a_err::A_ERR_GENERIC;
if (data_deleted_event_callback) data_deleted_event_callback(c->id);
free(c);
- return true;
+ return a_err::A_ERR_SUCCESS;
}
}
list_iterator_stop(&g_administration.projects);
- return false;
+ return a_err::A_ERR_NOT_FOUND;
}
project administration_project_create_empty()
@@ -1213,7 +1229,7 @@ country_tax_bracket administration_tax_bracket_create_empty()
return result;
}
-static bool administration_get_tax_bracket_by_id(country_tax_bracket* buffer, char* id)
+a_err administration_tax_bracket_get_by_id(country_tax_bracket* buffer, char* id)
{
assert(buffer);
@@ -1224,18 +1240,12 @@ static bool administration_get_tax_bracket_by_id(country_tax_bracket* buffer, ch
{
*buffer = c;
list_iterator_stop(&g_administration.tax_brackets);
- return true;
+ return a_err::A_ERR_SUCCESS;
}
}
list_iterator_stop(&g_administration.tax_brackets);
- return false;
-}
-
-// TODO refactor
-bool administration_tax_bracket_get_by_id(country_tax_bracket* buffer, char* id)
-{
- return administration_get_tax_bracket_by_id(buffer, id);
+ return a_err::A_ERR_NOT_FOUND;
}
u32 administration_tax_bracket_count()
@@ -1243,24 +1253,30 @@ u32 administration_tax_bracket_count()
return list_size(&g_administration.tax_brackets);
}
-bool administration_tax_bracket_import(country_tax_bracket data)
+a_err administration_tax_bracket_import(country_tax_bracket data)
{
country_tax_bracket* tb = (country_tax_bracket*)malloc(sizeof(country_tax_bracket));
- memcpy((void*)tb, (void*)&data, sizeof(country_tax_bracket));
+ if (!tb) return a_err::A_ERR_GENERIC;
- list_append(&g_administration.tax_brackets, tb);
+ memcpy((void*)tb, (void*)&data, sizeof(country_tax_bracket));
+ if (!list_append(&g_administration.tax_brackets, tb)) {
+ return a_err::A_ERR_GENERIC;
+ }
list_attributes_comparator(&g_administration.tax_brackets, compare_tax_countries);
list_sort(&g_administration.tax_brackets, -1);
- return true;
+ return a_err::A_ERR_SUCCESS;
}
-bool administration_tax_bracket_add(country_tax_bracket data)
+a_err administration_tax_bracket_add(country_tax_bracket data)
{
country_tax_bracket* tb = (country_tax_bracket*)malloc(sizeof(country_tax_bracket));
- memcpy((void*)tb, (void*)&data, sizeof(country_tax_bracket));
+ if (!tb) return a_err::A_ERR_GENERIC;
- list_append(&g_administration.tax_brackets, tb);
+ memcpy((void*)tb, (void*)&data, sizeof(country_tax_bracket));
+ if (!list_append(&g_administration.tax_brackets, tb)) {
+ return a_err::A_ERR_GENERIC;
+ }
g_administration.next_id++;
@@ -1270,7 +1286,7 @@ bool administration_tax_bracket_add(country_tax_bracket data)
if (taxbracket_changed_event_callback) taxbracket_changed_event_callback(&data);
if (data_changed_event_callback) data_changed_event_callback();
- return true;
+ return a_err::A_ERR_SUCCESS;
}
u32 administration_tax_bracket_get_by_country(country_tax_bracket* buffer, char* country_code)
@@ -1306,7 +1322,7 @@ u32 administration_tax_bracket_get_all(country_tax_bracket* buffer)
return write_cursor;
}
-bool administration_tax_bracket_update(country_tax_bracket data)
+a_err administration_tax_bracket_update(country_tax_bracket data)
{
list_iterator_start(&g_administration.tax_brackets);
while (list_iterator_hasnext(&g_administration.tax_brackets)) {
@@ -1319,12 +1335,12 @@ bool administration_tax_bracket_update(country_tax_bracket data)
if (taxbracket_changed_event_callback) taxbracket_changed_event_callback(c);
if (data_changed_event_callback) data_changed_event_callback();
- return true;
+ return a_err::A_ERR_SUCCESS;
}
}
list_iterator_stop(&g_administration.tax_brackets);
- return false;
+ return a_err::A_ERR_NOT_FOUND;
}
// Cost center functions.
@@ -1394,6 +1410,7 @@ static bool administration_get_cost_center_by_code(char* code, cost_center* buff
return result;
}
+// TODO merge these 2 info cost_center_is_valid
bool administration_cost_center_verify_description(char* text)
{
return strlen(text) != 0;
@@ -1422,6 +1439,8 @@ bool administration_cost_center_add(cost_center data)
bool found = administration_get_cost_center_by_code(data.code, &cs);
if (found) return false;
+ if (!administration_cost_center_verify_description(data.description)) return false;
+
cost_center* tb = (cost_center*)malloc(sizeof(cost_center));
memcpy((void*)tb, (void*)&data, sizeof(cost_center));
list_append(&g_administration.cost_centers, tb);
@@ -1794,8 +1813,8 @@ u32 administration_invoice_get_tax_brackets(invoice* invoice, country_tax_bracke
billing_item c = *(billing_item *)list_iterator_next(&invoice->billing_items);
country_tax_bracket bracket;
- bool found = administration_get_tax_bracket_by_id(&bracket, c.tax_bracket_id);
- if (found) {
+ a_err found = administration_tax_bracket_get_by_id(&bracket, c.tax_bracket_id);
+ if (found == a_err::A_ERR_SUCCESS) {
bool exists = false;
for (u32 i = 0; i < write_cursor; i++) {
@@ -1895,7 +1914,7 @@ static void administration_recalculate_billing_item_totals(billing_item* item)
}
country_tax_bracket bracket;
- if (administration_get_tax_bracket_by_id(&bracket, item->tax_bracket_id))
+ if (administration_tax_bracket_get_by_id(&bracket, item->tax_bracket_id) == a_err::A_ERR_SUCCESS)
{
item->tax = item->net * (bracket.rate/100.0f);
}
diff --git a/tests/administration_rw_tests.cpp b/tests/administration_rw_tests.cpp
index 6d57596..8f38046 100644
--- a/tests/administration_rw_tests.cpp
+++ b/tests/administration_rw_tests.cpp
@@ -1,6 +1,7 @@
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
+#include <stdlib.h>
#include "greatest.h"
#include "timer.h"
@@ -10,7 +11,14 @@
#include "administration_reader.hpp"
#include "administration_writer.hpp"
-char* test_file_path = "C:\\Users\\aldri\\Desktop\\Vault\\Projects\\accounting\\build\\test.openbook";
+#ifdef _WIN32
+ #include <io.h> // for _unlink
+ #define unlink _unlink
+#else
+ #include <unistd.h> // for unlink
+#endif
+
+char* test_file_path = "build\\test.openbook";
static void setup_cb(void *data) {
(void)data;
@@ -145,6 +153,26 @@ TEST _administration_rw_contact(void)
PASS();
}
+TEST _administration_rw_info(void)
+{
+ administration_writer_create();
+
+ s32 next_id, next_sequence_number;
+ administration_create_empty(test_file_path);
+ {
+ next_id = administration_get_next_id();
+ next_sequence_number = administration_get_next_sequence_number();
+ }
+
+ administration_reader_open_existing(test_file_path);
+ {
+ ASSERT_EQ(next_id, administration_get_next_id());
+ ASSERT_EQ(next_sequence_number, administration_get_next_sequence_number());
+ }
+
+ PASS();
+}
+
SUITE(administration_rw) {
SET_SETUP(setup_cb, NULL);
SET_TEARDOWN(teardown_cb, NULL);
@@ -153,13 +181,5 @@ SUITE(administration_rw) {
RUN_TEST(_administration_rw_costcenter);
RUN_TEST(_administration_rw_project);
RUN_TEST(_administration_rw_contact);
-}
-
-GREATEST_MAIN_DEFS();
-int main(int argc, char **argv) {
- timer_lib_initialize();
-
- GREATEST_MAIN_BEGIN();
- RUN_SUITE(administration_rw);
- GREATEST_MAIN_END();
+ RUN_TEST(_administration_rw_info);
} \ No newline at end of file
diff --git a/tests/administration_validation_tests.cpp b/tests/administration_validation_tests.cpp
new file mode 100644
index 0000000..a2fe46a
--- /dev/null
+++ b/tests/administration_validation_tests.cpp
@@ -0,0 +1,91 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "greatest.h"
+#include "timer.h"
+
+#include "strops.hpp"
+#include "administration.hpp"
+
+TEST _administration_validate_project_add(void)
+{
+ administration_create_empty("");
+ project p1 = administration_project_create_empty();
+
+ ASSERT_EQ(administration_project_add(p1), a_err::A_ERR_MISSING_DESCRIPTION);
+
+ strops_copy(p1.description, "test project", sizeof(p1.description));
+ ASSERT_EQ(administration_project_add(p1), a_err::A_ERR_SUCCESS);
+
+ PASS();
+}
+
+TEST _administration_validate_project_import(void)
+{
+ administration_create_empty("");
+ project p1 = administration_project_create_empty();
+
+ ASSERT_EQ(administration_project_import(p1), a_err::A_ERR_MISSING_DESCRIPTION);
+
+ strops_copy(p1.description, "test project", sizeof(p1.description));
+ ASSERT_EQ(administration_project_import(p1), a_err::A_ERR_SUCCESS);
+
+ PASS();
+}
+
+
+TEST _administration_validate_project_update(void)
+{
+ administration_create_empty("");
+ project p1 = administration_project_create_empty();
+
+ strops_copy(p1.description, "test project", sizeof(p1.description));
+ ASSERT_EQ(administration_project_add(p1), a_err::A_ERR_SUCCESS);
+
+ strops_copy(p1.description, "", sizeof(p1.description));
+ ASSERT_EQ(administration_project_update(p1), a_err::A_ERR_MISSING_DESCRIPTION);
+
+ strops_copy(p1.id, "-1", sizeof(p1.description));
+ strops_copy(p1.description, "test project 2", sizeof(p1.description));
+ ASSERT_EQ(administration_project_update(p1), a_err::A_ERR_NOT_FOUND);
+
+ PASS();
+}
+
+TEST _administration_validate_project_remove(void)
+{
+ administration_create_empty("");
+ project p1 = administration_project_create_empty();
+
+ strops_copy(p1.description, "test project", sizeof(p1.description));
+ ASSERT_EQ(administration_project_add(p1), a_err::A_ERR_SUCCESS);
+
+ ASSERT_EQ(administration_project_remove(p1), a_err::A_ERR_SUCCESS);
+
+ ASSERT_EQ(administration_project_add(p1), a_err::A_ERR_SUCCESS);
+ strops_copy(p1.id, "-1", sizeof(p1.description));
+ ASSERT_EQ(administration_project_remove(p1), a_err::A_ERR_NOT_FOUND);
+
+ PASS();
+}
+
+TEST _administration_validate_project_isvalid(void)
+{
+ administration_create_empty("");
+ project p1 = administration_project_create_empty();
+
+ ASSERT_EQ(administration_project_is_valid(p1), a_err::A_ERR_MISSING_DESCRIPTION);
+
+ strops_copy(p1.description, "test project", sizeof(p1.description));
+ ASSERT_EQ(administration_project_is_valid(p1), a_err::A_ERR_SUCCESS);
+
+ PASS();
+}
+
+SUITE(administration_validate) {
+ RUN_TEST(_administration_validate_project_add);
+ RUN_TEST(_administration_validate_project_import);
+ RUN_TEST(_administration_validate_project_update);
+ RUN_TEST(_administration_validate_project_remove);
+ RUN_TEST(_administration_validate_project_isvalid);
+} \ No newline at end of file
diff --git a/tests/main.cpp b/tests/main.cpp
new file mode 100644
index 0000000..fd3557a
--- /dev/null
+++ b/tests/main.cpp
@@ -0,0 +1,12 @@
+#include "administration_rw_tests.cpp"
+#include "administration_validation_tests.cpp"
+
+GREATEST_MAIN_DEFS();
+int main(int argc, char **argv) {
+ timer_lib_initialize();
+
+ GREATEST_MAIN_BEGIN();
+ RUN_SUITE(administration_rw);
+ RUN_SUITE(administration_validate);
+ GREATEST_MAIN_END();
+} \ No newline at end of file