diff options
| -rw-r--r-- | docs/CHANGES.rst | 5 | ||||
| -rw-r--r-- | include/administration.hpp | 43 | ||||
| -rw-r--r-- | run.bat | 2 | ||||
| -rw-r--r-- | src/administration.cpp | 105 | ||||
| -rw-r--r-- | tests/administration_rw_tests.cpp | 40 | ||||
| -rw-r--r-- | tests/administration_validation_tests.cpp | 91 | ||||
| -rw-r--r-- | tests/main.cpp | 12 |
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); @@ -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 |
