summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAldrik Ramaekers <aldrikboy@gmail.com>2025-08-10 14:38:09 +0200
committerAldrik Ramaekers <aldrikboy@gmail.com>2025-08-10 14:38:09 +0200
commit0327e06b59aa20dbfec137b2287b950b5cb84960 (patch)
tree468a884cc65647f2f6b980a97dd010c9d0470ef6
parent7a29dfbc37f2440b7e5461e905651b25615d2d02 (diff)
refactors, comments, documentation
-rw-r--r--NOTES.md132
-rw-r--r--README.md14
-rw-r--r--docs/CHANGES.rst23
-rw-r--r--docs/README.rst40
-rw-r--r--docs/STORAGE.rst131
-rw-r--r--include/administration.hpp9
-rw-r--r--src/administration.cpp3
-rw-r--r--src/locales/en.cpp3
-rw-r--r--src/ui/ui_contacts.cpp56
9 files changed, 248 insertions, 163 deletions
diff --git a/NOTES.md b/NOTES.md
deleted file mode 100644
index 1477ed1..0000000
--- a/NOTES.md
+++ /dev/null
@@ -1,132 +0,0 @@
-## List of work to do
-
-Manage invoices and all information associated with invoices.
-- create, read, update, delete
-- send invoice by email
-- create invoice from pdf file
-- create invoice from image of receipt
-
-Manage contacts and view transaction history.
-- Create, read, update, delete
-- View complete transaction history
-
-Manage projects.
-- Create, read, update, delete
-
-View monthly and quarterly results (per project).
-Create quarterly tax reports.
-
-## Save file
-Your administration is stored in a single `.money` file.
-This is a zip file with the following format:
-
-administration.money/
-├── administration.json
-├── incoming/
-├───── invoice_1.pdf
-├───── invoice_2.pdf
-├───── receipt_3.png
-├── outgoing/
-├───── invoice_4.pdf
-├───── invoice_5.pdf
-├───── invoice_6.png
-
-`administration.json` contains all the information described below in the `Administration` data structure.
-
-
-## Data structures
-
-| Administration||
-|-|-|
-| info | Contact information |
-| next id | Id reserved for new data entry |
-| path | File path to save to |
-| program version | Version of data format |
-| country | Country code (used for tax reports) |
-| contacts `list` | List of contacts |
-| projects `list` | List of projects |
-| invoices `list` | List of invoices |
-| tax brackets `list` | List of available tax brackets |
-| AI service | Which AI service to use |
-| AI key | API key for selected AI service |
-| email service | Which email service to use |
-| email key | API key for selected email service |
-
-| Contact||
-|-|-|
-| id `auto` | reference id `C/[id]` |
-| name `required` | Full name of individual or company name |
-| address line 1 `required` | Address |
-| address line 2 `required` | Zip, place |
-| country `required` | Country |
-| type `required` | Contact type, `business` or `consumer` |
-| tax number | Tax identification number |
-| business number | Business number |
-| email | Email address |
-| Phone number | Phone number |
-| bank account | Bank account number (to display on outgoing invoices) |
-
-| Invoice [docs](https://accountancyeurope.eu/wp-content/uploads/2025/04/250423-VAT-and-the-Digital-Age-Factsheet-Accountancy-Europe.pdf)||
-|-|-|
-| id `auto` | reference id `I/[id]` |
-| sequential number `auto` | Generated sequential invoice number |
-| customer `required` | Customer contact information, stored as reference `C/[id]` |
-| supplier `required` | Supplier contact information, stored as reference `C/[id]` |
-| issue at `required` | Date when invoice was issued |
-| delivered at `required` | Date when goods or services were delivered |
-| 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 | 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 |
-| status `required` | Payment status `paid` `expired` `cancelled` `refunded`, `corrected` |
-| shipping address line 1 `required` | Address |
-| shipping address line 2 `required` | Zip, place |
-| country `required` | Country |
-| currency `required` | Currency used for billing |
-| keep untill `required` | Date untill invoice needs to be stored legally |
-| payment on account date | If advance payment received and differs from invoice date (Ireland only) |
-| tax representative | If supplier uses tax representative in another Member State (Belgium only) |
-| corrected sequential number | for corrective invoices, the sequential number that identifies the original invoice to be corrected |
-
-| Billing item||
-|-|-|
-| id `auto` | reference id `B/[id]` |
-| invoice `auto` | reference to invoice `I/[id]` |
-| 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, 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 |
-| is intra-community `required` | If applicable, note like “intra-Community supply of goods” |
-| is triangulation `required` | For EU triangulation: note scheme and buyer liable |
-| currency `required` | Currency used for billing |
-| internal code | Article or service code |
-
-| Project||
-|-|-|
-| id `auto` | reference id `P/[id]` |
-| description `required` | Description of project |
-| start date `required` | Project started at date |
-| status `required` | Status of project `running` `paused` `cancelled` |
-| end date | Project cancelled at date |
-
-| Cost center||
-|-|-|
-| id `auto` | reference id `E/[id]` |
-| code `required` | Internal code of cost center |
-| description `required` | Description of cost center |
-
-| Tax bracket||
-|-|-|
-| id `auto` | reference id `T/[id]` |
-| country code `required` | 2 letter country code |
-| description `required` | Description of tax bracket |
-| rate `required` | Tax rate % | \ No newline at end of file
diff --git a/README.md b/README.md
deleted file mode 100644
index 408d162..0000000
--- a/README.md
+++ /dev/null
@@ -1,14 +0,0 @@
-# OpenLedger accounting software
-A simple and portable accounting tool. This tool tries to be EU VAT Directive compliant and has country specific invoice keeping and tax reporting.
-
-## Supported countries
-1.
-2.
-
-## Features
-1.
-2.
-
-## Dependencies
-- ImGui 1.92.1
-- simclist 1.5 \ No newline at end of file
diff --git a/docs/CHANGES.rst b/docs/CHANGES.rst
new file mode 100644
index 0000000..34f37fa
--- /dev/null
+++ b/docs/CHANGES.rst
@@ -0,0 +1,23 @@
+.. _changes:
+
+- Manage invoices and all information associated with invoices.
+- Send invoice by email
+- create invoice from PDF file
+- create invoice from image of receipt
+- create invoice from UBL file
+- read invoice from Holodeck instance
+- send invoice via Holodeck instance
+- View invoice history for contacts
+- View invoice history for projects
+- Create monthly and quarterly results (per project).
+- Create quarterly tax reports for NL.
+- Store invoices in Peppol BIS 3.0 format.
+
+v0.1 (master)
+
+- Create/read/update contacts
+- Create/read/update projects
+- Create/read/update VAT rates
+- Create/read/update cost centers
+- Generate default VAT rates
+- Generate default cost centers \ No newline at end of file
diff --git a/docs/README.rst b/docs/README.rst
new file mode 100644
index 0000000..2762bf1
--- /dev/null
+++ b/docs/README.rst
@@ -0,0 +1,40 @@
+.. _readme:
+
+===============================
+OpenBooks - Accounting software
+===============================
+
+Author: Aldrik Ramaekers <aldrik.ramaekers@gmail.com>
+
+The master is hosted on github: https://github.com/aldrik-ramaekers/open-books
+
+All documentation can be found inside the docs folder.
+
+1. Introduction
+---------------
+OpenBooks is a simple and portable accounting tool. This tool aims to be EU VAT Directive compliant and has country specific compliance, invoice keeping and tax reporting.
+
+What OpenBooks **can** do:
+
+- OpenBooks handles administration of invoices, expenses, receipts, contacts, projects, VAT rates and cost centers.
+- OpenBooks can export/email invoices in PDF, UBL and Peppol format.
+- OpenBooks can send and receive invoices over the Peppol network (using a locally run `Holodeck <https://holodeck-b2b.org/>`_ instance).
+- OpenBooks can create country specific tax reports and audit files (see supported countries list).
+
+What OpenBooks **can't** do:
+
+- Intrastat reporting
+- Sustainability Reporting (CSRD)
+- PoS Fiscalization
+- Submit tax and audit reports
+
+2. Supported countries
+----------------------
+This section lists all country specific implementations to be compliant. If the country you operate from is not listed below, some reporting requirements might not be handled by OpenBooks.
+
+- **The Netherlands**: Quarterly tax reports, Archiving (Peppol BIS 3.0).
+
+3. Dependencies
+---------------
+- ImGui 1.92.1
+- simclist 1.5 \ No newline at end of file
diff --git a/docs/STORAGE.rst b/docs/STORAGE.rst
new file mode 100644
index 0000000..29ad037
--- /dev/null
+++ b/docs/STORAGE.rst
@@ -0,0 +1,131 @@
+.. _storage:
+
+================================
+OpenBooks - file and data format
+================================
+
+Your administration is stored in a single **.openbooks** file.
+This is a zip file with the following format:
+
+::
+
+ administration.openbooks/
+ ├── administration.xml
+ ├── invoices/
+ │ ├── invoice_0000000001.xml
+ │ ├── invoice_0000000002.xml
+ ├── documents/
+ │ ├── invoice_1.pdf
+ │ ├── invoice_2.pdf
+ │ ├── receipt_3.png
+
+**administration.xml** contains all company settings and data. This includes company info, projects, contacts, vat rates and cost centers.
+
+**invoices/** Contains all incomming and outgoing invoices, stored in Peppol BIS 3.0 format.
+
+**documents/** Contains all documents from which invoices have been generated.
+
+Data structures
+===============
+
+Administration
+--------------
+
+- info — Contact information, stored as **Contact**
+- next id — Id reserved for new data entry
+- path — File path to save to
+- program version — Version of data format
+- country — Country code (used for tax reports)
+- contacts **list** — List of contacts
+- projects **list** — List of projects
+- invoices **list** — List of invoices
+- tax brackets **list** — List of available tax brackets
+- AI service — Which AI service to use
+- AI key — API key for selected AI service
+- email service — Which email service to use
+- email key — API key for selected email service
+
+Contact
+-------
+
+- id **auto** — reference id **C/[id]**
+- name **required** — Full name of individual or company name
+- address line 1 **required** — Address
+- address line 2 **required** — Zip, place
+- country **required** — Country
+- type **required** — Contact type, **business** or **consumer**
+- tax number — Tax identification number
+- business number — Business number
+- email — Email address
+- Phone number — Phone number
+- bank account — Bank account number (to display on outgoing invoices)
+
+Invoice (`docs <https://accountancyeurope.eu/wp-content/uploads/2025/04/250423-VAT-and-the-Digital-Age-Factsheet-Accountancy-Europe.pdf>`_)
+-------------------------------------------------------------------------------------------------------------------------------------------
+
+- id **auto** — reference id **I/[id]**
+- sequential number **auto** — Generated sequential invoice number
+- customer **required** — Customer contact information, stored as reference **C/[id]**
+- supplier **required** — Supplier contact information, stored as reference **C/[id]**
+- issue at **required** — Date when invoice was issued
+- delivered at **required** — Date when goods or services were delivered
+- 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 — Project, stored as reference **P/[id]**
+- cost center id — Cost center, stored as reference **E/[id]** (incoming invoices only)
+- total **required** — Total amount billed
+- tax **required** — Total tax billed
+- net **required** — Total amount excl. tax
+- status **required** — Payment status **paid** **expired** **cancelled** **refunded**, **corrected**
+- shipping address line 1 **required** — Address
+- shipping address line 2 **required** — Zip, place
+- country **required** — Country
+- currency **required** — Currency used for billing
+- keep until **required** — Date until invoice needs to be stored legally
+- payment on account date — If advance payment received and differs from invoice date (Ireland only)
+- tax representative — If supplier uses tax representative in another Member State (Belgium only)
+- corrected sequential number — For corrective invoices, the sequential number that identifies the original invoice to be corrected
+
+Billing item
+------------
+
+- id **auto** — reference id **B/[id]**
+- invoice **auto** — reference to invoice **I/[id]**
+- 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 up to 4b in The Netherlands)
+- 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
+- is intra-community **required** — If applicable, note like “intra-Community supply of goods”
+- is triangulation **required** — For EU triangulation: note scheme and buyer liable
+- currency **required** — Currency used for billing
+- internal code — Article or service code
+
+Project
+-------
+
+- id **auto** — reference id **P/[id]**
+- description **required** — Description of project
+- start date **required** — Project started at date
+- status **required** — Status of project **running** **paused** **cancelled**
+- end date — Project cancelled at date
+
+Cost center
+-----------
+
+- id **auto** — reference id **E/[id]**
+- code **required** — Internal code of cost center
+- description **required** — Description of cost center
+
+Tax bracket
+-----------
+
+- id **auto** — reference id **T/[id]**
+- country code **required** — 2 letter country code
+- description **required** — Description of tax bracket
+- rate **required** — Tax rate %
diff --git a/include/administration.hpp b/include/administration.hpp
index b567f6f..a0dd803 100644
--- a/include/administration.hpp
+++ b/include/administration.hpp
@@ -22,14 +22,21 @@ typedef struct
{
char address1[128];
char address2[128];
- char country[128];
+ char country_code[3]; // 2 letter country code.
} address;
+typedef enum
+{
+ CONTACT_BUSINESS,
+ CONTACT_CONSUMER,
+} contact_type;
+
typedef struct
{
char id[16];
char name[64];
address address;
+ contact_type type;
char taxid[32];
char businessid[32];
char email[64];
diff --git a/src/administration.cpp b/src/administration.cpp
index 8a0b104..c6ece04 100644
--- a/src/administration.cpp
+++ b/src/administration.cpp
@@ -203,6 +203,9 @@ void administration_create()
list_init(&g_administration.cost_centers);
strops_copy(g_administration.path, "", sizeof(g_administration.path));
+ snprintf(g_administration.company_info.id, sizeof(g_administration.company_info.id), "C/%d", administration_create_id());
+ g_administration.next_id++;
+
administration_create_default_tax_brackets();
administration_create_default_cost_centers();
}
diff --git a/src/locales/en.cpp b/src/locales/en.cpp
index 3c6bafd..5aba4e2 100644
--- a/src/locales/en.cpp
+++ b/src/locales/en.cpp
@@ -84,6 +84,9 @@ locale_entry en_locales[] = {
{"contact.form.address1", "Street name + house number, appt. number, etc."},
{"contact.form.address2", "Zip, city"},
{"contact.form.country", "Country"},
+ {"contact.form.type", "Customer type"},
+ {"contact.form.type.business", "Business"},
+ {"contact.form.type.consumer", "Consumer"},
{"contact.form.taxnumber", "Tax number"},
{"contact.form.businessnumber", "Business number"},
{"contact.form.email", "Email address"},
diff --git a/src/ui/ui_contacts.cpp b/src/ui/ui_contacts.cpp
index e00fb78..0ade9b0 100644
--- a/src/ui/ui_contacts.cpp
+++ b/src/ui/ui_contacts.cpp
@@ -31,29 +31,31 @@ bool draw_contact_form(contact* buffer, bool back_button_enabled = true, bool vi
}
}
ImGui::Spacing();
-
- ImGui::BeginDisabled();
-
float widthAvailable = ImGui::GetContentRegionAvail().x;
- ImGui::SetNextItemWidth(widthAvailable*0.2f);
- ImGui::InputText(localize("contact.form.identifier"), buffer->id, IM_ARRAYSIZE(buffer->id));
- if (!viewing_only) ImGui::EndDisabled();
+ // 1. Identifier
+ //ImGui::BeginDisabled();
+ //ImGui::SetNextItemWidth(widthAvailable*0.2f);
+ //ImGui::InputText(localize("contact.form.identifier"), buffer->id, IM_ARRAYSIZE(buffer->id));
+ //if (!viewing_only) ImGui::EndDisabled();
+ // 2. Full name
ImGui::SetNextItemWidth(widthAvailable*0.5f);
ImGui::InputTextWithHint(localize("contact.form.fullname"), localize("contact.form.fullname"), buffer->name, IM_ARRAYSIZE(buffer->name));
ImGui::SameLine();ui_helper_draw_required_tag();
+ // 3. Address line 1
ImGui::SetNextItemWidth(widthAvailable*0.5f);
ImGui::InputTextWithHint(localize("contact.form.address1"), localize("contact.form.address1"), buffer->address.address1, IM_ARRAYSIZE(buffer->address.address1));
ImGui::SameLine();ui_helper_draw_required_tag();
+ // 4. Address line 2
ImGui::SetNextItemWidth(widthAvailable*0.5f);
ImGui::InputTextWithHint(localize("contact.form.address2"), localize("contact.form.address2"), buffer->address.address2, IM_ARRAYSIZE(buffer->address.address2));
ImGui::SameLine();ui_helper_draw_required_tag();
+ // 5. Country dropdown.
ImGui::SetNextItemWidth(widthAvailable*0.5f);
-
const char* countries[] = { localize("country.AT"),localize("country.BE"),localize("country.BG"),localize("country.HR"),localize("country.CY"),localize("country.CZ"),localize("country.DK"),localize("country.EE"),localize("country.FI"),localize("country.FR"),localize("country.DE"),localize("country.GR"),localize("country.HU"),localize("country.IE"),localize("country.IT"),localize("country.LV"),localize("country.LT"),localize("country.LU"),localize("country.MT"),localize("country.NL"),localize("country.PL"),localize("country.PT"),localize("country.RO"),localize("country.SK"),localize("country.SI"),localize("country.ES"),localize("country.SE") };
const char* country_codes[] = {
"AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR",
@@ -64,7 +66,7 @@ bool draw_contact_form(contact* buffer, bool back_button_enabled = true, bool vi
if (selected_country == 0) {
for (int i = 0; i < country_count; i++)
{
- if (strcmp(country_codes[i], buffer->address.country) == 0)
+ if (strcmp(country_codes[i], buffer->address.country_code) == 0)
{
selected_country = countries[i];
break;
@@ -86,22 +88,40 @@ bool draw_contact_form(contact* buffer, bool back_button_enabled = true, bool vi
ImGui::EndCombo();
}
if (selected_country_index != -1) {
- strops_copy(buffer->address.country, country_codes[selected_country_index], IM_ARRAYSIZE(buffer->address.country));
+ strops_copy(buffer->address.country_code, country_codes[selected_country_index], IM_ARRAYSIZE(buffer->address.country_code));
}
ImGui::SameLine();ui_helper_draw_required_tag();
-
- ImGui::SetNextItemWidth(widthAvailable*0.5f);
- ImGui::InputTextWithHint(localize("contact.form.taxnumber"), localize("contact.form.taxnumber"), buffer->taxid, IM_ARRAYSIZE(buffer->taxid));
-
+
+ // 6. Contact type dropdown.
ImGui::SetNextItemWidth(widthAvailable*0.5f);
- ImGui::InputTextWithHint(localize("contact.form.businessnumber"), localize("contact.form.businessnumber"), buffer->businessid, IM_ARRAYSIZE(buffer->businessid));
-
+ const char* customer_types[2] = { localize("contact.form.type.business"), localize("contact.form.type.consumer") };
+ int currentItem = static_cast<int>(buffer->type);
+ if (ImGui::Combo(localize("contact.form.type"), &currentItem, customer_types, IM_ARRAYSIZE(customer_types)))
+ {
+ buffer->type = static_cast<contact_type>(currentItem);
+ }
+
+ // Fields only required for businesses.
+ if (buffer->type == contact_type::CONTACT_BUSINESS)
+ {
+ // 7. Tax number
+ ImGui::SetNextItemWidth(widthAvailable*0.5f);
+ ImGui::InputTextWithHint(localize("contact.form.taxnumber"), localize("contact.form.taxnumber"), buffer->taxid, IM_ARRAYSIZE(buffer->taxid));
+
+ // 8. Business number / Chamber of commerce
+ ImGui::SetNextItemWidth(widthAvailable*0.5f);
+ ImGui::InputTextWithHint(localize("contact.form.businessnumber"), localize("contact.form.businessnumber"), buffer->businessid, IM_ARRAYSIZE(buffer->businessid));
+ }
+
+ // 9. Email
ImGui::SetNextItemWidth(widthAvailable*0.5f);
ImGui::InputTextWithHint(localize("contact.form.email"), localize("contact.form.email"), buffer->email, IM_ARRAYSIZE(buffer->email));
+ // 10. Phone number
ImGui::SetNextItemWidth(widthAvailable*0.5f);
ImGui::InputTextWithHint(localize("contact.form.phonenumber"), localize("contact.form.phonenumber"), buffer->phone_number, IM_ARRAYSIZE(buffer->phone_number));
+ // 11. Bank account.
ImGui::SetNextItemWidth(widthAvailable*0.5f);
ImGui::InputTextWithHint(localize("contact.form.bankaccount"), localize("contact.form.bankaccount"), buffer->bank_account, IM_ARRAYSIZE(buffer->bank_account));
@@ -109,7 +129,7 @@ bool draw_contact_form(contact* buffer, bool back_button_enabled = true, bool vi
if (!viewing_only) {
bool can_save = strlen(buffer->name) > 0 && strlen(buffer->address.address1) > 0 &&
- strlen(buffer->address.address2) > 0 && strlen(buffer->address.country) > 0;
+ strlen(buffer->address.address2) > 0 && strlen(buffer->address.country_code) > 0;
if (!can_save) ImGui::BeginDisabled();
// Save button
@@ -132,6 +152,7 @@ static void draw_contact_list()
s32 max_page = (administration_get_contact_count() + items_per_page - 1) / items_per_page;
if (max_page == 0) max_page = 1;
+ // Table header controls: create button and pagination.
if (ImGui::Button(localize("form.create")))
{
current_view_state = view_state::CREATE;
@@ -142,6 +163,7 @@ static void draw_contact_list()
if (current_page >= max_page-1) current_page = max_page-1;
if (current_page < 0) current_page = 0;
+ // Navigate to prev page button.
ImGui::SameLine();
bool enable_prev = current_page > 0;
if (!enable_prev) ImGui::BeginDisabled();
@@ -151,6 +173,7 @@ static void draw_contact_list()
ImGui::SameLine();
ImGui::Text("(%d/%d)", current_page+1, max_page);
+ // Navigate to next page button.
ImGui::SameLine();
bool enable_next = current_page < max_page-1;
if (!enable_next) ImGui::BeginDisabled();
@@ -206,6 +229,7 @@ static void draw_contact_list()
}
}
+ // Confirmation popup before contact is deleted definitively.
if (ImGui::BeginPopupModal("ConfirmDeletePopup", nullptr, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoTitleBar)) {
ImGui::Text(localize("form.confirmDelete"));
ImGui::Separator();