From 1c53bd3ac83cc7a985983ac656bc2599276808a4 Mon Sep 17 00:00:00 2001 From: Aldrik Ramaekers Date: Sun, 5 Oct 2025 20:28:18 +0200 Subject: country data, working on tax reports --- src/administration.cpp | 92 ++++++++++++++++++++++++++++++++++++++++++++- src/countries.cpp | 88 +++++++++++++++++++++++++++++++++++++++++++ src/countries/nl.cpp | 100 +++++++++++++++++++++++++++++++++++++++++++++++++ src/logger.cpp | 2 - 4 files changed, 278 insertions(+), 4 deletions(-) create mode 100644 src/countries.cpp create mode 100644 src/countries/nl.cpp (limited to 'src') diff --git a/src/administration.cpp b/src/administration.cpp index 9790281..9adce55 100644 --- a/src/administration.cpp +++ b/src/administration.cpp @@ -20,6 +20,7 @@ #include "memops.hpp" #include "logger.hpp" #include "strops.hpp" +#include "countries.hpp" #include "administration.hpp" #include "administration_writer.hpp" @@ -45,7 +46,7 @@ static int compare_tax_countries(const void *a, const void *b) return strcmp(objA->country_code, objB->country_code); } -time_t administration::get_default_invoice_expire_duration() +time_t administration::get_default_invoice_expire_duration() // TODO depricated { return (30 * 24 * 60 * 60); // 30 days } @@ -475,6 +476,93 @@ static void administration_debug_print_income_statement(income_statement* statem } #endif +void administration::create_tax_statement(tax_statement* statement) +{ + STOPWATCH_START; + + char* country_code = company_info_get().address.country_code; + + assert(statement); + statement->quarter_count = 0; + + u32 invoice_count = administration::invoice_count(); + if (invoice_count == 0) return; + invoice* invoice_buffer = (invoice*)memops::alloc(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); + oldest_quarter = 0; + + u16 youngest_year; + u8 youngest_quarter; + time_t_to_quarter(youngest, &youngest_year, &youngest_quarter); + youngest_quarter = 3; + + u32 num_quarters = (youngest_quarter+1 + (youngest_year*4)) - (oldest_quarter + (oldest_year*4)); + assert(num_quarters <= MAX_LEN_INCOME_STATEMENT_REPORT_QUARTERS); + assert(num_quarters % 4 == 0); + + // Generate quarters. + for (u32 i = 0; i < num_quarters; i++) + { + tax_report quarter; + quarter.year = oldest_year + (u16)((oldest_quarter+i) / 4); + quarter.quarter = (u8)((oldest_quarter + (i)) % 4); + quarter.line_count = 0; + quarter.is_empty = 1; + strops::format(quarter.quarter_str, MAX_LEN_SHORT_DESC, "%dQ%d", quarter.quarter+1, quarter.year); + + country::fill_tax_report_with_categories(country_code, &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->issued_at, &yy, &qq); + + u32 report_index = (qq + (yy*4)) - (oldest_quarter + (oldest_year*4)); + + tax_report* quarter = &statement->reports[report_index]; + assert(yy == quarter->year && qq == quarter->quarter); + quarter->is_empty = 0; + + billing_item* item_buffer = (billing_item*)memops::alloc(sizeof(billing_item)*billing_item_count(inv)); + u32 invoice_items = administration::billing_item_get_all_for_invoice(inv, item_buffer); + for (u32 x = 0; x < invoice_items; x++) + { + char* tax_category = country::get_tax_category_for_billing_item(country_code, inv, &item_buffer[x]); + for (u32 t = 0; t < quarter->line_count; t++) + { + if (strops::equals(quarter->lines[t].tax_category, tax_category)) + { + quarter->lines[t].total_net += inv->net; + quarter->lines[t].total_tax += inv->tax; + } + } + + memops::unalloc(item_buffer); + } + } + + memops::unalloc(invoice_buffer); + logger::info("Created tax statement in %.3fms.", STOPWATCH_TIME); +} + void administration::create_income_statement(income_statement* statement) { STOPWATCH_START; @@ -573,7 +661,7 @@ void administration::create_income_statement(income_statement* statement) u16 yy; u8 qq; - time_t_to_quarter(inv->delivered_at, &yy, &qq); + time_t_to_quarter(inv->issued_at, &yy, &qq); u32 report_index = (qq + (yy*4)) - (oldest_quarter + (oldest_year*4)); diff --git a/src/countries.cpp b/src/countries.cpp new file mode 100644 index 0000000..d4bcae1 --- /dev/null +++ b/src/countries.cpp @@ -0,0 +1,88 @@ +/* +* Copyright (c) 2025 Aldrik Ramaekers +* +* Permission to use, copy, modify, and/or distribute this software for any +* purpose with or without fee is hereby granted, provided that the above +* copyright notice and this permission notice appear in all copies. +* +* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "strops.hpp" +#include "countries.hpp" + +#include "countries/nl.cpp" + +static country_impl country_map[] = { + _nl_country_impl, + // Add new locales here. +}; +static const u32 country_map_count = sizeof(country_map) / sizeof(country_map[0]); + +s32 country::get_count() +{ + return country_map_count; +} + +char* country::get_code_by_index(s32 index) +{ + return country_map[index].country_code; +} + +static s32 get_index_by_country_code(char* country_code) +{ + for (u32 i = 0; i < country_map_count; i++) + { + if (strops::equals(country_map[i].country_code, country_code)) { + return i; + } + } + return -1; +} + +bool country::is_EU(char* country_code) +{ + s32 index = get_index_by_country_code(country_code); + if (index == -1) return 0; + + return country_map[index].is_EU; +} + +time_t country::get_default_invoice_expire_duration(char* country_code) +{ + s32 index = get_index_by_country_code(country_code); + if (index == -1) return 0; + + return country_map[index].get_default_invoice_expire_duration(); +} + +bool country::tax_is_implemented(char* country_code) +{ + s32 index = get_index_by_country_code(country_code); + if (index == -1) return false; + + return country_map[index].fill_tax_report_with_categories && + country_map[index].get_tax_category_for_billing_item; +} + +void country::fill_tax_report_with_categories(char* country_code, tax_report* report) +{ + s32 index = get_index_by_country_code(country_code); + assert(index != -1); + + country_map[index].fill_tax_report_with_categories(report); +} + +char* country::get_tax_category_for_billing_item(char* country_code, invoice* inv, billing_item* item) +{ + s32 index = get_index_by_country_code(country_code); + assert(index != -1); + + return country_map[index].get_tax_category_for_billing_item(inv, item); +} \ No newline at end of file diff --git a/src/countries/nl.cpp b/src/countries/nl.cpp new file mode 100644 index 0000000..85005d6 --- /dev/null +++ b/src/countries/nl.cpp @@ -0,0 +1,100 @@ +/* +* Copyright (c) 2025 Aldrik Ramaekers +* +* Permission to use, copy, modify, and/or distribute this software for any +* purpose with or without fee is hereby granted, provided that the above +* copyright notice and this permission notice appear in all copies. +* +* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "countries.hpp" + +time_t _nl_get_default_invoice_expire_duration() +{ + return (15 * 24 * 60 * 60); // 15 days +} + +void _nl_fill_tax_report_with_categories(tax_report* report) +{ + report->lines[report->line_count++] = tax_line {"1a", 0.0f, 0.0f}; + report->lines[report->line_count++] = tax_line {"1b", 0.0f, 0.0f}; + report->lines[report->line_count++] = tax_line {"1c", 0.0f, 0.0f}; + report->lines[report->line_count++] = tax_line {"1d", 0.0f, 0.0f}; + report->lines[report->line_count++] = tax_line {"1e", 0.0f, 0.0f}; + + report->lines[report->line_count++] = tax_line {"2a", 0.0f, 0.0f}; + + report->lines[report->line_count++] = tax_line {"3a", 0.0f, 0.0f}; + report->lines[report->line_count++] = tax_line {"3b", 0.0f, 0.0f}; + report->lines[report->line_count++] = tax_line {"3c", 0.0f, 0.0f}; + + report->lines[report->line_count++] = tax_line {"4a", 0.0f, 0.0f}; + report->lines[report->line_count++] = tax_line {"4b", 0.0f, 0.0f}; + + report->lines[report->line_count++] = tax_line {"5a", 0.0f, 0.0f}; + report->lines[report->line_count++] = tax_line {"5b", 0.0f, 0.0f}; + + report->lines[report->line_count++] = tax_line {"Total", 0.0f, 0.0f}; +} + +char* _nl_get_tax_category_for_billing_item(invoice* inv, billing_item* item) +{ + // https://goedestartbelastingdienst.nl/wiki/view/7494ecb4-f6d2-4f85-a200-5a3ee5d45b75/btw-aangifte-het-invullen-van-de-verschillende-rubrieken + + tax_rate rate; + a_err err = administration::tax_rate_get_by_id(&rate, item->tax_rate_id); + if (err != A_ERR_SUCCESS) return 0; + + + /* + We moeten 5a ook nog berekenen, dus misschien tax_report meegeven en hier alles doen wat nodig is? + we moeten ook nog het uiteindelijke bedrag uitregenen 5a-5b dus dat moet ook hier gebeuren. + + */ + + // Outgoing = 1 + 3 + if (inv->is_outgoing) { + + if (strops::equals(inv->customer.address.country_code, "NL")) + { + if (rate.rate == 21.0f) return "1a"; + else if (rate.rate == 9.0f) return "1b"; + // TODO 1c + else if (rate.rate > 0.0f) return "1d"; + else if (rate.rate == 0.0f) return "1e"; + } + else if (!country::is_EU(inv->customer.address.country_code)) return "3a"; + else return "3b"; + // TODO 3c + + } + + // Incomming = 2 + 4 + 5 + else { + + if (strops::equals(inv->customer.address.country_code, "NL")) { + if (strops::equals(rate.category_code, "AE")) return "2a"; // NL reverse charge. + else return "5b"; + } + + if (!country::is_EU(inv->customer.address.country_code)) return "4a"; + else return "4b"; + + } + return 0; +} + +country_impl _nl_country_impl = { + "NL", + true, + _nl_get_default_invoice_expire_duration, + _nl_fill_tax_report_with_categories, + _nl_get_tax_category_for_billing_item, +}; \ No newline at end of file diff --git a/src/logger.cpp b/src/logger.cpp index 0035948..e2c33e2 100644 --- a/src/logger.cpp +++ b/src/logger.cpp @@ -14,8 +14,6 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include -#include #include #include "timer.h" -- cgit v1.2.3-70-g09d2