From 4ce9711c3faf498597ca3919c202d500f22b847a Mon Sep 17 00:00:00 2001 From: GiorgioCALLIGARO Date: Fri, 8 May 2026 09:33:06 +0200 Subject: [PATCH] Add GL-001 OpenXML simulation outputs --- Assessments/Tradon/GL_001/decisions.md | 21 ++ Assessments/Tradon/GL_001/exposure_notes.md | 3 + Assessments/Tradon/GL_001/gap_mapping.md | 3 + Assessments/Tradon/GL_001/open_points.md | 2 + DevelopmentBacklog/Tradon/GL_001_backlog.md | 37 ++ Docs/reporting_fields_catalog.md | 2 + Docs/reporting_standards.md | 9 + Reports/GL/GL_001_general_ledger.md | 10 + build_gl001_openxml.ps1 | 337 ++++++++++++++++++ .../GL-001_Simulation_20260508_091451.xlsx | Bin 0 -> 30735 bytes 10 files changed, 424 insertions(+) create mode 100644 build_gl001_openxml.ps1 create mode 100644 outputs/019e065a-3d5e-7d70-9797-7e208041f996/GL-001_Simulation_20260508_091451.xlsx diff --git a/Assessments/Tradon/GL_001/decisions.md b/Assessments/Tradon/GL_001/decisions.md index ba32d67..d66f1e1 100644 --- a/Assessments/Tradon/GL_001/decisions.md +++ b/Assessments/Tradon/GL_001/decisions.md @@ -31,6 +31,7 @@ A decision is `Confirmed` when it has: | GL001-PD005 | Decide whether commodity fields `payable_qty` and `uom` are required in the first implementation. | GL001-Q007, GL001-Q013 | Open | | GL001-PD006 | Decide whether GL-001 requires a new flat reporting dataset. | GL001-Q008 | Open | | GL001-PD007 | Decide whether audit and source reference mappings are generic across accounting reports or specific to GL-001 first. | GL001-Q010 | Open | +| GL001-PD008 | Decide the `Currency_Rate` source, rate date, direct/inverse convention, precision and field naming convention. | GL001-Q014, GL001-Q015 | Open | ## Decision details @@ -173,3 +174,23 @@ TBD Validation expectation: TBD + +### GL001-PD008 Currency rate mapping and naming + +Status: Open + +Linked open points: GL001-Q014, GL001-Q015 + +Impacted backlog items: DEV-GL001-002, DEV-GL001-011 + +Decision: +TBD + +Rationale: +TBD + +Impact: +TBD + +Validation expectation: +TBD diff --git a/Assessments/Tradon/GL_001/exposure_notes.md b/Assessments/Tradon/GL_001/exposure_notes.md index 806c72b..9afb81d 100644 --- a/Assessments/Tradon/GL_001/exposure_notes.md +++ b/Assessments/Tradon/GL_001/exposure_notes.md @@ -49,6 +49,7 @@ Tryton covers the accounting foundation and several filters, but the target repo | Transaction-currency filter | Missing as an explicit GL parameter | | Opening and closing rows by account and transaction currency | Missing | | Transaction-currency running balance | Missing | +| FX rate exposure for `Currency_Rate` | Source, rate date, direct/inverse convention and precision not yet mapped | | Rich audit columns | Raw ingredients exist, but not exposed in normalized GL output | | Document, voucher and reference mapping | Requires functional arbitration | | Dedicated XLSX export | Not identified | @@ -85,3 +86,5 @@ Tryton supports important base-currency controls: - Trial Balance reuses the same account model, helping base-currency reconciliation. The main remaining control gap is transaction-currency reconciliation by account and currency, including opening, movement and closing balances. + +The new GL-001 `Currency_Rate` field also requires a conversion-control rule: for non-base-currency lines, exported transaction-currency amounts, base-currency amounts and the displayed FX rate should reconcile within the agreed rounding precision. diff --git a/Assessments/Tradon/GL_001/gap_mapping.md b/Assessments/Tradon/GL_001/gap_mapping.md index 77f4894..bab158b 100644 --- a/Assessments/Tradon/GL_001/gap_mapping.md +++ b/Assessments/Tradon/GL_001/gap_mapping.md @@ -50,6 +50,7 @@ | Running balance in base currency | Available through line balance logic | Covered in base currency | Covered | Confirmed | | Running balance in transaction currency | `amount_second_currency` is a signed amount | Missing transaction-currency running balance | High | Open | | Debit / credit split in transaction currency | Tryton stores signed `amount_second_currency` | Missing explicit debit and credit transaction-currency columns | Medium | Open | +| FX rate between transaction and base currency | Currency conversion data may exist on accounting moves or lines depending on origin | Source, rate date, direct/inverse convention and precision not mapped | High | Open | | Transaction-currency totals | Not explicit | Missing | High | Open | ## Output columns @@ -73,6 +74,7 @@ | `debit_base_currency` | line `debit` | Sign convention transformation may be required | Open | | `credit_base_currency` | line `credit` | Target expects negative credits | Open | | `balance_base_currency` | line `balance` | None identified | Confirmed | +| `Currency_Rate` | likely accounting move line currency conversion rate, company currency rate or derived base / transaction amount ratio | Mapping, naming convention and rate direction to confirm | Open | | `entered_date` | line or move `create_date` | Exposure in GL output required | Open | | `modified_date` | line or move `write_date` | Exposure in GL output required | Open | @@ -116,6 +118,7 @@ | --- | --- | --- | | High | Build or expose a flat detailed GL dataset matching `GL-001`, not only account summary plus drill-down | Open | | High | Add transaction-currency grouping, filtering and running balances | Open | +| High | Expose and document `Currency_Rate` with confirmed source, rate date, direct/inverse convention and precision | Open | | High | Add opening and closing rows by account and transaction currency | Open | | High | Define mapping for `document_number`, `journal_entry_number`, `voucher_number` and `reference` | Open | | High | Normalize source document metadata: document type, document date and supplier invoice number | Open | diff --git a/Assessments/Tradon/GL_001/open_points.md b/Assessments/Tradon/GL_001/open_points.md index 8942582..4c8a844 100644 --- a/Assessments/Tradon/GL_001/open_points.md +++ b/Assessments/Tradon/GL_001/open_points.md @@ -11,6 +11,8 @@ | GL001-Q005 | Q: Should credit columns be exported as negative values to match the target GL, even though Tryton UI displays credit as positive? | Sign convention | Open | | GL001-Q006 | Q: Which origin models should feed `document_date` and `document_type` in the first implementation? | Source document metadata | Open | | GL001-Q007 | Q: Can `payable_qty` and `uom` be derived reliably from `account.move.line.lot`, fees or invoice line origin for all commodity postings? | Commodity fields | Open | +| GL001-Q014 | Q: Which source, rate date, direct/inverse convention and precision should be used for `Currency_Rate`? | FX rate | Open | +| GL001-Q015 | Q: Should `Currency_Rate` keep its current report-specific capitalization or be harmonized to `currency_rate` with the wider field naming convention? | Field naming | Open | ## Implementation questions diff --git a/DevelopmentBacklog/Tradon/GL_001_backlog.md b/DevelopmentBacklog/Tradon/GL_001_backlog.md index 12d3c7a..74e90fb 100644 --- a/DevelopmentBacklog/Tradon/GL_001_backlog.md +++ b/DevelopmentBacklog/Tradon/GL_001_backlog.md @@ -29,6 +29,7 @@ This file is a working conversion layer between the documented GL-001 gaps and f | DEV-GL001-008 | Medium | Blocked | Audit metadata columns | GL001-PD007 | | DEV-GL001-009 | Medium | Blocked | CSV and XLSX export | DEV-GL001-001 | | DEV-GL001-010 | Low | Blocked | Commodity quantity fields | GL001-PD005 | +| DEV-GL001-011 | High | Blocked | Currency rate exposure | GL001-PD008 | ## DEV-GL001-001 Flat detailed GL dataset @@ -76,16 +77,19 @@ Development scope: - Add transaction currency as a grouping key for GL-001. - Add transaction-currency filtering where supported by the selected implementation design. - Calculate or expose `balance_transaction_currency`. +- Preserve the `Currency_Rate` value needed to explain conversion from transaction currency to base currency. Out of scope: - Deciding whether empty `second_currency` means company currency; this is GL001-PD002. +- Deciding the `Currency_Rate` source, direction and precision; this is GL001-PD008. Acceptance criteria: - GL-001 can be grouped by account and transaction currency. - Running transaction-currency balances are available for movement rows. - Base-currency lines have a documented transaction-currency fallback or exclusion rule. +- Non-base-currency rows retain a rate value or documented blank rule through DEV-GL001-011. Validation: @@ -319,3 +323,36 @@ Acceptance criteria: Validation: - Review a sample of commodity AP postings against source invoice, lot or fee evidence. + +## DEV-GL001-011 Currency rate exposure + +| Field | Value | +| --- | --- | +| Source gap | Expose and document `Currency_Rate` with confirmed source, rate date, direct/inverse convention and precision | +| Linked questions | GL001-Q014, GL001-Q015 | +| Linked decision | GL001-PD008 | +| Priority | High | +| Status | Blocked | + +Development scope: + +- Populate `Currency_Rate` for GL-001 rows where transaction currency and base currency differ. +- Define the blank or default rule for same-currency rows. +- Preserve enough information to reconcile transaction-currency amounts, base-currency amounts and the displayed FX rate. +- Keep the output column name aligned with `Reports/GL/GL_001_general_ledger.md` until naming is arbitrated. + +Out of scope: + +- Creating a global FX-rate service or replacing source accounting conversion logic. +- Renaming `Currency_Rate` to `currency_rate` before GL001-PD008 is confirmed. + +Acceptance criteria: + +- GL-001 output includes `Currency_Rate` in the required column order. +- Rate source and direct/inverse convention are documented for each supported origin. +- Same-currency and missing-rate cases follow a documented rule. +- Exported transaction/base amounts reconcile to `Currency_Rate` within agreed rounding tolerance. + +Validation: + +- Review a sample of foreign-currency accounting lines against source accounting amounts and stored currency rates. diff --git a/Docs/reporting_fields_catalog.md b/Docs/reporting_fields_catalog.md index 7e1e201..cd6d9d8 100644 --- a/Docs/reporting_fields_catalog.md +++ b/Docs/reporting_fields_catalog.md @@ -36,6 +36,7 @@ It is intended to prepare the future mapping with the target CTRM / ERP. The map | `counterparty` | Customer, supplier, broker or other party. | `GL-001`, `GL-002` | | `credit_base_currency` | Credit amount or movement in base currency. | `GL-001`, `GL-002`, `FIN-003`, `AR-001`, `AR-003`, `AP-001`, `AP-003` | | `credit_transaction_currency` | Credit amount or movement in transaction currency. | `GL-001`, `GL-002`, `FIN-003`, `AR-001`, `AR-003`, `AP-001`, `AP-003` | +| `Currency_Rate` | FX rate applied to convert the transaction-currency amount to base currency for the GL-001 line. | `GL-001` | | `customer` | Customer account code. | `AR-001`, `AR-002`, `AR-003` | | `customer_name` | Customer legal or commercial name. | `AR-001`, `AR-002`, `AR-003` | | `customer_reference` | Customer reference or external customer document reference, when available. | `AR-003` | @@ -95,6 +96,7 @@ It is intended to prepare the future mapping with the target CTRM / ERP. The map | `settlement_status` | Open, partially settled, settled and reversed logic depends on the ERP subledger. | | `voucher_number` | Should be mapped only if it is distinct from `document_number`, `supplier_invoice_number`, customer references and payment references. | | `supplier_invoice_number` | May map to a vendor invoice number, supplier reference or AP invoice field depending on ERP terminology. | +| `Currency_Rate` | GL-001-specific FX-rate field; mapping must confirm rate source, rate date, direct versus inverse convention, precision and naming convention. | | `purchase_invoice_number` | Should be mapped only if the ERP exposes a separate internal purchase invoice number. | | `customer_reference` | Should be mapped only if the ERP exposes a distinct customer-facing reference. | | `payment_reference` | Should be mapped only if payment references are stored separately from document references. | diff --git a/Docs/reporting_standards.md b/Docs/reporting_standards.md index 016e4c2..a6ad84c 100644 --- a/Docs/reporting_standards.md +++ b/Docs/reporting_standards.md @@ -19,6 +19,15 @@ These conventions define the default reporting vocabulary and presentation rules There is no separate `reporting_currency` in the current reporting scope. +When a report exposes an FX rate between transaction currency and base currency, it should document: + +- the rate source; +- the rate date; +- whether the rate is direct or inverse; +- the number of decimal places preserved in the export. + +`GL-001 General Ledger` currently defines the FX-rate field as `Currency_Rate`. This report-specific name should be preserved in GL-001 exports until the field naming convention is explicitly harmonized. + --- ## Amount fields diff --git a/Reports/GL/GL_001_general_ledger.md b/Reports/GL/GL_001_general_ledger.md index b6a0091..69e5100 100644 --- a/Reports/GL/GL_001_general_ledger.md +++ b/Reports/GL/GL_001_general_ledger.md @@ -140,6 +140,7 @@ Required columns: | `debit_transaction_currency` | Debit amount in transaction currency | | `credit_transaction_currency` | Credit amount in transaction currency | | `balance_transaction_currency` | Running balance in transaction currency | +| `Currency_Rate` | FX rate of the transaction | | `debit_base_currency` | Debit amount in base currency | | `credit_base_currency` | Credit amount in base currency | | `balance_base_currency` | Running balance in base currency | @@ -158,6 +159,12 @@ Amount sign convention for this report: - credit amounts are displayed as negative amounts; - running balances are calculated using this debit-positive and credit-negative convention. +FX rate presentation for this report: + +- `Currency_Rate` should display the FX rate used to convert transaction-currency amounts into base-currency amounts; +- for same-currency transactions, `Currency_Rate` should be either `1.000000` or blank according to the confirmed ERP export convention; +- the source of the rate, rate date, direct versus inverse convention and precision remain to be confirmed during mapping. + --- ## 9 Estimated volume @@ -181,6 +188,7 @@ The report may contain a large number of accounting lines and should support exp - Total debit and credit amounts should reconcile by selected period, account and transaction currency. - For each account and transaction currency grouping, opening balance plus movements should reconcile to closing balance. - The report total in base currency should reconcile with the trial balance for the same entity, period and account selection. +- For non-base-currency lines, transaction-currency amounts, `Currency_Rate` and base-currency amounts should reconcile within the agreed rounding tolerance. - Each line should retain enough audit references to trace back to the accounting document and source document where available. - AP-related entries should be identifiable by both `document_number` and `supplier_invoice_number` when the supplier invoice number is available. - Debit, credit and balance signs should follow the GL-specific convention defined in this report. @@ -190,3 +198,5 @@ The report may contain a large number of accounting lines and should support exp ## 12 Open questions - Should `posting_status` be displayed in the first implementation? This depends on the target ERP capabilities. +- Should `Currency_Rate` use the ERP stored accounting rate, a reporting FX rate, or a derived rate from transaction/base amounts? +- Should `Currency_Rate` remain capitalized as currently specified, or be renamed to `currency_rate` to match the wider field naming convention? diff --git a/build_gl001_openxml.ps1 b/build_gl001_openxml.ps1 new file mode 100644 index 0000000..2223a8a --- /dev/null +++ b/build_gl001_openxml.ps1 @@ -0,0 +1,337 @@ +$ErrorActionPreference = "Stop" + +$root = Split-Path -Parent $MyInvocation.MyCommand.Path +$threadId = if ($env:CODEX_THREAD_ID) { $env:CODEX_THREAD_ID } else { "local" } +$outDir = Join-Path $root ("outputs\" + $threadId) +New-Item -ItemType Directory -Force -Path $outDir | Out-Null +$outPath = Join-Path $outDir "GL-001_Simulation.xlsx" +if (Test-Path -LiteralPath $outPath) { + $outPath = Join-Path $outDir ("GL-001_Simulation_" + (Get-Date -Format "yyyyMMdd_HHmmss") + ".xlsx") +} +$buildDir = Join-Path $outDir ("_xlsx_build_" + [guid]::NewGuid().ToString("N")) +New-Item -ItemType Directory -Force -Path $buildDir | Out-Null + +function XmlEscape([object]$value) { + if ($null -eq $value) { return "" } + return [System.Security.SecurityElement]::Escape([string]$value) +} + +function ColName([int]$index) { + $name = "" + while ($index -gt 0) { + $mod = ($index - 1) % 26 + $name = [char](65 + $mod) + $name + $index = [math]::Floor(($index - $mod) / 26) + } + return $name +} + +function CellXml([int]$row, [int]$col, [object]$value, [int]$style = 0, [string]$formula = $null) { + $ref = "$(ColName $col)$row" + $s = if ($style -gt 0) { " s=`"$style`"" } else { "" } + if ($formula) { + return "$(XmlEscape $formula)" + } + if ($null -eq $value -or $value -eq "") { + return "" + } + if ($value -is [string] -and $value -match '^\d{4}-\d{2}-\d{2}$') { + $date = [datetime]::ParseExact($value, "yyyy-MM-dd", [Globalization.CultureInfo]::InvariantCulture) + $serial = [int](($date - [datetime]"1899-12-30").TotalDays) + return "$serial" + } + if ($value -is [int] -or $value -is [double] -or $value -is [decimal]) { + return "$value" + } + return "$(XmlEscape $value)" +} + +function RowXml([int]$rowNum, [object[]]$values, [int[]]$styles = $null) { + $cells = New-Object System.Collections.Generic.List[string] + for ($i = 0; $i -lt $values.Count; $i++) { + $style = if ($styles -and $i -lt $styles.Count) { $styles[$i] } else { 0 } + $cells.Add((CellXml $rowNum ($i + 1) $values[$i] $style)) + } + return "$($cells -join '')" +} + +function Write-Utf8File([string]$path, [string]$content) { + $dir = Split-Path -Parent $path + New-Item -ItemType Directory -Force -Path $dir | Out-Null + [System.IO.File]::WriteAllText($path, $content, [System.Text.UTF8Encoding]::new($false)) +} + +$accounts = @( + @("1000", "Cash and bank", "Asset", 85000, "Debit"), + @("1100", "Accounts receivable", "Asset", 42000, "Debit"), + @("1200", "Prepaid expenses", "Asset", 12000, "Debit"), + @("2000", "Accounts payable", "Liability", -36500, "Credit"), + @("2200", "VAT payable", "Liability", -8200, "Credit"), + @("3000", "Share capital", "Equity", -100000, "Credit"), + @("4000", "Consulting revenue", "Revenue", 0, "Credit"), + @("5000", "Subcontractor costs", "Expense", 0, "Debit"), + @("6000", "Operating expenses", "Expense", 0, "Debit"), + @("6900", "Bank fees", "Expense", 0, "Debit") +) + +$glHeaders = @( + "entity", "account", "account_name", "transaction_currency", "base_currency", + "posting_date", "document_date", "journal", "journal_entry_number", "document_number", + "document_type", "posting_status", "supplier_invoice_number", "counterparty", "voucher_number", + "description", "reference", "payable_qty", "uom", + "debit_transaction_currency", "credit_transaction_currency", "balance_transaction_currency", + "debit_base_currency", "credit_base_currency", "balance_base_currency", + "entered_date", "entered_by", "modified_date", "modified_by" +) + +$entries = @( + @("Open Squared SA", "1000", "Cash and bank", "CHF", "CHF", "2026-01-03", "2026-01-03", "BNK", "GL001-0001", "BNK-260103", "BANK", "Posted", "", "UBS", "VCH-260001", "Opening payroll funding transfer", "TRF-260103", "", "", 30000, 0, "", 30000, 0, "", "2026-01-03", "jsmith", "2026-01-03", "jsmith"), + @("Open Squared SA", "1000", "Cash and bank", "CHF", "CHF", "2026-01-20", "2026-01-20", "BNK", "GL001-0004", "PAY-260020", "PAYMENT", "Posted", "", "Partner X", "VCH-260004", "Supplier payment", "PAY-260020", "", "", 0, -18500, "", 0, -18500, "", "2026-01-20", "mdupont", "2026-01-20", "mdupont"), + @("Open Squared SA", "1000", "Cash and bank", "CHF", "CHF", "2026-02-17", "2026-02-17", "BNK", "GL001-0007", "REC-260217", "RECEIPT", "Posted", "", "Client B", "VCH-260007", "Receipt from Client B", "REC-260217", "", "", 48600, 0, "", 48600, 0, "", "2026-02-17", "jsmith", "2026-02-17", "jsmith"), + @("Open Squared SA", "1000", "Cash and bank", "CHF", "CHF", "2026-02-28", "2026-02-28", "BNK", "GL001-0008", "BNK-260228", "BANK", "Posted", "", "UBS", "VCH-260008", "Bank fees paid", "FEE-260228", "", "", 0, -85, "", 0, -85, "", "2026-02-28", "jsmith", "2026-02-28", "jsmith"), + @("Open Squared SA", "1000", "Cash and bank", "CHF", "CHF", "2026-03-22", "2026-03-22", "BNK", "GL001-0011", "PAY-260322", "PAYMENT", "Posted", "", "Microsoft", "VCH-260011", "Payment to Microsoft", "PAY-260322", "", "", 0, -2592, "", 0, -2592, "", "2026-03-22", "mdupont", "2026-03-22", "mdupont"), + @("Open Squared SA", "1000", "Cash and bank", "CHF", "CHF", "2026-03-29", "2026-03-29", "BNK", "GL001-0012", "REC-260329", "RECEIPT", "Posted", "", "Client C", "VCH-260012", "Receipt from Client C", "REC-260329", "", "", 70200, 0, "", 70200, 0, "", "2026-03-29", "jsmith", "2026-03-29", "jsmith"), + @("Open Squared SA", "1100", "Accounts receivable", "CHF", "CHF", "2026-01-03", "2026-01-03", "BNK", "GL001-0001", "BNK-260103", "BANK", "Posted", "", "Client A", "VCH-260001", "Collection of December receivable", "TRF-260103", "", "", 0, -30000, "", 0, -30000, "", "2026-01-03", "jsmith", "2026-01-03", "jsmith"), + @("Open Squared SA", "1100", "Accounts receivable", "CHF", "CHF", "2026-01-09", "2026-01-09", "SAJ", "GL001-0002", "INV-260001", "AR_INV", "Posted", "", "Client B", "VCH-260002", "January consulting invoice", "SOW-OS-001", "", "", 48600, 0, "", 48600, 0, "", "2026-01-09", "akeller", "2026-01-09", "akeller"), + @("Open Squared SA", "1100", "Accounts receivable", "CHF", "CHF", "2026-02-12", "2026-02-12", "SAJ", "GL001-0006", "INV-260019", "AR_INV", "Posted", "", "Client C", "VCH-260006", "February implementation invoice", "SOW-OS-019", "", "", 70200, 0, "", 70200, 0, "", "2026-02-12", "akeller", "2026-02-12", "akeller"), + @("Open Squared SA", "1100", "Accounts receivable", "CHF", "CHF", "2026-02-17", "2026-02-17", "BNK", "GL001-0007", "REC-260217", "RECEIPT", "Posted", "", "Client B", "VCH-260007", "Customer payment", "REC-260217", "", "", 0, -48600, "", 0, -48600, "", "2026-02-17", "jsmith", "2026-02-17", "jsmith"), + @("Open Squared SA", "1100", "Accounts receivable", "CHF", "CHF", "2026-03-15", "2026-03-15", "SAJ", "GL001-0010", "INV-260037", "AR_INV", "Posted", "", "Client A", "VCH-260010", "March advisory invoice", "SOW-OS-037", "", "", 37800, 0, "", 37800, 0, "", "2026-03-15", "akeller", "2026-03-15", "akeller"), + @("Open Squared SA", "1100", "Accounts receivable", "CHF", "CHF", "2026-03-29", "2026-03-29", "BNK", "GL001-0012", "REC-260329", "RECEIPT", "Posted", "", "Client C", "VCH-260012", "Customer payment", "REC-260329", "", "", 0, -70200, "", 0, -70200, "", "2026-03-29", "jsmith", "2026-03-29", "jsmith"), + @("Open Squared SA", "1200", "Prepaid expenses", "CHF", "CHF", "2026-03-04", "2026-03-01", "PUR", "GL001-0009", "BILL-260304", "AP_INV", "Posted", "INS-2026-031", "Insurer", "VCH-260009", "Annual insurance prepayment", "POL-2026", "", "", 9600, 0, "", 9600, 0, "", "2026-03-04", "mdupont", "2026-03-04", "mdupont"), + @("Open Squared SA", "2000", "Accounts payable", "CHF", "CHF", "2026-01-14", "2026-01-12", "PUR", "GL001-0003", "BILL-260014", "AP_INV", "Posted", "PX-2026-014", "Partner X", "VCH-260003", "Supplier payable", "PO-260014", "", "", 0, -18500, "", 0, -18500, "", "2026-01-14", "mdupont", "2026-01-14", "mdupont"), + @("Open Squared SA", "2000", "Accounts payable", "CHF", "CHF", "2026-01-20", "2026-01-20", "BNK", "GL001-0004", "PAY-260020", "PAYMENT", "Posted", "PX-2026-014", "Partner X", "VCH-260004", "Payment to supplier", "PAY-260020", "", "", 18500, 0, "", 18500, 0, "", "2026-01-20", "mdupont", "2026-01-20", "mdupont"), + @("Open Squared SA", "2000", "Accounts payable", "CHF", "CHF", "2026-02-01", "2026-01-31", "PUR", "GL001-0005", "BILL-260201", "AP_INV", "Posted", "MS-2026-002", "Microsoft", "VCH-260005", "Supplier payable", "MS-AZ-260201", "", "", 0, -2592, "", 0, -2592, "", "2026-02-01", "mdupont", "2026-02-01", "mdupont"), + @("Open Squared SA", "2000", "Accounts payable", "CHF", "CHF", "2026-03-04", "2026-03-01", "PUR", "GL001-0009", "BILL-260304", "AP_INV", "Posted", "INS-2026-031", "Insurer", "VCH-260009", "Insurance payable", "POL-2026", "", "", 0, -9600, "", 0, -9600, "", "2026-03-04", "mdupont", "2026-03-04", "mdupont"), + @("Open Squared SA", "2000", "Accounts payable", "CHF", "CHF", "2026-03-22", "2026-03-22", "BNK", "GL001-0011", "PAY-260322", "PAYMENT", "Posted", "MS-2026-002", "Microsoft", "VCH-260011", "Cloud subscriptions paid", "PAY-260322", "", "", 2592, 0, "", 2592, 0, "", "2026-03-22", "mdupont", "2026-03-22", "mdupont"), + @("Open Squared SA", "2200", "VAT payable", "CHF", "CHF", "2026-01-09", "2026-01-09", "SAJ", "GL001-0002", "INV-260001", "TAX", "Posted", "", "Client B", "VCH-260002", "Output VAT", "VAT-OUT", "", "", 0, -3600, "", 0, -3600, "", "2026-01-09", "akeller", "2026-01-09", "akeller"), + @("Open Squared SA", "2200", "VAT payable", "CHF", "CHF", "2026-02-01", "2026-01-31", "PUR", "GL001-0005", "BILL-260201", "TAX", "Posted", "MS-2026-002", "Microsoft", "VCH-260005", "Input VAT", "VAT-IN", "", "", 192, 0, "", 192, 0, "", "2026-02-01", "mdupont", "2026-02-01", "mdupont"), + @("Open Squared SA", "2200", "VAT payable", "CHF", "CHF", "2026-02-12", "2026-02-12", "SAJ", "GL001-0006", "INV-260019", "TAX", "Posted", "", "Client C", "VCH-260006", "Output VAT", "VAT-OUT", "", "", 0, -5200, "", 0, -5200, "", "2026-02-12", "akeller", "2026-02-12", "akeller"), + @("Open Squared SA", "2200", "VAT payable", "CHF", "CHF", "2026-03-15", "2026-03-15", "SAJ", "GL001-0010", "INV-260037", "TAX", "Posted", "", "Client A", "VCH-260010", "Output VAT", "VAT-OUT", "", "", 0, -2800, "", 0, -2800, "", "2026-03-15", "akeller", "2026-03-15", "akeller"), + @("Open Squared SA", "4000", "Consulting revenue", "CHF", "CHF", "2026-01-09", "2026-01-09", "SAJ", "GL001-0002", "INV-260001", "AR_INV", "Posted", "", "Client B", "VCH-260002", "January consulting revenue", "SOW-OS-001", "", "", 0, -45000, "", 0, -45000, "", "2026-01-09", "akeller", "2026-01-09", "akeller"), + @("Open Squared SA", "4000", "Consulting revenue", "CHF", "CHF", "2026-02-12", "2026-02-12", "SAJ", "GL001-0006", "INV-260019", "AR_INV", "Posted", "", "Client C", "VCH-260006", "February implementation revenue", "SOW-OS-019", "", "", 0, -65000, "", 0, -65000, "", "2026-02-12", "akeller", "2026-02-12", "akeller"), + @("Open Squared SA", "4000", "Consulting revenue", "CHF", "CHF", "2026-03-15", "2026-03-15", "SAJ", "GL001-0010", "INV-260037", "AR_INV", "Posted", "", "Client A", "VCH-260010", "March advisory revenue", "SOW-OS-037", "", "", 0, -35000, "", 0, -35000, "", "2026-03-15", "akeller", "2026-03-15", "akeller"), + @("Open Squared SA", "5000", "Subcontractor costs", "CHF", "CHF", "2026-01-14", "2026-01-12", "PUR", "GL001-0003", "BILL-260014", "AP_INV", "Posted", "PX-2026-014", "Partner X", "VCH-260003", "Subcontractor delivery costs", "PO-260014", "", "", 18500, 0, "", 18500, 0, "", "2026-01-14", "mdupont", "2026-01-14", "mdupont"), + @("Open Squared SA", "6000", "Operating expenses", "CHF", "CHF", "2026-02-01", "2026-01-31", "PUR", "GL001-0005", "BILL-260201", "AP_INV", "Posted", "MS-2026-002", "Microsoft", "VCH-260005", "Cloud subscriptions", "MS-AZ-260201", "", "", 2400, 0, "", 2400, 0, "", "2026-02-01", "mdupont", "2026-02-01", "mdupont"), + @("Open Squared SA", "6900", "Bank fees", "CHF", "CHF", "2026-02-28", "2026-02-28", "BNK", "GL001-0008", "BNK-260228", "BANK", "Posted", "", "UBS", "VCH-260008", "Monthly banking charges", "FEE-260228", "", "", 85, 0, "", 85, 0, "", "2026-02-28", "jsmith", "2026-02-28", "jsmith") +) + +$styles = @' + + + + + + + + + + + + + + + + + + + + + + + + + +'@ + +function SheetDoc([string]$sheetData, [string]$cols, [string]$merge = "", [string]$autoFilter = "", [string]$pane = "") { + return "$pane$cols$sheetData$autoFilter$merge" +} + +$coverRows = @() +$coverRows += "$(CellXml 2 2 'GL-001 - Simulation de sortie du rapport' 1)" +$coverRows += "$(CellXml 4 2 'Ce classeur simule une sortie GL-001 pour validation de format, de colonnes, de totaux et de contrôles. Les écritures et soldes sont fictifs.' 0)" +$coverData = @( + @("Entité", "Open Squared SA"), + @("Rapport", "GL-001 - General Ledger Detail"), + @("Période", "2026-01-01 à 2026-03-31"), + @("Devise", "CHF"), + @("Base", "Comptabilité d'exercice"), + @("Statut", "Simulation"), + @("Préparé le", (Get-Date -Format "yyyy-MM-dd")) +) +for ($i = 0; $i -lt $coverData.Count; $i++) { $coverRows += RowXml (6 + $i) @("", $coverData[$i][0], $coverData[$i][1]) @(0,4,4) } +$coverRows += "$(CellXml 14 2 'Structure du classeur' 2)" +$coverRows += RowXml 15 @("", "00_Cover", "Conventions, statut et navigation") @(0,4,4) +$coverRows += RowXml 16 @("", "01_Parameters", "Paramètres de simulation et soldes d'ouverture") @(0,4,4) +$coverRows += RowXml 17 @("", "02_GL001_Output", "Sortie détaillée simulée du rapport GL-001") @(0,4,4) +$coverRows += RowXml 18 @("", "03_Controls", "Contrôles de balance, complétude et cohérence") @(0,4,4) +$coverCols = "" +$coverMerge = "" + +$paramRows = @() +$paramRows += "$(CellXml 1 1 'Paramètres et soldes d''ouverture' 1)" +$paramInfo = @( + @("Entity", "Open Squared SA"), + @("Report code", "GL-001"), + @("Period start", "2026-01-01"), + @("Period end", "2026-03-31"), + @("Currency", "CHF"), + @("Scenario", "Simulated output") +) +for ($i = 0; $i -lt $paramInfo.Count; $i++) { + $valueStyle = if ((3 + $i) -in @(5, 6)) { 6 } else { 5 } + $paramRows += RowXml (3 + $i) $paramInfo[$i] @(4,$valueStyle) +} +$paramRows += RowXml 10 @("Account", "Account name", "Type", "Opening balance CHF", "Normal sign") @(2,2,2,2,2) +for ($i = 0; $i -lt $accounts.Count; $i++) { $paramRows += RowXml (11 + $i) $accounts[$i] @(4,4,4,3,4) } +$paramCols = "" +$paramMerge = "" + +$glRows = @() +$glRows += "$(CellXml 1 1 'GL-001 - General Ledger Detail (simulation)' 1)" +$glRows += RowXml 3 $glHeaders @(2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2) +for ($i = 0; $i -lt $entries.Count; $i++) { + $r = 4 + $i + $e = $entries[$i] + $stylesRow = @(4,4,4,4,4,6,6,4,4,4,4,4,4,4,4,4,4,3,4,3,3,3,3,3,3,6,4,6,4) + $cells = New-Object System.Collections.Generic.List[string] + for ($c = 1; $c -le $glHeaders.Count; $c++) { + if ($c -eq 22) { + if ($r -eq 4) { + $formula = "INDEX('01_Parameters'!`$D`$11:`$D`$20,MATCH(B4,'01_Parameters'!`$A`$11:`$A`$20,0))+T4+U4" + } else { + $formula = "IF(AND(B$r=B$($r-1),D$r=D$($r-1)),V$($r-1)+T$r+U$r,INDEX('01_Parameters'!`$D`$11:`$D`$20,MATCH(B$r,'01_Parameters'!`$A`$11:`$A`$20,0))+T$r+U$r)" + } + $cells.Add((CellXml $r $c "" 3 $formula)) + } elseif ($c -eq 25) { + if ($r -eq 4) { + $formula = "INDEX('01_Parameters'!`$D`$11:`$D`$20,MATCH(B4,'01_Parameters'!`$A`$11:`$A`$20,0))+W4+X4" + } else { + $formula = "IF(AND(B$r=B$($r-1),E$r=E$($r-1)),Y$($r-1)+W$r+X$r,INDEX('01_Parameters'!`$D`$11:`$D`$20,MATCH(B$r,'01_Parameters'!`$A`$11:`$A`$20,0))+W$r+X$r)" + } + $cells.Add((CellXml $r $c "" 3 $formula)) + } else { + $cells.Add((CellXml $r $c $e[$c-1] $stylesRow[$c-1])) + } + } + $glRows += "$($cells -join '')" +} +$lastGlRow = 3 + $entries.Count +$glCols = "" +$glMerge = "" +$glFilter = "" +$glPane = "" + +$checkRows = @() +$checkRows += "$(CellXml 1 1 'Contrôles GL-001' 1)" +$checkRows += RowXml 3 @("Check", "Actual", "Expected", "Difference", "Tolerance", "Status", "Notes") @(2,2,2,2,2,2,2) +$checkLabels = @( + @("Transaction currency movements net to zero", "SUM('02_GL001_Output'!T4:T$lastGlRow)+SUM('02_GL001_Output'!U4:U$lastGlRow)", 0, "B4-C4", 0.01, "IF(ABS(D4)<=E4,`"OK`",`"Review`")", "Credits are displayed as negative amounts per GL-001 sign convention."), + @("Base currency movements net to zero", "SUM('02_GL001_Output'!W4:W$lastGlRow)+SUM('02_GL001_Output'!X4:X$lastGlRow)", 0, "B5-C5", 0.01, "IF(ABS(D5)<=E5,`"OK`",`"Review`")", "Base currency should reconcile to the same balanced journal movements."), + @("No blank account codes", "COUNTBLANK('02_GL001_Output'!B4:B$lastGlRow)", 0, "B6-C6", 0, "IF(D6=0,`"OK`",`"Review`")", "Every transaction line should carry an account code."), + @("All rows posted", "COUNTIF('02_GL001_Output'!L4:L$lastGlRow,`"<>Posted`")", 0, "B7-C7", 0, "IF(D7=0,`"OK`",`"Review`")", "Simulation assumes only posted lines are in scope."), + @("Report period start", "MIN('02_GL001_Output'!F4:F$lastGlRow)", "'01_Parameters'!B5", "B8-C8", 0, "IF(B8>=C8,`"OK`",`"Review`")", "No transaction before selected period start."), + @("Report period end", "MAX('02_GL001_Output'!F4:F$lastGlRow)", "'01_Parameters'!B6", "B9-C9", 0, "IF(B9<=C9,`"OK`",`"Review`")", "No transaction after selected period end."), + @("AP invoices carry supplier invoice number", "COUNTIFS('02_GL001_Output'!K4:K$lastGlRow,`"AP_INV`",'02_GL001_Output'!M4:M$lastGlRow,`"`")", 0, "B10-C10", 0, "IF(D10=0,`"OK`",`"Review`")", "AP-related entries should expose supplier_invoice_number when available.") +) +for ($i = 0; $i -lt $checkLabels.Count; $i++) { + $r = 4 + $i + $item = $checkLabels[$i] + if ($item[2] -is [int]) { + $expectedCell = CellXml $r 3 $item[2] 3 + } else { + $expectedCell = CellXml $r 3 "" 3 $item[2] + } + $cells = @( + (CellXml $r 1 $item[0] 4), + (CellXml $r 2 "" 3 $item[1]), + $expectedCell, + (CellXml $r 4 "" 3 $item[3]), + (CellXml $r 5 $item[4] 3), + (CellXml $r 6 "" 4 $item[5]), + (CellXml $r 7 $item[6] 4) + ) + $checkRows += "$($cells -join '')" +} +$checkRows += RowXml 13 @("Account", "Account name", "Currency", "Opening TC", "Debits TC", "Credits TC", "Closing TC", "Debits base", "Credits base", "Closing base") @(2,2,2,2,2,2,2,2,2,2) +for ($i = 0; $i -lt $accounts.Count; $i++) { + $r = 14 + $i + $src = 11 + $i + $cells = @( + (CellXml $r 1 "" 4 "'01_Parameters'!A$src"), + (CellXml $r 2 "" 4 "'01_Parameters'!B$src"), + (CellXml $r 3 "CHF" 4), + (CellXml $r 4 "" 3 "'01_Parameters'!D$src"), + (CellXml $r 5 "" 3 "SUMIFS('02_GL001_Output'!T:T,'02_GL001_Output'!B:B,A$r,'02_GL001_Output'!D:D,C$r)"), + (CellXml $r 6 "" 3 "SUMIFS('02_GL001_Output'!U:U,'02_GL001_Output'!B:B,A$r,'02_GL001_Output'!D:D,C$r)"), + (CellXml $r 7 "" 3 "D$r+E$r+F$r"), + (CellXml $r 8 "" 3 "SUMIFS('02_GL001_Output'!W:W,'02_GL001_Output'!B:B,A$r,'02_GL001_Output'!E:E,C$r)"), + (CellXml $r 9 "" 3 "SUMIFS('02_GL001_Output'!X:X,'02_GL001_Output'!B:B,A$r,'02_GL001_Output'!E:E,C$r)"), + (CellXml $r 10 "" 3 "D$r+H$r+I$r") + ) + $checkRows += "$($cells -join '')" +} +$checkCols = "" +$checkMerge = "" + +Write-Utf8File (Join-Path $buildDir "[Content_Types].xml") @' + + + + + + + + + + + +'@ +Write-Utf8File (Join-Path $buildDir "_rels\.rels") @' + + + + +'@ +Write-Utf8File (Join-Path $buildDir "xl\_rels\workbook.xml.rels") @' + + + + + + + + +'@ +Write-Utf8File (Join-Path $buildDir "xl\workbook.xml") @' + + + + + + + + + + +'@ +Write-Utf8File (Join-Path $buildDir "xl\styles.xml") $styles +Write-Utf8File (Join-Path $buildDir "xl\worksheets\sheet1.xml") (SheetDoc ($coverRows -join "") $coverCols $coverMerge) +Write-Utf8File (Join-Path $buildDir "xl\worksheets\sheet2.xml") (SheetDoc ($paramRows -join "") $paramCols $paramMerge "") +Write-Utf8File (Join-Path $buildDir "xl\worksheets\sheet3.xml") (SheetDoc ($glRows -join "") $glCols $glMerge $glFilter $glPane) +Write-Utf8File (Join-Path $buildDir "xl\worksheets\sheet4.xml") (SheetDoc ($checkRows -join "") $checkCols $checkMerge) + +Add-Type -AssemblyName System.IO.Compression.FileSystem +Add-Type -AssemblyName System.IO.Compression +$zip = [System.IO.Compression.ZipFile]::Open($outPath, [System.IO.Compression.ZipArchiveMode]::Create) +try { + Get-ChildItem -LiteralPath $buildDir -Recurse -File | ForEach-Object { + $relative = $_.FullName.Substring($buildDir.Length + 1).Replace('\', '/') + [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($zip, $_.FullName, $relative) | Out-Null + } +} +finally { + $zip.Dispose() +} +Remove-Item -LiteralPath $buildDir -Recurse -Force -ErrorAction SilentlyContinue +Write-Output $outPath diff --git a/outputs/019e065a-3d5e-7d70-9797-7e208041f996/GL-001_Simulation_20260508_091451.xlsx b/outputs/019e065a-3d5e-7d70-9797-7e208041f996/GL-001_Simulation_20260508_091451.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..8c459d9078050a1fdd2ba665e768098ed1fed8e6 GIT binary patch literal 30735 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 H*Sr4%EF&>c literal 0 HcmV?d00001