summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAldrik Ramaekers <aldrikboy@gmail.com>2025-10-10 22:03:05 +0200
committerAldrik Ramaekers <aldrikboy@gmail.com>2025-10-10 22:03:05 +0200
commitd976c1227f367a4547a004597b8d360a8958eba9 (patch)
tree891b89263176375f058598b629b145177531a54a /src
parent1c53bd3ac83cc7a985983ac656bc2599276808a4 (diff)
working on NL tax reports
Diffstat (limited to 'src')
-rw-r--r--src/administration.cpp70
-rw-r--r--src/administration_writer.cpp7
-rw-r--r--src/countries.cpp22
-rw-r--r--src/countries/nl.cpp125
-rw-r--r--src/locales/en.cpp3
-rw-r--r--src/strops.cpp5
6 files changed, 187 insertions, 45 deletions
diff --git a/src/administration.cpp b/src/administration.cpp
index 9adce55..0ad9684 100644
--- a/src/administration.cpp
+++ b/src/administration.cpp
@@ -78,7 +78,8 @@ static void create_default_tax_rates()
ADD_BRACKET("00", 0.0f, "Z"); // Zero rated goods
ADD_BRACKET("00", 0.0f, "G"); // Free export item, VAT not charged
ADD_BRACKET("00", 0.0f, "O"); // Services outside scope of tax
- ADD_BRACKET("00", 0.0f, "K"); // VAT exempt for EEA intra-community supply of goods and services
+ ADD_BRACKET("00", 0.0f, "K#G"); // VAT exempt for EEA intra-community supply of goods
+ ADD_BRACKET("00", 0.0f, "K#S"); // VAT exempt for EEA intra-community supply of services
// Austria
ADD_BRACKET("AT", 20.0f, "S");
@@ -502,16 +503,16 @@ void administration::create_tax_statement(tax_statement* statement)
u16 oldest_year;
u8 oldest_quarter;
time_t_to_quarter(oldest, &oldest_year, &oldest_quarter);
- oldest_quarter = 0;
+ //oldest_quarter = 0;
u16 youngest_year;
u8 youngest_quarter;
time_t_to_quarter(youngest, &youngest_year, &youngest_quarter);
- youngest_quarter = 3;
+ //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);
+ //assert(num_quarters % 4 == 0);
// Generate quarters.
for (u32 i = 0; i < num_quarters; i++)
@@ -524,6 +525,8 @@ void administration::create_tax_statement(tax_statement* statement)
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);
+
+ statement->reports[statement->quarter_count++] = quarter;
}
// Fill quarters.
@@ -533,7 +536,7 @@ void administration::create_tax_statement(tax_statement* statement)
u16 yy;
u8 qq;
- time_t_to_quarter(inv->issued_at, &yy, &qq);
+ time_t_to_quarter(country::get_invoice_date_to_use_for_tax_report(country_code, inv), &yy, &qq);
u32 report_index = (qq + (yy*4)) - (oldest_quarter + (oldest_year*4));
@@ -545,24 +548,30 @@ void administration::create_tax_statement(tax_statement* statement)
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;
- }
- }
+ bool success = country::add_billing_item_to_tax_report(country_code, quarter, inv, &item_buffer[x]);
+ if (!success) logger::error("Failed to add billing item to tax report: %s - %s.", inv->sequential_number, item_buffer[x].description);
+ }
+ country::calculate_tax_report_final(country_code, quarter);
- memops::unalloc(item_buffer);
- }
+ memops::unalloc(item_buffer);
}
-
+
memops::unalloc(invoice_buffer);
logger::info("Created tax statement in %.3fms.", STOPWATCH_TIME);
}
+tax_line* administration::get_tax_line_from_report(tax_report* quarter, char* category)
+{
+ for (u32 t = 0; t < quarter->line_count; t++)
+ {
+ if (strops::equals(quarter->lines[t].tax_category, category))
+ {
+ return &quarter->lines[t];
+ }
+ }
+ return 0;
+}
+
void administration::create_income_statement(income_statement* statement)
{
STOPWATCH_START;
@@ -1216,6 +1225,13 @@ a_err administration::tax_rate_get_by_shorthandle(tax_rate* buffer, char* handle
list_iterator_stop(&g_administration.tax_rates);
return A_ERR_SUCCESS;
}
+ strops::format(compare_str, 20, "%s/%s", c.country_code, c.category_code);
+ if (strcmp(compare_str, handle) == 0)
+ {
+ *buffer = c;
+ list_iterator_stop(&g_administration.tax_rates);
+ return A_ERR_SUCCESS;
+ }
}
list_iterator_stop(&g_administration.tax_rates);
@@ -1527,6 +1543,26 @@ static char* get_default_currency_for_country(char* country_code)
return "EUR";
}
+bool administration::invoice_has_intra_community_services(invoice* invoice)
+{
+ list_iterator_start(&invoice->billing_items);
+ while (list_iterator_hasnext(&invoice->billing_items)) {
+ billing_item* c = (billing_item *)list_iterator_next(&invoice->billing_items);
+
+ tax_rate rate;
+ a_err found = administration::tax_rate_get_by_id(&rate, c->tax_rate_id);
+ if (found == A_ERR_SUCCESS) {
+ if (strops::equals(rate.category_code, "K#S")) {
+ list_iterator_stop(&invoice->billing_items);
+ return true;
+ }
+ }
+ }
+ list_iterator_stop(&invoice->billing_items);
+
+ return false;
+}
+
invoice administration::invoice_create_empty()
{
invoice result;
diff --git a/src/administration_writer.cpp b/src/administration_writer.cpp
index f7069a6..81c90fa 100644
--- a/src/administration_writer.cpp
+++ b/src/administration_writer.cpp
@@ -426,7 +426,12 @@ bool administration_writer::save_invoice_blocking(invoice inv)
strops::replace(tax_entry_file_content, tax_entry_buf_length, "{{CURRENCY}}", inv.currency);
strops::replace_float(tax_entry_file_content, tax_entry_buf_length, "{{TAXABLE_AMOUNT}}", subtotal.net, 2);
strops::replace_float(tax_entry_file_content, tax_entry_buf_length, "{{TAX_AMOUNT}}", subtotal.tax, 2);
- strops::replace(tax_entry_file_content, tax_entry_buf_length, "{{TAX_CATEGORY}}", tax_rate_buffer[i].category_code);
+
+ char tax_category[10];
+ strops::copy(tax_category, tax_rate_buffer[i].category_code, sizeof(tax_category));
+ strops::tokenize(tax_category, "#");
+
+ strops::replace(tax_entry_file_content, tax_entry_buf_length, "{{TAX_CATEGORY}}", tax_category);
strops::replace_float(tax_entry_file_content, tax_entry_buf_length, "{{TAX_PERCENT}}", tax_rate_buffer[i].rate, 2);
u32 content_len = (u32)strlen(tax_entry_file_content);
diff --git a/src/countries.cpp b/src/countries.cpp
index d4bcae1..5712a7f 100644
--- a/src/countries.cpp
+++ b/src/countries.cpp
@@ -68,7 +68,7 @@ bool country::tax_is_implemented(char* 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;
+ country_map[index].add_billing_item_to_tax_report;
}
void country::fill_tax_report_with_categories(char* country_code, tax_report* report)
@@ -79,10 +79,26 @@ void country::fill_tax_report_with_categories(char* country_code, tax_report* re
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)
+bool country::add_billing_item_to_tax_report(char* country_code, tax_report* report, 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);
+ return country_map[index].add_billing_item_to_tax_report(report, inv, item);
+}
+
+float country::calculate_tax_report_final(char* country_code, tax_report* report)
+{
+ s32 index = get_index_by_country_code(country_code);
+ assert(index != -1);
+
+ return country_map[index].calculate_tax_report_final(report);
+}
+
+time_t country::get_invoice_date_to_use_for_tax_report(char* country_code, invoice* inv)
+{
+ s32 index = get_index_by_country_code(country_code);
+ assert(index != -1);
+
+ return country_map[index].get_invoice_date_to_use_for_tax_report(inv);
} \ No newline at end of file
diff --git a/src/countries/nl.cpp b/src/countries/nl.cpp
index 85005d6..9c8a20b 100644
--- a/src/countries/nl.cpp
+++ b/src/countries/nl.cpp
@@ -14,7 +14,10 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#include <math.h>
+
#include "countries.hpp"
+#include "strops.hpp"
time_t _nl_get_default_invoice_expire_duration()
{
@@ -44,7 +47,7 @@ void _nl_fill_tax_report_with_categories(tax_report* report)
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)
+bool _nl_add_billing_item_to_tax_report(tax_report* report, invoice* inv, billing_item* item)
{
// https://goedestartbelastingdienst.nl/wiki/view/7494ecb4-f6d2-4f85-a200-5a3ee5d45b75/btw-aangifte-het-invullen-van-de-verschillende-rubrieken
@@ -52,43 +55,117 @@ char* _nl_get_tax_category_for_billing_item(invoice* inv, billing_item* item)
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) {
+ tax_line* a5 = administration::get_tax_line_from_report(report, "5a"); // Total owed.
+
if (strops::equals(inv->customer.address.country_code, "NL"))
{
- if (rate.rate == 21.0f) return "1a";
- else if (rate.rate == 9.0f) return "1b";
+ if (rate.rate == 21.0f) {
+ tax_line* tl = administration::get_tax_line_from_report(report, "1a");
+ tl->total_net += item->net;
+ tl->total_tax += item->tax;
+
+ a5->total_tax += item->tax;
+ }
+ else if (rate.rate == 9.0f) {
+ tax_line* tl = administration::get_tax_line_from_report(report, "1b");
+ tl->total_net += item->net;
+ tl->total_tax += item->tax;
+
+ a5->total_tax += item->tax;
+ }
// TODO 1c
- else if (rate.rate > 0.0f) return "1d";
- else if (rate.rate == 0.0f) return "1e";
+ else if (rate.rate > 0.0f) {
+ tax_line* tl = administration::get_tax_line_from_report(report, "1d");
+ tl->total_net += item->net;
+ tl->total_tax += item->tax;
+
+ a5->total_tax += item->tax;
+ }
+ else if (rate.rate == 0.0f) {
+ tax_line* tl = administration::get_tax_line_from_report(report, "1e");
+ tl->total_net += item->net;
+ tl->total_tax += item->tax;
+
+ a5->total_tax += item->tax;
+ }
+ }
+ else if (!country::is_EU(inv->customer.address.country_code)) {
+ tax_line* tl = administration::get_tax_line_from_report(report, "3a");
+ tl->total_net += item->net;
+ // Tax is paid to country of customer.
+ }
+ else {
+ tax_line* tl = administration::get_tax_line_from_report(report, "3b");
+ tl->total_net += item->net;
+ // Tax is paid to country of customer.
}
- else if (!country::is_EU(inv->customer.address.country_code)) return "3a";
- else return "3b";
// TODO 3c
}
-
- // Incomming = 2 + 4 + 5
else {
+ tax_line* a5 = administration::get_tax_line_from_report(report, "5a"); // Total owed.
+ tax_line* b5 = administration::get_tax_line_from_report(report, "5b"); // Input tax.
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 (strops::equals(rate.category_code, "AE")) { // NL reverse charge.
+ tax_line* tl = administration::get_tax_line_from_report(report, "2a");
+
+ tl->total_net += item->net;
+ tl->total_tax += item->net * 0.21f; // TODO fr?
+
+ a5->total_tax += item->net * 0.21f;
+ b5->total_tax += item->net * 0.21f;
+ }
+ else {
+ b5->total_tax += item->tax;
+ }
+ }
+
+ if (!country::is_EU(inv->customer.address.country_code)) {
+ tax_line* tl = administration::get_tax_line_from_report(report, "4a");
+
+ tl->total_net += item->net;
+ tl->total_tax += item->net * 0.21f;
+
+ a5->total_tax += item->net * 0.21f;
+ b5->total_tax += item->net * 0.21f;
}
+ else {
+ tax_line* tl = administration::get_tax_line_from_report(report, "4b");
+
+ tl->total_net += item->net;
+ tl->total_tax += item->net * 0.21f;
- if (!country::is_EU(inv->customer.address.country_code)) return "4a";
- else return "4b";
+ a5->total_tax += item->net * 0.21f;
+ b5->total_tax += item->net * 0.21f;
+ }
}
- return 0;
+ return 1;
+}
+
+float _nl_calculate_tax_report_final(tax_report* report)
+{
+ tax_line* a5 = administration::get_tax_line_from_report(report, "5a"); // Total owed.
+ tax_line* b5 = administration::get_tax_line_from_report(report, "5b"); // Input tax.
+ tax_line* total = administration::get_tax_line_from_report(report, "Total");
+
+ total->total_tax = a5->total_tax - b5->total_tax;
+ return (float)ceil(total->total_tax);
+}
+
+time_t _nl_get_invoice_date_to_use_for_tax_report(invoice* inv)
+{
+ if (!inv->is_outgoing) { // Intra-Community services need to be reported in the quarter the service was delivered.
+ if (administration::invoice_has_intra_community_services(inv)) {
+ return inv->delivered_at;
+ }
+ }
+
+ return inv->issued_at;
}
country_impl _nl_country_impl = {
@@ -96,5 +173,7 @@ country_impl _nl_country_impl = {
true,
_nl_get_default_invoice_expire_duration,
_nl_fill_tax_report_with_categories,
- _nl_get_tax_category_for_billing_item,
+ _nl_add_billing_item_to_tax_report,
+ _nl_calculate_tax_report_final,
+ _nl_get_invoice_date_to_use_for_tax_report,
}; \ No newline at end of file
diff --git a/src/locales/en.cpp b/src/locales/en.cpp
index aa04853..e7ad41b 100644
--- a/src/locales/en.cpp
+++ b/src/locales/en.cpp
@@ -63,7 +63,8 @@ locale_entry en_locales[] = {
{"taxcategory.Z", "Zero rated goods"},
{"taxcategory.G", "Free export item, VAT not charged"},
{"taxcategory.O", "Services outside scope of tax"},
- {"taxcategory.K", "Intra-community supply of goods"},
+ {"taxcategory.K#G", "Intra-community supply of goods"},
+ {"taxcategory.K#S", "Intra-community supply of services"},
// Countries
{ "country.AT", "Austria" },
diff --git a/src/strops.cpp b/src/strops.cpp
index 146a909..d47ec2e 100644
--- a/src/strops.cpp
+++ b/src/strops.cpp
@@ -63,6 +63,11 @@ namespace strops {
return 0;
}
+ char* tokenize(char* a, const char* find)
+ {
+ return strtok(a, find);
+ }
+
s32 format_va(char* s, size_t n, const char* format, va_list args)
{
s32 result = vsnprintf(s, n, format, args);