diff options
| -rw-r--r-- | NOTES.md | 132 | ||||
| -rw-r--r-- | README.md | 14 | ||||
| -rw-r--r-- | docs/CHANGES.rst | 23 | ||||
| -rw-r--r-- | docs/README.rst | 40 | ||||
| -rw-r--r-- | docs/STORAGE.rst | 131 | ||||
| -rw-r--r-- | include/administration.hpp | 9 | ||||
| -rw-r--r-- | src/administration.cpp | 3 | ||||
| -rw-r--r-- | src/locales/en.cpp | 3 | ||||
| -rw-r--r-- | src/ui/ui_contacts.cpp | 56 |
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"), ¤tItem, 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(); |
