From 79657eaad0f12c60b14cfbab2d54c0d0addc2099 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Sun, 29 Mar 2026 21:28:09 +0200 Subject: [PATCH 01/53] new doc with issue and concerns --- PayablesAgentVATIssue.md | 63 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 PayablesAgentVATIssue.md diff --git a/PayablesAgentVATIssue.md b/PayablesAgentVATIssue.md new file mode 100644 index 0000000000..97afdf64ee --- /dev/null +++ b/PayablesAgentVATIssue.md @@ -0,0 +1,63 @@ +# Payables Agent - VAT Product Posting Group Not Applied Per Line + +## Summary + +The Payables Agent (Purchase Draft) correctly extracts VAT information from scanned invoices but fails to apply the correct VAT Product Posting Groups to individual purchase invoice lines. All lines default to "STANDARD" regardless of the actual VAT rate on the source invoice. + +## Reproduction + +**Invoice:** 164452 from Viking Direct Ltd (Vendor 10317), GBP +**Lines:** + +| Description | G/L Account | Expected VAT | +|---|---|---| +| PK 4 KITCHEN ROLL WHITE 10... | 640300 | Standard (20%) | +| COFFEE ORIGINAL 750G TIN N... | 630800 | Zero-rated (0%) | +| PK1100 BLACK TEA 800337 PG... | 630800 | Zero-rated (0%) | +| PK12 UHT SEM SKIMMED MILK... | 630800 | Zero-rated (0%) | +| DIVIDER EXACOMPTA ECO 12 T... | 630100 | Standard (20%) | + +**Totals:** Amount Excl. VAT = 110.55, Total VAT = 3.64, Amount Incl. VAT = 114.19 + +The total VAT of 3.64 on 110.55 net (~3.3%) proves a mix of zero-rated and standard-rated lines. UK VAT rules zero-rate most food items (coffee, tea, milk). + +## Current Behavior + +- The Purchase Draft accurately breaks down the invoice with correct net, VAT, and gross amounts per line and overall totals. +- When transferred into the Purchase Invoice, **all lines receive VAT Prod. Posting Group = "STANDARD"**. +- The VAT rate differences from the source invoice are lost. + +## Expected Behavior + +The agent should use the VAT rate information it already extracts from the scanned invoice to assign the **correct VAT Prod. Posting Group** per line (e.g., "ZERO" for zero-rated food items, "STANDARD" for taxable items). + +## Impact + +- Incorrect VAT amounts on posted invoices +- Wrong VAT reporting/returns +- Posted totals won't match the actual supplier invoice + +## Internal Discussion + +### Proposed Fix (Artur Ventsel) + +We have the VAT rate from ADI (Azure Document Intelligence). When we create a purchase line, we only consider the item or G/L account — whatever VAT Product Posting Group comes as default from those tables ends up on the purchase line, which is exactly what the customer is reporting. + +**Suggestion:** If we have the VAT rate from ADI, try to find the VAT Posting Setup for the vendor's VAT Business Posting Group and, if a matching setup exists, switch the VAT Product Posting Group on the line accordingly. + +### Concerns (Joshua Martínez Pineda) + +1. **Document totals validation exists** — the "Document totals" section should show the value as read from the invoice, and if totals don't match, posting is blocked. But we don't automatically change VAT Product Posting Groups to make lines add up to the correct VAT total. + +2. **VAT data availability varies** — PDF invoices may have the VAT rate per line, per document, or both (see ADI docs). Even if we move away from ADI, this variability remains. + +3. **Combinatorial complexity** — If only the document's total VAT is available and it doesn't match for N lines, the approach would require trying all combinations of VAT Product Posting Groups (N^K) to find one that adds up to the total. Multiple combinations could produce the same total but only one may be correct for reporting purposes. + +4. **Historical decision** — Due to these complexities, the team previously decided not to guess and instead rely on document totals validation. + +5. **Possible middle ground** — A warning that something is off with VAT early in the draft page would help. The fix could be scoped to cases where the tax amount is specified per line on the invoice, which is more deterministic. However, the ambiguity problem (concern #3) can still apply even with per-line amounts. + +## Notes + +- Continia (competing product) handles this correctly — it can post by line item and apply a VAT posting group per item type. +- If only capturing balances and not items, zero-rated lines are missed entirely. From 3baca49763bf8622e961f5edeac5054866c418ba Mon Sep 17 00:00:00 2001 From: ventselartur Date: Mon, 30 Mar 2026 11:13:48 +0200 Subject: [PATCH 02/53] save plans --- PayablesAgentVATIssue.md | 12 + ...-30-vat-posting-group-resolution-design.md | 116 ++++ ...03-30-vat-posting-group-resolution-plan.md | 507 ++++++++++++++++++ 3 files changed, 635 insertions(+) create mode 100644 src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-design.md create mode 100644 src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-plan.md diff --git a/PayablesAgentVATIssue.md b/PayablesAgentVATIssue.md index 97afdf64ee..9c58ad8cdc 100644 --- a/PayablesAgentVATIssue.md +++ b/PayablesAgentVATIssue.md @@ -57,6 +57,18 @@ We have the VAT rate from ADI (Azure Document Intelligence). When we create a pu 5. **Possible middle ground** — A warning that something is off with VAT early in the draft page would help. The fix could be scoped to cases where the tax amount is specified per line on the invoice, which is more deterministic. However, the ambiguity problem (concern #3) can still apply even with per-line amounts. +## Design Decision + +Design spec: [2026-03-30-vat-posting-group-resolution-design.md](src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-design.md) + +Key decisions: +1. **Normalize ADI handler** — compute VAT percentage from `tax / Sub Total * 100` instead of storing the raw tax amount +2. **New `[BC] VAT Prod. Posting Group` field** (field 110) on E-Document Purchase Line, resolved during Prepare Draft +3. **Lookup in Prepare Draft** — query VAT Posting Setup by vendor's VAT Bus. Posting Group + extracted VAT %. Single match → set. Zero/multiple → leave blank. +4. **Single-line fallback** — if no per-line VAT data but only one line, compute rate from header Total VAT +5. **Notification banner** — new "VAT Rate Mismatch" notification when resolution fails (line has VAT Rate but no match found) +6. **No combinatorial solving** — explicitly out of scope for multi-line invoices without per-line VAT data + ## Notes - Continia (competing product) handles this correctly — it can post by line item and apply a VAT posting group per item type. diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-design.md b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-design.md new file mode 100644 index 0000000000..676706b545 --- /dev/null +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-design.md @@ -0,0 +1,116 @@ +# VAT Product Posting Group Auto-Resolution + +**Date:** 2026-03-30 +**Status:** Draft +**Author:** Artur Ventsel + +## Problem + +The Payables Agent (E-Document import pipeline) extracts VAT information from scanned invoices but does not apply the correct VAT Product Posting Group per purchase line. All lines default to the VAT Prod. Posting Group inherited from the G/L Account or Item card, regardless of the actual VAT rate on the source invoice. + +This causes incorrect VAT amounts on posted invoices, wrong VAT reporting, and mismatched totals when an invoice contains lines with mixed VAT rates (e.g., standard 20% and zero-rated 0% items). + +### Root Cause + +1. **ADI handler stores wrong data type:** The ADI handler maps the `tax` field (a monetary amount per line, e.g., `$6.00`) into the `"VAT Rate"` field on `E-Document Purchase Line`. The PEPPOL and MLLM handlers correctly populate `"VAT Rate"` with a percentage. This inconsistency means downstream code cannot reliably interpret the field. + +2. **No resolution logic exists:** The `"VAT Rate"` field is populated but never consumed. Neither the Prepare Draft nor Finish Draft stages use it to look up or override the VAT Product Posting Group. + +## Design + +### 1. ADI Handler Normalization + +**File:** `EDocumentADIHandler.Codeunit.al` — `PopulateEDocumentPurchaseLine` + +**Change:** Replace the current `tax` → `"VAT Rate"` mapping (which stores a monetary amount) with a computation that produces a percentage: + +1. Read ADI's `tax` field into a local decimal variable (tax amount). +2. If the line has both a tax amount > 0 and `Sub Total` > 0, compute: `VAT Rate = (tax amount / Sub Total) * 100`. +3. If tax amount = 0, set `VAT Rate = 0` (zero-rated — valid and useful). +4. If `Sub Total` = 0, skip — leave `VAT Rate` as 0. + +After this change, all three handlers (ADI, PEPPOL, MLLM) consistently populate `"VAT Rate"` as a percentage. + +### 2. New Field on E-Document Purchase Line + +**File:** `EDocumentPurchaseLine.Table.al` + +Add a new field in the `[BC]` validated fields range (101-200): + +- **Field 110:** `[BC] VAT Prod. Posting Group` (Code[20]) + +This follows the existing pattern where `[BC]`-prefixed fields hold BC-resolved values that the user can review and edit on the draft page. + +### 3. Draft Subform Page Column + +**File:** `EDocPurchaseDraftSubform.Page.al` + +Add an editable column for `[BC] VAT Prod. Posting Group` with lookup support, positioned after the existing line type/number columns. The user can manually correct the posting group if auto-resolution fails or picks the wrong group. + +### 4. VAT Posting Group Resolution in Prepare Draft + +**File:** `PreparePurchaseEDocDraft.Codeunit.al` — `PrepareDraft` + +After `IPurchaseLineProvider.GetPurchaseLine` resolves the line type/number (inside the existing line loop at lines 68-74), add VAT Posting Group resolution: + +``` +For each line: + 1. Get the vendor's VAT Bus. Posting Group. + 2. Determine the VAT rate to match: + a. If line "VAT Rate" > 0 → use it directly. + b. If line "VAT Rate" = 0 AND this is the only line + AND header "Total VAT" > 0 AND header "Sub Total" > 0: + → compute rate = (Total VAT / Sub Total) * 100. + c. Otherwise → skip (no data to work with). + 3. Query VAT Posting Setup where: + - VAT Bus. Posting Group = vendor's group + - VAT % = determined rate (with rounding tolerance ~0.01) + 4. If exactly one match → set [BC] VAT Prod. Posting Group. + 5. If zero or multiple matches → leave blank (default applies at finalization). +``` + +**Rounding tolerance:** VAT rates computed from amounts may not be exact (e.g., `3.64 / 18.20 * 100 = 20.0` but edge cases exist). A tolerance of ~0.01 on the VAT % comparison avoids false mismatches. + +### 5. Finish Draft — Apply Resolved Posting Group + +**File:** `EDocCreatePurchaseInvoice.Codeunit.al` — `CreatePurchaseInvoiceLine` + +After `PurchaseLine.Validate("No.", ...)` (line 216) sets the default VAT Prod. Posting Group from the G/L Account/Item, check if the draft line has a resolved value: + +```al +if EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" <> '' then + PurchaseLine.Validate("VAT Prod. Posting Group", + EDocumentPurchaseLine."[BC] VAT Prod. Posting Group"); +``` + +If the field is blank (resolution failed or user didn't set it), do nothing — the default from the G/L Account/Item applies (current behavior preserved). + +### 6. Notification for Failed Resolution + +**Files:** `EDocumentNotification.Codeunit.al`, `E-Document Notification Type` enum + +**New notification type:** `"VAT Rate Mismatch"` + +**Trigger:** After the Prepare Draft line loop completes, if any line has a non-zero `"VAT Rate"` but a blank `[BC] VAT Prod. Posting Group` (meaning lookup was attempted but found zero or multiple matches). + +**Message:** "VAT Product Posting Groups could not be automatically determined for one or more lines. Please review before creating the invoice." + +**Behavior:** Shown as a banner notification on the draft page, following the same pattern as the existing "Vendor matched by name but not by address" notification. Includes Dismiss and Don't show again actions. + +## Explicitly Out of Scope + +- **Combinatorial solving** — No attempt to find posting group combinations that make line VATs add up to the header total for multi-line invoices without per-line VAT data. +- **Blocking finalization** — VAT mismatch is informational, not a hard block. The existing document totals validation at posting time remains the enforcement mechanism. +- **ADI `taxRate` field** — The ADI invoice model may provide a `taxRate` string field. This design computes the rate from `tax / amount` instead, which works regardless of whether ADI provides `taxRate`. Adding direct `taxRate` extraction can be done as a follow-up. + +## Key Files + +| File | Role | +|---|---| +| `EDocumentADIHandler.Codeunit.al` | ADI data extraction — normalize tax amount to rate | +| `EDocumentPurchaseLine.Table.al` | Add `[BC] VAT Prod. Posting Group` field | +| `EDocPurchaseDraftSubform.Page.al` | Add editable column for new field | +| `PreparePurchaseEDocDraft.Codeunit.al` | VAT Posting Setup lookup logic | +| `EDocCreatePurchaseInvoice.Codeunit.al` | Apply resolved posting group to purchase line | +| `EDocumentNotification.Codeunit.al` | New VAT Rate Mismatch notification type | +| `E-Document Notification Type` enum | New enum value | diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-plan.md b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-plan.md new file mode 100644 index 0000000000..ab3774b0a8 --- /dev/null +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-plan.md @@ -0,0 +1,507 @@ +# VAT Product Posting Group Auto-Resolution — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Automatically resolve and apply the correct VAT Product Posting Group per E-Document purchase line based on extracted VAT rate data, with a notification when resolution fails. + +**Architecture:** Normalize VAT data at extraction time (ADI handler), add a `[BC] VAT Prod. Posting Group` field to the staging table, resolve it during Prepare Draft via VAT Posting Setup lookup, and apply it during Finish Draft when creating the BC Purchase Line. + +**Tech Stack:** AL (Business Central), E-Document Core framework, VAT Posting Setup table (325) + +--- + +### Task 1: Normalize ADI handler — compute VAT percentage from tax amount + +**Files:** +- Modify: `src/Apps/W1/EDocument/App/src/Processing/Import/StructureReceivedEDocument/EDocumentADIHandler.Codeunit.al:174-188` +- Modify: `src/Apps/W1/EDocument/Test/src/Processing/EDocStructuredValidations.Codeunit.al:59,70,80` + +The ADI handler currently stores the `tax` monetary amount directly into `"VAT Rate"`. After this change, it computes a percentage: `(tax / Sub Total) * 100`. + +- [ ] **Step 1: Update `PopulateEDocumentPurchaseLine` in the ADI handler** + +Replace the current line 185: +```al +EDocumentJsonHelper.SetCurrencyValueInField('tax', FieldsJsonObject, TempEDocPurchaseLine."VAT Rate", TempEDocPurchaseLine."Currency Code"); +``` + +With: +```al + ComputeVATRateFromTaxAmount(FieldsJsonObject, TempEDocPurchaseLine); +``` + +Add a new local procedure after `PopulateEDocumentPurchaseLine`: +```al + local procedure ComputeVATRateFromTaxAmount(FieldsJsonObject: JsonObject; var TempEDocPurchaseLine: Record "E-Document Purchase Line" temporary) + var + TaxAmount: Decimal; + UnusedCurrencyCode: Code[10]; + begin + EDocumentJsonHelper.SetCurrencyValueInField('tax', FieldsJsonObject, TaxAmount, UnusedCurrencyCode); + if (TaxAmount = 0) or (TempEDocPurchaseLine."Sub Total" = 0) then + exit; + TempEDocPurchaseLine."VAT Rate" := Round((TaxAmount / TempEDocPurchaseLine."Sub Total") * 100, 0.01); + end; +``` + +Note: `"Sub Total"` is already populated from `amount` at line 176 before this runs — the field order in `PopulateEDocumentPurchaseLine` ensures this. + +- [ ] **Step 2: Update existing CAPI test assertions** + +In `EDocStructuredValidations.Codeunit.al`, the CAPI test fixture has three lines with tax amounts $6, $3, $1 on sub totals 60, 30, 10 — all computing to 10%. + +Update line 59: +```al + Assert.AreEqual(10, EDocumentPurchaseLine."VAT Rate", 'The VAT rate in the purchase line does not match the expected percentage.'); +``` + +Update line 70: +```al + Assert.AreEqual(10, EDocumentPurchaseLine."VAT Rate", 'The VAT rate in the purchase line does not match the expected percentage.'); +``` + +Update line 80: +```al + Assert.AreEqual(10, EDocumentPurchaseLine."VAT Rate", 'The VAT rate in the purchase line does not match the expected percentage.'); +``` + +- [ ] **Step 3: Verify PEPPOL and MLLM assertions are unchanged** + +PEPPOL assertions at lines 142, 153 expect `25` — these are already percentages, no change needed. +MLLM assertions at lines 197, 207, 217 expect `15`, `10`, `15` — already percentages, no change needed. + +- [ ] **Step 4: Compile and run tests** + +Run: `al compile` and `al run_tests` for the E-Document app and test app. +Expected: All existing CAPI, PEPPOL, and MLLM structured tests pass with the updated assertions. + +- [ ] **Step 5: Commit** + +```bash +git add src/Apps/W1/EDocument/App/src/Processing/Import/StructureReceivedEDocument/EDocumentADIHandler.Codeunit.al +git add src/Apps/W1/EDocument/Test/src/Processing/EDocStructuredValidations.Codeunit.al +git commit -m "fix: normalize ADI tax amount to VAT percentage in E-Document Purchase Line" +``` + +--- + +### Task 2: Add `[BC] VAT Prod. Posting Group` field to E-Document Purchase Line + +**Files:** +- Modify: `src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al:211` + +- [ ] **Step 1: Add field 110 to the table** + +Insert before the `#endregion Validated fields` comment (line 211): + +```al + field(110; "[BC] VAT Prod. Posting Group"; Code[20]) + { + Caption = 'VAT Prod. Posting Group'; + ToolTip = 'Specifies the VAT product posting group resolved from the extracted VAT rate.'; + TableRelation = "VAT Product Posting Group"; + } +``` + +Also add a `using` statement at the top of the file for `Microsoft.Finance.VAT.Setup` if not already present. + +- [ ] **Step 2: Compile** + +Run: `al compile` for the E-Document app. +Expected: Clean compilation, no errors. + +- [ ] **Step 3: Commit** + +```bash +git add src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al +git commit -m "feat: add [BC] VAT Prod. Posting Group field to E-Document Purchase Line" +``` + +--- + +### Task 3: Add `[BC] VAT Prod. Posting Group` column to draft subform page + +**Files:** +- Modify: `src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al:73` + +- [ ] **Step 1: Add the column to the repeater** + +Insert after the `"No."` field block (after line 73, the closing brace of the `"No."` field): + +```al + field("VAT Prod. Posting Group"; Rec."[BC] VAT Prod. Posting Group") + { + ApplicationArea = All; + Lookup = true; + } +``` + +- [ ] **Step 2: Compile** + +Run: `al compile` for the E-Document app. +Expected: Clean compilation, no errors. + +- [ ] **Step 3: Commit** + +```bash +git add src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al +git commit -m "feat: add VAT Prod. Posting Group column to E-Document draft subform" +``` + +--- + +### Task 4: Add VAT Rate Mismatch notification type and codeunit logic + +**Files:** +- Modify: `src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotificationType.Enum.al:18` +- Modify: `src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotification.Codeunit.al` +- Modify: `src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al:505` + +- [ ] **Step 1: Add enum value** + +In `EDocumentNotificationType.Enum.al`, add after the `"Vendor Matched By Name Not Address"` value (after line 18): + +```al + value(2; "VAT Rate Mismatch") + { + Caption = 'VAT Rate Mismatch'; + } +``` + +- [ ] **Step 2: Add notification procedures to the codeunit** + +In `EDocumentNotification.Codeunit.al`, add these procedures: + +After `AddVendorMatchedByNameNotAddressNotification` (after line 37): + +```al + procedure AddVATRateMismatchNotification(EDocumentEntryNo: Integer) + var + EDocumentNotification: Record "E-Document Notification"; + MyNotifications: Record "My Notifications"; + VATRateMismatchMsg: Label 'VAT Product Posting Groups could not be automatically determined for one or more lines. Please review before creating the invoice.'; + begin + if not GuiAllowed() then + exit; + if not MyNotifications.IsEnabled(GetVATRateMismatchNotificationId()) then + exit; + if EDocumentNotification.Get(EDocumentEntryNo, GetVATRateMismatchNotificationId(), UserId()) then + exit; + EDocumentNotification.Validate("E-Document Entry No.", EDocumentEntryNo); + EDocumentNotification.Validate(ID, GetVATRateMismatchNotificationId()); + EDocumentNotification.Validate("User Id", UserId()); + EDocumentNotification.Validate(Type, "E-Document Notification Type"::"VAT Rate Mismatch"); + EDocumentNotification.Validate(Message, VATRateMismatchMsg); + EDocumentNotification.Insert(true); + end; +``` + +- [ ] **Step 3: Update `SendPurchaseDocumentDraftNotifications` to include the new type** + +Replace the existing `SendPurchaseDocumentDraftNotifications` (lines 43-59) with: + +```al + procedure SendPurchaseDocumentDraftNotifications(EDocumentEntryNo: Integer) + var + EDocumentNotification: Record "E-Document Notification"; + begin + if not GuiAllowed() then + exit; + + EDocumentNotification.SetRange("E-Document Entry No.", EDocumentEntryNo); + EDocumentNotification.SetFilter(Type, '%1|%2', + "E-Document Notification Type"::"Vendor Matched By Name Not Address", + "E-Document Notification Type"::"VAT Rate Mismatch"); + EDocumentNotification.SetRange("User Id", UserId()); + if not EDocumentNotification.FindSet() then + exit; + + repeat + SendNotification(EDocumentNotification); + until EDocumentNotification.Next() = 0; + end; +``` + +- [ ] **Step 4: Update `AddActionsToNotification` to handle the new type** + +Replace the existing `AddActionsToNotification` (lines 112-123) with: + +```al + local procedure AddActionsToNotification(var Notification: Notification; EDocumentNotification: Record "E-Document Notification") + var + DismissMsg: Label 'Dismiss'; + DontShowThisAgainMsg: Label 'Don''t show this again.'; + begin + Notification.SetData(EDocumentNotification.FieldName("E-Document Entry No."), Format(EDocumentNotification."E-Document Entry No.")); + Notification.SetData(EDocumentNotification.FieldName(ID), EDocumentNotification.ID); + case EDocumentNotification.Type of + "E-Document Notification Type"::"Vendor Matched By Name Not Address": + begin + Notification.AddAction(DismissMsg, Codeunit::"E-Document Notification", 'DismissVendorMatchedByNameNotAddressNotification'); + Notification.AddAction(DontShowThisAgainMsg, Codeunit::"E-Document Notification", 'DisableVendorMatchedByNameNotAddressNotification'); + end; + "E-Document Notification Type"::"VAT Rate Mismatch": + begin + Notification.AddAction(DismissMsg, Codeunit::"E-Document Notification", 'DismissVATRateMismatchNotification'); + Notification.AddAction(DontShowThisAgainMsg, Codeunit::"E-Document Notification", 'DisableVATRateMismatchNotification'); + end; + end; + end; +``` + +- [ ] **Step 5: Add dismiss and disable procedures for the new notification** + +Add after `DisableVendorMatchedByNameNotAddressNotification` (after line 95): + +```al + procedure DismissVATRateMismatchNotification(Notification: Notification) + var + EDocumentNotification: Record "E-Document Notification"; + EDocumentEntryNo: Integer; + Id: Guid; + begin + Evaluate(EDocumentEntryNo, Notification.GetData(EDocumentNotification.FieldName("E-Document Entry No."))); + Evaluate(Id, Notification.GetData(EDocumentNotification.FieldName(ID))); + if not EDocumentNotification.Get(EDocumentEntryNo, Id, UserId()) then + exit; + EDocumentNotification.Delete(true); + end; + + procedure DisableVATRateMismatchNotification(Notification: Notification) + var + MyNotifications: Record "My Notifications"; + EDocumentNotification: Record "E-Document Notification"; + VATRateMismatchNotificationNameTok: Label 'Notify user of Purchase Document Draft that VAT posting groups could not be auto-resolved.'; + VATRateMismatchNotificationDescTok: Label 'Show a notification when VAT Product Posting Groups could not be automatically determined from the extracted VAT rate.'; + begin + if MyNotifications.WritePermission() then + if not MyNotifications.Disable(GetVATRateMismatchNotificationId()) then + MyNotifications.InsertDefault(GetVATRateMismatchNotificationId(), VATRateMismatchNotificationNameTok, VATRateMismatchNotificationDescTok, false); + EDocumentNotification.SetRange(Type, "E-Document Notification Type"::"VAT Rate Mismatch"); + EDocumentNotification.SetRange("User Id", UserId()); + EDocumentNotification.DeleteAll(true); + end; +``` + +- [ ] **Step 6: Add the GUID getter for the new notification** + +Add after `GetVendorMatchedByNameNotAddressNotificationId` (after line 128): + +```al + local procedure GetVATRateMismatchNotificationId(): Guid + begin + exit('d4a7e1c3-5f92-4b8a-ae67-1c3d5f924b8a'); + end; +``` + +- [ ] **Step 7: Compile** + +Run: `al compile` for the E-Document app. +Expected: Clean compilation, no errors. + +- [ ] **Step 8: Commit** + +```bash +git add src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotificationType.Enum.al +git add src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotification.Codeunit.al +git commit -m "feat: add VAT Rate Mismatch notification type and handlers" +``` + +--- + +### Task 5: Implement VAT Posting Group resolution in Prepare Draft + +**Files:** +- Modify: `src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al:62-78` + +This is the core logic. After the existing line loop resolves type/number/UOM, we add a second pass that resolves VAT Posting Group. We also trigger the notification if resolution fails. + +- [ ] **Step 1: Add the VAT resolution call after the line loop** + +In `PrepareDraft`, insert after the existing line loop (after line 74, the `until` line) and before `CopilotLineMatching` (line 77): + +```al + // Resolve VAT Product Posting Groups from extracted VAT rates + ResolveVATProductPostingGroups(EDocument."Entry No", EDocumentPurchaseHeader); +``` + +- [ ] **Step 2: Add `using` statements** + +Add to the top of the file: +```al +using Microsoft.Finance.VAT.Setup; +using Microsoft.eServices.EDocument; +``` + +(If `Microsoft.eServices.EDocument` is already present via `EDocument` record, that's fine. The key addition is `Microsoft.Finance.VAT.Setup` for the `VAT Posting Setup` table.) + +- [ ] **Step 3: Implement `ResolveVATProductPostingGroups`** + +Add as a new local procedure: + +```al + local procedure ResolveVATProductPostingGroups(EDocumentEntryNo: Integer; EDocumentPurchaseHeader: Record "E-Document Purchase Header") + var + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + Vendor: Record Vendor; + EDocumentNotification: Codeunit "E-Document Notification"; + VATBusPostingGroup: Code[20]; + VATRate: Decimal; + LineCount: Integer; + HasUnresolvedVATLines: Boolean; + begin + if EDocumentPurchaseHeader."[BC] Vendor No." = '' then + exit; + if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then + exit; + VATBusPostingGroup := Vendor."VAT Bus. Posting Group"; + if VATBusPostingGroup = '' then + exit; + + EDocumentPurchaseLine.SetRange("E-Document Entry No.", EDocumentEntryNo); + LineCount := EDocumentPurchaseLine.Count(); + if LineCount = 0 then + exit; + + if EDocumentPurchaseLine.FindSet() then + repeat + VATRate := EDocumentPurchaseLine."VAT Rate"; + + // Single-line fallback: compute from header Total VAT + if (VATRate = 0) and (LineCount = 1) and + (EDocumentPurchaseHeader."Total VAT" > 0) and (EDocumentPurchaseHeader."Sub Total" > 0) + then + VATRate := Round((EDocumentPurchaseHeader."Total VAT" / EDocumentPurchaseHeader."Sub Total") * 100, 0.01); + + if VATRate > 0 then begin + EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" := + FindVATProductPostingGroup(VATBusPostingGroup, VATRate); + if EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" = '' then + HasUnresolvedVATLines := true; + EDocumentPurchaseLine.Modify(); + end; + until EDocumentPurchaseLine.Next() = 0; + + if HasUnresolvedVATLines then + EDocumentNotification.AddVATRateMismatchNotification(EDocumentEntryNo); + end; + + local procedure FindVATProductPostingGroup(VATBusPostingGroup: Code[20]; VATRate: Decimal): Code[20] + var + VATPostingSetup: Record "VAT Posting Setup"; + RoundingTolerance: Decimal; + begin + RoundingTolerance := 0.01; + VATPostingSetup.SetRange("VAT Bus. Posting Group", VATBusPostingGroup); + VATPostingSetup.SetFilter("VAT %", '>=%1&<=%2', VATRate - RoundingTolerance, VATRate + RoundingTolerance); + if VATPostingSetup.Count() = 1 then begin + VATPostingSetup.FindFirst(); + exit(VATPostingSetup."VAT Prod. Posting Group"); + end; + // Zero or multiple matches — return blank to signal resolution failure + exit(''); + end; +``` + +- [ ] **Step 4: Compile** + +Run: `al compile` for the E-Document app. +Expected: Clean compilation, no errors. + +- [ ] **Step 5: Commit** + +```bash +git add src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al +git commit -m "feat: resolve VAT Prod. Posting Group from extracted VAT rate during Prepare Draft" +``` + +--- + +### Task 6: Apply resolved VAT Posting Group in Finish Draft + +**Files:** +- Modify: `src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocCreatePurchaseInvoice.Codeunit.al:216-219` + +- [ ] **Step 1: Add the VAT Posting Group override in `CreatePurchaseInvoiceLine`** + +After line 216 (`PurchaseLine.Validate("No.", EDocumentPurchaseLine."[BC] Purchase Type No.");`) and before line 217 (`if (PurchaseLine.Type = ...`), insert: + +```al + if EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" <> '' then + PurchaseLine.Validate("VAT Prod. Posting Group", EDocumentPurchaseLine."[BC] VAT Prod. Posting Group"); +``` + +The `Validate("No.", ...)` call at line 216 sets the default VAT Posting Group from the G/L Account/Item card. Our new lines override it with the resolved value. `Validate("VAT Prod. Posting Group", ...)` will also update `"VAT %"` on the purchase line based on the VAT Posting Setup. + +- [ ] **Step 2: Compile** + +Run: `al compile` for the E-Document app. +Expected: Clean compilation, no errors. + +- [ ] **Step 3: Commit** + +```bash +git add src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocCreatePurchaseInvoice.Codeunit.al +git commit -m "feat: apply resolved VAT Prod. Posting Group when creating purchase invoice line" +``` + +--- + +### Task 7: Write integration tests for VAT Posting Group resolution + +**Files:** +- Modify or create: `src/Apps/W1/EDocument/Test/src/Processing/EDocumentStructuredTests.Codeunit.al` (add new test procedures) + +These tests validate the end-to-end flow: ADI extracts VAT data → Prepare Draft resolves the posting group → Finish Draft applies it. + +- [ ] **Step 1: Add a test for per-line VAT rate resolution** + +Add a new test procedure to `EDocumentStructuredTests`: + +```al + [Test] + procedure TestCAPIInvoice_VATPostingGroupResolvedFromLineRate() + var + EDocument: Record "E-Document"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + VATPostingSetup: Record "VAT Posting Setup"; + begin + // [SCENARIO] When CAPI extracts tax amounts per line, Prepare Draft resolves VAT Prod. Posting Group + Initialize(Enum::"Service Integration"::"Mock"); + SetupCAPIEDocumentService(); + CreateInboundEDocumentFromJSON(EDocument, 'capi/capi-invoice-valid-0.json'); + + // Process through Read into Draft (extracts VAT rates) + Assert.IsTrue( + ProcessEDocumentToStep(EDocument, "Import E-Document Steps"::"Read into Draft"), + 'Failed to process to Read into Draft'); + + // Verify VAT Rate is now a percentage (10% for all lines: $6/$60, $3/$30, $1/$10) + EDocumentPurchaseLine.SetRange("E-Document Entry No.", EDocument."Entry No"); + EDocumentPurchaseLine.FindSet(); + repeat + Assert.AreEqual(10, EDocumentPurchaseLine."VAT Rate", + 'VAT Rate should be normalized to percentage'); + until EDocumentPurchaseLine.Next() = 0; + + // Process through Prepare Draft (resolves VAT Prod. Posting Group) + // Note: This requires a vendor with a VAT Bus. Posting Group and a matching + // VAT Posting Setup with VAT % = 10. If the test vendor setup has this, + // the [BC] VAT Prod. Posting Group field should be populated. + // The exact assertion depends on the test vendor's VAT configuration. + end; +``` + +Note: The exact assertions for the Prepare Draft step depend on the test data setup (vendor's VAT Bus. Posting Group and VAT Posting Setup). Adapt the test to match the fixture vendor's configuration, or create the required VAT Posting Setup in the test's `Initialize` procedure. + +- [ ] **Step 2: Compile and run tests** + +Run: `al compile` and `al run_tests` for the E-Document test app. +Expected: All tests pass. + +- [ ] **Step 3: Commit** + +```bash +git add src/Apps/W1/EDocument/Test/src/Processing/EDocumentStructuredTests.Codeunit.al +git commit -m "test: add integration test for VAT Posting Group resolution from ADI data" +``` From 98fae1a561cecf8eac12697ac4e839d534a40151 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Mon, 30 Mar 2026 11:16:36 +0200 Subject: [PATCH 03/53] update plans --- ...-30-vat-posting-group-resolution-design.md | 15 ++-- ...03-30-vat-posting-group-resolution-plan.md | 68 ++++++++++++++++--- 2 files changed, 69 insertions(+), 14 deletions(-) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-design.md b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-design.md index 676706b545..911e844141 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-design.md +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-design.md @@ -22,12 +22,15 @@ This causes incorrect VAT amounts on posted invoices, wrong VAT reporting, and m **File:** `EDocumentADIHandler.Codeunit.al` — `PopulateEDocumentPurchaseLine` -**Change:** Replace the current `tax` → `"VAT Rate"` mapping (which stores a monetary amount) with a computation that produces a percentage: +**ADI schema context ([2024-11-30-ga](https://github.com/Azure-Samples/document-intelligence-code-samples/blob/main/schema/2024-11-30-ga/invoice.md)):** The `Items.*.Tax` field is ambiguous by design — "Possible values include tax amount, tax %, and tax Y/N". A separate `Items.*.TaxRate` (string) field provides the unambiguous percentage. -1. Read ADI's `tax` field into a local decimal variable (tax amount). -2. If the line has both a tax amount > 0 and `Sub Total` > 0, compute: `VAT Rate = (tax amount / Sub Total) * 100`. -3. If tax amount = 0, set `VAT Rate = 0` (zero-rated — valid and useful). -4. If `Sub Total` = 0, skip — leave `VAT Rate` as 0. +**Change:** Replace the current `tax` → `"VAT Rate"` mapping with a multi-step resolution: + +1. **Prefer `TaxRate`** (string field) — parse the numeric percentage from it (e.g., "20%", "VAT 20%", "20" → 20). This is the unambiguous source. +2. **Fallback to `Tax`** — if `TaxRate` is unavailable, read the `Tax` field. Check `value_text` to disambiguate: + - If `value_text` contains `%` → the value is a percentage, use it directly. + - Otherwise → assume monetary amount, compute: `VAT Rate = (Tax / Sub Total) * 100`. +3. If neither field provides usable data, leave `VAT Rate` as 0. After this change, all three handlers (ADI, PEPPOL, MLLM) consistently populate `"VAT Rate"` as a percentage. @@ -101,7 +104,7 @@ If the field is blank (resolution failed or user didn't set it), do nothing — - **Combinatorial solving** — No attempt to find posting group combinations that make line VATs add up to the header total for multi-line invoices without per-line VAT data. - **Blocking finalization** — VAT mismatch is informational, not a hard block. The existing document totals validation at posting time remains the enforcement mechanism. -- **ADI `taxRate` field** — The ADI invoice model may provide a `taxRate` string field. This design computes the rate from `tax / amount` instead, which works regardless of whether ADI provides `taxRate`. Adding direct `taxRate` extraction can be done as a follow-up. +- **ADI `TaxDetails` header field** — The ADI model provides `TaxDetails` with per-rate breakdowns at the header level. This could be used as an additional data source but is not consumed in this design. ## Key Files diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-plan.md b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-plan.md index ab3774b0a8..490a26223f 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-plan.md +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-plan.md @@ -10,13 +10,21 @@ --- -### Task 1: Normalize ADI handler — compute VAT percentage from tax amount +### Task 1: Normalize ADI handler — extract VAT percentage from ADI data **Files:** - Modify: `src/Apps/W1/EDocument/App/src/Processing/Import/StructureReceivedEDocument/EDocumentADIHandler.Codeunit.al:174-188` - Modify: `src/Apps/W1/EDocument/Test/src/Processing/EDocStructuredValidations.Codeunit.al:59,70,80` -The ADI handler currently stores the `tax` monetary amount directly into `"VAT Rate"`. After this change, it computes a percentage: `(tax / Sub Total) * 100`. +**Background — ADI schema ambiguity (from [ADI invoice schema 2024-11-30-ga](https://github.com/Azure-Samples/document-intelligence-code-samples/blob/main/schema/2024-11-30-ga/invoice.md)):** +- `Items.*.Tax` (currency type) — "Possible values include tax amount, tax %, and tax Y/N" — **ambiguous by design** +- `Items.*.TaxRate` (string type) — "Tax rate associated with each line item" — **unambiguous percentage** +- `TotalTax` (currency) — header-level total tax amount +- `TaxDetails.*.Rate` (string) — per-rate breakdown at header level + +The current code maps `Tax` directly to `"VAT Rate"`, storing a monetary amount (e.g., `$6.00` → `6`) in a field meant for a percentage. + +**Strategy:** Prefer `TaxRate` (unambiguous string percentage). Fall back to computing from `Tax / Sub Total * 100` only when `TaxRate` is absent and `Tax` looks like a monetary amount (check `value_text` for `%` sign to detect when `Tax` contains a percentage instead of an amount). - [ ] **Step 1: Update `PopulateEDocumentPurchaseLine` in the ADI handler** @@ -27,20 +35,60 @@ EDocumentJsonHelper.SetCurrencyValueInField('tax', FieldsJsonObject, TempEDocPur With: ```al - ComputeVATRateFromTaxAmount(FieldsJsonObject, TempEDocPurchaseLine); + ResolveVATRateFromADI(FieldsJsonObject, TempEDocPurchaseLine); ``` Add a new local procedure after `PopulateEDocumentPurchaseLine`: ```al - local procedure ComputeVATRateFromTaxAmount(FieldsJsonObject: JsonObject; var TempEDocPurchaseLine: Record "E-Document Purchase Line" temporary) + local procedure ResolveVATRateFromADI(FieldsJsonObject: JsonObject; var TempEDocPurchaseLine: Record "E-Document Purchase Line" temporary) var + TaxRateText: Text; TaxAmount: Decimal; + TaxValueText: Text; + ParsedRate: Decimal; UnusedCurrencyCode: Code[10]; begin + // 1. Prefer TaxRate (string) — unambiguous percentage field from ADI + EDocumentJsonHelper.SetStringValueInField('taxRate', MaxStrLen(TaxRateText), FieldsJsonObject, TaxRateText); + if TaxRateText <> '' then begin + ParsedRate := ParsePercentageFromText(TaxRateText); + if ParsedRate >= 0 then begin + TempEDocPurchaseLine."VAT Rate" := ParsedRate; + exit; + end; + end; + + // 2. Fallback to Tax field — but it's ambiguous (can be amount, %, or Y/N) EDocumentJsonHelper.SetCurrencyValueInField('tax', FieldsJsonObject, TaxAmount, UnusedCurrencyCode); - if (TaxAmount = 0) or (TempEDocPurchaseLine."Sub Total" = 0) then + if TaxAmount = 0 then exit; - TempEDocPurchaseLine."VAT Rate" := Round((TaxAmount / TempEDocPurchaseLine."Sub Total") * 100, 0.01); + + // Check value_text to disambiguate + EDocumentJsonHelper.SetStringValueInField('tax', MaxStrLen(TaxValueText), FieldsJsonObject, TaxValueText); + if TaxValueText.Contains('%') then + // Tax field contains a percentage (e.g., "20%") + TempEDocPurchaseLine."VAT Rate" := TaxAmount + else + // Tax field contains a monetary amount (e.g., "$6.00") — compute percentage + if TempEDocPurchaseLine."Sub Total" > 0 then + TempEDocPurchaseLine."VAT Rate" := Round((TaxAmount / TempEDocPurchaseLine."Sub Total") * 100, 0.01); + end; + + local procedure ParsePercentageFromText(TaxRateText: Text): Decimal + var + CleanedText: Text; + ParsedValue: Decimal; + begin + // Strip common non-numeric prefixes/suffixes: "VAT 20%", "20%", "20.0%", "20" + CleanedText := TaxRateText.Replace('%', '').Trim(); + // Remove common prefixes like "VAT ", "Tax ", etc. + if CleanedText.StartsWith('VAT ') then + CleanedText := CopyStr(CleanedText, 5).Trim(); + if CleanedText.StartsWith('Tax ') then + CleanedText := CopyStr(CleanedText, 5).Trim(); + if Evaluate(ParsedValue, CleanedText) then + exit(ParsedValue); + exit(-1); // Signal parse failure end; ``` @@ -48,7 +96,7 @@ Note: `"Sub Total"` is already populated from `amount` at line 176 before this r - [ ] **Step 2: Update existing CAPI test assertions** -In `EDocStructuredValidations.Codeunit.al`, the CAPI test fixture has three lines with tax amounts $6, $3, $1 on sub totals 60, 30, 10 — all computing to 10%. +In `EDocStructuredValidations.Codeunit.al`, the CAPI test fixture has three lines with `Tax` as monetary amounts (`$6.00`, `$3.00`, `$1.00`) and no `TaxRate` field. The `value_text` contains `$` (not `%`), so the fallback path computes: `$6/$60 = 10%`, `$3/$30 = 10%`, `$1/$10 = 10%`. Update line 59: ```al @@ -80,7 +128,11 @@ Expected: All existing CAPI, PEPPOL, and MLLM structured tests pass with the upd ```bash git add src/Apps/W1/EDocument/App/src/Processing/Import/StructureReceivedEDocument/EDocumentADIHandler.Codeunit.al git add src/Apps/W1/EDocument/Test/src/Processing/EDocStructuredValidations.Codeunit.al -git commit -m "fix: normalize ADI tax amount to VAT percentage in E-Document Purchase Line" +git commit -m "fix: normalize ADI tax data to VAT percentage in E-Document Purchase Line + +Prefer TaxRate (string, unambiguous percentage) over Tax (currency, +ambiguous). Fall back to computing Tax/Amount*100 only when Tax +contains a monetary amount (no % in value_text)." ``` --- From b8fb56620a447824c085a16f41087ef28915f180 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Fri, 3 Apr 2026 15:39:10 +0200 Subject: [PATCH 04/53] fix: normalize ADI tax data to VAT percentage in E-Document Purchase Line Prefer TaxRate (string, unambiguous percentage) over Tax (currency, ambiguous). Fall back to computing Tax/Amount*100 only when Tax contains a monetary amount (no % in value_text). Co-Authored-By: Claude Sonnet 4.6 --- .../EDocumentADIHandler.Codeunit.al | 53 ++++++++++++++++++- .../EDocStructuredValidations.Codeunit.al | 6 +-- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/StructureReceivedEDocument/EDocumentADIHandler.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/StructureReceivedEDocument/EDocumentADIHandler.Codeunit.al index 1e2c79bca8..c6c24cedf1 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/StructureReceivedEDocument/EDocumentADIHandler.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/StructureReceivedEDocument/EDocumentADIHandler.Codeunit.al @@ -182,9 +182,60 @@ codeunit 6174 "E-Document ADI Handler" implements IStructureReceivedEDocument, I EDocumentJsonHelper.SetStringValueInField('productCode', MaxStrLen(TempEDocPurchaseLine."Product Code"), FieldsJsonObject, TempEDocPurchaseLine."Product Code"); EDocumentJsonHelper.SetStringValueInField('unit', MaxStrLen(TempEDocPurchaseLine."Unit of Measure"), FieldsJsonObject, TempEDocPurchaseLine."Unit of Measure"); EDocumentJsonHelper.SetDateValueInField('date', FieldsJsonObject, TempEDocPurchaseLine.Date); - EDocumentJsonHelper.SetCurrencyValueInField('tax', FieldsJsonObject, TempEDocPurchaseLine."VAT Rate", TempEDocPurchaseLine."Currency Code"); + ResolveVATRateFromADI(FieldsJsonObject, TempEDocPurchaseLine); if TempEDocPurchaseLine."Unit Price" <> 0 then TempEDocPurchaseLine."Total Discount" := (TempEDocPurchaseLine."Unit Price" * TempEDocPurchaseLine.Quantity) - TempEDocPurchaseLine."Sub Total"; end; + + local procedure ResolveVATRateFromADI(FieldsJsonObject: JsonObject; var TempEDocPurchaseLine: Record "E-Document Purchase Line" temporary) + var + TaxRateText: Text; + TaxAmount: Decimal; + TaxValueText: Text; + ParsedRate: Decimal; + UnusedCurrencyCode: Code[10]; + begin + // 1. Prefer TaxRate (string) — unambiguous percentage field from ADI + EDocumentJsonHelper.SetStringValueInField('taxRate', MaxStrLen(TaxRateText), FieldsJsonObject, TaxRateText); + if TaxRateText <> '' then begin + ParsedRate := ParsePercentageFromText(TaxRateText); + if ParsedRate >= 0 then begin + TempEDocPurchaseLine."VAT Rate" := ParsedRate; + exit; + end; + end; + + // 2. Fallback to Tax field — but it's ambiguous (can be amount, %, or Y/N) + EDocumentJsonHelper.SetCurrencyValueInField('tax', FieldsJsonObject, TaxAmount, UnusedCurrencyCode); + if TaxAmount = 0 then + exit; + + // Check value_text to disambiguate + EDocumentJsonHelper.SetStringValueInField('tax', MaxStrLen(TaxValueText), FieldsJsonObject, TaxValueText); + if TaxValueText.Contains('%') then + // Tax field contains a percentage (e.g., "20%") + TempEDocPurchaseLine."VAT Rate" := TaxAmount + else + // Tax field contains a monetary amount (e.g., "$6.00") — compute percentage + if TempEDocPurchaseLine."Sub Total" > 0 then + TempEDocPurchaseLine."VAT Rate" := Round((TaxAmount / TempEDocPurchaseLine."Sub Total") * 100, 0.01); + end; + + local procedure ParsePercentageFromText(TaxRateText: Text): Decimal + var + CleanedText: Text; + ParsedValue: Decimal; + begin + // Strip common non-numeric prefixes/suffixes: "VAT 20%", "20%", "20.0%", "20" + CleanedText := TaxRateText.Replace('%', '').Trim(); + // Remove common prefixes like "VAT ", "Tax ", etc. + if CleanedText.StartsWith('VAT ') then + CleanedText := CopyStr(CleanedText, 5).Trim(); + if CleanedText.StartsWith('Tax ') then + CleanedText := CopyStr(CleanedText, 5).Trim(); + if Evaluate(ParsedValue, CleanedText) then + exit(ParsedValue); + exit(-1); // Signal parse failure + end; #pragma warning restore AA0139 } diff --git a/src/Apps/W1/EDocument/Test/src/Processing/EDocStructuredValidations.Codeunit.al b/src/Apps/W1/EDocument/Test/src/Processing/EDocStructuredValidations.Codeunit.al index 076298e65c..29ef0e7408 100644 --- a/src/Apps/W1/EDocument/Test/src/Processing/EDocStructuredValidations.Codeunit.al +++ b/src/Apps/W1/EDocument/Test/src/Processing/EDocStructuredValidations.Codeunit.al @@ -56,7 +56,7 @@ codeunit 139894 "EDoc Structured Validations" Assert.AreEqual('A123', EDocumentPurchaseLine."Product Code", 'The product code in the purchase line does not allign with the mock data.'); Assert.AreEqual('hours', EDocumentPurchaseLine."Unit of Measure", 'The unit of measure in the purchase line does not allign with the mock data.'); Assert.AreEqual(DMY2Date(4, 3, 2021), EDocumentPurchaseLine.Date, 'The date in the purchase line does not allign with the mock data.'); - Assert.AreEqual(6, EDocumentPurchaseLine."VAT Rate", 'The amount in the purchase line does not allign with the mock data.'); + Assert.AreEqual(10, EDocumentPurchaseLine."VAT Rate", 'The VAT rate in the purchase line does not match the expected percentage.'); EDocumentPurchaseLine.Next(); Assert.AreEqual(30, EDocumentPurchaseLine."Sub Total", 'The amount in the purchase line does not allign with the mock data.'); @@ -67,7 +67,7 @@ codeunit 139894 "EDoc Structured Validations" Assert.AreEqual('B456', EDocumentPurchaseLine."Product Code", 'The product code in the purchase line does not allign with the mock data.'); Assert.AreEqual('', EDocumentPurchaseLine."Unit of Measure", 'The unit of measure in the purchase line does not allign with the mock data.'); Assert.AreEqual(DMY2Date(5, 3, 2021), EDocumentPurchaseLine.Date, 'The date in the purchase line does not allign with the mock data.'); - Assert.AreEqual(3, EDocumentPurchaseLine."VAT Rate", 'The amount in the purchase line does not allign with the mock data.'); + Assert.AreEqual(10, EDocumentPurchaseLine."VAT Rate", 'The VAT rate in the purchase line does not match the expected percentage.'); EDocumentPurchaseLine.Next(); Assert.AreEqual(10, EDocumentPurchaseLine."Sub Total", 'The amount does not allign with the mock data.'); @@ -77,7 +77,7 @@ codeunit 139894 "EDoc Structured Validations" Assert.AreEqual('C789', EDocumentPurchaseLine."Product Code", 'The product code does not allign with the mock data.'); Assert.AreEqual('pages', EDocumentPurchaseLine."Unit of Measure", 'The unit of measure does not allign with the mock data.'); Assert.AreEqual(DMY2Date(6, 3, 2021), EDocumentPurchaseLine.Date, 'The date does not allign with the mock data.'); - Assert.AreEqual(1, EDocumentPurchaseLine."VAT Rate", 'The amount does not allign with the mock data.'); + Assert.AreEqual(10, EDocumentPurchaseLine."VAT Rate", 'The VAT rate in the purchase line does not match the expected percentage.'); end; internal procedure AssertMinimalCAPIDocumentParsed(EDocumentEntryNo: Integer) From 34b22074c86bbc61f00ab887e2d46f8322f453df Mon Sep 17 00:00:00 2001 From: ventselartur Date: Fri, 3 Apr 2026 15:43:52 +0200 Subject: [PATCH 05/53] feat: add [BC] VAT Prod. Posting Group field to E-Document Purchase Line Co-Authored-By: Claude Sonnet 4.6 --- .../Import/Purchase/EDocumentPurchaseLine.Table.al | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al index df4c126db9..8c7de9a825 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al @@ -11,6 +11,7 @@ using Microsoft.Finance.AllocationAccount; using Microsoft.Finance.Deferral; using Microsoft.Finance.Dimension; using Microsoft.Finance.GeneralLedger.Account; +using Microsoft.Finance.VAT.Setup; using Microsoft.FixedAssets.FixedAsset; using Microsoft.Foundation.UOM; using Microsoft.Inventory.Item; @@ -208,6 +209,12 @@ table 6101 "E-Document Purchase Line" DimMgt.UpdateGlobalDimFromDimSetID("[BC] Dimension Set ID", "[BC] Shortcut Dimension 1 Code", "[BC] Shortcut Dimension 2 Code"); end; } + field(110; "[BC] VAT Prod. Posting Group"; Code[20]) + { + Caption = 'VAT Prod. Posting Group'; + ToolTip = 'Specifies the VAT product posting group resolved from the extracted VAT rate.'; + TableRelation = "VAT Product Posting Group"; + } #endregion Validated fields #region Metadata fields [201-300] From 47c64e8870cab7df79a0d8dc3af5ff46b3103f5f Mon Sep 17 00:00:00 2001 From: ventselartur Date: Fri, 3 Apr 2026 15:44:12 +0200 Subject: [PATCH 06/53] feat: add VAT Prod. Posting Group column to E-Document draft subform Co-Authored-By: Claude Sonnet 4.6 --- .../Import/Purchase/EDocPurchaseDraftSubform.Page.al | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al index a49e0ab6aa..27ff4396aa 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al @@ -71,6 +71,11 @@ page 6183 "E-Doc. Purchase Draft Subform" Lookup = true; ShowMandatory = true; } + field("VAT Prod. Posting Group"; Rec."[BC] VAT Prod. Posting Group") + { + ApplicationArea = All; + Lookup = true; + } field("Item Reference No."; Rec."[BC] Item Reference No.") { ApplicationArea = All; From dbb35b72e39de108611181fa9a528b8017cfcd53 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Fri, 3 Apr 2026 15:46:44 +0200 Subject: [PATCH 07/53] feat: add VAT Rate Mismatch notification type and handlers Adds enum value 2 ("VAT Rate Mismatch") to E-Document Notification Type, with parallel Add/Dismiss/Disable procedures in the notification codeunit and an updated SendPurchaseDocumentDraftNotifications that fans out to both notification types via SetFilter. Co-Authored-By: Claude Sonnet 4.6 --- .../EDocumentNotification.Codeunit.al | 73 +++++++++++++++++-- .../EDocumentNotificationType.Enum.al | 4 + 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotification.Codeunit.al b/src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotification.Codeunit.al index acefe71c5f..fb92b00bc3 100644 --- a/src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotification.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotification.Codeunit.al @@ -36,6 +36,26 @@ codeunit 6123 "E-Document Notification" EDocumentNotification.Insert(true); end; + procedure AddVATRateMismatchNotification(EDocumentEntryNo: Integer) + var + EDocumentNotification: Record "E-Document Notification"; + MyNotifications: Record "My Notifications"; + VATRateMismatchMsg: Label 'VAT Product Posting Groups could not be automatically determined for one or more lines. Please review before creating the invoice.'; + begin + if not GuiAllowed() then + exit; + if not MyNotifications.IsEnabled(GetVATRateMismatchNotificationId()) then + exit; + if EDocumentNotification.Get(EDocumentEntryNo, GetVATRateMismatchNotificationId(), UserId()) then + exit; + EDocumentNotification.Validate("E-Document Entry No.", EDocumentEntryNo); + EDocumentNotification.Validate(ID, GetVATRateMismatchNotificationId()); + EDocumentNotification.Validate("User Id", UserId()); + EDocumentNotification.Validate(Type, "E-Document Notification Type"::"VAT Rate Mismatch"); + EDocumentNotification.Validate(Message, VATRateMismatchMsg); + EDocumentNotification.Insert(true); + end; + /// /// Send notifications for Purchase Document Draft page /// Id of e-document @@ -48,7 +68,9 @@ codeunit 6123 "E-Document Notification" exit; EDocumentNotification.SetRange("E-Document Entry No.", EDocumentEntryNo); - EDocumentNotification.SetRange(Type, "E-Document Notification Type"::"Vendor Matched By Name Not Address"); + EDocumentNotification.SetFilter(Type, '%1|%2', + "E-Document Notification Type"::"Vendor Matched By Name Not Address", + "E-Document Notification Type"::"VAT Rate Mismatch"); EDocumentNotification.SetRange("User Id", UserId()); if not EDocumentNotification.FindSet() then exit; @@ -94,6 +116,34 @@ codeunit 6123 "E-Document Notification" EDocumentNotification.DeleteAll(true); end; + procedure DismissVATRateMismatchNotification(Notification: Notification) + var + EDocumentNotification: Record "E-Document Notification"; + EDocumentEntryNo: Integer; + Id: Guid; + begin + Evaluate(EDocumentEntryNo, Notification.GetData(EDocumentNotification.FieldName("E-Document Entry No."))); + Evaluate(Id, Notification.GetData(EDocumentNotification.FieldName(ID))); + if not EDocumentNotification.Get(EDocumentEntryNo, Id, UserId()) then + exit; + EDocumentNotification.Delete(true); + end; + + procedure DisableVATRateMismatchNotification(Notification: Notification) + var + MyNotifications: Record "My Notifications"; + EDocumentNotification: Record "E-Document Notification"; + VATRateMismatchNotificationNameTok: Label 'Notify user of Purchase Document Draft that VAT posting groups could not be auto-resolved.'; + VATRateMismatchNotificationDescTok: Label 'Show a notification when VAT Product Posting Groups could not be automatically determined from the extracted VAT rate.'; + begin + if MyNotifications.WritePermission() then + if not MyNotifications.Disable(GetVATRateMismatchNotificationId()) then + MyNotifications.InsertDefault(GetVATRateMismatchNotificationId(), VATRateMismatchNotificationNameTok, VATRateMismatchNotificationDescTok, false); + EDocumentNotification.SetRange(Type, "E-Document Notification Type"::"VAT Rate Mismatch"); + EDocumentNotification.SetRange("User Id", UserId()); + EDocumentNotification.DeleteAll(true); + end; + local procedure SendNotification(EDocumentNotification: Record "E-Document Notification") var MyNotifications: Record "My Notifications"; @@ -114,16 +164,29 @@ codeunit 6123 "E-Document Notification" DismissMsg: Label 'Dismiss'; DontShowThisAgainMsg: Label 'Don''t show this again.'; begin - if EDocumentNotification.Type <> "E-Document Notification Type"::"Vendor Matched By Name Not Address" then - exit; Notification.SetData(EDocumentNotification.FieldName("E-Document Entry No."), Format(EDocumentNotification."E-Document Entry No.")); Notification.SetData(EDocumentNotification.FieldName(ID), EDocumentNotification.ID); - Notification.AddAction(DismissMsg, Codeunit::"E-Document Notification", 'DismissVendorMatchedByNameNotAddressNotification'); - Notification.AddAction(DontShowThisAgainMsg, Codeunit::"E-Document Notification", 'DisableVendorMatchedByNameNotAddressNotification'); + case EDocumentNotification.Type of + "E-Document Notification Type"::"Vendor Matched By Name Not Address": + begin + Notification.AddAction(DismissMsg, Codeunit::"E-Document Notification", 'DismissVendorMatchedByNameNotAddressNotification'); + Notification.AddAction(DontShowThisAgainMsg, Codeunit::"E-Document Notification", 'DisableVendorMatchedByNameNotAddressNotification'); + end; + "E-Document Notification Type"::"VAT Rate Mismatch": + begin + Notification.AddAction(DismissMsg, Codeunit::"E-Document Notification", 'DismissVATRateMismatchNotification'); + Notification.AddAction(DontShowThisAgainMsg, Codeunit::"E-Document Notification", 'DisableVATRateMismatchNotification'); + end; + end; end; local procedure GetVendorMatchedByNameNotAddressNotificationId(): Guid begin exit('bc0d8537-8e8d-4d94-a07a-a5a54c729d2a'); end; + + local procedure GetVATRateMismatchNotificationId(): Guid + begin + exit('d4a7e1c3-5f92-4b8a-ae67-1c3d5f924b8a'); + end; } \ No newline at end of file diff --git a/src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotificationType.Enum.al b/src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotificationType.Enum.al index 7324cbae95..df8633171f 100644 --- a/src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotificationType.Enum.al +++ b/src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotificationType.Enum.al @@ -16,4 +16,8 @@ enum 6126 "E-Document Notification Type" { Caption = 'Vendor Matched By Name Not Address'; } + value(2; "VAT Rate Mismatch") + { + Caption = 'VAT Rate Mismatch'; + } } \ No newline at end of file From e0dd7f787303d464ff8ccf9c2e4e4f713bdb81a2 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Fri, 3 Apr 2026 15:48:33 +0200 Subject: [PATCH 08/53] feat: resolve VAT Prod. Posting Group from extracted VAT rate during Prepare Draft Co-Authored-By: Claude Sonnet 4.6 --- .../PreparePurchaseEDocDraft.Codeunit.al | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al index 7997ca6d44..a69c566a97 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al @@ -10,6 +10,7 @@ using Microsoft.eServices.EDocument.Processing.Import.Purchase; using Microsoft.eServices.EDocument.Processing.Interfaces; using Microsoft.Foundation.UOM; using Microsoft.Purchases.Document; +using Microsoft.Finance.VAT.Setup; using Microsoft.Purchases.Vendor; using System.Log; @@ -73,6 +74,9 @@ codeunit 6125 "Prepare Purchase E-Doc. Draft" implements IProcessStructuredData EDocumentPurchaseLine.Modify(); until EDocumentPurchaseLine.Next() = 0; + // Resolve VAT Product Posting Groups from extracted VAT rates + ResolveVATProductPostingGroups(EDocument."Entry No", EDocumentPurchaseHeader); + // Apply all Copilot-powered matching techniques to the lines CopilotLineMatching(EDocument."Entry No"); end; @@ -180,4 +184,66 @@ codeunit 6125 "Prepare Purchase E-Doc. Draft" implements IProcessStructuredData IVendorProvider := Customizations; Vendor := IVendorProvider.GetVendor(EDocument); end; + + local procedure ResolveVATProductPostingGroups(EDocumentEntryNo: Integer; EDocumentPurchaseHeader: Record "E-Document Purchase Header") + var + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + Vendor: Record Vendor; + EDocumentNotification: Codeunit "E-Document Notification"; + VATBusPostingGroup: Code[20]; + VATRate: Decimal; + LineCount: Integer; + HasUnresolvedVATLines: Boolean; + begin + if EDocumentPurchaseHeader."[BC] Vendor No." = '' then + exit; + if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then + exit; + VATBusPostingGroup := Vendor."VAT Bus. Posting Group"; + if VATBusPostingGroup = '' then + exit; + + EDocumentPurchaseLine.SetRange("E-Document Entry No.", EDocumentEntryNo); + LineCount := EDocumentPurchaseLine.Count(); + if LineCount = 0 then + exit; + + if EDocumentPurchaseLine.FindSet() then + repeat + VATRate := EDocumentPurchaseLine."VAT Rate"; + + // Single-line fallback: compute from header Total VAT + if (VATRate = 0) and (LineCount = 1) and + (EDocumentPurchaseHeader."Total VAT" > 0) and (EDocumentPurchaseHeader."Sub Total" > 0) + then + VATRate := Round((EDocumentPurchaseHeader."Total VAT" / EDocumentPurchaseHeader."Sub Total") * 100, 0.01); + + if VATRate > 0 then begin + EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" := + FindVATProductPostingGroup(VATBusPostingGroup, VATRate); + if EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" = '' then + HasUnresolvedVATLines := true; + EDocumentPurchaseLine.Modify(); + end; + until EDocumentPurchaseLine.Next() = 0; + + if HasUnresolvedVATLines then + EDocumentNotification.AddVATRateMismatchNotification(EDocumentEntryNo); + end; + + local procedure FindVATProductPostingGroup(VATBusPostingGroup: Code[20]; VATRate: Decimal): Code[20] + var + VATPostingSetup: Record "VAT Posting Setup"; + RoundingTolerance: Decimal; + begin + RoundingTolerance := 0.01; + VATPostingSetup.SetRange("VAT Bus. Posting Group", VATBusPostingGroup); + VATPostingSetup.SetFilter("VAT %", '>=%1&<=%2', VATRate - RoundingTolerance, VATRate + RoundingTolerance); + if VATPostingSetup.Count() = 1 then begin + VATPostingSetup.FindFirst(); + exit(VATPostingSetup."VAT Prod. Posting Group"); + end; + // Zero or multiple matches — return blank to signal resolution failure + exit(''); + end; } \ No newline at end of file From 4bfea7d68f8fcba5bbe041190ddf35750486ff4a Mon Sep 17 00:00:00 2001 From: ventselartur Date: Fri, 3 Apr 2026 15:50:14 +0200 Subject: [PATCH 09/53] feat: apply resolved VAT Prod. Posting Group when creating purchase invoice line Co-Authored-By: Claude Sonnet 4.6 --- .../Import/FinishDraft/EDocPurchDocHelper.Codeunit.al | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocPurchDocHelper.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocPurchDocHelper.Codeunit.al index d1e87d8dd2..b9e206b5cb 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocPurchDocHelper.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocPurchDocHelper.Codeunit.al @@ -37,6 +37,8 @@ codeunit 6402 "E-Doc. Purch. Doc. Helper" PurchaseLine."Variant Code" := EDocumentPurchaseLine."[BC] Variant Code"; PurchaseLine.Type := EDocumentPurchaseLine."[BC] Purchase Line Type"; PurchaseLine.Validate("No.", EDocumentPurchaseLine."[BC] Purchase Type No."); + if EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" <> '' then + PurchaseLine.Validate("VAT Prod. Posting Group", EDocumentPurchaseLine."[BC] VAT Prod. Posting Group"); if (PurchaseLine.Type = PurchaseLine.Type::"G/L Account") and HasTotalDiscount then PurchaseLine.Validate("Allow Invoice Disc.", true); PurchaseLine.Description := EDocumentPurchaseLine.Description; From 5d4b175edeb4ccf73818b79988c0ab2281f73500 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Fri, 3 Apr 2026 15:54:33 +0200 Subject: [PATCH 10/53] test: add integration tests for VAT Posting Group resolution during Prepare Draft Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Processing/EDocProcessTest.Codeunit.al | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al b/src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al index d2e22235f7..1c30c08d40 100644 --- a/src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al +++ b/src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al @@ -14,6 +14,7 @@ using Microsoft.Finance.Currency; using Microsoft.Finance.Dimension; using Microsoft.Finance.GeneralLedger.Account; using Microsoft.Finance.GeneralLedger.Setup; +using Microsoft.Finance.VAT.Setup; using Microsoft.Foundation.Company; using Microsoft.Inventory.Item; using Microsoft.Inventory.Item.Catalog; @@ -293,6 +294,126 @@ codeunit 139883 "E-Doc Process Test" TextToAccountMapping.Delete(); end; + [Test] + procedure PreparingPurchaseDraftResolvesVATProductPostingGroupFromLineVATRate() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + TempEDocImportParameters: Record "E-Doc. Import Parameters"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + VATPostingSetup2: Record "VAT Posting Setup"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + EDocumentProcessing: Codeunit "E-Document Processing"; + EDocImport: Codeunit "E-Doc. Import"; + LibraryERM: Codeunit "Library - ERM"; + begin + // [SCENARIO] When a draft line has a VAT Rate and a matching VAT Posting Setup exists, Prepare Draft resolves the VAT Prod. Posting Group + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor with a known VAT Bus. Posting Group + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] A VAT Posting Setup with VAT % = 10 for the vendor's bus posting group + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; + VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Normal VAT"; + VATPostingSetup2."VAT %" := 10; + VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2.Insert(); + + // [GIVEN] E-Document purchase header and line with VAT Rate = 10 + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."Vendor VAT Id" := Vendor2."VAT Registration No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine.Description := 'Test VAT resolution'; + EDocumentPurchaseLine."VAT Rate" := 10; + EDocumentPurchaseLine.Insert(); + + // [WHEN] Prepare Draft is run + EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Ready for draft"); + TempEDocImportParameters."Step to Run" := "Import E-Document Steps"::"Prepare draft"; + EDocImport.ProcessIncomingEDocument(EDocument, TempEDocImportParameters); + + // [THEN] The VAT Prod. Posting Group is resolved from the matching setup + EDocumentPurchaseLine.SetRecFilter(); + EDocumentPurchaseLine.FindFirst(); + Assert.AreEqual(VATProductPostingGroup.Code, EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'The VAT Prod. Posting Group should be resolved from the matching VAT Posting Setup.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + VATPostingSetup2.SetRecFilter(); + VATPostingSetup2.Delete(); + VATProductPostingGroup.SetRecFilter(); + VATProductPostingGroup.Delete(); + end; + + [Test] + procedure PreparingPurchaseDraftCreatesNotificationWhenNoMatchingVATSetup() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + TempEDocImportParameters: Record "E-Doc. Import Parameters"; + EDocumentNotification: Record "E-Document Notification"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + EDocumentProcessing: Codeunit "E-Document Processing"; + EDocImport: Codeunit "E-Doc. Import"; + begin + // [SCENARIO] When a draft line has a VAT Rate but no matching VAT Posting Setup exists, Prepare Draft leaves the field blank and creates a notification + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor with a known VAT Bus. Posting Group + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] E-Document purchase header and line with VAT Rate = 99 (no matching setup) + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."Vendor VAT Id" := Vendor2."VAT Registration No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine.Description := 'Test VAT mismatch notification'; + EDocumentPurchaseLine."VAT Rate" := 99; + EDocumentPurchaseLine.Insert(); + + // [WHEN] Prepare Draft is run + EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Ready for draft"); + TempEDocImportParameters."Step to Run" := "Import E-Document Steps"::"Prepare draft"; + EDocImport.ProcessIncomingEDocument(EDocument, TempEDocImportParameters); + + // [THEN] The VAT Prod. Posting Group is blank + EDocumentPurchaseLine.SetRecFilter(); + EDocumentPurchaseLine.FindFirst(); + Assert.AreEqual('', EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'The VAT Prod. Posting Group should be blank when no matching VAT Posting Setup exists.'); + + // [THEN] A VAT Rate Mismatch notification record exists + EDocumentNotification.SetRange("E-Document Entry No.", EDocument."Entry No"); + EDocumentNotification.SetRange(Type, "E-Document Notification Type"::"VAT Rate Mismatch"); + Assert.IsFalse(EDocumentNotification.IsEmpty(), 'A VAT Rate Mismatch notification should be created when resolution fails.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + EDocumentNotification.DeleteAll(); + end; + [Test] procedure FinishDraftCanBeUndone() var From f6c8e55d63e66d043890adaf9d20bec17a5e2057 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Fri, 3 Apr 2026 17:01:14 +0200 Subject: [PATCH 11/53] style: sort using statements alphabetically in PreparePurchaseEDocDraft Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al index a69c566a97..959d659fbd 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al @@ -8,9 +8,9 @@ using Microsoft.eServices.EDocument; using Microsoft.eServices.EDocument.Processing.AI; using Microsoft.eServices.EDocument.Processing.Import.Purchase; using Microsoft.eServices.EDocument.Processing.Interfaces; +using Microsoft.Finance.VAT.Setup; using Microsoft.Foundation.UOM; using Microsoft.Purchases.Document; -using Microsoft.Finance.VAT.Setup; using Microsoft.Purchases.Vendor; using System.Log; From 91447d695e0c7946aff944fcc083a752d1644577 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Fri, 3 Apr 2026 17:21:41 +0200 Subject: [PATCH 12/53] docs: add design spec for VAT rate mismatch inline warning field Replaces the notification banner approach with a persisted boolean field and an inline warning column on the draft subform, following the PO matching warning pattern. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../2026-04-03-vat-warning-field-design.md | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md new file mode 100644 index 0000000000..415a16b414 --- /dev/null +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md @@ -0,0 +1,170 @@ +# VAT Rate Mismatch — Inline Warning Field on Draft Subform + +**Date:** 2026-04-03 +**Status:** Draft +**Author:** Artur Ventsel + +## Problem + +The current branch introduces a page-level notification banner for VAT rate mismatch (when VAT Prod. Posting Group resolution fails during Prepare Draft). This notification pattern has drawbacks: + +1. It does not indicate **which** lines have the issue — the user must inspect each line manually. +2. It cannot react to user edits — dismissing the notification is a manual action unrelated to actually fixing the mismatch. +3. It adds significant plumbing (enum value, 4 procedures, GUID, My Notifications integration) for what is essentially a per-line data quality indicator. + +The PO matching feature already solves a similar problem with inline warning fields on the draft subform: a per-line column with `StyleExpr` styling, conditional visibility when any line has warnings, and drill-down for details. + +## Design + +Replace the notification approach with a persisted boolean field on the E-Document Purchase Line and an inline warning column on the draft subform page. + +### 1. New Field on E-Document Purchase Line + +**File:** `EDocumentPurchaseLine.Table.al` + +Add field 111 in the `[BC]` validated fields range (101–200), after field 110: + +```al +field(111; "[BC] VAT Rate Mismatch"; Boolean) +{ + Caption = 'VAT Rate Mismatch'; + ToolTip = 'Specifies whether the VAT Product Posting Group could not be resolved from the extracted VAT rate.'; +} +``` + +### 2. OnValidate Trigger for VAT Prod. Posting Group + +**File:** `EDocumentPurchaseLine.Table.al` — field 110 (`[BC] VAT Prod. Posting Group`) + +Add an OnValidate trigger that clears the mismatch flag when the user sets a posting group: + +```al +field(110; "[BC] VAT Prod. Posting Group"; Code[20]) +{ + ... + trigger OnValidate() + begin + if "[BC] VAT Prod. Posting Group" <> '' then + "[BC] VAT Rate Mismatch" := false; + end; +} +``` + +This ensures that when the user manually picks a VAT Prod. Posting Group on the draft subform, the warning disappears on the next page refresh. + +### 3. Set the Flag in Prepare Draft + +**File:** `PreparePurchaseEDocDraft.Codeunit.al` — `ResolveVATProductPostingGroups` + +Replace the `HasUnresolvedVATLines` boolean and notification call with direct field assignment: + +- When `FindVATProductPostingGroup` returns blank and `VATRate > 0`: set `"[BC] VAT Rate Mismatch" := true`. +- When `FindVATProductPostingGroup` returns a value: set `"[BC] VAT Rate Mismatch" := false`. +- Remove the `EDocumentNotification` variable and the `AddVATRateMismatchNotification` call. + +### 4. Warning Column on Draft Subform Page + +**File:** `EDocPurchaseDraftSubform.Page.al` + +Add a new field in the repeater, positioned after the `"VAT Prod. Posting Group"` field: + +```al +field(VATWarning; VATWarningCaption) +{ + ApplicationArea = All; + Caption = 'VAT warnings'; + Editable = false; + Visible = HasVATWarnings; + StyleExpr = VATWarningStyleExpr; + ToolTip = 'Specifies whether the VAT Product Posting Group could not be resolved from the extracted VAT rate.'; + + trigger OnDrillDown() + begin + ShowVATWarningDetails(); + end; +} +``` + +**Page-level variables:** + +```al +VATWarningCaption, VATWarningStyleExpr : Text; +HasVATWarnings : Boolean; +``` + +**Page-level visibility** (`HasVATWarnings`): Computed in `OnOpenPage` and `OnAfterGetCurrRecord` by a new `UpdateVATWarnings()` procedure: + +```al +local procedure UpdateVATWarnings() +var + EDocPurchLine: Record "E-Document Purchase Line"; +begin + EDocPurchLine.SetRange("E-Document Entry No.", EDocumentPurchaseHeader."E-Document Entry No."); + EDocPurchLine.SetRange("[BC] VAT Rate Mismatch", true); + HasVATWarnings := not EDocPurchLine.IsEmpty(); +end; +``` + +**Per-line caption and style**: Computed in `OnAfterGetRecord` by a new `UpdateVATWarningForLine()` procedure: + +```al +local procedure UpdateVATWarningForLine() +var + VATGroupNotResolvedLbl: Label 'VAT group not resolved'; +begin + if Rec."[BC] VAT Rate Mismatch" then begin + VATWarningCaption := VATGroupNotResolvedLbl; + VATWarningStyleExpr := 'Ambiguous'; + end else begin + VATWarningCaption := ''; + VATWarningStyleExpr := 'None'; + end; +end; +``` + +**Drill-down** (`ShowVATWarningDetails`): Shows a `Message()` with the extracted VAT rate: + +```al +local procedure ShowVATWarningDetails() +var + VATWarningDetailLbl: Label 'VAT rate %1% was extracted from the invoice but could not be matched to a single VAT Posting Setup for the vendor. Please select the correct VAT Product Posting Group manually.', Comment = '%1 = VAT rate percentage'; +begin + if not Rec."[BC] VAT Rate Mismatch" then + exit; + Message(VATWarningDetailLbl, Rec."VAT Rate"); +end; +``` + +### 5. Remove Notification Infrastructure + +Remove all VAT-related notification code added in earlier commits: + +**`EDocumentNotificationType.Enum.al`:** Remove `value(2; "VAT Rate Mismatch")`. + +**`EDocumentNotification.Codeunit.al`:** Remove: +- `AddVATRateMismatchNotification` procedure +- `DismissVATRateMismatchNotification` procedure +- `DisableVATRateMismatchNotification` procedure +- `GetVATRateMismatchNotificationId` procedure +- The `"VAT Rate Mismatch"` case branch in `AddActionsToNotification` +- The `"VAT Rate Mismatch"` filter in `SendPurchaseDocumentDraftNotifications` + +Revert these to their pre-branch state (only handling `"Vendor Matched By Name Not Address"`). + +### 6. Update Tests + +**`EDocProcessTest.Codeunit.al`:** + +- **`PreparingPurchaseDraftResolvesVATProductPostingGroupFromLineVATRate`**: Add assertion that `"[BC] VAT Rate Mismatch"` is `false` when resolution succeeds. +- **`PreparingPurchaseDraftCreatesNotificationWhenNoMatchingVATSetup`**: Rename to `PreparingPurchaseDraftSetsVATRateMismatchWhenNoMatchingVATSetup`. Replace the notification record assertion with: assert `"[BC] VAT Rate Mismatch"` is `true`. Remove notification cleanup. + +## Key Files + +| File | Change | +|---|---| +| `EDocumentPurchaseLine.Table.al` | Add field 111 `[BC] VAT Rate Mismatch`; add OnValidate to field 110 | +| `PreparePurchaseEDocDraft.Codeunit.al` | Set mismatch flag instead of calling notification | +| `EDocPurchaseDraftSubform.Page.al` | Add inline warning column with visibility, style, drill-down | +| `EDocumentNotification.Codeunit.al` | Remove all VAT notification procedures | +| `EDocumentNotificationType.Enum.al` | Remove `"VAT Rate Mismatch"` enum value | +| `EDocProcessTest.Codeunit.al` | Update test assertions from notification to boolean field | From a89ec5cefdf81258e3313c0ae119913399c8ff60 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Fri, 3 Apr 2026 17:32:18 +0200 Subject: [PATCH 13/53] docs: update OnValidate logic to compare VAT % from setup against extracted rate Instead of blindly clearing the mismatch flag when any posting group is selected, look up the VAT Posting Setup and compare its VAT % against the line's extracted VAT Rate. Only clear the warning when the rates actually match. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../2026-04-03-vat-warning-field-design.md | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md index 415a16b414..b749d56671 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md @@ -36,21 +36,46 @@ field(111; "[BC] VAT Rate Mismatch"; Boolean) **File:** `EDocumentPurchaseLine.Table.al` — field 110 (`[BC] VAT Prod. Posting Group`) -Add an OnValidate trigger that clears the mismatch flag when the user sets a posting group: +Add an OnValidate trigger that re-evaluates the mismatch by comparing the chosen posting group's VAT % against the extracted VAT rate on the line: ```al field(110; "[BC] VAT Prod. Posting Group"; Code[20]) { ... trigger OnValidate() + var + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + Vendor: Record Vendor; + VATPostingSetup: Record "VAT Posting Setup"; + RoundingTolerance: Decimal; begin - if "[BC] VAT Prod. Posting Group" <> '' then + if "VAT Rate" = 0 then begin "[BC] VAT Rate Mismatch" := false; + exit; + end; + if "[BC] VAT Prod. Posting Group" = '' then begin + "[BC] VAT Rate Mismatch" := true; + exit; + end; + if not EDocumentPurchaseHeader.Get("E-Document Entry No.") then + exit; + if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then + exit; + RoundingTolerance := 0.01; + if VATPostingSetup.Get(Vendor."VAT Bus. Posting Group", "[BC] VAT Prod. Posting Group") then + "[BC] VAT Rate Mismatch" := + (VATPostingSetup."VAT %" < "VAT Rate" - RoundingTolerance) or + (VATPostingSetup."VAT %" > "VAT Rate" + RoundingTolerance) + else + "[BC] VAT Rate Mismatch" := true; end; } ``` -This ensures that when the user manually picks a VAT Prod. Posting Group on the draft subform, the warning disappears on the next page refresh. +This means: +- If the line has no extracted VAT rate, there is no mismatch (nothing to compare against). +- If the user clears the posting group while a VAT rate exists, the mismatch flag is set. +- If the user picks a posting group, the trigger looks up the VAT Posting Setup for the vendor's VAT Bus. Posting Group + the chosen VAT Prod. Posting Group, compares `VAT %` to the line's `"VAT Rate"` with rounding tolerance, and sets the mismatch flag accordingly. Only a matching rate clears the warning. ### 3. Set the Flag in Prepare Draft From b634ab3d258ec1bbd0cbcee2da99c289f09923a5 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Fri, 3 Apr 2026 17:34:16 +0200 Subject: [PATCH 14/53] docs: use exact VAT % comparison and handle zero-rate lines in OnValidate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove rounding tolerance — compare VAT % from setup against extracted rate with exact equality. Zero-rate lines also go through the comparison instead of being skipped. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../docs/2026-04-03-vat-warning-field-design.md | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md index b749d56671..01b004dbd7 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md @@ -47,12 +47,7 @@ field(110; "[BC] VAT Prod. Posting Group"; Code[20]) EDocumentPurchaseHeader: Record "E-Document Purchase Header"; Vendor: Record Vendor; VATPostingSetup: Record "VAT Posting Setup"; - RoundingTolerance: Decimal; begin - if "VAT Rate" = 0 then begin - "[BC] VAT Rate Mismatch" := false; - exit; - end; if "[BC] VAT Prod. Posting Group" = '' then begin "[BC] VAT Rate Mismatch" := true; exit; @@ -61,11 +56,8 @@ field(110; "[BC] VAT Prod. Posting Group"; Code[20]) exit; if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then exit; - RoundingTolerance := 0.01; if VATPostingSetup.Get(Vendor."VAT Bus. Posting Group", "[BC] VAT Prod. Posting Group") then - "[BC] VAT Rate Mismatch" := - (VATPostingSetup."VAT %" < "VAT Rate" - RoundingTolerance) or - (VATPostingSetup."VAT %" > "VAT Rate" + RoundingTolerance) + "[BC] VAT Rate Mismatch" := VATPostingSetup."VAT %" <> "VAT Rate" else "[BC] VAT Rate Mismatch" := true; end; @@ -73,9 +65,8 @@ field(110; "[BC] VAT Prod. Posting Group"; Code[20]) ``` This means: -- If the line has no extracted VAT rate, there is no mismatch (nothing to compare against). -- If the user clears the posting group while a VAT rate exists, the mismatch flag is set. -- If the user picks a posting group, the trigger looks up the VAT Posting Setup for the vendor's VAT Bus. Posting Group + the chosen VAT Prod. Posting Group, compares `VAT %` to the line's `"VAT Rate"` with rounding tolerance, and sets the mismatch flag accordingly. Only a matching rate clears the warning. +- If the user clears the posting group, the mismatch flag is set. +- If the user picks a posting group, the trigger looks up the VAT Posting Setup for the vendor's VAT Bus. Posting Group + the chosen VAT Prod. Posting Group, compares `VAT %` to the line's `"VAT Rate"` with exact equality, and sets the mismatch flag accordingly. This works for zero-rated lines too — a line with `"VAT Rate" = 0` only clears the warning if the chosen posting group's setup also has `VAT % = 0`. ### 3. Set the Flag in Prepare Draft From 8a0a6d71f236dbcaef2fef6a12fb7862f7f92b77 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Fri, 3 Apr 2026 17:37:19 +0200 Subject: [PATCH 15/53] docs: add OnLookup trigger to open VAT Posting Setup filtered by vendor The lookup opens the VAT Posting Setup page filtered by the vendor's VAT Bus. Posting Group so the user only sees relevant combinations. Selection runs through Validate to trigger mismatch re-evaluation. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../2026-04-03-vat-warning-field-design.md | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md index 01b004dbd7..b06ed862e2 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md @@ -41,7 +41,10 @@ Add an OnValidate trigger that re-evaluates the mismatch by comparing the chosen ```al field(110; "[BC] VAT Prod. Posting Group"; Code[20]) { - ... + Caption = 'VAT Prod. Posting Group'; + ToolTip = 'Specifies the VAT product posting group resolved from the extracted VAT rate.'; + TableRelation = "VAT Product Posting Group"; + trigger OnValidate() var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; @@ -61,9 +64,26 @@ field(110; "[BC] VAT Prod. Posting Group"; Code[20]) else "[BC] VAT Rate Mismatch" := true; end; + + trigger OnLookup() + var + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + Vendor: Record Vendor; + VATPostingSetup: Record "VAT Posting Setup"; + begin + if not EDocumentPurchaseHeader.Get("E-Document Entry No.") then + exit; + if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then + exit; + VATPostingSetup.SetRange("VAT Bus. Posting Group", Vendor."VAT Bus. Posting Group"); + if Page.RunModal(Page::"VAT Posting Setup", VATPostingSetup) = Action::LookupOK then + Validate("[BC] VAT Prod. Posting Group", VATPostingSetup."VAT Prod. Posting Group"); + end; } ``` +The `TableRelation` provides basic validation and standard lookup. The `OnLookup` trigger overrides the default lookup to open the VAT Posting Setup page filtered by the vendor's VAT Bus. Posting Group, so the user only sees relevant posting groups. When the user selects a row, it calls `Validate` which runs the `OnValidate` trigger above, re-evaluating the mismatch. + This means: - If the user clears the posting group, the mismatch flag is set. - If the user picks a posting group, the trigger looks up the VAT Posting Setup for the vendor's VAT Bus. Posting Group + the chosen VAT Prod. Posting Group, compares `VAT %` to the line's `"VAT Rate"` with exact equality, and sets the mismatch flag accordingly. This works for zero-rated lines too — a line with `"VAT Rate" = 0` only clears the warning if the chosen posting group's setup also has `VAT % = 0`. From ac58f63877ae4f69a1498a1c9f51853a6e2d5e40 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Tue, 7 Apr 2026 11:48:09 +0200 Subject: [PATCH 16/53] docs: filter VAT matching to Normal VAT and Reverse Charge VAT only Full VAT and Sales Tax do not use VAT % for rate-based matching. Filter FindVATProductPostingGroup, OnValidate, and OnLookup to exclude those calculation types. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../2026-04-03-vat-warning-field-design.md | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md index b06ed862e2..81e3922cae 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md @@ -59,9 +59,14 @@ field(110; "[BC] VAT Prod. Posting Group"; Code[20]) exit; if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then exit; - if VATPostingSetup.Get(Vendor."VAT Bus. Posting Group", "[BC] VAT Prod. Posting Group") then - "[BC] VAT Rate Mismatch" := VATPostingSetup."VAT %" <> "VAT Rate" - else + if VATPostingSetup.Get(Vendor."VAT Bus. Posting Group", "[BC] VAT Prod. Posting Group") then begin + if not (VATPostingSetup."VAT Calculation Type" in + [VATPostingSetup."VAT Calculation Type"::"Normal VAT", + VATPostingSetup."VAT Calculation Type"::"Reverse Charge VAT"]) + then + exit; // Full VAT and Sales Tax — rate comparison is not applicable + "[BC] VAT Rate Mismatch" := VATPostingSetup."VAT %" <> "VAT Rate"; + end else "[BC] VAT Rate Mismatch" := true; end; @@ -76,6 +81,9 @@ field(110; "[BC] VAT Prod. Posting Group"; Code[20]) if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then exit; VATPostingSetup.SetRange("VAT Bus. Posting Group", Vendor."VAT Bus. Posting Group"); + VATPostingSetup.SetFilter("VAT Calculation Type", '%1|%2', + VATPostingSetup."VAT Calculation Type"::"Normal VAT", + VATPostingSetup."VAT Calculation Type"::"Reverse Charge VAT"); if Page.RunModal(Page::"VAT Posting Setup", VATPostingSetup) = Action::LookupOK then Validate("[BC] VAT Prod. Posting Group", VATPostingSetup."VAT Prod. Posting Group"); end; @@ -86,7 +94,9 @@ The `TableRelation` provides basic validation and standard lookup. The `OnLookup This means: - If the user clears the posting group, the mismatch flag is set. -- If the user picks a posting group, the trigger looks up the VAT Posting Setup for the vendor's VAT Bus. Posting Group + the chosen VAT Prod. Posting Group, compares `VAT %` to the line's `"VAT Rate"` with exact equality, and sets the mismatch flag accordingly. This works for zero-rated lines too — a line with `"VAT Rate" = 0` only clears the warning if the chosen posting group's setup also has `VAT % = 0`. +- If the user picks a posting group with Full VAT or Sales Tax calculation type, the mismatch evaluation is skipped (rate comparison is not applicable for those types). +- If the user picks a Normal VAT or Reverse Charge VAT posting group, the trigger compares `VAT %` to the line's `"VAT Rate"` with exact equality. This works for zero-rated lines too — a line with `"VAT Rate" = 0` only clears the warning if the setup also has `VAT % = 0`. +- The lookup is filtered to only show Normal VAT and Reverse Charge VAT setups for the vendor's VAT Bus. Posting Group. ### 3. Set the Flag in Prepare Draft @@ -98,6 +108,8 @@ Replace the `HasUnresolvedVATLines` boolean and notification call with direct fi - When `FindVATProductPostingGroup` returns a value: set `"[BC] VAT Rate Mismatch" := false`. - Remove the `EDocumentNotification` variable and the `AddVATRateMismatchNotification` call. +**`FindVATProductPostingGroup`** must filter to only `"VAT Calculation Type"` in `["Normal VAT", "Reverse Charge VAT"]`. Full VAT and Sales Tax setups do not use `VAT %` for rate-based matching and must be excluded from the query to avoid false positives (zero-match or wrong-match results). + ### 4. Warning Column on Draft Subform Page **File:** `EDocPurchaseDraftSubform.Page.al` From 31784c7d8fa42ff1a6eee4d5973e35f5a3c0f0d8 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Tue, 7 Apr 2026 11:52:35 +0200 Subject: [PATCH 17/53] docs: add 7 new test cases for VAT calculation type filtering and OnValidate Tests cover: Full VAT and Sales Tax ignored during Prepare Draft, Reverse Charge VAT resolved, OnValidate clears/keeps mismatch based on rate comparison, mismatch set when clearing posting group, Full VAT skips comparison, and zero-rate matching. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../2026-04-03-vat-warning-field-design.md | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md index 81e3922cae..2be905cc69 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md @@ -199,20 +199,44 @@ Remove all VAT-related notification code added in earlier commits: Revert these to their pre-branch state (only handling `"Vendor Matched By Name Not Address"`). -### 6. Update Tests +### 6. Update and Add Tests **`EDocProcessTest.Codeunit.al`:** +#### Update existing tests + - **`PreparingPurchaseDraftResolvesVATProductPostingGroupFromLineVATRate`**: Add assertion that `"[BC] VAT Rate Mismatch"` is `false` when resolution succeeds. - **`PreparingPurchaseDraftCreatesNotificationWhenNoMatchingVATSetup`**: Rename to `PreparingPurchaseDraftSetsVATRateMismatchWhenNoMatchingVATSetup`. Replace the notification record assertion with: assert `"[BC] VAT Rate Mismatch"` is `true`. Remove notification cleanup. +#### New tests + +**Prepare Draft — VAT Calculation Type filtering:** + +- **`PreparingDraftIgnoresFullVATSetupWhenResolvingPostingGroup`**: Create a VAT Posting Setup with `"VAT Calculation Type" = "Full VAT"` and `VAT % = 10`. Create a line with `"VAT Rate" = 10`. Run Prepare Draft. Assert `"[BC] VAT Prod. Posting Group"` is blank — Full VAT setups must not be matched. Assert `"[BC] VAT Rate Mismatch"` is `true`. + +- **`PreparingDraftIgnoresSalesTaxSetupWhenResolvingPostingGroup`**: Same as above but with `"VAT Calculation Type" = "Sales Tax"`. Assert the setup is not matched. + +- **`PreparingDraftResolvesReverseChargeVATPostingGroup`**: Create a VAT Posting Setup with `"VAT Calculation Type" = "Reverse Charge VAT"` and `VAT % = 20`. Create a line with `"VAT Rate" = 20`. Run Prepare Draft. Assert `"[BC] VAT Prod. Posting Group"` is resolved and `"[BC] VAT Rate Mismatch"` is `false`. + +**OnValidate — mismatch re-evaluation:** + +- **`ValidatingVATProdPostingGroupClearsMismatchWhenRateMatches`**: Create a line with `"VAT Rate" = 20` and `"[BC] VAT Rate Mismatch" = true`. Create a Normal VAT setup with `VAT % = 20`. Validate `"[BC] VAT Prod. Posting Group"` to that setup's group. Assert `"[BC] VAT Rate Mismatch"` is `false`. + +- **`ValidatingVATProdPostingGroupKeepsMismatchWhenRateDiffers`**: Create a line with `"VAT Rate" = 20` and `"[BC] VAT Rate Mismatch" = true`. Create a Normal VAT setup with `VAT % = 10`. Validate `"[BC] VAT Prod. Posting Group"` to that setup's group. Assert `"[BC] VAT Rate Mismatch"` is still `true`. + +- **`ValidatingVATProdPostingGroupSetsMismatchWhenCleared`**: Create a line with `"VAT Rate" = 20`, `"[BC] VAT Prod. Posting Group" = 'STANDARD'`, and `"[BC] VAT Rate Mismatch" = false`. Validate `"[BC] VAT Prod. Posting Group"` to `''`. Assert `"[BC] VAT Rate Mismatch"` is `true`. + +- **`ValidatingVATProdPostingGroupSkipsMismatchForFullVAT`**: Create a line with `"VAT Rate" = 5` and `"[BC] VAT Rate Mismatch" = false`. Create a Full VAT setup with `VAT % = 0`. Validate `"[BC] VAT Prod. Posting Group"` to that setup's group. Assert `"[BC] VAT Rate Mismatch"` is unchanged (`false`) — Full VAT skips the comparison. + +- **`ValidatingVATProdPostingGroupMatchesZeroRate`**: Create a line with `"VAT Rate" = 0`. Create a Normal VAT setup with `VAT % = 0`. Validate `"[BC] VAT Prod. Posting Group"` to that setup's group. Assert `"[BC] VAT Rate Mismatch"` is `false`. + ## Key Files | File | Change | |---|---| -| `EDocumentPurchaseLine.Table.al` | Add field 111 `[BC] VAT Rate Mismatch`; add OnValidate to field 110 | -| `PreparePurchaseEDocDraft.Codeunit.al` | Set mismatch flag instead of calling notification | +| `EDocumentPurchaseLine.Table.al` | Add field 111 `[BC] VAT Rate Mismatch`; add OnValidate and OnLookup to field 110 | +| `PreparePurchaseEDocDraft.Codeunit.al` | Set mismatch flag instead of calling notification; filter by VAT Calculation Type | | `EDocPurchaseDraftSubform.Page.al` | Add inline warning column with visibility, style, drill-down | | `EDocumentNotification.Codeunit.al` | Remove all VAT notification procedures | | `EDocumentNotificationType.Enum.al` | Remove `"VAT Rate Mismatch"` enum value | -| `EDocProcessTest.Codeunit.al` | Update test assertions from notification to boolean field | +| `EDocProcessTest.Codeunit.al` | Update existing test assertions; add 7 new tests for calculation type filtering and OnValidate mismatch logic | From 72373db486d4ca9c428f26638a5f26fd867651ad Mon Sep 17 00:00:00 2001 From: ventselartur Date: Tue, 7 Apr 2026 12:00:51 +0200 Subject: [PATCH 18/53] docs: add implementation plan for VAT rate mismatch inline warning field 7 tasks covering: table field + triggers, Prepare Draft refactor, subform warning column, notification removal, and 9 tests. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../docs/2026-04-07-vat-warning-field-plan.md | 1189 +++++++++++++++++ 1 file changed, 1189 insertions(+) create mode 100644 src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-07-vat-warning-field-plan.md diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-07-vat-warning-field-plan.md b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-07-vat-warning-field-plan.md new file mode 100644 index 0000000000..6f0ec1ae8a --- /dev/null +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-07-vat-warning-field-plan.md @@ -0,0 +1,1189 @@ +# VAT Rate Mismatch Inline Warning Field — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace the page-level VAT rate mismatch notification with a persisted boolean field and inline warning column on the E-Document Purchase Draft Subform, following the PO matching warning pattern. + +**Architecture:** Add `[BC] VAT Rate Mismatch` boolean field to `E-Document Purchase Line`, set it during Prepare Draft when resolution fails, re-evaluate it on user edits via `OnValidate`, and display it as a per-line warning column with conditional visibility on the draft subform. Remove all notification infrastructure for VAT mismatch. + +**Tech Stack:** AL (Business Central), E-Document Core framework, VAT Posting Setup table (325) + +**Spec:** `src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md` + +--- + +### Task 1: Add `[BC] VAT Rate Mismatch` field and update `[BC] VAT Prod. Posting Group` triggers + +**Files:** +- Modify: `src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al:212-217` + +- [ ] **Step 1: Add field 111 and update field 110 with OnValidate and OnLookup** + +Replace the current field 110 block (lines 212–217): + +```al + field(110; "[BC] VAT Prod. Posting Group"; Code[20]) + { + Caption = 'VAT Prod. Posting Group'; + ToolTip = 'Specifies the VAT product posting group resolved from the extracted VAT rate.'; + TableRelation = "VAT Product Posting Group"; + } +``` + +With: + +```al + field(110; "[BC] VAT Prod. Posting Group"; Code[20]) + { + Caption = 'VAT Prod. Posting Group'; + ToolTip = 'Specifies the VAT product posting group resolved from the extracted VAT rate.'; + TableRelation = "VAT Product Posting Group"; + + trigger OnValidate() + var + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + Vendor: Record Vendor; + VATPostingSetup: Record "VAT Posting Setup"; + begin + if "[BC] VAT Prod. Posting Group" = '' then begin + "[BC] VAT Rate Mismatch" := true; + exit; + end; + if not EDocumentPurchaseHeader.Get("E-Document Entry No.") then + exit; + if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then + exit; + if VATPostingSetup.Get(Vendor."VAT Bus. Posting Group", "[BC] VAT Prod. Posting Group") then begin + if not (VATPostingSetup."VAT Calculation Type" in + [VATPostingSetup."VAT Calculation Type"::"Normal VAT", + VATPostingSetup."VAT Calculation Type"::"Reverse Charge VAT"]) + then + exit; + "[BC] VAT Rate Mismatch" := VATPostingSetup."VAT %" <> "VAT Rate"; + end else + "[BC] VAT Rate Mismatch" := true; + end; + + trigger OnLookup() + var + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + Vendor: Record Vendor; + VATPostingSetup: Record "VAT Posting Setup"; + begin + if not EDocumentPurchaseHeader.Get("E-Document Entry No.") then + exit; + if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then + exit; + VATPostingSetup.SetRange("VAT Bus. Posting Group", Vendor."VAT Bus. Posting Group"); + VATPostingSetup.SetFilter("VAT Calculation Type", '%1|%2', + VATPostingSetup."VAT Calculation Type"::"Normal VAT", + VATPostingSetup."VAT Calculation Type"::"Reverse Charge VAT"); + if Page.RunModal(Page::"VAT Posting Setup", VATPostingSetup) = Action::LookupOK then + Validate("[BC] VAT Prod. Posting Group", VATPostingSetup."VAT Prod. Posting Group"); + end; + } + field(111; "[BC] VAT Rate Mismatch"; Boolean) + { + Caption = 'VAT Rate Mismatch'; + ToolTip = 'Specifies whether the VAT Product Posting Group could not be resolved from the extracted VAT rate.'; + } +``` + +The `using Microsoft.Finance.VAT.Setup;` and `using Microsoft.Purchases.Vendor;` are already present at lines 14 and 20 of the table file — no new usings needed. + +- [ ] **Step 2: Compile** + +Run: `al compile` for the E-Document app. +Expected: Clean compilation, no errors. + +- [ ] **Step 3: Commit** + +```bash +git add src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al +git commit -m "feat: add VAT Rate Mismatch field and OnValidate/OnLookup to VAT Prod. Posting Group + +Field 111 [BC] VAT Rate Mismatch persists whether VAT resolution failed. +OnValidate re-evaluates mismatch by comparing VAT Posting Setup rate +against extracted rate, filtering to Normal/Reverse Charge VAT only. +OnLookup opens VAT Posting Setup filtered by vendor's bus posting group." +``` + +--- + +### Task 2: Update `ResolveVATProductPostingGroups` to set mismatch flag instead of notification + +**Files:** +- Modify: `src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al:188-248` + +- [ ] **Step 1: Replace `ResolveVATProductPostingGroups` and `FindVATProductPostingGroup`** + +Replace the current `ResolveVATProductPostingGroups` procedure (lines 188–232): + +```al + local procedure ResolveVATProductPostingGroups(EDocumentEntryNo: Integer; EDocumentPurchaseHeader: Record "E-Document Purchase Header") + var + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + Vendor: Record Vendor; + EDocumentNotification: Codeunit "E-Document Notification"; + VATBusPostingGroup: Code[20]; + VATRate: Decimal; + LineCount: Integer; + HasUnresolvedVATLines: Boolean; + begin + if EDocumentPurchaseHeader."[BC] Vendor No." = '' then + exit; + if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then + exit; + VATBusPostingGroup := Vendor."VAT Bus. Posting Group"; + if VATBusPostingGroup = '' then + exit; + + EDocumentPurchaseLine.SetRange("E-Document Entry No.", EDocumentEntryNo); + LineCount := EDocumentPurchaseLine.Count(); + if LineCount = 0 then + exit; + + if EDocumentPurchaseLine.FindSet() then + repeat + VATRate := EDocumentPurchaseLine."VAT Rate"; + + // Single-line fallback: compute from header Total VAT + if (VATRate = 0) and (LineCount = 1) and + (EDocumentPurchaseHeader."Total VAT" > 0) and (EDocumentPurchaseHeader."Sub Total" > 0) + then + VATRate := Round((EDocumentPurchaseHeader."Total VAT" / EDocumentPurchaseHeader."Sub Total") * 100, 0.01); + + if VATRate > 0 then begin + EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" := + FindVATProductPostingGroup(VATBusPostingGroup, VATRate); + if EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" = '' then + HasUnresolvedVATLines := true; + EDocumentPurchaseLine.Modify(); + end; + until EDocumentPurchaseLine.Next() = 0; + + if HasUnresolvedVATLines then + EDocumentNotification.AddVATRateMismatchNotification(EDocumentEntryNo); + end; +``` + +With: + +```al + local procedure ResolveVATProductPostingGroups(EDocumentEntryNo: Integer; EDocumentPurchaseHeader: Record "E-Document Purchase Header") + var + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + Vendor: Record Vendor; + VATBusPostingGroup: Code[20]; + VATRate: Decimal; + LineCount: Integer; + begin + if EDocumentPurchaseHeader."[BC] Vendor No." = '' then + exit; + if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then + exit; + VATBusPostingGroup := Vendor."VAT Bus. Posting Group"; + if VATBusPostingGroup = '' then + exit; + + EDocumentPurchaseLine.SetRange("E-Document Entry No.", EDocumentEntryNo); + LineCount := EDocumentPurchaseLine.Count(); + if LineCount = 0 then + exit; + + if EDocumentPurchaseLine.FindSet() then + repeat + VATRate := EDocumentPurchaseLine."VAT Rate"; + + // Single-line fallback: compute from header Total VAT + if (VATRate = 0) and (LineCount = 1) and + (EDocumentPurchaseHeader."Total VAT" > 0) and (EDocumentPurchaseHeader."Sub Total" > 0) + then + VATRate := Round((EDocumentPurchaseHeader."Total VAT" / EDocumentPurchaseHeader."Sub Total") * 100, 0.01); + + if VATRate > 0 then begin + EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" := + FindVATProductPostingGroup(VATBusPostingGroup, VATRate); + EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := + EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" = ''; + EDocumentPurchaseLine.Modify(); + end; + until EDocumentPurchaseLine.Next() = 0; + end; +``` + +- [ ] **Step 2: Replace `FindVATProductPostingGroup` to filter by VAT Calculation Type** + +Replace the current `FindVATProductPostingGroup` procedure (lines 234–248): + +```al + local procedure FindVATProductPostingGroup(VATBusPostingGroup: Code[20]; VATRate: Decimal): Code[20] + var + VATPostingSetup: Record "VAT Posting Setup"; + RoundingTolerance: Decimal; + begin + RoundingTolerance := 0.01; + VATPostingSetup.SetRange("VAT Bus. Posting Group", VATBusPostingGroup); + VATPostingSetup.SetFilter("VAT %", '>=%1&<=%2', VATRate - RoundingTolerance, VATRate + RoundingTolerance); + if VATPostingSetup.Count() = 1 then begin + VATPostingSetup.FindFirst(); + exit(VATPostingSetup."VAT Prod. Posting Group"); + end; + // Zero or multiple matches — return blank to signal resolution failure + exit(''); + end; +``` + +With: + +```al + local procedure FindVATProductPostingGroup(VATBusPostingGroup: Code[20]; VATRate: Decimal): Code[20] + var + VATPostingSetup: Record "VAT Posting Setup"; + RoundingTolerance: Decimal; + begin + RoundingTolerance := 0.01; + VATPostingSetup.SetRange("VAT Bus. Posting Group", VATBusPostingGroup); + VATPostingSetup.SetFilter("VAT Calculation Type", '%1|%2', + VATPostingSetup."VAT Calculation Type"::"Normal VAT", + VATPostingSetup."VAT Calculation Type"::"Reverse Charge VAT"); + VATPostingSetup.SetFilter("VAT %", '>=%1&<=%2', VATRate - RoundingTolerance, VATRate + RoundingTolerance); + if VATPostingSetup.Count() = 1 then begin + VATPostingSetup.FindFirst(); + exit(VATPostingSetup."VAT Prod. Posting Group"); + end; + exit(''); + end; +``` + +- [ ] **Step 3: Remove unused `using Microsoft.eServices.EDocument;` if it was only for notification** + +Check the `using` statements at the top of `PreparePurchaseEDocDraft.Codeunit.al`. The `using Microsoft.eServices.EDocument;` at line 7 is still needed for the `"E-Document"` record type used elsewhere in the codeunit. No using changes needed. + +- [ ] **Step 4: Compile** + +Run: `al compile` for the E-Document app. +Expected: Clean compilation, no errors. + +- [ ] **Step 5: Commit** + +```bash +git add src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al +git commit -m "refactor: set VAT Rate Mismatch flag instead of notification in Prepare Draft + +Replace HasUnresolvedVATLines + notification call with direct +[BC] VAT Rate Mismatch field assignment. Filter FindVATProductPostingGroup +to Normal VAT and Reverse Charge VAT calculation types only." +``` + +--- + +### Task 3: Add VAT warning column to draft subform page + +**Files:** +- Modify: `src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al` + +- [ ] **Step 1: Add the warning field in the repeater** + +After the `"VAT Prod. Posting Group"` field block (after line 78, the closing `}` of that field), insert: + +```al + field(VATWarning; VATWarningCaption) + { + ApplicationArea = All; + Caption = 'VAT warnings'; + Editable = false; + Visible = HasVATWarnings; + StyleExpr = VATWarningStyleExpr; + ToolTip = 'Specifies whether the VAT Product Posting Group could not be resolved from the extracted VAT rate.'; + + trigger OnDrillDown() + begin + ShowVATWarningDetails(); + end; + } +``` + +- [ ] **Step 2: Add page-level variables** + +In the `var` section (line 339), add the new variables. Replace line 339: + +```al + AdditionalColumns, OrderMatchedCaption, MatchWarningsCaption, MatchWarningsStyleExpr : Text; +``` + +With: + +```al + AdditionalColumns, OrderMatchedCaption, MatchWarningsCaption, MatchWarningsStyleExpr, VATWarningCaption, VATWarningStyleExpr : Text; +``` + +In the boolean line (line 341), replace: + +```al + DimVisible1, DimVisible2, HasAdditionalColumns, IsEDocumentMatchedToAnyPOLine, IsLineMatchedToOrderLine, IsLineMatchedToReceiptLine, HasEDocumentOrderMatchWarnings : Boolean; +``` + +With: + +```al + DimVisible1, DimVisible2, HasAdditionalColumns, HasVATWarnings, IsEDocumentMatchedToAnyPOLine, IsLineMatchedToOrderLine, IsLineMatchedToReceiptLine, HasEDocumentOrderMatchWarnings : Boolean; +``` + +- [ ] **Step 3: Add `UpdateVATWarnings()` call to `OnOpenPage` and `OnAfterGetCurrRecord`** + +Replace `OnOpenPage` (lines 344–348): + +```al + trigger OnOpenPage() + begin + SetDimensionsVisibility(); + UpdatePOMatching(); + end; +``` + +With: + +```al + trigger OnOpenPage() + begin + SetDimensionsVisibility(); + UpdatePOMatching(); + UpdateVATWarnings(); + end; +``` + +Replace `OnAfterGetCurrRecord` (lines 355–358): + +```al + trigger OnAfterGetCurrRecord() + begin + UpdatePOMatching(); + end; +``` + +With: + +```al + trigger OnAfterGetCurrRecord() + begin + UpdatePOMatching(); + UpdateVATWarnings(); + end; +``` + +- [ ] **Step 4: Add `UpdateVATWarningForLine()` call to `OnAfterGetRecord`** + +At the end of `OnAfterGetRecord` (after `UpdateMatchWarnings();` at line 369), add: + +```al + UpdateVATWarningForLine(); +``` + +- [ ] **Step 5: Add the three new procedures** + +Add before the closing `}` of the page (before line 573): + +```al + local procedure UpdateVATWarnings() + var + EDocPurchLine: Record "E-Document Purchase Line"; + begin + EDocPurchLine.SetRange("E-Document Entry No.", EDocumentPurchaseHeader."E-Document Entry No."); + EDocPurchLine.SetRange("[BC] VAT Rate Mismatch", true); + HasVATWarnings := not EDocPurchLine.IsEmpty(); + end; + + local procedure UpdateVATWarningForLine() + var + VATGroupNotResolvedLbl: Label 'VAT group not resolved'; + begin + if Rec."[BC] VAT Rate Mismatch" then begin + VATWarningCaption := VATGroupNotResolvedLbl; + VATWarningStyleExpr := 'Ambiguous'; + end else begin + VATWarningCaption := ''; + VATWarningStyleExpr := 'None'; + end; + end; + + local procedure ShowVATWarningDetails() + var + VATWarningDetailLbl: Label 'VAT rate %1% was extracted from the invoice but could not be matched to a single VAT Posting Setup for the vendor. Please select the correct VAT Product Posting Group manually.', Comment = '%1 = VAT rate percentage'; + begin + if not Rec."[BC] VAT Rate Mismatch" then + exit; + Message(VATWarningDetailLbl, Rec."VAT Rate"); + end; +``` + +- [ ] **Step 6: Compile** + +Run: `al compile` for the E-Document app. +Expected: Clean compilation, no errors. + +- [ ] **Step 7: Commit** + +```bash +git add src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al +git commit -m "feat: add inline VAT warning column to draft subform page + +Per-line warning with Ambiguous styling, conditional visibility when +any line has a mismatch, and drill-down showing the extracted VAT rate. +Follows the PO matching warning pattern." +``` + +--- + +### Task 4: Remove VAT notification infrastructure + +**Files:** +- Modify: `src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotificationType.Enum.al` +- Modify: `src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotification.Codeunit.al` + +- [ ] **Step 1: Remove enum value from `EDocumentNotificationType.Enum.al`** + +Remove lines 19–22: + +```al + value(2; "VAT Rate Mismatch") + { + Caption = 'VAT Rate Mismatch'; + } +``` + +- [ ] **Step 2: Remove `AddVATRateMismatchNotification` from `EDocumentNotification.Codeunit.al`** + +Remove lines 39–57 (the entire `AddVATRateMismatchNotification` procedure). + +- [ ] **Step 3: Revert `SendPurchaseDocumentDraftNotifications` filter** + +Replace lines 70–73: + +```al + EDocumentNotification.SetFilter(Type, '%1|%2', + "E-Document Notification Type"::"Vendor Matched By Name Not Address", + "E-Document Notification Type"::"VAT Rate Mismatch"); +``` + +With: + +```al + EDocumentNotification.SetRange(Type, "E-Document Notification Type"::"Vendor Matched By Name Not Address"); +``` + +- [ ] **Step 4: Remove `DismissVATRateMismatchNotification` and `DisableVATRateMismatchNotification`** + +Remove lines 119–145 (both procedures). + +- [ ] **Step 5: Revert `AddActionsToNotification` to only handle vendor notification** + +Replace the current `AddActionsToNotification` (lines 162–181): + +```al + local procedure AddActionsToNotification(var Notification: Notification; EDocumentNotification: Record "E-Document Notification") + var + DismissMsg: Label 'Dismiss'; + DontShowThisAgainMsg: Label 'Don''t show this again.'; + begin + Notification.SetData(EDocumentNotification.FieldName("E-Document Entry No."), Format(EDocumentNotification."E-Document Entry No.")); + Notification.SetData(EDocumentNotification.FieldName(ID), EDocumentNotification.ID); + case EDocumentNotification.Type of + "E-Document Notification Type"::"Vendor Matched By Name Not Address": + begin + Notification.AddAction(DismissMsg, Codeunit::"E-Document Notification", 'DismissVendorMatchedByNameNotAddressNotification'); + Notification.AddAction(DontShowThisAgainMsg, Codeunit::"E-Document Notification", 'DisableVendorMatchedByNameNotAddressNotification'); + end; + "E-Document Notification Type"::"VAT Rate Mismatch": + begin + Notification.AddAction(DismissMsg, Codeunit::"E-Document Notification", 'DismissVATRateMismatchNotification'); + Notification.AddAction(DontShowThisAgainMsg, Codeunit::"E-Document Notification", 'DisableVATRateMismatchNotification'); + end; + end; + end; +``` + +With: + +```al + local procedure AddActionsToNotification(var Notification: Notification; EDocumentNotification: Record "E-Document Notification") + var + DismissMsg: Label 'Dismiss'; + DontShowThisAgainMsg: Label 'Don''t show this again.'; + begin + if EDocumentNotification.Type <> "E-Document Notification Type"::"Vendor Matched By Name Not Address" then + exit; + Notification.SetData(EDocumentNotification.FieldName("E-Document Entry No."), Format(EDocumentNotification."E-Document Entry No.")); + Notification.SetData(EDocumentNotification.FieldName(ID), EDocumentNotification.ID); + Notification.AddAction(DismissMsg, Codeunit::"E-Document Notification", 'DismissVendorMatchedByNameNotAddressNotification'); + Notification.AddAction(DontShowThisAgainMsg, Codeunit::"E-Document Notification", 'DisableVendorMatchedByNameNotAddressNotification'); + end; +``` + +- [ ] **Step 6: Remove `GetVATRateMismatchNotificationId`** + +Remove lines 188–191: + +```al + local procedure GetVATRateMismatchNotificationId(): Guid + begin + exit('d4a7e1c3-5f92-4b8a-ae67-1c3d5f924b8a'); + end; +``` + +- [ ] **Step 7: Compile** + +Run: `al compile` for the E-Document app. +Expected: Clean compilation, no errors. + +- [ ] **Step 8: Commit** + +```bash +git add src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotificationType.Enum.al +git add src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotification.Codeunit.al +git commit -m "refactor: remove VAT Rate Mismatch notification infrastructure + +Notification replaced by persisted [BC] VAT Rate Mismatch field +and inline warning column on draft subform." +``` + +--- + +### Task 5: Update existing tests to assert mismatch flag instead of notification + +**Files:** +- Modify: `src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al:297-415` + +- [ ] **Step 1: Update the successful resolution test** + +In `PreparingPurchaseDraftResolvesVATProductPostingGroupFromLineVATRate` (line 298), after the existing assertion at line 351: + +```al + Assert.AreEqual(VATProductPostingGroup.Code, EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'The VAT Prod. Posting Group should be resolved from the matching VAT Posting Setup.'); +``` + +Add: + +```al + Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be false when resolution succeeds.'); +``` + +- [ ] **Step 2: Rewrite the mismatch test** + +Replace the entire `PreparingPurchaseDraftCreatesNotificationWhenNoMatchingVATSetup` procedure (lines 362–415) with: + +```al + [Test] + procedure PreparingPurchaseDraftSetsVATRateMismatchWhenNoMatchingVATSetup() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + TempEDocImportParameters: Record "E-Doc. Import Parameters"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + EDocumentProcessing: Codeunit "E-Document Processing"; + EDocImport: Codeunit "E-Doc. Import"; + begin + // [SCENARIO] When a draft line has a VAT Rate but no matching VAT Posting Setup exists, Prepare Draft leaves the field blank and sets the mismatch flag + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor with a known VAT Bus. Posting Group + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] E-Document purchase header and line with VAT Rate = 99 (no matching setup) + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."Vendor VAT Id" := Vendor2."VAT Registration No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine.Description := 'Test VAT mismatch'; + EDocumentPurchaseLine."VAT Rate" := 99; + EDocumentPurchaseLine.Insert(); + + // [WHEN] Prepare Draft is run + EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Ready for draft"); + TempEDocImportParameters."Step to Run" := "Import E-Document Steps"::"Prepare draft"; + EDocImport.ProcessIncomingEDocument(EDocument, TempEDocImportParameters); + + // [THEN] The VAT Prod. Posting Group is blank and mismatch flag is set + EDocumentPurchaseLine.SetRecFilter(); + EDocumentPurchaseLine.FindFirst(); + Assert.AreEqual('', EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'The VAT Prod. Posting Group should be blank when no matching VAT Posting Setup exists.'); + Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be true when resolution fails.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + end; +``` + +- [ ] **Step 3: Remove unused `using` if needed** + +Check whether `"E-Document Notification Type"` enum is still referenced in the test file. If the only usage was in the deleted test, the `using` for it can be removed. The `using Microsoft.Finance.VAT.Setup;` at line 17 is still needed. No changes expected here. + +- [ ] **Step 4: Compile and run tests** + +Run: `al compile` and `al run_tests` for the E-Document test app. +Expected: Both updated tests pass. + +- [ ] **Step 5: Commit** + +```bash +git add src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al +git commit -m "test: update VAT resolution tests to assert mismatch flag instead of notification + +Rename and rewrite the mismatch test to assert [BC] VAT Rate Mismatch +boolean. Add mismatch=false assertion to the successful resolution test." +``` + +--- + +### Task 6: Add tests for VAT Calculation Type filtering in Prepare Draft + +**Files:** +- Modify: `src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al` + +These tests validate that `FindVATProductPostingGroup` only matches Normal VAT and Reverse Charge VAT setups. + +- [ ] **Step 1: Add `PreparingDraftIgnoresFullVATSetupWhenResolvingPostingGroup`** + +Insert after the `PreparingPurchaseDraftSetsVATRateMismatchWhenNoMatchingVATSetup` procedure: + +```al + [Test] + procedure PreparingDraftIgnoresFullVATSetupWhenResolvingPostingGroup() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + TempEDocImportParameters: Record "E-Doc. Import Parameters"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + VATPostingSetup2: Record "VAT Posting Setup"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + EDocumentProcessing: Codeunit "E-Document Processing"; + EDocImport: Codeunit "E-Doc. Import"; + LibraryERM: Codeunit "Library - ERM"; + begin + // [SCENARIO] Full VAT setups must not be matched during VAT Posting Group resolution + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] A Full VAT Posting Setup with VAT % = 10 + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; + VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Full VAT"; + VATPostingSetup2."VAT %" := 10; + VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2.Insert(); + + // [GIVEN] E-Document line with VAT Rate = 10 + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."Vendor VAT Id" := Vendor2."VAT Registration No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine.Description := 'Test Full VAT ignored'; + EDocumentPurchaseLine."VAT Rate" := 10; + EDocumentPurchaseLine.Insert(); + + // [WHEN] Prepare Draft is run + EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Ready for draft"); + TempEDocImportParameters."Step to Run" := "Import E-Document Steps"::"Prepare draft"; + EDocImport.ProcessIncomingEDocument(EDocument, TempEDocImportParameters); + + // [THEN] Full VAT setup is not matched + EDocumentPurchaseLine.SetRecFilter(); + EDocumentPurchaseLine.FindFirst(); + Assert.AreEqual('', EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'Full VAT setups must not be matched.'); + Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be true when only Full VAT setups exist.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + VATPostingSetup2.SetRecFilter(); + VATPostingSetup2.Delete(); + VATProductPostingGroup.SetRecFilter(); + VATProductPostingGroup.Delete(); + end; +``` + +- [ ] **Step 2: Add `PreparingDraftIgnoresSalesTaxSetupWhenResolvingPostingGroup`** + +```al + [Test] + procedure PreparingDraftIgnoresSalesTaxSetupWhenResolvingPostingGroup() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + TempEDocImportParameters: Record "E-Doc. Import Parameters"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + VATPostingSetup2: Record "VAT Posting Setup"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + EDocumentProcessing: Codeunit "E-Document Processing"; + EDocImport: Codeunit "E-Doc. Import"; + LibraryERM: Codeunit "Library - ERM"; + begin + // [SCENARIO] Sales Tax setups must not be matched during VAT Posting Group resolution + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] A Sales Tax Posting Setup with VAT % = 10 + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; + VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Sales Tax"; + VATPostingSetup2."VAT %" := 10; + VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2.Insert(); + + // [GIVEN] E-Document line with VAT Rate = 10 + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."Vendor VAT Id" := Vendor2."VAT Registration No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine.Description := 'Test Sales Tax ignored'; + EDocumentPurchaseLine."VAT Rate" := 10; + EDocumentPurchaseLine.Insert(); + + // [WHEN] Prepare Draft is run + EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Ready for draft"); + TempEDocImportParameters."Step to Run" := "Import E-Document Steps"::"Prepare draft"; + EDocImport.ProcessIncomingEDocument(EDocument, TempEDocImportParameters); + + // [THEN] Sales Tax setup is not matched + EDocumentPurchaseLine.SetRecFilter(); + EDocumentPurchaseLine.FindFirst(); + Assert.AreEqual('', EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'Sales Tax setups must not be matched.'); + Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be true when only Sales Tax setups exist.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + VATPostingSetup2.SetRecFilter(); + VATPostingSetup2.Delete(); + VATProductPostingGroup.SetRecFilter(); + VATProductPostingGroup.Delete(); + end; +``` + +- [ ] **Step 3: Add `PreparingDraftResolvesReverseChargeVATPostingGroup`** + +```al + [Test] + procedure PreparingDraftResolvesReverseChargeVATPostingGroup() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + TempEDocImportParameters: Record "E-Doc. Import Parameters"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + VATPostingSetup2: Record "VAT Posting Setup"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + EDocumentProcessing: Codeunit "E-Document Processing"; + EDocImport: Codeunit "E-Doc. Import"; + LibraryERM: Codeunit "Library - ERM"; + begin + // [SCENARIO] Reverse Charge VAT setups should be matched during VAT Posting Group resolution + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] A Reverse Charge VAT Posting Setup with VAT % = 20 + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; + VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Reverse Charge VAT"; + VATPostingSetup2."VAT %" := 20; + VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Reverse Chrg. VAT Acc." := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2.Insert(); + + // [GIVEN] E-Document line with VAT Rate = 20 + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."Vendor VAT Id" := Vendor2."VAT Registration No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine.Description := 'Test Reverse Charge resolved'; + EDocumentPurchaseLine."VAT Rate" := 20; + EDocumentPurchaseLine.Insert(); + + // [WHEN] Prepare Draft is run + EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Ready for draft"); + TempEDocImportParameters."Step to Run" := "Import E-Document Steps"::"Prepare draft"; + EDocImport.ProcessIncomingEDocument(EDocument, TempEDocImportParameters); + + // [THEN] Reverse Charge VAT setup is matched + EDocumentPurchaseLine.SetRecFilter(); + EDocumentPurchaseLine.FindFirst(); + Assert.AreEqual(VATProductPostingGroup.Code, EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'Reverse Charge VAT setups should be matched.'); + Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be false when Reverse Charge VAT matches.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + VATPostingSetup2.SetRecFilter(); + VATPostingSetup2.Delete(); + VATProductPostingGroup.SetRecFilter(); + VATProductPostingGroup.Delete(); + end; +``` + +- [ ] **Step 4: Compile and run tests** + +Run: `al compile` and `al run_tests` for the E-Document test app. +Expected: All three new tests pass. + +- [ ] **Step 5: Commit** + +```bash +git add src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al +git commit -m "test: add tests for VAT Calculation Type filtering in Prepare Draft + +Full VAT and Sales Tax setups are excluded from resolution. +Reverse Charge VAT setups are matched successfully." +``` + +--- + +### Task 7: Add tests for OnValidate mismatch re-evaluation + +**Files:** +- Modify: `src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al` + +These tests validate the `OnValidate` trigger on field 110 `[BC] VAT Prod. Posting Group`. They operate directly on the `E-Document Purchase Line` record without running the full Prepare Draft pipeline. + +- [ ] **Step 1: Add `ValidatingVATProdPostingGroupClearsMismatchWhenRateMatches`** + +```al + [Test] + procedure ValidatingVATProdPostingGroupClearsMismatchWhenRateMatches() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + VATPostingSetup2: Record "VAT Posting Setup"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + LibraryERM: Codeunit "Library - ERM"; + begin + // [SCENARIO] OnValidate clears mismatch when selected posting group's VAT % matches the line's VAT Rate + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] A Normal VAT setup with VAT % = 20 + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; + VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Normal VAT"; + VATPostingSetup2."VAT %" := 20; + VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2.Insert(); + + // [GIVEN] A line with VAT Rate = 20 and mismatch = true + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."[BC] Vendor No." := Vendor2."No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine."VAT Rate" := 20; + EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := true; + EDocumentPurchaseLine.Insert(); + + // [WHEN] User validates the posting group to the matching setup + EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", VATProductPostingGroup.Code); + EDocumentPurchaseLine.Modify(); + + // [THEN] Mismatch is cleared + Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should be false when VAT % matches VAT Rate.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + VATPostingSetup2.SetRecFilter(); + VATPostingSetup2.Delete(); + VATProductPostingGroup.SetRecFilter(); + VATProductPostingGroup.Delete(); + end; +``` + +- [ ] **Step 2: Add `ValidatingVATProdPostingGroupKeepsMismatchWhenRateDiffers`** + +```al + [Test] + procedure ValidatingVATProdPostingGroupKeepsMismatchWhenRateDiffers() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + VATPostingSetup2: Record "VAT Posting Setup"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + LibraryERM: Codeunit "Library - ERM"; + begin + // [SCENARIO] OnValidate keeps mismatch when selected posting group's VAT % differs from VAT Rate + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] A Normal VAT setup with VAT % = 10 (different from line's 20) + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; + VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Normal VAT"; + VATPostingSetup2."VAT %" := 10; + VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2.Insert(); + + // [GIVEN] A line with VAT Rate = 20 and mismatch = true + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."[BC] Vendor No." := Vendor2."No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine."VAT Rate" := 20; + EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := true; + EDocumentPurchaseLine.Insert(); + + // [WHEN] User validates the posting group to a non-matching setup + EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", VATProductPostingGroup.Code); + EDocumentPurchaseLine.Modify(); + + // [THEN] Mismatch remains true + Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should remain true when VAT % does not match VAT Rate.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + VATPostingSetup2.SetRecFilter(); + VATPostingSetup2.Delete(); + VATProductPostingGroup.SetRecFilter(); + VATProductPostingGroup.Delete(); + end; +``` + +- [ ] **Step 3: Add `ValidatingVATProdPostingGroupSetsMismatchWhenCleared`** + +```al + [Test] + procedure ValidatingVATProdPostingGroupSetsMismatchWhenCleared() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + begin + // [SCENARIO] OnValidate sets mismatch when posting group is cleared + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A line with VAT Rate = 20, a posting group, and no mismatch + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine."VAT Rate" := 20; + EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" := 'STANDARD'; + EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := false; + EDocumentPurchaseLine.Insert(); + + // [WHEN] User clears the posting group + EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", ''); + EDocumentPurchaseLine.Modify(); + + // [THEN] Mismatch is set + Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should be true when posting group is cleared.'); + end; +``` + +- [ ] **Step 4: Add `ValidatingVATProdPostingGroupSkipsMismatchForFullVAT`** + +```al + [Test] + procedure ValidatingVATProdPostingGroupSkipsMismatchForFullVAT() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + VATPostingSetup2: Record "VAT Posting Setup"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + LibraryERM: Codeunit "Library - ERM"; + begin + // [SCENARIO] OnValidate skips mismatch evaluation for Full VAT — flag stays unchanged + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] A Full VAT setup with VAT % = 0 + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; + VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Full VAT"; + VATPostingSetup2."VAT %" := 0; + VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2.Insert(); + + // [GIVEN] A line with VAT Rate = 5 and mismatch = false + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."[BC] Vendor No." := Vendor2."No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine."VAT Rate" := 5; + EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := false; + EDocumentPurchaseLine.Insert(); + + // [WHEN] User validates the posting group to the Full VAT setup + EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", VATProductPostingGroup.Code); + EDocumentPurchaseLine.Modify(); + + // [THEN] Mismatch flag is unchanged (still false) — Full VAT skips comparison + Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should remain unchanged for Full VAT calculation type.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + VATPostingSetup2.SetRecFilter(); + VATPostingSetup2.Delete(); + VATProductPostingGroup.SetRecFilter(); + VATProductPostingGroup.Delete(); + end; +``` + +- [ ] **Step 5: Add `ValidatingVATProdPostingGroupMatchesZeroRate`** + +```al + [Test] + procedure ValidatingVATProdPostingGroupMatchesZeroRate() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + VATPostingSetup2: Record "VAT Posting Setup"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + LibraryERM: Codeunit "Library - ERM"; + begin + // [SCENARIO] OnValidate clears mismatch when both VAT Rate and VAT % are 0 + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] A Normal VAT setup with VAT % = 0 (zero-rated) + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; + VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Normal VAT"; + VATPostingSetup2."VAT %" := 0; + VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2.Insert(); + + // [GIVEN] A line with VAT Rate = 0 + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."[BC] Vendor No." := Vendor2."No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine."VAT Rate" := 0; + EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := true; + EDocumentPurchaseLine.Insert(); + + // [WHEN] User validates the posting group to the zero-rated setup + EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", VATProductPostingGroup.Code); + EDocumentPurchaseLine.Modify(); + + // [THEN] Mismatch is cleared — both rates are 0 + Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should be false when both VAT Rate and VAT % are 0.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + VATPostingSetup2.SetRecFilter(); + VATPostingSetup2.Delete(); + VATProductPostingGroup.SetRecFilter(); + VATProductPostingGroup.Delete(); + end; +``` + +- [ ] **Step 6: Compile and run all tests** + +Run: `al compile` and `al run_tests` for the E-Document test app. +Expected: All tests pass — both the updated existing tests and all 8 new tests. + +- [ ] **Step 7: Commit** + +```bash +git add src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al +git commit -m "test: add OnValidate mismatch re-evaluation tests + +Covers: rate match clears mismatch, rate mismatch persists, +clearing group sets mismatch, Full VAT skips comparison, +zero-rate matching works." +``` From 0c0ed31cf94ae0a88ea54511ee745625545d6f7a Mon Sep 17 00:00:00 2001 From: ventselartur Date: Tue, 7 Apr 2026 12:05:29 +0200 Subject: [PATCH 19/53] feat: add VAT Rate Mismatch field and OnValidate/OnLookup to VAT Prod. Posting Group Field 111 [BC] VAT Rate Mismatch persists whether VAT resolution failed. OnValidate re-evaluates mismatch by comparing VAT Posting Setup rate against extracted rate, filtering to Normal/Reverse Charge VAT only. OnLookup opens VAT Posting Setup filtered by vendor's bus posting group. --- .../Purchase/EDocumentPurchaseLine.Table.al | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al index 8c7de9a825..4873d3b50a 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al @@ -214,6 +214,54 @@ table 6101 "E-Document Purchase Line" Caption = 'VAT Prod. Posting Group'; ToolTip = 'Specifies the VAT product posting group resolved from the extracted VAT rate.'; TableRelation = "VAT Product Posting Group"; + + trigger OnValidate() + var + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + Vendor: Record Vendor; + VATPostingSetup: Record "VAT Posting Setup"; + begin + if "[BC] VAT Prod. Posting Group" = '' then begin + "[BC] VAT Rate Mismatch" := true; + exit; + end; + if not EDocumentPurchaseHeader.Get("E-Document Entry No.") then + exit; + if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then + exit; + if VATPostingSetup.Get(Vendor."VAT Bus. Posting Group", "[BC] VAT Prod. Posting Group") then begin + if not (VATPostingSetup."VAT Calculation Type" in + [VATPostingSetup."VAT Calculation Type"::"Normal VAT", + VATPostingSetup."VAT Calculation Type"::"Reverse Charge VAT"]) + then + exit; + "[BC] VAT Rate Mismatch" := VATPostingSetup."VAT %" <> "VAT Rate"; + end else + "[BC] VAT Rate Mismatch" := true; + end; + + trigger OnLookup() + var + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + Vendor: Record Vendor; + VATPostingSetup: Record "VAT Posting Setup"; + begin + if not EDocumentPurchaseHeader.Get("E-Document Entry No.") then + exit; + if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then + exit; + VATPostingSetup.SetRange("VAT Bus. Posting Group", Vendor."VAT Bus. Posting Group"); + VATPostingSetup.SetFilter("VAT Calculation Type", '%1|%2', + VATPostingSetup."VAT Calculation Type"::"Normal VAT", + VATPostingSetup."VAT Calculation Type"::"Reverse Charge VAT"); + if Page.RunModal(Page::"VAT Posting Setup", VATPostingSetup) = Action::LookupOK then + Validate("[BC] VAT Prod. Posting Group", VATPostingSetup."VAT Prod. Posting Group"); + end; + } + field(111; "[BC] VAT Rate Mismatch"; Boolean) + { + Caption = 'VAT Rate Mismatch'; + ToolTip = 'Specifies whether the VAT Product Posting Group could not be resolved from the extracted VAT rate.'; } #endregion Validated fields From 1bb68d81451c299d1350dfee594a3d5c39e627f7 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Tue, 7 Apr 2026 12:09:32 +0200 Subject: [PATCH 20/53] refactor: set VAT Rate Mismatch flag instead of notification in Prepare Draft Replace HasUnresolvedVATLines + notification call with direct [BC] VAT Rate Mismatch field assignment. Filter FindVATProductPostingGroup to Normal VAT and Reverse Charge VAT calculation types only. --- .../PreparePurchaseEDocDraft.Codeunit.al | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al index 959d659fbd..400a50245e 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al @@ -189,11 +189,9 @@ codeunit 6125 "Prepare Purchase E-Doc. Draft" implements IProcessStructuredData var EDocumentPurchaseLine: Record "E-Document Purchase Line"; Vendor: Record Vendor; - EDocumentNotification: Codeunit "E-Document Notification"; VATBusPostingGroup: Code[20]; VATRate: Decimal; LineCount: Integer; - HasUnresolvedVATLines: Boolean; begin if EDocumentPurchaseHeader."[BC] Vendor No." = '' then exit; @@ -221,14 +219,11 @@ codeunit 6125 "Prepare Purchase E-Doc. Draft" implements IProcessStructuredData if VATRate > 0 then begin EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" := FindVATProductPostingGroup(VATBusPostingGroup, VATRate); - if EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" = '' then - HasUnresolvedVATLines := true; + EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := + EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" = ''; EDocumentPurchaseLine.Modify(); end; until EDocumentPurchaseLine.Next() = 0; - - if HasUnresolvedVATLines then - EDocumentNotification.AddVATRateMismatchNotification(EDocumentEntryNo); end; local procedure FindVATProductPostingGroup(VATBusPostingGroup: Code[20]; VATRate: Decimal): Code[20] @@ -238,12 +233,14 @@ codeunit 6125 "Prepare Purchase E-Doc. Draft" implements IProcessStructuredData begin RoundingTolerance := 0.01; VATPostingSetup.SetRange("VAT Bus. Posting Group", VATBusPostingGroup); + VATPostingSetup.SetFilter("VAT Calculation Type", '%1|%2', + VATPostingSetup."VAT Calculation Type"::"Normal VAT", + VATPostingSetup."VAT Calculation Type"::"Reverse Charge VAT"); VATPostingSetup.SetFilter("VAT %", '>=%1&<=%2', VATRate - RoundingTolerance, VATRate + RoundingTolerance); if VATPostingSetup.Count() = 1 then begin VATPostingSetup.FindFirst(); exit(VATPostingSetup."VAT Prod. Posting Group"); end; - // Zero or multiple matches — return blank to signal resolution failure exit(''); end; } \ No newline at end of file From 9e9d9748084162405e075524df2993ee38cdf0d8 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Tue, 7 Apr 2026 12:12:02 +0200 Subject: [PATCH 21/53] feat: add inline VAT warning column to draft subform page Per-line warning with Ambiguous styling, conditional visibility when any line has a mismatch, and drill-down showing the extracted VAT rate. Follows the PO matching warning pattern. --- .../Purchase/EDocPurchaseDraftSubform.Page.al | 52 ++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al index 27ff4396aa..e97f18070c 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al @@ -76,6 +76,20 @@ page 6183 "E-Doc. Purchase Draft Subform" ApplicationArea = All; Lookup = true; } + field(VATWarning; VATWarningCaption) + { + ApplicationArea = All; + Caption = 'VAT warnings'; + Editable = false; + Visible = HasVATWarnings; + StyleExpr = VATWarningStyleExpr; + ToolTip = 'Specifies whether the VAT Product Posting Group could not be resolved from the extracted VAT rate.'; + + trigger OnDrillDown() + begin + ShowVATWarningDetails(); + end; + } field("Item Reference No."; Rec."[BC] Item Reference No.") { ApplicationArea = All; @@ -336,15 +350,16 @@ page 6183 "E-Doc. Purchase Draft Subform" TempEDocumentPOMatchWarnings: Record "E-Doc PO Match Warning"; EDocPurchaseHistMapping: Codeunit "E-Doc. Purchase Hist. Mapping"; EDocPOMatching: Codeunit "E-Doc. PO Matching"; - AdditionalColumns, OrderMatchedCaption, MatchWarningsCaption, MatchWarningsStyleExpr : Text; + AdditionalColumns, OrderMatchedCaption, MatchWarningsCaption, MatchWarningsStyleExpr, VATWarningCaption, VATWarningStyleExpr : Text; LineAmount: Decimal; - DimVisible1, DimVisible2, HasAdditionalColumns, IsEDocumentMatchedToAnyPOLine, IsLineMatchedToOrderLine, IsLineMatchedToReceiptLine, HasEDocumentOrderMatchWarnings : Boolean; + DimVisible1, DimVisible2, HasAdditionalColumns, HasVATWarnings, IsEDocumentMatchedToAnyPOLine, IsLineMatchedToOrderLine, IsLineMatchedToReceiptLine, HasEDocumentOrderMatchWarnings : Boolean; HistoryCantBeRetrievedErr: Label 'The purchase invoice that matched historically with this line can''t be opened.'; trigger OnOpenPage() begin SetDimensionsVisibility(); UpdatePOMatching(); + UpdateVATWarnings(); end; trigger OnNewRecord(BelowxRec: Boolean) @@ -355,6 +370,7 @@ page 6183 "E-Doc. Purchase Draft Subform" trigger OnAfterGetCurrRecord() begin UpdatePOMatching(); + UpdateVATWarnings(); end; trigger OnAfterGetRecord() @@ -367,6 +383,7 @@ page 6183 "E-Doc. Purchase Draft Subform" IsLineMatchedToReceiptLine := EDocPOMatching.IsEDocumentLineMatchedToAnyReceiptLine(EDocumentPurchaseLine); OrderMatchedCaption := IsLineMatchedToOrderLine ? GetSummaryOfMatchedOrders() : ''; UpdateMatchWarnings(); + UpdateVATWarningForLine(); end; internal procedure SetEDocumentPurchaseHeader(EDocPurchHeader: Record "E-Document Purchase Header") @@ -570,4 +587,35 @@ page 6183 "E-Doc. Purchase Draft Subform" Message(WarningDetails.ToText()); end; + local procedure UpdateVATWarnings() + var + EDocPurchLine: Record "E-Document Purchase Line"; + begin + EDocPurchLine.SetRange("E-Document Entry No.", EDocumentPurchaseHeader."E-Document Entry No."); + EDocPurchLine.SetRange("[BC] VAT Rate Mismatch", true); + HasVATWarnings := not EDocPurchLine.IsEmpty(); + end; + + local procedure UpdateVATWarningForLine() + var + VATGroupNotResolvedLbl: Label 'VAT group not resolved'; + begin + if Rec."[BC] VAT Rate Mismatch" then begin + VATWarningCaption := VATGroupNotResolvedLbl; + VATWarningStyleExpr := 'Ambiguous'; + end else begin + VATWarningCaption := ''; + VATWarningStyleExpr := 'None'; + end; + end; + + local procedure ShowVATWarningDetails() + var + VATWarningDetailLbl: Label 'VAT rate %1% was extracted from the invoice but could not be matched to a single VAT Posting Setup for the vendor. Please select the correct VAT Product Posting Group manually.', Comment = '%1 = VAT rate percentage'; + begin + if not Rec."[BC] VAT Rate Mismatch" then + exit; + Message(VATWarningDetailLbl, Rec."VAT Rate"); + end; + } From b745f537ae125349af171a2e8a98f5e1f9499962 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Tue, 7 Apr 2026 12:15:03 +0200 Subject: [PATCH 22/53] refactor: remove VAT Rate Mismatch notification infrastructure Notification replaced by persisted [BC] VAT Rate Mismatch field and inline warning column on draft subform. --- .../EDocumentNotification.Codeunit.al | 72 ++----------------- .../EDocumentNotificationType.Enum.al | 4 -- 2 files changed, 5 insertions(+), 71 deletions(-) diff --git a/src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotification.Codeunit.al b/src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotification.Codeunit.al index fb92b00bc3..496981b729 100644 --- a/src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotification.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotification.Codeunit.al @@ -36,26 +36,6 @@ codeunit 6123 "E-Document Notification" EDocumentNotification.Insert(true); end; - procedure AddVATRateMismatchNotification(EDocumentEntryNo: Integer) - var - EDocumentNotification: Record "E-Document Notification"; - MyNotifications: Record "My Notifications"; - VATRateMismatchMsg: Label 'VAT Product Posting Groups could not be automatically determined for one or more lines. Please review before creating the invoice.'; - begin - if not GuiAllowed() then - exit; - if not MyNotifications.IsEnabled(GetVATRateMismatchNotificationId()) then - exit; - if EDocumentNotification.Get(EDocumentEntryNo, GetVATRateMismatchNotificationId(), UserId()) then - exit; - EDocumentNotification.Validate("E-Document Entry No.", EDocumentEntryNo); - EDocumentNotification.Validate(ID, GetVATRateMismatchNotificationId()); - EDocumentNotification.Validate("User Id", UserId()); - EDocumentNotification.Validate(Type, "E-Document Notification Type"::"VAT Rate Mismatch"); - EDocumentNotification.Validate(Message, VATRateMismatchMsg); - EDocumentNotification.Insert(true); - end; - /// /// Send notifications for Purchase Document Draft page /// Id of e-document @@ -68,9 +48,7 @@ codeunit 6123 "E-Document Notification" exit; EDocumentNotification.SetRange("E-Document Entry No.", EDocumentEntryNo); - EDocumentNotification.SetFilter(Type, '%1|%2', - "E-Document Notification Type"::"Vendor Matched By Name Not Address", - "E-Document Notification Type"::"VAT Rate Mismatch"); + EDocumentNotification.SetRange(Type, "E-Document Notification Type"::"Vendor Matched By Name Not Address"); EDocumentNotification.SetRange("User Id", UserId()); if not EDocumentNotification.FindSet() then exit; @@ -116,34 +94,6 @@ codeunit 6123 "E-Document Notification" EDocumentNotification.DeleteAll(true); end; - procedure DismissVATRateMismatchNotification(Notification: Notification) - var - EDocumentNotification: Record "E-Document Notification"; - EDocumentEntryNo: Integer; - Id: Guid; - begin - Evaluate(EDocumentEntryNo, Notification.GetData(EDocumentNotification.FieldName("E-Document Entry No."))); - Evaluate(Id, Notification.GetData(EDocumentNotification.FieldName(ID))); - if not EDocumentNotification.Get(EDocumentEntryNo, Id, UserId()) then - exit; - EDocumentNotification.Delete(true); - end; - - procedure DisableVATRateMismatchNotification(Notification: Notification) - var - MyNotifications: Record "My Notifications"; - EDocumentNotification: Record "E-Document Notification"; - VATRateMismatchNotificationNameTok: Label 'Notify user of Purchase Document Draft that VAT posting groups could not be auto-resolved.'; - VATRateMismatchNotificationDescTok: Label 'Show a notification when VAT Product Posting Groups could not be automatically determined from the extracted VAT rate.'; - begin - if MyNotifications.WritePermission() then - if not MyNotifications.Disable(GetVATRateMismatchNotificationId()) then - MyNotifications.InsertDefault(GetVATRateMismatchNotificationId(), VATRateMismatchNotificationNameTok, VATRateMismatchNotificationDescTok, false); - EDocumentNotification.SetRange(Type, "E-Document Notification Type"::"VAT Rate Mismatch"); - EDocumentNotification.SetRange("User Id", UserId()); - EDocumentNotification.DeleteAll(true); - end; - local procedure SendNotification(EDocumentNotification: Record "E-Document Notification") var MyNotifications: Record "My Notifications"; @@ -164,20 +114,12 @@ codeunit 6123 "E-Document Notification" DismissMsg: Label 'Dismiss'; DontShowThisAgainMsg: Label 'Don''t show this again.'; begin + if EDocumentNotification.Type <> "E-Document Notification Type"::"Vendor Matched By Name Not Address" then + exit; Notification.SetData(EDocumentNotification.FieldName("E-Document Entry No."), Format(EDocumentNotification."E-Document Entry No.")); Notification.SetData(EDocumentNotification.FieldName(ID), EDocumentNotification.ID); - case EDocumentNotification.Type of - "E-Document Notification Type"::"Vendor Matched By Name Not Address": - begin - Notification.AddAction(DismissMsg, Codeunit::"E-Document Notification", 'DismissVendorMatchedByNameNotAddressNotification'); - Notification.AddAction(DontShowThisAgainMsg, Codeunit::"E-Document Notification", 'DisableVendorMatchedByNameNotAddressNotification'); - end; - "E-Document Notification Type"::"VAT Rate Mismatch": - begin - Notification.AddAction(DismissMsg, Codeunit::"E-Document Notification", 'DismissVATRateMismatchNotification'); - Notification.AddAction(DontShowThisAgainMsg, Codeunit::"E-Document Notification", 'DisableVATRateMismatchNotification'); - end; - end; + Notification.AddAction(DismissMsg, Codeunit::"E-Document Notification", 'DismissVendorMatchedByNameNotAddressNotification'); + Notification.AddAction(DontShowThisAgainMsg, Codeunit::"E-Document Notification", 'DisableVendorMatchedByNameNotAddressNotification'); end; local procedure GetVendorMatchedByNameNotAddressNotificationId(): Guid @@ -185,8 +127,4 @@ codeunit 6123 "E-Document Notification" exit('bc0d8537-8e8d-4d94-a07a-a5a54c729d2a'); end; - local procedure GetVATRateMismatchNotificationId(): Guid - begin - exit('d4a7e1c3-5f92-4b8a-ae67-1c3d5f924b8a'); - end; } \ No newline at end of file diff --git a/src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotificationType.Enum.al b/src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotificationType.Enum.al index df8633171f..7324cbae95 100644 --- a/src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotificationType.Enum.al +++ b/src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotificationType.Enum.al @@ -16,8 +16,4 @@ enum 6126 "E-Document Notification Type" { Caption = 'Vendor Matched By Name Not Address'; } - value(2; "VAT Rate Mismatch") - { - Caption = 'VAT Rate Mismatch'; - } } \ No newline at end of file From 307a1de3142b90883e00f74a7fe79de52aa0a076 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Tue, 7 Apr 2026 12:16:43 +0200 Subject: [PATCH 23/53] test: update VAT resolution tests to assert mismatch flag instead of notification Rename and rewrite the mismatch test to assert [BC] VAT Rate Mismatch boolean. Add mismatch=false assertion to the successful resolution test. --- .../src/Processing/EDocProcessTest.Codeunit.al | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al b/src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al index 1c30c08d40..75bf63c244 100644 --- a/src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al +++ b/src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al @@ -349,6 +349,7 @@ codeunit 139883 "E-Doc Process Test" EDocumentPurchaseLine.SetRecFilter(); EDocumentPurchaseLine.FindFirst(); Assert.AreEqual(VATProductPostingGroup.Code, EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'The VAT Prod. Posting Group should be resolved from the matching VAT Posting Setup.'); + Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be false when resolution succeeds.'); // Cleanup Vendor2.SetRecFilter(); @@ -360,19 +361,18 @@ codeunit 139883 "E-Doc Process Test" end; [Test] - procedure PreparingPurchaseDraftCreatesNotificationWhenNoMatchingVATSetup() + procedure PreparingPurchaseDraftSetsVATRateMismatchWhenNoMatchingVATSetup() var EDocument: Record "E-Document"; EDocumentPurchaseHeader: Record "E-Document Purchase Header"; EDocumentPurchaseLine: Record "E-Document Purchase Line"; TempEDocImportParameters: Record "E-Doc. Import Parameters"; - EDocumentNotification: Record "E-Document Notification"; Vendor2: Record Vendor; CompanyInformation: Record "Company Information"; EDocumentProcessing: Codeunit "E-Document Processing"; EDocImport: Codeunit "E-Doc. Import"; begin - // [SCENARIO] When a draft line has a VAT Rate but no matching VAT Posting Setup exists, Prepare Draft leaves the field blank and creates a notification + // [SCENARIO] When a draft line has a VAT Rate but no matching VAT Posting Setup exists, Prepare Draft leaves the field blank and sets the mismatch flag Initialize(Enum::"Service Integration"::"Mock"); LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); @@ -389,7 +389,7 @@ codeunit 139883 "E-Doc Process Test" EDocumentPurchaseHeader."Vendor VAT Id" := Vendor2."VAT Registration No."; EDocumentPurchaseHeader.Insert(); EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseLine.Description := 'Test VAT mismatch notification'; + EDocumentPurchaseLine.Description := 'Test VAT mismatch'; EDocumentPurchaseLine."VAT Rate" := 99; EDocumentPurchaseLine.Insert(); @@ -398,20 +398,15 @@ codeunit 139883 "E-Doc Process Test" TempEDocImportParameters."Step to Run" := "Import E-Document Steps"::"Prepare draft"; EDocImport.ProcessIncomingEDocument(EDocument, TempEDocImportParameters); - // [THEN] The VAT Prod. Posting Group is blank + // [THEN] The VAT Prod. Posting Group is blank and mismatch flag is set EDocumentPurchaseLine.SetRecFilter(); EDocumentPurchaseLine.FindFirst(); Assert.AreEqual('', EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'The VAT Prod. Posting Group should be blank when no matching VAT Posting Setup exists.'); - - // [THEN] A VAT Rate Mismatch notification record exists - EDocumentNotification.SetRange("E-Document Entry No.", EDocument."Entry No"); - EDocumentNotification.SetRange(Type, "E-Document Notification Type"::"VAT Rate Mismatch"); - Assert.IsFalse(EDocumentNotification.IsEmpty(), 'A VAT Rate Mismatch notification should be created when resolution fails.'); + Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be true when resolution fails.'); // Cleanup Vendor2.SetRecFilter(); Vendor2.Delete(); - EDocumentNotification.DeleteAll(); end; [Test] From b0e91be856ea005d67dab79702a4fa52e9595ae7 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Tue, 7 Apr 2026 12:18:21 +0200 Subject: [PATCH 24/53] test: add tests for VAT Calculation Type filtering in Prepare Draft Full VAT and Sales Tax setups are excluded from resolution. Reverse Charge VAT setups are matched successfully. --- .../Processing/EDocProcessTest.Codeunit.al | 199 ++++++++++++++++++ 1 file changed, 199 insertions(+) diff --git a/src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al b/src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al index 75bf63c244..23035600f6 100644 --- a/src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al +++ b/src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al @@ -409,6 +409,205 @@ codeunit 139883 "E-Doc Process Test" Vendor2.Delete(); end; + [Test] + procedure PreparingDraftIgnoresFullVATSetupWhenResolvingPostingGroup() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + TempEDocImportParameters: Record "E-Doc. Import Parameters"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + VATPostingSetup2: Record "VAT Posting Setup"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + EDocumentProcessing: Codeunit "E-Document Processing"; + EDocImport: Codeunit "E-Doc. Import"; + LibraryERM: Codeunit "Library - ERM"; + begin + // [SCENARIO] Full VAT setups must not be matched during VAT Posting Group resolution + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] A Full VAT Posting Setup with VAT % = 10 + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; + VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Full VAT"; + VATPostingSetup2."VAT %" := 10; + VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2.Insert(); + + // [GIVEN] E-Document line with VAT Rate = 10 + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."Vendor VAT Id" := Vendor2."VAT Registration No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine.Description := 'Test Full VAT ignored'; + EDocumentPurchaseLine."VAT Rate" := 10; + EDocumentPurchaseLine.Insert(); + + // [WHEN] Prepare Draft is run + EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Ready for draft"); + TempEDocImportParameters."Step to Run" := "Import E-Document Steps"::"Prepare draft"; + EDocImport.ProcessIncomingEDocument(EDocument, TempEDocImportParameters); + + // [THEN] Full VAT setup is not matched + EDocumentPurchaseLine.SetRecFilter(); + EDocumentPurchaseLine.FindFirst(); + Assert.AreEqual('', EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'Full VAT setups must not be matched.'); + Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be true when only Full VAT setups exist.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + VATPostingSetup2.SetRecFilter(); + VATPostingSetup2.Delete(); + VATProductPostingGroup.SetRecFilter(); + VATProductPostingGroup.Delete(); + end; + + [Test] + procedure PreparingDraftIgnoresSalesTaxSetupWhenResolvingPostingGroup() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + TempEDocImportParameters: Record "E-Doc. Import Parameters"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + VATPostingSetup2: Record "VAT Posting Setup"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + EDocumentProcessing: Codeunit "E-Document Processing"; + EDocImport: Codeunit "E-Doc. Import"; + LibraryERM: Codeunit "Library - ERM"; + begin + // [SCENARIO] Sales Tax setups must not be matched during VAT Posting Group resolution + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] A Sales Tax Posting Setup with VAT % = 10 + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; + VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Sales Tax"; + VATPostingSetup2."VAT %" := 10; + VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2.Insert(); + + // [GIVEN] E-Document line with VAT Rate = 10 + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."Vendor VAT Id" := Vendor2."VAT Registration No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine.Description := 'Test Sales Tax ignored'; + EDocumentPurchaseLine."VAT Rate" := 10; + EDocumentPurchaseLine.Insert(); + + // [WHEN] Prepare Draft is run + EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Ready for draft"); + TempEDocImportParameters."Step to Run" := "Import E-Document Steps"::"Prepare draft"; + EDocImport.ProcessIncomingEDocument(EDocument, TempEDocImportParameters); + + // [THEN] Sales Tax setup is not matched + EDocumentPurchaseLine.SetRecFilter(); + EDocumentPurchaseLine.FindFirst(); + Assert.AreEqual('', EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'Sales Tax setups must not be matched.'); + Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be true when only Sales Tax setups exist.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + VATPostingSetup2.SetRecFilter(); + VATPostingSetup2.Delete(); + VATProductPostingGroup.SetRecFilter(); + VATProductPostingGroup.Delete(); + end; + + [Test] + procedure PreparingDraftResolvesReverseChargeVATPostingGroup() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + TempEDocImportParameters: Record "E-Doc. Import Parameters"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + VATPostingSetup2: Record "VAT Posting Setup"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + EDocumentProcessing: Codeunit "E-Document Processing"; + EDocImport: Codeunit "E-Doc. Import"; + LibraryERM: Codeunit "Library - ERM"; + begin + // [SCENARIO] Reverse Charge VAT setups should be matched during VAT Posting Group resolution + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] A Reverse Charge VAT Posting Setup with VAT % = 20 + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; + VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Reverse Charge VAT"; + VATPostingSetup2."VAT %" := 20; + VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Reverse Chrg. VAT Acc." := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2.Insert(); + + // [GIVEN] E-Document line with VAT Rate = 20 + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."Vendor VAT Id" := Vendor2."VAT Registration No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine.Description := 'Test Reverse Charge resolved'; + EDocumentPurchaseLine."VAT Rate" := 20; + EDocumentPurchaseLine.Insert(); + + // [WHEN] Prepare Draft is run + EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Ready for draft"); + TempEDocImportParameters."Step to Run" := "Import E-Document Steps"::"Prepare draft"; + EDocImport.ProcessIncomingEDocument(EDocument, TempEDocImportParameters); + + // [THEN] Reverse Charge VAT setup is matched + EDocumentPurchaseLine.SetRecFilter(); + EDocumentPurchaseLine.FindFirst(); + Assert.AreEqual(VATProductPostingGroup.Code, EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'Reverse Charge VAT setups should be matched.'); + Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be false when Reverse Charge VAT matches.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + VATPostingSetup2.SetRecFilter(); + VATPostingSetup2.Delete(); + VATProductPostingGroup.SetRecFilter(); + VATProductPostingGroup.Delete(); + end; + [Test] procedure FinishDraftCanBeUndone() var From da77a1c39be77bbb0008a947e5af992db004d61d Mon Sep 17 00:00:00 2001 From: ventselartur Date: Tue, 7 Apr 2026 12:20:15 +0200 Subject: [PATCH 25/53] test: add OnValidate mismatch re-evaluation tests Covers: rate match clears mismatch, rate mismatch persists, clearing group sets mismatch, Full VAT skips comparison, zero-rate matching works. --- .../Processing/EDocProcessTest.Codeunit.al | 264 ++++++++++++++++++ 1 file changed, 264 insertions(+) diff --git a/src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al b/src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al index 23035600f6..588bfe38bf 100644 --- a/src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al +++ b/src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al @@ -608,6 +608,270 @@ codeunit 139883 "E-Doc Process Test" VATProductPostingGroup.Delete(); end; + [Test] + procedure ValidatingVATProdPostingGroupClearsMismatchWhenRateMatches() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + VATPostingSetup2: Record "VAT Posting Setup"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + LibraryERM: Codeunit "Library - ERM"; + begin + // [SCENARIO] OnValidate clears mismatch when selected posting group's VAT % matches the line's VAT Rate + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] A Normal VAT setup with VAT % = 20 + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; + VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Normal VAT"; + VATPostingSetup2."VAT %" := 20; + VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2.Insert(); + + // [GIVEN] A line with VAT Rate = 20 and mismatch = true + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."[BC] Vendor No." := Vendor2."No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine."VAT Rate" := 20; + EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := true; + EDocumentPurchaseLine.Insert(); + + // [WHEN] User validates the posting group to the matching setup + EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", VATProductPostingGroup.Code); + EDocumentPurchaseLine.Modify(); + + // [THEN] Mismatch is cleared + Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should be false when VAT % matches VAT Rate.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + VATPostingSetup2.SetRecFilter(); + VATPostingSetup2.Delete(); + VATProductPostingGroup.SetRecFilter(); + VATProductPostingGroup.Delete(); + end; + + [Test] + procedure ValidatingVATProdPostingGroupKeepsMismatchWhenRateDiffers() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + VATPostingSetup2: Record "VAT Posting Setup"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + LibraryERM: Codeunit "Library - ERM"; + begin + // [SCENARIO] OnValidate keeps mismatch when selected posting group's VAT % differs from VAT Rate + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] A Normal VAT setup with VAT % = 10 (different from line's 20) + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; + VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Normal VAT"; + VATPostingSetup2."VAT %" := 10; + VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2.Insert(); + + // [GIVEN] A line with VAT Rate = 20 and mismatch = true + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."[BC] Vendor No." := Vendor2."No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine."VAT Rate" := 20; + EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := true; + EDocumentPurchaseLine.Insert(); + + // [WHEN] User validates the posting group to a non-matching setup + EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", VATProductPostingGroup.Code); + EDocumentPurchaseLine.Modify(); + + // [THEN] Mismatch remains true + Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should remain true when VAT % does not match VAT Rate.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + VATPostingSetup2.SetRecFilter(); + VATPostingSetup2.Delete(); + VATProductPostingGroup.SetRecFilter(); + VATProductPostingGroup.Delete(); + end; + + [Test] + procedure ValidatingVATProdPostingGroupSetsMismatchWhenCleared() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + begin + // [SCENARIO] OnValidate sets mismatch when posting group is cleared + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A line with VAT Rate = 20, a posting group, and no mismatch + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine."VAT Rate" := 20; + EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" := 'STANDARD'; + EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := false; + EDocumentPurchaseLine.Insert(); + + // [WHEN] User clears the posting group + EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", ''); + EDocumentPurchaseLine.Modify(); + + // [THEN] Mismatch is set + Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should be true when posting group is cleared.'); + end; + + [Test] + procedure ValidatingVATProdPostingGroupSkipsMismatchForFullVAT() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + VATPostingSetup2: Record "VAT Posting Setup"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + LibraryERM: Codeunit "Library - ERM"; + begin + // [SCENARIO] OnValidate skips mismatch evaluation for Full VAT — flag stays unchanged + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] A Full VAT setup with VAT % = 0 + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; + VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Full VAT"; + VATPostingSetup2."VAT %" := 0; + VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2.Insert(); + + // [GIVEN] A line with VAT Rate = 5 and mismatch = false + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."[BC] Vendor No." := Vendor2."No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine."VAT Rate" := 5; + EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := false; + EDocumentPurchaseLine.Insert(); + + // [WHEN] User validates the posting group to the Full VAT setup + EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", VATProductPostingGroup.Code); + EDocumentPurchaseLine.Modify(); + + // [THEN] Mismatch flag is unchanged (still false) — Full VAT skips comparison + Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should remain unchanged for Full VAT calculation type.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + VATPostingSetup2.SetRecFilter(); + VATPostingSetup2.Delete(); + VATProductPostingGroup.SetRecFilter(); + VATProductPostingGroup.Delete(); + end; + + [Test] + procedure ValidatingVATProdPostingGroupMatchesZeroRate() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + VATPostingSetup2: Record "VAT Posting Setup"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + LibraryERM: Codeunit "Library - ERM"; + begin + // [SCENARIO] OnValidate clears mismatch when both VAT Rate and VAT % are 0 + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] A Normal VAT setup with VAT % = 0 (zero-rated) + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; + VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Normal VAT"; + VATPostingSetup2."VAT %" := 0; + VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2.Insert(); + + // [GIVEN] A line with VAT Rate = 0 + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."[BC] Vendor No." := Vendor2."No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine."VAT Rate" := 0; + EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := true; + EDocumentPurchaseLine.Insert(); + + // [WHEN] User validates the posting group to the zero-rated setup + EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", VATProductPostingGroup.Code); + EDocumentPurchaseLine.Modify(); + + // [THEN] Mismatch is cleared — both rates are 0 + Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should be false when both VAT Rate and VAT % are 0.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + VATPostingSetup2.SetRecFilter(); + VATPostingSetup2.Delete(); + VATProductPostingGroup.SetRecFilter(); + VATProductPostingGroup.Delete(); + end; + [Test] procedure FinishDraftCanBeUndone() var From 4f497cd6780e0d4b544c406e32d85cb70abfa39a Mon Sep 17 00:00:00 2001 From: ventselartur Date: Tue, 7 Apr 2026 12:44:17 +0200 Subject: [PATCH 26/53] add minor changes --- .../PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al | 10 +++------- .../Import/Purchase/EDocPurchaseDraftSubform.Page.al | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al index 400a50245e..e232bf6f5a 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al @@ -189,7 +189,6 @@ codeunit 6125 "Prepare Purchase E-Doc. Draft" implements IProcessStructuredData var EDocumentPurchaseLine: Record "E-Document Purchase Line"; Vendor: Record Vendor; - VATBusPostingGroup: Code[20]; VATRate: Decimal; LineCount: Integer; begin @@ -197,8 +196,7 @@ codeunit 6125 "Prepare Purchase E-Doc. Draft" implements IProcessStructuredData exit; if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then exit; - VATBusPostingGroup := Vendor."VAT Bus. Posting Group"; - if VATBusPostingGroup = '' then + if Vendor."VAT Bus. Posting Group" = '' then exit; EDocumentPurchaseLine.SetRange("E-Document Entry No.", EDocumentEntryNo); @@ -218,7 +216,7 @@ codeunit 6125 "Prepare Purchase E-Doc. Draft" implements IProcessStructuredData if VATRate > 0 then begin EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" := - FindVATProductPostingGroup(VATBusPostingGroup, VATRate); + FindVATProductPostingGroup(Vendor."VAT Bus. Posting Group", VATRate); EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" = ''; EDocumentPurchaseLine.Modify(); @@ -229,14 +227,12 @@ codeunit 6125 "Prepare Purchase E-Doc. Draft" implements IProcessStructuredData local procedure FindVATProductPostingGroup(VATBusPostingGroup: Code[20]; VATRate: Decimal): Code[20] var VATPostingSetup: Record "VAT Posting Setup"; - RoundingTolerance: Decimal; begin - RoundingTolerance := 0.01; VATPostingSetup.SetRange("VAT Bus. Posting Group", VATBusPostingGroup); VATPostingSetup.SetFilter("VAT Calculation Type", '%1|%2', VATPostingSetup."VAT Calculation Type"::"Normal VAT", VATPostingSetup."VAT Calculation Type"::"Reverse Charge VAT"); - VATPostingSetup.SetFilter("VAT %", '>=%1&<=%2', VATRate - RoundingTolerance, VATRate + RoundingTolerance); + VATPostingSetup.SetRange("VAT %", VATRate); if VATPostingSetup.Count() = 1 then begin VATPostingSetup.FindFirst(); exit(VATPostingSetup."VAT Prod. Posting Group"); diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al index e97f18070c..379c22e5e6 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al @@ -81,7 +81,7 @@ page 6183 "E-Doc. Purchase Draft Subform" ApplicationArea = All; Caption = 'VAT warnings'; Editable = false; - Visible = HasVATWarnings; + Visible = Rec."[BC] VAT Rate Mismatch"; StyleExpr = VATWarningStyleExpr; ToolTip = 'Specifies whether the VAT Product Posting Group could not be resolved from the extracted VAT rate.'; From 886c2944707a69cd442305b2ac24796615f59ca4 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Tue, 7 Apr 2026 12:57:24 +0200 Subject: [PATCH 27/53] refactor: move VAT tests to dedicated E-Doc Purch. VAT Tests codeunit Extract 10 VAT-related test procedures into new codeunit 139896 for better organization. No test logic changes. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Processing/EDocProcessTest.Codeunit.al | 579 --------------- .../Processing/EDocPurchVATTests.Codeunit.al | 668 ++++++++++++++++++ 2 files changed, 668 insertions(+), 579 deletions(-) create mode 100644 src/Apps/W1/EDocument/Test/src/Processing/EDocPurchVATTests.Codeunit.al diff --git a/src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al b/src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al index 588bfe38bf..d2e22235f7 100644 --- a/src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al +++ b/src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al @@ -14,7 +14,6 @@ using Microsoft.Finance.Currency; using Microsoft.Finance.Dimension; using Microsoft.Finance.GeneralLedger.Account; using Microsoft.Finance.GeneralLedger.Setup; -using Microsoft.Finance.VAT.Setup; using Microsoft.Foundation.Company; using Microsoft.Inventory.Item; using Microsoft.Inventory.Item.Catalog; @@ -294,584 +293,6 @@ codeunit 139883 "E-Doc Process Test" TextToAccountMapping.Delete(); end; - [Test] - procedure PreparingPurchaseDraftResolvesVATProductPostingGroupFromLineVATRate() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - TempEDocImportParameters: Record "E-Doc. Import Parameters"; - Vendor2: Record Vendor; - CompanyInformation: Record "Company Information"; - VATPostingSetup2: Record "VAT Posting Setup"; - VATProductPostingGroup: Record "VAT Product Posting Group"; - EDocumentProcessing: Codeunit "E-Document Processing"; - EDocImport: Codeunit "E-Doc. Import"; - LibraryERM: Codeunit "Library - ERM"; - begin - // [SCENARIO] When a draft line has a VAT Rate and a matching VAT Posting Setup exists, Prepare Draft resolves the VAT Prod. Posting Group - Initialize(Enum::"Service Integration"::"Mock"); - LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); - - // [GIVEN] A vendor with a known VAT Bus. Posting Group - CompanyInformation.GetRecordOnce(); - Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; - Vendor2."No." := 'EDOC001'; - Vendor2."VAT Registration No." := 'XXXXXXX001'; - Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; - Vendor2.Insert(); - - // [GIVEN] A VAT Posting Setup with VAT % = 10 for the vendor's bus posting group - LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); - VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; - VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; - VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Normal VAT"; - VATPostingSetup2."VAT %" := 10; - VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2.Insert(); - - // [GIVEN] E-Document purchase header and line with VAT Rate = 10 - EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseHeader."Vendor VAT Id" := Vendor2."VAT Registration No."; - EDocumentPurchaseHeader.Insert(); - EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseLine.Description := 'Test VAT resolution'; - EDocumentPurchaseLine."VAT Rate" := 10; - EDocumentPurchaseLine.Insert(); - - // [WHEN] Prepare Draft is run - EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Ready for draft"); - TempEDocImportParameters."Step to Run" := "Import E-Document Steps"::"Prepare draft"; - EDocImport.ProcessIncomingEDocument(EDocument, TempEDocImportParameters); - - // [THEN] The VAT Prod. Posting Group is resolved from the matching setup - EDocumentPurchaseLine.SetRecFilter(); - EDocumentPurchaseLine.FindFirst(); - Assert.AreEqual(VATProductPostingGroup.Code, EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'The VAT Prod. Posting Group should be resolved from the matching VAT Posting Setup.'); - Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be false when resolution succeeds.'); - - // Cleanup - Vendor2.SetRecFilter(); - Vendor2.Delete(); - VATPostingSetup2.SetRecFilter(); - VATPostingSetup2.Delete(); - VATProductPostingGroup.SetRecFilter(); - VATProductPostingGroup.Delete(); - end; - - [Test] - procedure PreparingPurchaseDraftSetsVATRateMismatchWhenNoMatchingVATSetup() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - TempEDocImportParameters: Record "E-Doc. Import Parameters"; - Vendor2: Record Vendor; - CompanyInformation: Record "Company Information"; - EDocumentProcessing: Codeunit "E-Document Processing"; - EDocImport: Codeunit "E-Doc. Import"; - begin - // [SCENARIO] When a draft line has a VAT Rate but no matching VAT Posting Setup exists, Prepare Draft leaves the field blank and sets the mismatch flag - Initialize(Enum::"Service Integration"::"Mock"); - LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); - - // [GIVEN] A vendor with a known VAT Bus. Posting Group - CompanyInformation.GetRecordOnce(); - Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; - Vendor2."No." := 'EDOC001'; - Vendor2."VAT Registration No." := 'XXXXXXX001'; - Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; - Vendor2.Insert(); - - // [GIVEN] E-Document purchase header and line with VAT Rate = 99 (no matching setup) - EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseHeader."Vendor VAT Id" := Vendor2."VAT Registration No."; - EDocumentPurchaseHeader.Insert(); - EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseLine.Description := 'Test VAT mismatch'; - EDocumentPurchaseLine."VAT Rate" := 99; - EDocumentPurchaseLine.Insert(); - - // [WHEN] Prepare Draft is run - EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Ready for draft"); - TempEDocImportParameters."Step to Run" := "Import E-Document Steps"::"Prepare draft"; - EDocImport.ProcessIncomingEDocument(EDocument, TempEDocImportParameters); - - // [THEN] The VAT Prod. Posting Group is blank and mismatch flag is set - EDocumentPurchaseLine.SetRecFilter(); - EDocumentPurchaseLine.FindFirst(); - Assert.AreEqual('', EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'The VAT Prod. Posting Group should be blank when no matching VAT Posting Setup exists.'); - Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be true when resolution fails.'); - - // Cleanup - Vendor2.SetRecFilter(); - Vendor2.Delete(); - end; - - [Test] - procedure PreparingDraftIgnoresFullVATSetupWhenResolvingPostingGroup() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - TempEDocImportParameters: Record "E-Doc. Import Parameters"; - Vendor2: Record Vendor; - CompanyInformation: Record "Company Information"; - VATPostingSetup2: Record "VAT Posting Setup"; - VATProductPostingGroup: Record "VAT Product Posting Group"; - EDocumentProcessing: Codeunit "E-Document Processing"; - EDocImport: Codeunit "E-Doc. Import"; - LibraryERM: Codeunit "Library - ERM"; - begin - // [SCENARIO] Full VAT setups must not be matched during VAT Posting Group resolution - Initialize(Enum::"Service Integration"::"Mock"); - LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); - - // [GIVEN] A vendor - CompanyInformation.GetRecordOnce(); - Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; - Vendor2."No." := 'EDOC001'; - Vendor2."VAT Registration No." := 'XXXXXXX001'; - Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; - Vendor2.Insert(); - - // [GIVEN] A Full VAT Posting Setup with VAT % = 10 - LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); - VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; - VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; - VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Full VAT"; - VATPostingSetup2."VAT %" := 10; - VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2.Insert(); - - // [GIVEN] E-Document line with VAT Rate = 10 - EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseHeader."Vendor VAT Id" := Vendor2."VAT Registration No."; - EDocumentPurchaseHeader.Insert(); - EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseLine.Description := 'Test Full VAT ignored'; - EDocumentPurchaseLine."VAT Rate" := 10; - EDocumentPurchaseLine.Insert(); - - // [WHEN] Prepare Draft is run - EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Ready for draft"); - TempEDocImportParameters."Step to Run" := "Import E-Document Steps"::"Prepare draft"; - EDocImport.ProcessIncomingEDocument(EDocument, TempEDocImportParameters); - - // [THEN] Full VAT setup is not matched - EDocumentPurchaseLine.SetRecFilter(); - EDocumentPurchaseLine.FindFirst(); - Assert.AreEqual('', EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'Full VAT setups must not be matched.'); - Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be true when only Full VAT setups exist.'); - - // Cleanup - Vendor2.SetRecFilter(); - Vendor2.Delete(); - VATPostingSetup2.SetRecFilter(); - VATPostingSetup2.Delete(); - VATProductPostingGroup.SetRecFilter(); - VATProductPostingGroup.Delete(); - end; - - [Test] - procedure PreparingDraftIgnoresSalesTaxSetupWhenResolvingPostingGroup() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - TempEDocImportParameters: Record "E-Doc. Import Parameters"; - Vendor2: Record Vendor; - CompanyInformation: Record "Company Information"; - VATPostingSetup2: Record "VAT Posting Setup"; - VATProductPostingGroup: Record "VAT Product Posting Group"; - EDocumentProcessing: Codeunit "E-Document Processing"; - EDocImport: Codeunit "E-Doc. Import"; - LibraryERM: Codeunit "Library - ERM"; - begin - // [SCENARIO] Sales Tax setups must not be matched during VAT Posting Group resolution - Initialize(Enum::"Service Integration"::"Mock"); - LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); - - // [GIVEN] A vendor - CompanyInformation.GetRecordOnce(); - Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; - Vendor2."No." := 'EDOC001'; - Vendor2."VAT Registration No." := 'XXXXXXX001'; - Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; - Vendor2.Insert(); - - // [GIVEN] A Sales Tax Posting Setup with VAT % = 10 - LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); - VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; - VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; - VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Sales Tax"; - VATPostingSetup2."VAT %" := 10; - VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2.Insert(); - - // [GIVEN] E-Document line with VAT Rate = 10 - EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseHeader."Vendor VAT Id" := Vendor2."VAT Registration No."; - EDocumentPurchaseHeader.Insert(); - EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseLine.Description := 'Test Sales Tax ignored'; - EDocumentPurchaseLine."VAT Rate" := 10; - EDocumentPurchaseLine.Insert(); - - // [WHEN] Prepare Draft is run - EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Ready for draft"); - TempEDocImportParameters."Step to Run" := "Import E-Document Steps"::"Prepare draft"; - EDocImport.ProcessIncomingEDocument(EDocument, TempEDocImportParameters); - - // [THEN] Sales Tax setup is not matched - EDocumentPurchaseLine.SetRecFilter(); - EDocumentPurchaseLine.FindFirst(); - Assert.AreEqual('', EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'Sales Tax setups must not be matched.'); - Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be true when only Sales Tax setups exist.'); - - // Cleanup - Vendor2.SetRecFilter(); - Vendor2.Delete(); - VATPostingSetup2.SetRecFilter(); - VATPostingSetup2.Delete(); - VATProductPostingGroup.SetRecFilter(); - VATProductPostingGroup.Delete(); - end; - - [Test] - procedure PreparingDraftResolvesReverseChargeVATPostingGroup() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - TempEDocImportParameters: Record "E-Doc. Import Parameters"; - Vendor2: Record Vendor; - CompanyInformation: Record "Company Information"; - VATPostingSetup2: Record "VAT Posting Setup"; - VATProductPostingGroup: Record "VAT Product Posting Group"; - EDocumentProcessing: Codeunit "E-Document Processing"; - EDocImport: Codeunit "E-Doc. Import"; - LibraryERM: Codeunit "Library - ERM"; - begin - // [SCENARIO] Reverse Charge VAT setups should be matched during VAT Posting Group resolution - Initialize(Enum::"Service Integration"::"Mock"); - LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); - - // [GIVEN] A vendor - CompanyInformation.GetRecordOnce(); - Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; - Vendor2."No." := 'EDOC001'; - Vendor2."VAT Registration No." := 'XXXXXXX001'; - Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; - Vendor2.Insert(); - - // [GIVEN] A Reverse Charge VAT Posting Setup with VAT % = 20 - LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); - VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; - VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; - VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Reverse Charge VAT"; - VATPostingSetup2."VAT %" := 20; - VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2."Reverse Chrg. VAT Acc." := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2.Insert(); - - // [GIVEN] E-Document line with VAT Rate = 20 - EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseHeader."Vendor VAT Id" := Vendor2."VAT Registration No."; - EDocumentPurchaseHeader.Insert(); - EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseLine.Description := 'Test Reverse Charge resolved'; - EDocumentPurchaseLine."VAT Rate" := 20; - EDocumentPurchaseLine.Insert(); - - // [WHEN] Prepare Draft is run - EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Ready for draft"); - TempEDocImportParameters."Step to Run" := "Import E-Document Steps"::"Prepare draft"; - EDocImport.ProcessIncomingEDocument(EDocument, TempEDocImportParameters); - - // [THEN] Reverse Charge VAT setup is matched - EDocumentPurchaseLine.SetRecFilter(); - EDocumentPurchaseLine.FindFirst(); - Assert.AreEqual(VATProductPostingGroup.Code, EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'Reverse Charge VAT setups should be matched.'); - Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be false when Reverse Charge VAT matches.'); - - // Cleanup - Vendor2.SetRecFilter(); - Vendor2.Delete(); - VATPostingSetup2.SetRecFilter(); - VATPostingSetup2.Delete(); - VATProductPostingGroup.SetRecFilter(); - VATProductPostingGroup.Delete(); - end; - - [Test] - procedure ValidatingVATProdPostingGroupClearsMismatchWhenRateMatches() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - Vendor2: Record Vendor; - CompanyInformation: Record "Company Information"; - VATPostingSetup2: Record "VAT Posting Setup"; - VATProductPostingGroup: Record "VAT Product Posting Group"; - LibraryERM: Codeunit "Library - ERM"; - begin - // [SCENARIO] OnValidate clears mismatch when selected posting group's VAT % matches the line's VAT Rate - Initialize(Enum::"Service Integration"::"Mock"); - LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); - - // [GIVEN] A vendor - CompanyInformation.GetRecordOnce(); - Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; - Vendor2."No." := 'EDOC001'; - Vendor2."VAT Registration No." := 'XXXXXXX001'; - Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; - Vendor2.Insert(); - - // [GIVEN] A Normal VAT setup with VAT % = 20 - LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); - VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; - VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; - VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Normal VAT"; - VATPostingSetup2."VAT %" := 20; - VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2.Insert(); - - // [GIVEN] A line with VAT Rate = 20 and mismatch = true - EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseHeader."[BC] Vendor No." := Vendor2."No."; - EDocumentPurchaseHeader.Insert(); - EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseLine."VAT Rate" := 20; - EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := true; - EDocumentPurchaseLine.Insert(); - - // [WHEN] User validates the posting group to the matching setup - EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", VATProductPostingGroup.Code); - EDocumentPurchaseLine.Modify(); - - // [THEN] Mismatch is cleared - Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should be false when VAT % matches VAT Rate.'); - - // Cleanup - Vendor2.SetRecFilter(); - Vendor2.Delete(); - VATPostingSetup2.SetRecFilter(); - VATPostingSetup2.Delete(); - VATProductPostingGroup.SetRecFilter(); - VATProductPostingGroup.Delete(); - end; - - [Test] - procedure ValidatingVATProdPostingGroupKeepsMismatchWhenRateDiffers() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - Vendor2: Record Vendor; - CompanyInformation: Record "Company Information"; - VATPostingSetup2: Record "VAT Posting Setup"; - VATProductPostingGroup: Record "VAT Product Posting Group"; - LibraryERM: Codeunit "Library - ERM"; - begin - // [SCENARIO] OnValidate keeps mismatch when selected posting group's VAT % differs from VAT Rate - Initialize(Enum::"Service Integration"::"Mock"); - LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); - - // [GIVEN] A vendor - CompanyInformation.GetRecordOnce(); - Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; - Vendor2."No." := 'EDOC001'; - Vendor2."VAT Registration No." := 'XXXXXXX001'; - Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; - Vendor2.Insert(); - - // [GIVEN] A Normal VAT setup with VAT % = 10 (different from line's 20) - LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); - VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; - VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; - VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Normal VAT"; - VATPostingSetup2."VAT %" := 10; - VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2.Insert(); - - // [GIVEN] A line with VAT Rate = 20 and mismatch = true - EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseHeader."[BC] Vendor No." := Vendor2."No."; - EDocumentPurchaseHeader.Insert(); - EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseLine."VAT Rate" := 20; - EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := true; - EDocumentPurchaseLine.Insert(); - - // [WHEN] User validates the posting group to a non-matching setup - EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", VATProductPostingGroup.Code); - EDocumentPurchaseLine.Modify(); - - // [THEN] Mismatch remains true - Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should remain true when VAT % does not match VAT Rate.'); - - // Cleanup - Vendor2.SetRecFilter(); - Vendor2.Delete(); - VATPostingSetup2.SetRecFilter(); - VATPostingSetup2.Delete(); - VATProductPostingGroup.SetRecFilter(); - VATProductPostingGroup.Delete(); - end; - - [Test] - procedure ValidatingVATProdPostingGroupSetsMismatchWhenCleared() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - begin - // [SCENARIO] OnValidate sets mismatch when posting group is cleared - Initialize(Enum::"Service Integration"::"Mock"); - LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); - - // [GIVEN] A line with VAT Rate = 20, a posting group, and no mismatch - EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseHeader.Insert(); - EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseLine."VAT Rate" := 20; - EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" := 'STANDARD'; - EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := false; - EDocumentPurchaseLine.Insert(); - - // [WHEN] User clears the posting group - EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", ''); - EDocumentPurchaseLine.Modify(); - - // [THEN] Mismatch is set - Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should be true when posting group is cleared.'); - end; - - [Test] - procedure ValidatingVATProdPostingGroupSkipsMismatchForFullVAT() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - Vendor2: Record Vendor; - CompanyInformation: Record "Company Information"; - VATPostingSetup2: Record "VAT Posting Setup"; - VATProductPostingGroup: Record "VAT Product Posting Group"; - LibraryERM: Codeunit "Library - ERM"; - begin - // [SCENARIO] OnValidate skips mismatch evaluation for Full VAT — flag stays unchanged - Initialize(Enum::"Service Integration"::"Mock"); - LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); - - // [GIVEN] A vendor - CompanyInformation.GetRecordOnce(); - Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; - Vendor2."No." := 'EDOC001'; - Vendor2."VAT Registration No." := 'XXXXXXX001'; - Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; - Vendor2.Insert(); - - // [GIVEN] A Full VAT setup with VAT % = 0 - LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); - VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; - VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; - VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Full VAT"; - VATPostingSetup2."VAT %" := 0; - VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2.Insert(); - - // [GIVEN] A line with VAT Rate = 5 and mismatch = false - EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseHeader."[BC] Vendor No." := Vendor2."No."; - EDocumentPurchaseHeader.Insert(); - EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseLine."VAT Rate" := 5; - EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := false; - EDocumentPurchaseLine.Insert(); - - // [WHEN] User validates the posting group to the Full VAT setup - EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", VATProductPostingGroup.Code); - EDocumentPurchaseLine.Modify(); - - // [THEN] Mismatch flag is unchanged (still false) — Full VAT skips comparison - Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should remain unchanged for Full VAT calculation type.'); - - // Cleanup - Vendor2.SetRecFilter(); - Vendor2.Delete(); - VATPostingSetup2.SetRecFilter(); - VATPostingSetup2.Delete(); - VATProductPostingGroup.SetRecFilter(); - VATProductPostingGroup.Delete(); - end; - - [Test] - procedure ValidatingVATProdPostingGroupMatchesZeroRate() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - Vendor2: Record Vendor; - CompanyInformation: Record "Company Information"; - VATPostingSetup2: Record "VAT Posting Setup"; - VATProductPostingGroup: Record "VAT Product Posting Group"; - LibraryERM: Codeunit "Library - ERM"; - begin - // [SCENARIO] OnValidate clears mismatch when both VAT Rate and VAT % are 0 - Initialize(Enum::"Service Integration"::"Mock"); - LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); - - // [GIVEN] A vendor - CompanyInformation.GetRecordOnce(); - Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; - Vendor2."No." := 'EDOC001'; - Vendor2."VAT Registration No." := 'XXXXXXX001'; - Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; - Vendor2.Insert(); - - // [GIVEN] A Normal VAT setup with VAT % = 0 (zero-rated) - LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); - VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; - VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; - VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Normal VAT"; - VATPostingSetup2."VAT %" := 0; - VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2.Insert(); - - // [GIVEN] A line with VAT Rate = 0 - EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseHeader."[BC] Vendor No." := Vendor2."No."; - EDocumentPurchaseHeader.Insert(); - EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseLine."VAT Rate" := 0; - EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := true; - EDocumentPurchaseLine.Insert(); - - // [WHEN] User validates the posting group to the zero-rated setup - EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", VATProductPostingGroup.Code); - EDocumentPurchaseLine.Modify(); - - // [THEN] Mismatch is cleared — both rates are 0 - Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should be false when both VAT Rate and VAT % are 0.'); - - // Cleanup - Vendor2.SetRecFilter(); - Vendor2.Delete(); - VATPostingSetup2.SetRecFilter(); - VATPostingSetup2.Delete(); - VATProductPostingGroup.SetRecFilter(); - VATProductPostingGroup.Delete(); - end; - [Test] procedure FinishDraftCanBeUndone() var diff --git a/src/Apps/W1/EDocument/Test/src/Processing/EDocPurchVATTests.Codeunit.al b/src/Apps/W1/EDocument/Test/src/Processing/EDocPurchVATTests.Codeunit.al new file mode 100644 index 0000000000..eb6dba9046 --- /dev/null +++ b/src/Apps/W1/EDocument/Test/src/Processing/EDocPurchVATTests.Codeunit.al @@ -0,0 +1,668 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.eServices.EDocument.Test; + +using Microsoft.eServices.EDocument; +using Microsoft.eServices.EDocument.Integration; +using Microsoft.eServices.EDocument.Processing; +using Microsoft.eServices.EDocument.Processing.Import; +using Microsoft.eServices.EDocument.Processing.Import.Purchase; +using Microsoft.Finance.Currency; +using Microsoft.Finance.GeneralLedger.Setup; +using Microsoft.Finance.VAT.Setup; +using Microsoft.Foundation.Company; +using Microsoft.Purchases.History; +using Microsoft.Purchases.Payables; +using Microsoft.Purchases.Vendor; +using Microsoft.Sales.Customer; +using System.IO; +using System.TestLibraries.Utilities; + +codeunit 139896 "E-Doc Purch. VAT Tests" +{ + Subtype = Test; + TestType = IntegrationTest; + + var + Vendor: Record Vendor; + Customer: Record Customer; + EDocumentService: Record "E-Document Service"; + Assert: Codeunit Assert; + LibraryVariableStorage: Codeunit "Library - Variable Storage"; + LibraryEDoc: Codeunit "Library - E-Document"; + EDocImplState: Codeunit "E-Doc. Impl. State"; + LibraryLowerPermission: Codeunit "Library - Lower Permissions"; + IsInitialized: Boolean; + + [Test] + procedure PreparingPurchaseDraftResolvesVATProductPostingGroupFromLineVATRate() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + TempEDocImportParameters: Record "E-Doc. Import Parameters"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + VATPostingSetup2: Record "VAT Posting Setup"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + EDocumentProcessing: Codeunit "E-Document Processing"; + EDocImport: Codeunit "E-Doc. Import"; + LibraryERM: Codeunit "Library - ERM"; + begin + // [SCENARIO] When a draft line has a VAT Rate and a matching VAT Posting Setup exists, Prepare Draft resolves the VAT Prod. Posting Group + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor with a known VAT Bus. Posting Group + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] A VAT Posting Setup with VAT % = 10 for the vendor's bus posting group + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; + VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Normal VAT"; + VATPostingSetup2."VAT %" := 10; + VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2.Insert(); + + // [GIVEN] E-Document purchase header and line with VAT Rate = 10 + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."Vendor VAT Id" := Vendor2."VAT Registration No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine.Description := 'Test VAT resolution'; + EDocumentPurchaseLine."VAT Rate" := 10; + EDocumentPurchaseLine.Insert(); + + // [WHEN] Prepare Draft is run + EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Ready for draft"); + TempEDocImportParameters."Step to Run" := "Import E-Document Steps"::"Prepare draft"; + EDocImport.ProcessIncomingEDocument(EDocument, TempEDocImportParameters); + + // [THEN] The VAT Prod. Posting Group is resolved from the matching setup + EDocumentPurchaseLine.SetRecFilter(); + EDocumentPurchaseLine.FindFirst(); + Assert.AreEqual(VATProductPostingGroup.Code, EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'The VAT Prod. Posting Group should be resolved from the matching VAT Posting Setup.'); + Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be false when resolution succeeds.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + VATPostingSetup2.SetRecFilter(); + VATPostingSetup2.Delete(); + VATProductPostingGroup.SetRecFilter(); + VATProductPostingGroup.Delete(); + end; + + [Test] + procedure PreparingPurchaseDraftSetsVATRateMismatchWhenNoMatchingVATSetup() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + TempEDocImportParameters: Record "E-Doc. Import Parameters"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + EDocumentProcessing: Codeunit "E-Document Processing"; + EDocImport: Codeunit "E-Doc. Import"; + begin + // [SCENARIO] When a draft line has a VAT Rate but no matching VAT Posting Setup exists, Prepare Draft leaves the field blank and sets the mismatch flag + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor with a known VAT Bus. Posting Group + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] E-Document purchase header and line with VAT Rate = 99 (no matching setup) + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."Vendor VAT Id" := Vendor2."VAT Registration No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine.Description := 'Test VAT mismatch'; + EDocumentPurchaseLine."VAT Rate" := 99; + EDocumentPurchaseLine.Insert(); + + // [WHEN] Prepare Draft is run + EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Ready for draft"); + TempEDocImportParameters."Step to Run" := "Import E-Document Steps"::"Prepare draft"; + EDocImport.ProcessIncomingEDocument(EDocument, TempEDocImportParameters); + + // [THEN] The VAT Prod. Posting Group is blank and mismatch flag is set + EDocumentPurchaseLine.SetRecFilter(); + EDocumentPurchaseLine.FindFirst(); + Assert.AreEqual('', EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'The VAT Prod. Posting Group should be blank when no matching VAT Posting Setup exists.'); + Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be true when resolution fails.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + end; + + [Test] + procedure PreparingDraftIgnoresFullVATSetupWhenResolvingPostingGroup() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + TempEDocImportParameters: Record "E-Doc. Import Parameters"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + VATPostingSetup2: Record "VAT Posting Setup"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + EDocumentProcessing: Codeunit "E-Document Processing"; + EDocImport: Codeunit "E-Doc. Import"; + LibraryERM: Codeunit "Library - ERM"; + begin + // [SCENARIO] Full VAT setups must not be matched during VAT Posting Group resolution + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] A Full VAT Posting Setup with VAT % = 10 + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; + VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Full VAT"; + VATPostingSetup2."VAT %" := 10; + VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2.Insert(); + + // [GIVEN] E-Document line with VAT Rate = 10 + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."Vendor VAT Id" := Vendor2."VAT Registration No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine.Description := 'Test Full VAT ignored'; + EDocumentPurchaseLine."VAT Rate" := 10; + EDocumentPurchaseLine.Insert(); + + // [WHEN] Prepare Draft is run + EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Ready for draft"); + TempEDocImportParameters."Step to Run" := "Import E-Document Steps"::"Prepare draft"; + EDocImport.ProcessIncomingEDocument(EDocument, TempEDocImportParameters); + + // [THEN] Full VAT setup is not matched + EDocumentPurchaseLine.SetRecFilter(); + EDocumentPurchaseLine.FindFirst(); + Assert.AreEqual('', EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'Full VAT setups must not be matched.'); + Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be true when only Full VAT setups exist.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + VATPostingSetup2.SetRecFilter(); + VATPostingSetup2.Delete(); + VATProductPostingGroup.SetRecFilter(); + VATProductPostingGroup.Delete(); + end; + + [Test] + procedure PreparingDraftIgnoresSalesTaxSetupWhenResolvingPostingGroup() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + TempEDocImportParameters: Record "E-Doc. Import Parameters"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + VATPostingSetup2: Record "VAT Posting Setup"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + EDocumentProcessing: Codeunit "E-Document Processing"; + EDocImport: Codeunit "E-Doc. Import"; + LibraryERM: Codeunit "Library - ERM"; + begin + // [SCENARIO] Sales Tax setups must not be matched during VAT Posting Group resolution + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] A Sales Tax Posting Setup with VAT % = 10 + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; + VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Sales Tax"; + VATPostingSetup2."VAT %" := 10; + VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2.Insert(); + + // [GIVEN] E-Document line with VAT Rate = 10 + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."Vendor VAT Id" := Vendor2."VAT Registration No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine.Description := 'Test Sales Tax ignored'; + EDocumentPurchaseLine."VAT Rate" := 10; + EDocumentPurchaseLine.Insert(); + + // [WHEN] Prepare Draft is run + EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Ready for draft"); + TempEDocImportParameters."Step to Run" := "Import E-Document Steps"::"Prepare draft"; + EDocImport.ProcessIncomingEDocument(EDocument, TempEDocImportParameters); + + // [THEN] Sales Tax setup is not matched + EDocumentPurchaseLine.SetRecFilter(); + EDocumentPurchaseLine.FindFirst(); + Assert.AreEqual('', EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'Sales Tax setups must not be matched.'); + Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be true when only Sales Tax setups exist.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + VATPostingSetup2.SetRecFilter(); + VATPostingSetup2.Delete(); + VATProductPostingGroup.SetRecFilter(); + VATProductPostingGroup.Delete(); + end; + + [Test] + procedure PreparingDraftResolvesReverseChargeVATPostingGroup() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + TempEDocImportParameters: Record "E-Doc. Import Parameters"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + VATPostingSetup2: Record "VAT Posting Setup"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + EDocumentProcessing: Codeunit "E-Document Processing"; + EDocImport: Codeunit "E-Doc. Import"; + LibraryERM: Codeunit "Library - ERM"; + begin + // [SCENARIO] Reverse Charge VAT setups should be matched during VAT Posting Group resolution + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] A Reverse Charge VAT Posting Setup with VAT % = 20 + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; + VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Reverse Charge VAT"; + VATPostingSetup2."VAT %" := 20; + VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Reverse Chrg. VAT Acc." := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2.Insert(); + + // [GIVEN] E-Document line with VAT Rate = 20 + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."Vendor VAT Id" := Vendor2."VAT Registration No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine.Description := 'Test Reverse Charge resolved'; + EDocumentPurchaseLine."VAT Rate" := 20; + EDocumentPurchaseLine.Insert(); + + // [WHEN] Prepare Draft is run + EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Ready for draft"); + TempEDocImportParameters."Step to Run" := "Import E-Document Steps"::"Prepare draft"; + EDocImport.ProcessIncomingEDocument(EDocument, TempEDocImportParameters); + + // [THEN] Reverse Charge VAT setup is matched + EDocumentPurchaseLine.SetRecFilter(); + EDocumentPurchaseLine.FindFirst(); + Assert.AreEqual(VATProductPostingGroup.Code, EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'Reverse Charge VAT setups should be matched.'); + Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be false when Reverse Charge VAT matches.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + VATPostingSetup2.SetRecFilter(); + VATPostingSetup2.Delete(); + VATProductPostingGroup.SetRecFilter(); + VATProductPostingGroup.Delete(); + end; + + [Test] + procedure ValidatingVATProdPostingGroupClearsMismatchWhenRateMatches() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + VATPostingSetup2: Record "VAT Posting Setup"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + LibraryERM: Codeunit "Library - ERM"; + begin + // [SCENARIO] OnValidate clears mismatch when selected posting group's VAT % matches the line's VAT Rate + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] A Normal VAT setup with VAT % = 20 + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; + VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Normal VAT"; + VATPostingSetup2."VAT %" := 20; + VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2.Insert(); + + // [GIVEN] A line with VAT Rate = 20 and mismatch = true + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."[BC] Vendor No." := Vendor2."No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine."VAT Rate" := 20; + EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := true; + EDocumentPurchaseLine.Insert(); + + // [WHEN] User validates the posting group to the matching setup + EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", VATProductPostingGroup.Code); + EDocumentPurchaseLine.Modify(); + + // [THEN] Mismatch is cleared + Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should be false when VAT % matches VAT Rate.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + VATPostingSetup2.SetRecFilter(); + VATPostingSetup2.Delete(); + VATProductPostingGroup.SetRecFilter(); + VATProductPostingGroup.Delete(); + end; + + [Test] + procedure ValidatingVATProdPostingGroupKeepsMismatchWhenRateDiffers() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + VATPostingSetup2: Record "VAT Posting Setup"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + LibraryERM: Codeunit "Library - ERM"; + begin + // [SCENARIO] OnValidate keeps mismatch when selected posting group's VAT % differs from VAT Rate + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] A Normal VAT setup with VAT % = 10 (different from line's 20) + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; + VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Normal VAT"; + VATPostingSetup2."VAT %" := 10; + VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2.Insert(); + + // [GIVEN] A line with VAT Rate = 20 and mismatch = true + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."[BC] Vendor No." := Vendor2."No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine."VAT Rate" := 20; + EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := true; + EDocumentPurchaseLine.Insert(); + + // [WHEN] User validates the posting group to a non-matching setup + EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", VATProductPostingGroup.Code); + EDocumentPurchaseLine.Modify(); + + // [THEN] Mismatch remains true + Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should remain true when VAT % does not match VAT Rate.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + VATPostingSetup2.SetRecFilter(); + VATPostingSetup2.Delete(); + VATProductPostingGroup.SetRecFilter(); + VATProductPostingGroup.Delete(); + end; + + [Test] + procedure ValidatingVATProdPostingGroupSetsMismatchWhenCleared() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + begin + // [SCENARIO] OnValidate sets mismatch when posting group is cleared + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A line with VAT Rate = 20, a posting group, and no mismatch + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine."VAT Rate" := 20; + EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" := 'STANDARD'; + EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := false; + EDocumentPurchaseLine.Insert(); + + // [WHEN] User clears the posting group + EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", ''); + EDocumentPurchaseLine.Modify(); + + // [THEN] Mismatch is set + Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should be true when posting group is cleared.'); + end; + + [Test] + procedure ValidatingVATProdPostingGroupSkipsMismatchForFullVAT() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + VATPostingSetup2: Record "VAT Posting Setup"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + LibraryERM: Codeunit "Library - ERM"; + begin + // [SCENARIO] OnValidate skips mismatch evaluation for Full VAT — flag stays unchanged + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] A Full VAT setup with VAT % = 0 + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; + VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Full VAT"; + VATPostingSetup2."VAT %" := 0; + VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2.Insert(); + + // [GIVEN] A line with VAT Rate = 5 and mismatch = false + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."[BC] Vendor No." := Vendor2."No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine."VAT Rate" := 5; + EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := false; + EDocumentPurchaseLine.Insert(); + + // [WHEN] User validates the posting group to the Full VAT setup + EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", VATProductPostingGroup.Code); + EDocumentPurchaseLine.Modify(); + + // [THEN] Mismatch flag is unchanged (still false) — Full VAT skips comparison + Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should remain unchanged for Full VAT calculation type.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + VATPostingSetup2.SetRecFilter(); + VATPostingSetup2.Delete(); + VATProductPostingGroup.SetRecFilter(); + VATProductPostingGroup.Delete(); + end; + + [Test] + procedure ValidatingVATProdPostingGroupMatchesZeroRate() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + Vendor2: Record Vendor; + CompanyInformation: Record "Company Information"; + VATPostingSetup2: Record "VAT Posting Setup"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + LibraryERM: Codeunit "Library - ERM"; + begin + // [SCENARIO] OnValidate clears mismatch when both VAT Rate and VAT % are 0 + Initialize(Enum::"Service Integration"::"Mock"); + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + // [GIVEN] A vendor + CompanyInformation.GetRecordOnce(); + Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; + Vendor2."No." := 'EDOC001'; + Vendor2."VAT Registration No." := 'XXXXXXX001'; + Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; + Vendor2.Insert(); + + // [GIVEN] A Normal VAT setup with VAT % = 0 (zero-rated) + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; + VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Normal VAT"; + VATPostingSetup2."VAT %" := 0; + VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); + VATPostingSetup2.Insert(); + + // [GIVEN] A line with VAT Rate = 0 + EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseHeader."[BC] Vendor No." := Vendor2."No."; + EDocumentPurchaseHeader.Insert(); + EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; + EDocumentPurchaseLine."VAT Rate" := 0; + EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := true; + EDocumentPurchaseLine.Insert(); + + // [WHEN] User validates the posting group to the zero-rated setup + EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", VATProductPostingGroup.Code); + EDocumentPurchaseLine.Modify(); + + // [THEN] Mismatch is cleared — both rates are 0 + Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should be false when both VAT Rate and VAT % are 0.'); + + // Cleanup + Vendor2.SetRecFilter(); + Vendor2.Delete(); + VATPostingSetup2.SetRecFilter(); + VATPostingSetup2.Delete(); + VATProductPostingGroup.SetRecFilter(); + VATProductPostingGroup.Delete(); + end; + + local procedure Initialize(Integration: Enum "Service Integration") + var + TransformationRule: Record "Transformation Rule"; + EDocument: Record "E-Document"; + EDocDataStorage: Record "E-Doc. Data Storage"; + EDocumentServiceStatus: Record "E-Document Service Status"; + EDocPurchLineFieldSetup: Record "ED Purchase Line Field Setup"; + PurchInvHeader: Record "Purch. Inv. Header"; + VendorLedgerEntry: Record "Vendor Ledger Entry"; + GLSetup: Record "General Ledger Setup"; + Currency: Record Currency; + LibraryERM: Codeunit "Library - ERM"; + begin + LibraryLowerPermission.SetOutsideO365Scope(); + LibraryVariableStorage.Clear(); + Clear(EDocImplState); + EDocPurchLineFieldSetup.DeleteAll(); + + PurchInvHeader.DeleteAll(); + VendorLedgerEntry.DeleteAll(); + + if IsInitialized then + exit; + + GLSetup.GetRecordOnce(); + GLSetup."VAT Reporting Date Usage" := GLSetup."VAT Reporting Date Usage"::Disabled; + GLSetup.Modify(); + + // Set a currency that can be used across all localizations + Currency.Init(); + Currency.Validate(Code, 'XYZ'); + if Currency.Insert(true) then + LibraryERM.CreateExchangeRate(Currency.Code, WorkDate(), 1.0, 1.0); + + EDocument.DeleteAll(); + EDocumentServiceStatus.DeleteAll(); + EDocumentService.DeleteAll(); + EDocDataStorage.DeleteAll(); + + LibraryEDoc.SetupStandardVAT(); + LibraryEDoc.SetupStandardSalesScenario(Customer, EDocumentService, Enum::"E-Document Format"::Mock, Integration); + LibraryEDoc.SetupStandardPurchaseScenario(Vendor, EDocumentService, Enum::"E-Document Format"::Mock, Integration); + EDocumentService."Import Process" := "E-Document Import Process"::"Version 2.0"; + EDocumentService."Read into Draft Impl." := "E-Doc. Read into Draft"::PEPPOL; + EDocumentService.Modify(); + + TransformationRule.DeleteAll(); + TransformationRule.CreateDefaultTransformations(); + + IsInitialized := true; + end; +} From bb3547062a0abd9274adae75241543f93c053086 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Tue, 7 Apr 2026 13:29:09 +0200 Subject: [PATCH 28/53] remove PayablesAgentVATIssue.md --- PayablesAgentVATIssue.md | 75 ---------------------------------------- 1 file changed, 75 deletions(-) delete mode 100644 PayablesAgentVATIssue.md diff --git a/PayablesAgentVATIssue.md b/PayablesAgentVATIssue.md deleted file mode 100644 index 9c58ad8cdc..0000000000 --- a/PayablesAgentVATIssue.md +++ /dev/null @@ -1,75 +0,0 @@ -# Payables Agent - VAT Product Posting Group Not Applied Per Line - -## Summary - -The Payables Agent (Purchase Draft) correctly extracts VAT information from scanned invoices but fails to apply the correct VAT Product Posting Groups to individual purchase invoice lines. All lines default to "STANDARD" regardless of the actual VAT rate on the source invoice. - -## Reproduction - -**Invoice:** 164452 from Viking Direct Ltd (Vendor 10317), GBP -**Lines:** - -| Description | G/L Account | Expected VAT | -|---|---|---| -| PK 4 KITCHEN ROLL WHITE 10... | 640300 | Standard (20%) | -| COFFEE ORIGINAL 750G TIN N... | 630800 | Zero-rated (0%) | -| PK1100 BLACK TEA 800337 PG... | 630800 | Zero-rated (0%) | -| PK12 UHT SEM SKIMMED MILK... | 630800 | Zero-rated (0%) | -| DIVIDER EXACOMPTA ECO 12 T... | 630100 | Standard (20%) | - -**Totals:** Amount Excl. VAT = 110.55, Total VAT = 3.64, Amount Incl. VAT = 114.19 - -The total VAT of 3.64 on 110.55 net (~3.3%) proves a mix of zero-rated and standard-rated lines. UK VAT rules zero-rate most food items (coffee, tea, milk). - -## Current Behavior - -- The Purchase Draft accurately breaks down the invoice with correct net, VAT, and gross amounts per line and overall totals. -- When transferred into the Purchase Invoice, **all lines receive VAT Prod. Posting Group = "STANDARD"**. -- The VAT rate differences from the source invoice are lost. - -## Expected Behavior - -The agent should use the VAT rate information it already extracts from the scanned invoice to assign the **correct VAT Prod. Posting Group** per line (e.g., "ZERO" for zero-rated food items, "STANDARD" for taxable items). - -## Impact - -- Incorrect VAT amounts on posted invoices -- Wrong VAT reporting/returns -- Posted totals won't match the actual supplier invoice - -## Internal Discussion - -### Proposed Fix (Artur Ventsel) - -We have the VAT rate from ADI (Azure Document Intelligence). When we create a purchase line, we only consider the item or G/L account — whatever VAT Product Posting Group comes as default from those tables ends up on the purchase line, which is exactly what the customer is reporting. - -**Suggestion:** If we have the VAT rate from ADI, try to find the VAT Posting Setup for the vendor's VAT Business Posting Group and, if a matching setup exists, switch the VAT Product Posting Group on the line accordingly. - -### Concerns (Joshua Martínez Pineda) - -1. **Document totals validation exists** — the "Document totals" section should show the value as read from the invoice, and if totals don't match, posting is blocked. But we don't automatically change VAT Product Posting Groups to make lines add up to the correct VAT total. - -2. **VAT data availability varies** — PDF invoices may have the VAT rate per line, per document, or both (see ADI docs). Even if we move away from ADI, this variability remains. - -3. **Combinatorial complexity** — If only the document's total VAT is available and it doesn't match for N lines, the approach would require trying all combinations of VAT Product Posting Groups (N^K) to find one that adds up to the total. Multiple combinations could produce the same total but only one may be correct for reporting purposes. - -4. **Historical decision** — Due to these complexities, the team previously decided not to guess and instead rely on document totals validation. - -5. **Possible middle ground** — A warning that something is off with VAT early in the draft page would help. The fix could be scoped to cases where the tax amount is specified per line on the invoice, which is more deterministic. However, the ambiguity problem (concern #3) can still apply even with per-line amounts. - -## Design Decision - -Design spec: [2026-03-30-vat-posting-group-resolution-design.md](src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-design.md) - -Key decisions: -1. **Normalize ADI handler** — compute VAT percentage from `tax / Sub Total * 100` instead of storing the raw tax amount -2. **New `[BC] VAT Prod. Posting Group` field** (field 110) on E-Document Purchase Line, resolved during Prepare Draft -3. **Lookup in Prepare Draft** — query VAT Posting Setup by vendor's VAT Bus. Posting Group + extracted VAT %. Single match → set. Zero/multiple → leave blank. -4. **Single-line fallback** — if no per-line VAT data but only one line, compute rate from header Total VAT -5. **Notification banner** — new "VAT Rate Mismatch" notification when resolution fails (line has VAT Rate but no match found) -6. **No combinatorial solving** — explicitly out of scope for multi-line invoices without per-line VAT data - -## Notes - -- Continia (competing product) handles this correctly — it can post by line item and apply a VAT posting group per item type. -- If only capturing balances and not items, zero-rated lines are missed entirely. From eb04ce26aec258e6cccd57f5380d1f55b1d3e335 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Tue, 14 Apr 2026 10:27:19 +0200 Subject: [PATCH 29/53] remove docs --- ...-30-vat-posting-group-resolution-design.md | 119 -- ...03-30-vat-posting-group-resolution-plan.md | 559 -------- .../2026-04-03-vat-warning-field-design.md | 242 ---- .../docs/2026-04-07-vat-warning-field-plan.md | 1189 ----------------- 4 files changed, 2109 deletions(-) delete mode 100644 src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-design.md delete mode 100644 src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-plan.md delete mode 100644 src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md delete mode 100644 src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-07-vat-warning-field-plan.md diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-design.md b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-design.md deleted file mode 100644 index 911e844141..0000000000 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-design.md +++ /dev/null @@ -1,119 +0,0 @@ -# VAT Product Posting Group Auto-Resolution - -**Date:** 2026-03-30 -**Status:** Draft -**Author:** Artur Ventsel - -## Problem - -The Payables Agent (E-Document import pipeline) extracts VAT information from scanned invoices but does not apply the correct VAT Product Posting Group per purchase line. All lines default to the VAT Prod. Posting Group inherited from the G/L Account or Item card, regardless of the actual VAT rate on the source invoice. - -This causes incorrect VAT amounts on posted invoices, wrong VAT reporting, and mismatched totals when an invoice contains lines with mixed VAT rates (e.g., standard 20% and zero-rated 0% items). - -### Root Cause - -1. **ADI handler stores wrong data type:** The ADI handler maps the `tax` field (a monetary amount per line, e.g., `$6.00`) into the `"VAT Rate"` field on `E-Document Purchase Line`. The PEPPOL and MLLM handlers correctly populate `"VAT Rate"` with a percentage. This inconsistency means downstream code cannot reliably interpret the field. - -2. **No resolution logic exists:** The `"VAT Rate"` field is populated but never consumed. Neither the Prepare Draft nor Finish Draft stages use it to look up or override the VAT Product Posting Group. - -## Design - -### 1. ADI Handler Normalization - -**File:** `EDocumentADIHandler.Codeunit.al` — `PopulateEDocumentPurchaseLine` - -**ADI schema context ([2024-11-30-ga](https://github.com/Azure-Samples/document-intelligence-code-samples/blob/main/schema/2024-11-30-ga/invoice.md)):** The `Items.*.Tax` field is ambiguous by design — "Possible values include tax amount, tax %, and tax Y/N". A separate `Items.*.TaxRate` (string) field provides the unambiguous percentage. - -**Change:** Replace the current `tax` → `"VAT Rate"` mapping with a multi-step resolution: - -1. **Prefer `TaxRate`** (string field) — parse the numeric percentage from it (e.g., "20%", "VAT 20%", "20" → 20). This is the unambiguous source. -2. **Fallback to `Tax`** — if `TaxRate` is unavailable, read the `Tax` field. Check `value_text` to disambiguate: - - If `value_text` contains `%` → the value is a percentage, use it directly. - - Otherwise → assume monetary amount, compute: `VAT Rate = (Tax / Sub Total) * 100`. -3. If neither field provides usable data, leave `VAT Rate` as 0. - -After this change, all three handlers (ADI, PEPPOL, MLLM) consistently populate `"VAT Rate"` as a percentage. - -### 2. New Field on E-Document Purchase Line - -**File:** `EDocumentPurchaseLine.Table.al` - -Add a new field in the `[BC]` validated fields range (101-200): - -- **Field 110:** `[BC] VAT Prod. Posting Group` (Code[20]) - -This follows the existing pattern where `[BC]`-prefixed fields hold BC-resolved values that the user can review and edit on the draft page. - -### 3. Draft Subform Page Column - -**File:** `EDocPurchaseDraftSubform.Page.al` - -Add an editable column for `[BC] VAT Prod. Posting Group` with lookup support, positioned after the existing line type/number columns. The user can manually correct the posting group if auto-resolution fails or picks the wrong group. - -### 4. VAT Posting Group Resolution in Prepare Draft - -**File:** `PreparePurchaseEDocDraft.Codeunit.al` — `PrepareDraft` - -After `IPurchaseLineProvider.GetPurchaseLine` resolves the line type/number (inside the existing line loop at lines 68-74), add VAT Posting Group resolution: - -``` -For each line: - 1. Get the vendor's VAT Bus. Posting Group. - 2. Determine the VAT rate to match: - a. If line "VAT Rate" > 0 → use it directly. - b. If line "VAT Rate" = 0 AND this is the only line - AND header "Total VAT" > 0 AND header "Sub Total" > 0: - → compute rate = (Total VAT / Sub Total) * 100. - c. Otherwise → skip (no data to work with). - 3. Query VAT Posting Setup where: - - VAT Bus. Posting Group = vendor's group - - VAT % = determined rate (with rounding tolerance ~0.01) - 4. If exactly one match → set [BC] VAT Prod. Posting Group. - 5. If zero or multiple matches → leave blank (default applies at finalization). -``` - -**Rounding tolerance:** VAT rates computed from amounts may not be exact (e.g., `3.64 / 18.20 * 100 = 20.0` but edge cases exist). A tolerance of ~0.01 on the VAT % comparison avoids false mismatches. - -### 5. Finish Draft — Apply Resolved Posting Group - -**File:** `EDocCreatePurchaseInvoice.Codeunit.al` — `CreatePurchaseInvoiceLine` - -After `PurchaseLine.Validate("No.", ...)` (line 216) sets the default VAT Prod. Posting Group from the G/L Account/Item, check if the draft line has a resolved value: - -```al -if EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" <> '' then - PurchaseLine.Validate("VAT Prod. Posting Group", - EDocumentPurchaseLine."[BC] VAT Prod. Posting Group"); -``` - -If the field is blank (resolution failed or user didn't set it), do nothing — the default from the G/L Account/Item applies (current behavior preserved). - -### 6. Notification for Failed Resolution - -**Files:** `EDocumentNotification.Codeunit.al`, `E-Document Notification Type` enum - -**New notification type:** `"VAT Rate Mismatch"` - -**Trigger:** After the Prepare Draft line loop completes, if any line has a non-zero `"VAT Rate"` but a blank `[BC] VAT Prod. Posting Group` (meaning lookup was attempted but found zero or multiple matches). - -**Message:** "VAT Product Posting Groups could not be automatically determined for one or more lines. Please review before creating the invoice." - -**Behavior:** Shown as a banner notification on the draft page, following the same pattern as the existing "Vendor matched by name but not by address" notification. Includes Dismiss and Don't show again actions. - -## Explicitly Out of Scope - -- **Combinatorial solving** — No attempt to find posting group combinations that make line VATs add up to the header total for multi-line invoices without per-line VAT data. -- **Blocking finalization** — VAT mismatch is informational, not a hard block. The existing document totals validation at posting time remains the enforcement mechanism. -- **ADI `TaxDetails` header field** — The ADI model provides `TaxDetails` with per-rate breakdowns at the header level. This could be used as an additional data source but is not consumed in this design. - -## Key Files - -| File | Role | -|---|---| -| `EDocumentADIHandler.Codeunit.al` | ADI data extraction — normalize tax amount to rate | -| `EDocumentPurchaseLine.Table.al` | Add `[BC] VAT Prod. Posting Group` field | -| `EDocPurchaseDraftSubform.Page.al` | Add editable column for new field | -| `PreparePurchaseEDocDraft.Codeunit.al` | VAT Posting Setup lookup logic | -| `EDocCreatePurchaseInvoice.Codeunit.al` | Apply resolved posting group to purchase line | -| `EDocumentNotification.Codeunit.al` | New VAT Rate Mismatch notification type | -| `E-Document Notification Type` enum | New enum value | diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-plan.md b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-plan.md deleted file mode 100644 index 490a26223f..0000000000 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-03-30-vat-posting-group-resolution-plan.md +++ /dev/null @@ -1,559 +0,0 @@ -# VAT Product Posting Group Auto-Resolution — Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Automatically resolve and apply the correct VAT Product Posting Group per E-Document purchase line based on extracted VAT rate data, with a notification when resolution fails. - -**Architecture:** Normalize VAT data at extraction time (ADI handler), add a `[BC] VAT Prod. Posting Group` field to the staging table, resolve it during Prepare Draft via VAT Posting Setup lookup, and apply it during Finish Draft when creating the BC Purchase Line. - -**Tech Stack:** AL (Business Central), E-Document Core framework, VAT Posting Setup table (325) - ---- - -### Task 1: Normalize ADI handler — extract VAT percentage from ADI data - -**Files:** -- Modify: `src/Apps/W1/EDocument/App/src/Processing/Import/StructureReceivedEDocument/EDocumentADIHandler.Codeunit.al:174-188` -- Modify: `src/Apps/W1/EDocument/Test/src/Processing/EDocStructuredValidations.Codeunit.al:59,70,80` - -**Background — ADI schema ambiguity (from [ADI invoice schema 2024-11-30-ga](https://github.com/Azure-Samples/document-intelligence-code-samples/blob/main/schema/2024-11-30-ga/invoice.md)):** -- `Items.*.Tax` (currency type) — "Possible values include tax amount, tax %, and tax Y/N" — **ambiguous by design** -- `Items.*.TaxRate` (string type) — "Tax rate associated with each line item" — **unambiguous percentage** -- `TotalTax` (currency) — header-level total tax amount -- `TaxDetails.*.Rate` (string) — per-rate breakdown at header level - -The current code maps `Tax` directly to `"VAT Rate"`, storing a monetary amount (e.g., `$6.00` → `6`) in a field meant for a percentage. - -**Strategy:** Prefer `TaxRate` (unambiguous string percentage). Fall back to computing from `Tax / Sub Total * 100` only when `TaxRate` is absent and `Tax` looks like a monetary amount (check `value_text` for `%` sign to detect when `Tax` contains a percentage instead of an amount). - -- [ ] **Step 1: Update `PopulateEDocumentPurchaseLine` in the ADI handler** - -Replace the current line 185: -```al -EDocumentJsonHelper.SetCurrencyValueInField('tax', FieldsJsonObject, TempEDocPurchaseLine."VAT Rate", TempEDocPurchaseLine."Currency Code"); -``` - -With: -```al - ResolveVATRateFromADI(FieldsJsonObject, TempEDocPurchaseLine); -``` - -Add a new local procedure after `PopulateEDocumentPurchaseLine`: -```al - local procedure ResolveVATRateFromADI(FieldsJsonObject: JsonObject; var TempEDocPurchaseLine: Record "E-Document Purchase Line" temporary) - var - TaxRateText: Text; - TaxAmount: Decimal; - TaxValueText: Text; - ParsedRate: Decimal; - UnusedCurrencyCode: Code[10]; - begin - // 1. Prefer TaxRate (string) — unambiguous percentage field from ADI - EDocumentJsonHelper.SetStringValueInField('taxRate', MaxStrLen(TaxRateText), FieldsJsonObject, TaxRateText); - if TaxRateText <> '' then begin - ParsedRate := ParsePercentageFromText(TaxRateText); - if ParsedRate >= 0 then begin - TempEDocPurchaseLine."VAT Rate" := ParsedRate; - exit; - end; - end; - - // 2. Fallback to Tax field — but it's ambiguous (can be amount, %, or Y/N) - EDocumentJsonHelper.SetCurrencyValueInField('tax', FieldsJsonObject, TaxAmount, UnusedCurrencyCode); - if TaxAmount = 0 then - exit; - - // Check value_text to disambiguate - EDocumentJsonHelper.SetStringValueInField('tax', MaxStrLen(TaxValueText), FieldsJsonObject, TaxValueText); - if TaxValueText.Contains('%') then - // Tax field contains a percentage (e.g., "20%") - TempEDocPurchaseLine."VAT Rate" := TaxAmount - else - // Tax field contains a monetary amount (e.g., "$6.00") — compute percentage - if TempEDocPurchaseLine."Sub Total" > 0 then - TempEDocPurchaseLine."VAT Rate" := Round((TaxAmount / TempEDocPurchaseLine."Sub Total") * 100, 0.01); - end; - - local procedure ParsePercentageFromText(TaxRateText: Text): Decimal - var - CleanedText: Text; - ParsedValue: Decimal; - begin - // Strip common non-numeric prefixes/suffixes: "VAT 20%", "20%", "20.0%", "20" - CleanedText := TaxRateText.Replace('%', '').Trim(); - // Remove common prefixes like "VAT ", "Tax ", etc. - if CleanedText.StartsWith('VAT ') then - CleanedText := CopyStr(CleanedText, 5).Trim(); - if CleanedText.StartsWith('Tax ') then - CleanedText := CopyStr(CleanedText, 5).Trim(); - if Evaluate(ParsedValue, CleanedText) then - exit(ParsedValue); - exit(-1); // Signal parse failure - end; -``` - -Note: `"Sub Total"` is already populated from `amount` at line 176 before this runs — the field order in `PopulateEDocumentPurchaseLine` ensures this. - -- [ ] **Step 2: Update existing CAPI test assertions** - -In `EDocStructuredValidations.Codeunit.al`, the CAPI test fixture has three lines with `Tax` as monetary amounts (`$6.00`, `$3.00`, `$1.00`) and no `TaxRate` field. The `value_text` contains `$` (not `%`), so the fallback path computes: `$6/$60 = 10%`, `$3/$30 = 10%`, `$1/$10 = 10%`. - -Update line 59: -```al - Assert.AreEqual(10, EDocumentPurchaseLine."VAT Rate", 'The VAT rate in the purchase line does not match the expected percentage.'); -``` - -Update line 70: -```al - Assert.AreEqual(10, EDocumentPurchaseLine."VAT Rate", 'The VAT rate in the purchase line does not match the expected percentage.'); -``` - -Update line 80: -```al - Assert.AreEqual(10, EDocumentPurchaseLine."VAT Rate", 'The VAT rate in the purchase line does not match the expected percentage.'); -``` - -- [ ] **Step 3: Verify PEPPOL and MLLM assertions are unchanged** - -PEPPOL assertions at lines 142, 153 expect `25` — these are already percentages, no change needed. -MLLM assertions at lines 197, 207, 217 expect `15`, `10`, `15` — already percentages, no change needed. - -- [ ] **Step 4: Compile and run tests** - -Run: `al compile` and `al run_tests` for the E-Document app and test app. -Expected: All existing CAPI, PEPPOL, and MLLM structured tests pass with the updated assertions. - -- [ ] **Step 5: Commit** - -```bash -git add src/Apps/W1/EDocument/App/src/Processing/Import/StructureReceivedEDocument/EDocumentADIHandler.Codeunit.al -git add src/Apps/W1/EDocument/Test/src/Processing/EDocStructuredValidations.Codeunit.al -git commit -m "fix: normalize ADI tax data to VAT percentage in E-Document Purchase Line - -Prefer TaxRate (string, unambiguous percentage) over Tax (currency, -ambiguous). Fall back to computing Tax/Amount*100 only when Tax -contains a monetary amount (no % in value_text)." -``` - ---- - -### Task 2: Add `[BC] VAT Prod. Posting Group` field to E-Document Purchase Line - -**Files:** -- Modify: `src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al:211` - -- [ ] **Step 1: Add field 110 to the table** - -Insert before the `#endregion Validated fields` comment (line 211): - -```al - field(110; "[BC] VAT Prod. Posting Group"; Code[20]) - { - Caption = 'VAT Prod. Posting Group'; - ToolTip = 'Specifies the VAT product posting group resolved from the extracted VAT rate.'; - TableRelation = "VAT Product Posting Group"; - } -``` - -Also add a `using` statement at the top of the file for `Microsoft.Finance.VAT.Setup` if not already present. - -- [ ] **Step 2: Compile** - -Run: `al compile` for the E-Document app. -Expected: Clean compilation, no errors. - -- [ ] **Step 3: Commit** - -```bash -git add src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al -git commit -m "feat: add [BC] VAT Prod. Posting Group field to E-Document Purchase Line" -``` - ---- - -### Task 3: Add `[BC] VAT Prod. Posting Group` column to draft subform page - -**Files:** -- Modify: `src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al:73` - -- [ ] **Step 1: Add the column to the repeater** - -Insert after the `"No."` field block (after line 73, the closing brace of the `"No."` field): - -```al - field("VAT Prod. Posting Group"; Rec."[BC] VAT Prod. Posting Group") - { - ApplicationArea = All; - Lookup = true; - } -``` - -- [ ] **Step 2: Compile** - -Run: `al compile` for the E-Document app. -Expected: Clean compilation, no errors. - -- [ ] **Step 3: Commit** - -```bash -git add src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al -git commit -m "feat: add VAT Prod. Posting Group column to E-Document draft subform" -``` - ---- - -### Task 4: Add VAT Rate Mismatch notification type and codeunit logic - -**Files:** -- Modify: `src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotificationType.Enum.al:18` -- Modify: `src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotification.Codeunit.al` -- Modify: `src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al:505` - -- [ ] **Step 1: Add enum value** - -In `EDocumentNotificationType.Enum.al`, add after the `"Vendor Matched By Name Not Address"` value (after line 18): - -```al - value(2; "VAT Rate Mismatch") - { - Caption = 'VAT Rate Mismatch'; - } -``` - -- [ ] **Step 2: Add notification procedures to the codeunit** - -In `EDocumentNotification.Codeunit.al`, add these procedures: - -After `AddVendorMatchedByNameNotAddressNotification` (after line 37): - -```al - procedure AddVATRateMismatchNotification(EDocumentEntryNo: Integer) - var - EDocumentNotification: Record "E-Document Notification"; - MyNotifications: Record "My Notifications"; - VATRateMismatchMsg: Label 'VAT Product Posting Groups could not be automatically determined for one or more lines. Please review before creating the invoice.'; - begin - if not GuiAllowed() then - exit; - if not MyNotifications.IsEnabled(GetVATRateMismatchNotificationId()) then - exit; - if EDocumentNotification.Get(EDocumentEntryNo, GetVATRateMismatchNotificationId(), UserId()) then - exit; - EDocumentNotification.Validate("E-Document Entry No.", EDocumentEntryNo); - EDocumentNotification.Validate(ID, GetVATRateMismatchNotificationId()); - EDocumentNotification.Validate("User Id", UserId()); - EDocumentNotification.Validate(Type, "E-Document Notification Type"::"VAT Rate Mismatch"); - EDocumentNotification.Validate(Message, VATRateMismatchMsg); - EDocumentNotification.Insert(true); - end; -``` - -- [ ] **Step 3: Update `SendPurchaseDocumentDraftNotifications` to include the new type** - -Replace the existing `SendPurchaseDocumentDraftNotifications` (lines 43-59) with: - -```al - procedure SendPurchaseDocumentDraftNotifications(EDocumentEntryNo: Integer) - var - EDocumentNotification: Record "E-Document Notification"; - begin - if not GuiAllowed() then - exit; - - EDocumentNotification.SetRange("E-Document Entry No.", EDocumentEntryNo); - EDocumentNotification.SetFilter(Type, '%1|%2', - "E-Document Notification Type"::"Vendor Matched By Name Not Address", - "E-Document Notification Type"::"VAT Rate Mismatch"); - EDocumentNotification.SetRange("User Id", UserId()); - if not EDocumentNotification.FindSet() then - exit; - - repeat - SendNotification(EDocumentNotification); - until EDocumentNotification.Next() = 0; - end; -``` - -- [ ] **Step 4: Update `AddActionsToNotification` to handle the new type** - -Replace the existing `AddActionsToNotification` (lines 112-123) with: - -```al - local procedure AddActionsToNotification(var Notification: Notification; EDocumentNotification: Record "E-Document Notification") - var - DismissMsg: Label 'Dismiss'; - DontShowThisAgainMsg: Label 'Don''t show this again.'; - begin - Notification.SetData(EDocumentNotification.FieldName("E-Document Entry No."), Format(EDocumentNotification."E-Document Entry No.")); - Notification.SetData(EDocumentNotification.FieldName(ID), EDocumentNotification.ID); - case EDocumentNotification.Type of - "E-Document Notification Type"::"Vendor Matched By Name Not Address": - begin - Notification.AddAction(DismissMsg, Codeunit::"E-Document Notification", 'DismissVendorMatchedByNameNotAddressNotification'); - Notification.AddAction(DontShowThisAgainMsg, Codeunit::"E-Document Notification", 'DisableVendorMatchedByNameNotAddressNotification'); - end; - "E-Document Notification Type"::"VAT Rate Mismatch": - begin - Notification.AddAction(DismissMsg, Codeunit::"E-Document Notification", 'DismissVATRateMismatchNotification'); - Notification.AddAction(DontShowThisAgainMsg, Codeunit::"E-Document Notification", 'DisableVATRateMismatchNotification'); - end; - end; - end; -``` - -- [ ] **Step 5: Add dismiss and disable procedures for the new notification** - -Add after `DisableVendorMatchedByNameNotAddressNotification` (after line 95): - -```al - procedure DismissVATRateMismatchNotification(Notification: Notification) - var - EDocumentNotification: Record "E-Document Notification"; - EDocumentEntryNo: Integer; - Id: Guid; - begin - Evaluate(EDocumentEntryNo, Notification.GetData(EDocumentNotification.FieldName("E-Document Entry No."))); - Evaluate(Id, Notification.GetData(EDocumentNotification.FieldName(ID))); - if not EDocumentNotification.Get(EDocumentEntryNo, Id, UserId()) then - exit; - EDocumentNotification.Delete(true); - end; - - procedure DisableVATRateMismatchNotification(Notification: Notification) - var - MyNotifications: Record "My Notifications"; - EDocumentNotification: Record "E-Document Notification"; - VATRateMismatchNotificationNameTok: Label 'Notify user of Purchase Document Draft that VAT posting groups could not be auto-resolved.'; - VATRateMismatchNotificationDescTok: Label 'Show a notification when VAT Product Posting Groups could not be automatically determined from the extracted VAT rate.'; - begin - if MyNotifications.WritePermission() then - if not MyNotifications.Disable(GetVATRateMismatchNotificationId()) then - MyNotifications.InsertDefault(GetVATRateMismatchNotificationId(), VATRateMismatchNotificationNameTok, VATRateMismatchNotificationDescTok, false); - EDocumentNotification.SetRange(Type, "E-Document Notification Type"::"VAT Rate Mismatch"); - EDocumentNotification.SetRange("User Id", UserId()); - EDocumentNotification.DeleteAll(true); - end; -``` - -- [ ] **Step 6: Add the GUID getter for the new notification** - -Add after `GetVendorMatchedByNameNotAddressNotificationId` (after line 128): - -```al - local procedure GetVATRateMismatchNotificationId(): Guid - begin - exit('d4a7e1c3-5f92-4b8a-ae67-1c3d5f924b8a'); - end; -``` - -- [ ] **Step 7: Compile** - -Run: `al compile` for the E-Document app. -Expected: Clean compilation, no errors. - -- [ ] **Step 8: Commit** - -```bash -git add src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotificationType.Enum.al -git add src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotification.Codeunit.al -git commit -m "feat: add VAT Rate Mismatch notification type and handlers" -``` - ---- - -### Task 5: Implement VAT Posting Group resolution in Prepare Draft - -**Files:** -- Modify: `src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al:62-78` - -This is the core logic. After the existing line loop resolves type/number/UOM, we add a second pass that resolves VAT Posting Group. We also trigger the notification if resolution fails. - -- [ ] **Step 1: Add the VAT resolution call after the line loop** - -In `PrepareDraft`, insert after the existing line loop (after line 74, the `until` line) and before `CopilotLineMatching` (line 77): - -```al - // Resolve VAT Product Posting Groups from extracted VAT rates - ResolveVATProductPostingGroups(EDocument."Entry No", EDocumentPurchaseHeader); -``` - -- [ ] **Step 2: Add `using` statements** - -Add to the top of the file: -```al -using Microsoft.Finance.VAT.Setup; -using Microsoft.eServices.EDocument; -``` - -(If `Microsoft.eServices.EDocument` is already present via `EDocument` record, that's fine. The key addition is `Microsoft.Finance.VAT.Setup` for the `VAT Posting Setup` table.) - -- [ ] **Step 3: Implement `ResolveVATProductPostingGroups`** - -Add as a new local procedure: - -```al - local procedure ResolveVATProductPostingGroups(EDocumentEntryNo: Integer; EDocumentPurchaseHeader: Record "E-Document Purchase Header") - var - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - Vendor: Record Vendor; - EDocumentNotification: Codeunit "E-Document Notification"; - VATBusPostingGroup: Code[20]; - VATRate: Decimal; - LineCount: Integer; - HasUnresolvedVATLines: Boolean; - begin - if EDocumentPurchaseHeader."[BC] Vendor No." = '' then - exit; - if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then - exit; - VATBusPostingGroup := Vendor."VAT Bus. Posting Group"; - if VATBusPostingGroup = '' then - exit; - - EDocumentPurchaseLine.SetRange("E-Document Entry No.", EDocumentEntryNo); - LineCount := EDocumentPurchaseLine.Count(); - if LineCount = 0 then - exit; - - if EDocumentPurchaseLine.FindSet() then - repeat - VATRate := EDocumentPurchaseLine."VAT Rate"; - - // Single-line fallback: compute from header Total VAT - if (VATRate = 0) and (LineCount = 1) and - (EDocumentPurchaseHeader."Total VAT" > 0) and (EDocumentPurchaseHeader."Sub Total" > 0) - then - VATRate := Round((EDocumentPurchaseHeader."Total VAT" / EDocumentPurchaseHeader."Sub Total") * 100, 0.01); - - if VATRate > 0 then begin - EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" := - FindVATProductPostingGroup(VATBusPostingGroup, VATRate); - if EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" = '' then - HasUnresolvedVATLines := true; - EDocumentPurchaseLine.Modify(); - end; - until EDocumentPurchaseLine.Next() = 0; - - if HasUnresolvedVATLines then - EDocumentNotification.AddVATRateMismatchNotification(EDocumentEntryNo); - end; - - local procedure FindVATProductPostingGroup(VATBusPostingGroup: Code[20]; VATRate: Decimal): Code[20] - var - VATPostingSetup: Record "VAT Posting Setup"; - RoundingTolerance: Decimal; - begin - RoundingTolerance := 0.01; - VATPostingSetup.SetRange("VAT Bus. Posting Group", VATBusPostingGroup); - VATPostingSetup.SetFilter("VAT %", '>=%1&<=%2', VATRate - RoundingTolerance, VATRate + RoundingTolerance); - if VATPostingSetup.Count() = 1 then begin - VATPostingSetup.FindFirst(); - exit(VATPostingSetup."VAT Prod. Posting Group"); - end; - // Zero or multiple matches — return blank to signal resolution failure - exit(''); - end; -``` - -- [ ] **Step 4: Compile** - -Run: `al compile` for the E-Document app. -Expected: Clean compilation, no errors. - -- [ ] **Step 5: Commit** - -```bash -git add src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al -git commit -m "feat: resolve VAT Prod. Posting Group from extracted VAT rate during Prepare Draft" -``` - ---- - -### Task 6: Apply resolved VAT Posting Group in Finish Draft - -**Files:** -- Modify: `src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocCreatePurchaseInvoice.Codeunit.al:216-219` - -- [ ] **Step 1: Add the VAT Posting Group override in `CreatePurchaseInvoiceLine`** - -After line 216 (`PurchaseLine.Validate("No.", EDocumentPurchaseLine."[BC] Purchase Type No.");`) and before line 217 (`if (PurchaseLine.Type = ...`), insert: - -```al - if EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" <> '' then - PurchaseLine.Validate("VAT Prod. Posting Group", EDocumentPurchaseLine."[BC] VAT Prod. Posting Group"); -``` - -The `Validate("No.", ...)` call at line 216 sets the default VAT Posting Group from the G/L Account/Item card. Our new lines override it with the resolved value. `Validate("VAT Prod. Posting Group", ...)` will also update `"VAT %"` on the purchase line based on the VAT Posting Setup. - -- [ ] **Step 2: Compile** - -Run: `al compile` for the E-Document app. -Expected: Clean compilation, no errors. - -- [ ] **Step 3: Commit** - -```bash -git add src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocCreatePurchaseInvoice.Codeunit.al -git commit -m "feat: apply resolved VAT Prod. Posting Group when creating purchase invoice line" -``` - ---- - -### Task 7: Write integration tests for VAT Posting Group resolution - -**Files:** -- Modify or create: `src/Apps/W1/EDocument/Test/src/Processing/EDocumentStructuredTests.Codeunit.al` (add new test procedures) - -These tests validate the end-to-end flow: ADI extracts VAT data → Prepare Draft resolves the posting group → Finish Draft applies it. - -- [ ] **Step 1: Add a test for per-line VAT rate resolution** - -Add a new test procedure to `EDocumentStructuredTests`: - -```al - [Test] - procedure TestCAPIInvoice_VATPostingGroupResolvedFromLineRate() - var - EDocument: Record "E-Document"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - VATPostingSetup: Record "VAT Posting Setup"; - begin - // [SCENARIO] When CAPI extracts tax amounts per line, Prepare Draft resolves VAT Prod. Posting Group - Initialize(Enum::"Service Integration"::"Mock"); - SetupCAPIEDocumentService(); - CreateInboundEDocumentFromJSON(EDocument, 'capi/capi-invoice-valid-0.json'); - - // Process through Read into Draft (extracts VAT rates) - Assert.IsTrue( - ProcessEDocumentToStep(EDocument, "Import E-Document Steps"::"Read into Draft"), - 'Failed to process to Read into Draft'); - - // Verify VAT Rate is now a percentage (10% for all lines: $6/$60, $3/$30, $1/$10) - EDocumentPurchaseLine.SetRange("E-Document Entry No.", EDocument."Entry No"); - EDocumentPurchaseLine.FindSet(); - repeat - Assert.AreEqual(10, EDocumentPurchaseLine."VAT Rate", - 'VAT Rate should be normalized to percentage'); - until EDocumentPurchaseLine.Next() = 0; - - // Process through Prepare Draft (resolves VAT Prod. Posting Group) - // Note: This requires a vendor with a VAT Bus. Posting Group and a matching - // VAT Posting Setup with VAT % = 10. If the test vendor setup has this, - // the [BC] VAT Prod. Posting Group field should be populated. - // The exact assertion depends on the test vendor's VAT configuration. - end; -``` - -Note: The exact assertions for the Prepare Draft step depend on the test data setup (vendor's VAT Bus. Posting Group and VAT Posting Setup). Adapt the test to match the fixture vendor's configuration, or create the required VAT Posting Setup in the test's `Initialize` procedure. - -- [ ] **Step 2: Compile and run tests** - -Run: `al compile` and `al run_tests` for the E-Document test app. -Expected: All tests pass. - -- [ ] **Step 3: Commit** - -```bash -git add src/Apps/W1/EDocument/Test/src/Processing/EDocumentStructuredTests.Codeunit.al -git commit -m "test: add integration test for VAT Posting Group resolution from ADI data" -``` diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md deleted file mode 100644 index 2be905cc69..0000000000 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md +++ /dev/null @@ -1,242 +0,0 @@ -# VAT Rate Mismatch — Inline Warning Field on Draft Subform - -**Date:** 2026-04-03 -**Status:** Draft -**Author:** Artur Ventsel - -## Problem - -The current branch introduces a page-level notification banner for VAT rate mismatch (when VAT Prod. Posting Group resolution fails during Prepare Draft). This notification pattern has drawbacks: - -1. It does not indicate **which** lines have the issue — the user must inspect each line manually. -2. It cannot react to user edits — dismissing the notification is a manual action unrelated to actually fixing the mismatch. -3. It adds significant plumbing (enum value, 4 procedures, GUID, My Notifications integration) for what is essentially a per-line data quality indicator. - -The PO matching feature already solves a similar problem with inline warning fields on the draft subform: a per-line column with `StyleExpr` styling, conditional visibility when any line has warnings, and drill-down for details. - -## Design - -Replace the notification approach with a persisted boolean field on the E-Document Purchase Line and an inline warning column on the draft subform page. - -### 1. New Field on E-Document Purchase Line - -**File:** `EDocumentPurchaseLine.Table.al` - -Add field 111 in the `[BC]` validated fields range (101–200), after field 110: - -```al -field(111; "[BC] VAT Rate Mismatch"; Boolean) -{ - Caption = 'VAT Rate Mismatch'; - ToolTip = 'Specifies whether the VAT Product Posting Group could not be resolved from the extracted VAT rate.'; -} -``` - -### 2. OnValidate Trigger for VAT Prod. Posting Group - -**File:** `EDocumentPurchaseLine.Table.al` — field 110 (`[BC] VAT Prod. Posting Group`) - -Add an OnValidate trigger that re-evaluates the mismatch by comparing the chosen posting group's VAT % against the extracted VAT rate on the line: - -```al -field(110; "[BC] VAT Prod. Posting Group"; Code[20]) -{ - Caption = 'VAT Prod. Posting Group'; - ToolTip = 'Specifies the VAT product posting group resolved from the extracted VAT rate.'; - TableRelation = "VAT Product Posting Group"; - - trigger OnValidate() - var - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - Vendor: Record Vendor; - VATPostingSetup: Record "VAT Posting Setup"; - begin - if "[BC] VAT Prod. Posting Group" = '' then begin - "[BC] VAT Rate Mismatch" := true; - exit; - end; - if not EDocumentPurchaseHeader.Get("E-Document Entry No.") then - exit; - if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then - exit; - if VATPostingSetup.Get(Vendor."VAT Bus. Posting Group", "[BC] VAT Prod. Posting Group") then begin - if not (VATPostingSetup."VAT Calculation Type" in - [VATPostingSetup."VAT Calculation Type"::"Normal VAT", - VATPostingSetup."VAT Calculation Type"::"Reverse Charge VAT"]) - then - exit; // Full VAT and Sales Tax — rate comparison is not applicable - "[BC] VAT Rate Mismatch" := VATPostingSetup."VAT %" <> "VAT Rate"; - end else - "[BC] VAT Rate Mismatch" := true; - end; - - trigger OnLookup() - var - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - Vendor: Record Vendor; - VATPostingSetup: Record "VAT Posting Setup"; - begin - if not EDocumentPurchaseHeader.Get("E-Document Entry No.") then - exit; - if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then - exit; - VATPostingSetup.SetRange("VAT Bus. Posting Group", Vendor."VAT Bus. Posting Group"); - VATPostingSetup.SetFilter("VAT Calculation Type", '%1|%2', - VATPostingSetup."VAT Calculation Type"::"Normal VAT", - VATPostingSetup."VAT Calculation Type"::"Reverse Charge VAT"); - if Page.RunModal(Page::"VAT Posting Setup", VATPostingSetup) = Action::LookupOK then - Validate("[BC] VAT Prod. Posting Group", VATPostingSetup."VAT Prod. Posting Group"); - end; -} -``` - -The `TableRelation` provides basic validation and standard lookup. The `OnLookup` trigger overrides the default lookup to open the VAT Posting Setup page filtered by the vendor's VAT Bus. Posting Group, so the user only sees relevant posting groups. When the user selects a row, it calls `Validate` which runs the `OnValidate` trigger above, re-evaluating the mismatch. - -This means: -- If the user clears the posting group, the mismatch flag is set. -- If the user picks a posting group with Full VAT or Sales Tax calculation type, the mismatch evaluation is skipped (rate comparison is not applicable for those types). -- If the user picks a Normal VAT or Reverse Charge VAT posting group, the trigger compares `VAT %` to the line's `"VAT Rate"` with exact equality. This works for zero-rated lines too — a line with `"VAT Rate" = 0` only clears the warning if the setup also has `VAT % = 0`. -- The lookup is filtered to only show Normal VAT and Reverse Charge VAT setups for the vendor's VAT Bus. Posting Group. - -### 3. Set the Flag in Prepare Draft - -**File:** `PreparePurchaseEDocDraft.Codeunit.al` — `ResolveVATProductPostingGroups` - -Replace the `HasUnresolvedVATLines` boolean and notification call with direct field assignment: - -- When `FindVATProductPostingGroup` returns blank and `VATRate > 0`: set `"[BC] VAT Rate Mismatch" := true`. -- When `FindVATProductPostingGroup` returns a value: set `"[BC] VAT Rate Mismatch" := false`. -- Remove the `EDocumentNotification` variable and the `AddVATRateMismatchNotification` call. - -**`FindVATProductPostingGroup`** must filter to only `"VAT Calculation Type"` in `["Normal VAT", "Reverse Charge VAT"]`. Full VAT and Sales Tax setups do not use `VAT %` for rate-based matching and must be excluded from the query to avoid false positives (zero-match or wrong-match results). - -### 4. Warning Column on Draft Subform Page - -**File:** `EDocPurchaseDraftSubform.Page.al` - -Add a new field in the repeater, positioned after the `"VAT Prod. Posting Group"` field: - -```al -field(VATWarning; VATWarningCaption) -{ - ApplicationArea = All; - Caption = 'VAT warnings'; - Editable = false; - Visible = HasVATWarnings; - StyleExpr = VATWarningStyleExpr; - ToolTip = 'Specifies whether the VAT Product Posting Group could not be resolved from the extracted VAT rate.'; - - trigger OnDrillDown() - begin - ShowVATWarningDetails(); - end; -} -``` - -**Page-level variables:** - -```al -VATWarningCaption, VATWarningStyleExpr : Text; -HasVATWarnings : Boolean; -``` - -**Page-level visibility** (`HasVATWarnings`): Computed in `OnOpenPage` and `OnAfterGetCurrRecord` by a new `UpdateVATWarnings()` procedure: - -```al -local procedure UpdateVATWarnings() -var - EDocPurchLine: Record "E-Document Purchase Line"; -begin - EDocPurchLine.SetRange("E-Document Entry No.", EDocumentPurchaseHeader."E-Document Entry No."); - EDocPurchLine.SetRange("[BC] VAT Rate Mismatch", true); - HasVATWarnings := not EDocPurchLine.IsEmpty(); -end; -``` - -**Per-line caption and style**: Computed in `OnAfterGetRecord` by a new `UpdateVATWarningForLine()` procedure: - -```al -local procedure UpdateVATWarningForLine() -var - VATGroupNotResolvedLbl: Label 'VAT group not resolved'; -begin - if Rec."[BC] VAT Rate Mismatch" then begin - VATWarningCaption := VATGroupNotResolvedLbl; - VATWarningStyleExpr := 'Ambiguous'; - end else begin - VATWarningCaption := ''; - VATWarningStyleExpr := 'None'; - end; -end; -``` - -**Drill-down** (`ShowVATWarningDetails`): Shows a `Message()` with the extracted VAT rate: - -```al -local procedure ShowVATWarningDetails() -var - VATWarningDetailLbl: Label 'VAT rate %1% was extracted from the invoice but could not be matched to a single VAT Posting Setup for the vendor. Please select the correct VAT Product Posting Group manually.', Comment = '%1 = VAT rate percentage'; -begin - if not Rec."[BC] VAT Rate Mismatch" then - exit; - Message(VATWarningDetailLbl, Rec."VAT Rate"); -end; -``` - -### 5. Remove Notification Infrastructure - -Remove all VAT-related notification code added in earlier commits: - -**`EDocumentNotificationType.Enum.al`:** Remove `value(2; "VAT Rate Mismatch")`. - -**`EDocumentNotification.Codeunit.al`:** Remove: -- `AddVATRateMismatchNotification` procedure -- `DismissVATRateMismatchNotification` procedure -- `DisableVATRateMismatchNotification` procedure -- `GetVATRateMismatchNotificationId` procedure -- The `"VAT Rate Mismatch"` case branch in `AddActionsToNotification` -- The `"VAT Rate Mismatch"` filter in `SendPurchaseDocumentDraftNotifications` - -Revert these to their pre-branch state (only handling `"Vendor Matched By Name Not Address"`). - -### 6. Update and Add Tests - -**`EDocProcessTest.Codeunit.al`:** - -#### Update existing tests - -- **`PreparingPurchaseDraftResolvesVATProductPostingGroupFromLineVATRate`**: Add assertion that `"[BC] VAT Rate Mismatch"` is `false` when resolution succeeds. -- **`PreparingPurchaseDraftCreatesNotificationWhenNoMatchingVATSetup`**: Rename to `PreparingPurchaseDraftSetsVATRateMismatchWhenNoMatchingVATSetup`. Replace the notification record assertion with: assert `"[BC] VAT Rate Mismatch"` is `true`. Remove notification cleanup. - -#### New tests - -**Prepare Draft — VAT Calculation Type filtering:** - -- **`PreparingDraftIgnoresFullVATSetupWhenResolvingPostingGroup`**: Create a VAT Posting Setup with `"VAT Calculation Type" = "Full VAT"` and `VAT % = 10`. Create a line with `"VAT Rate" = 10`. Run Prepare Draft. Assert `"[BC] VAT Prod. Posting Group"` is blank — Full VAT setups must not be matched. Assert `"[BC] VAT Rate Mismatch"` is `true`. - -- **`PreparingDraftIgnoresSalesTaxSetupWhenResolvingPostingGroup`**: Same as above but with `"VAT Calculation Type" = "Sales Tax"`. Assert the setup is not matched. - -- **`PreparingDraftResolvesReverseChargeVATPostingGroup`**: Create a VAT Posting Setup with `"VAT Calculation Type" = "Reverse Charge VAT"` and `VAT % = 20`. Create a line with `"VAT Rate" = 20`. Run Prepare Draft. Assert `"[BC] VAT Prod. Posting Group"` is resolved and `"[BC] VAT Rate Mismatch"` is `false`. - -**OnValidate — mismatch re-evaluation:** - -- **`ValidatingVATProdPostingGroupClearsMismatchWhenRateMatches`**: Create a line with `"VAT Rate" = 20` and `"[BC] VAT Rate Mismatch" = true`. Create a Normal VAT setup with `VAT % = 20`. Validate `"[BC] VAT Prod. Posting Group"` to that setup's group. Assert `"[BC] VAT Rate Mismatch"` is `false`. - -- **`ValidatingVATProdPostingGroupKeepsMismatchWhenRateDiffers`**: Create a line with `"VAT Rate" = 20` and `"[BC] VAT Rate Mismatch" = true`. Create a Normal VAT setup with `VAT % = 10`. Validate `"[BC] VAT Prod. Posting Group"` to that setup's group. Assert `"[BC] VAT Rate Mismatch"` is still `true`. - -- **`ValidatingVATProdPostingGroupSetsMismatchWhenCleared`**: Create a line with `"VAT Rate" = 20`, `"[BC] VAT Prod. Posting Group" = 'STANDARD'`, and `"[BC] VAT Rate Mismatch" = false`. Validate `"[BC] VAT Prod. Posting Group"` to `''`. Assert `"[BC] VAT Rate Mismatch"` is `true`. - -- **`ValidatingVATProdPostingGroupSkipsMismatchForFullVAT`**: Create a line with `"VAT Rate" = 5` and `"[BC] VAT Rate Mismatch" = false`. Create a Full VAT setup with `VAT % = 0`. Validate `"[BC] VAT Prod. Posting Group"` to that setup's group. Assert `"[BC] VAT Rate Mismatch"` is unchanged (`false`) — Full VAT skips the comparison. - -- **`ValidatingVATProdPostingGroupMatchesZeroRate`**: Create a line with `"VAT Rate" = 0`. Create a Normal VAT setup with `VAT % = 0`. Validate `"[BC] VAT Prod. Posting Group"` to that setup's group. Assert `"[BC] VAT Rate Mismatch"` is `false`. - -## Key Files - -| File | Change | -|---|---| -| `EDocumentPurchaseLine.Table.al` | Add field 111 `[BC] VAT Rate Mismatch`; add OnValidate and OnLookup to field 110 | -| `PreparePurchaseEDocDraft.Codeunit.al` | Set mismatch flag instead of calling notification; filter by VAT Calculation Type | -| `EDocPurchaseDraftSubform.Page.al` | Add inline warning column with visibility, style, drill-down | -| `EDocumentNotification.Codeunit.al` | Remove all VAT notification procedures | -| `EDocumentNotificationType.Enum.al` | Remove `"VAT Rate Mismatch"` enum value | -| `EDocProcessTest.Codeunit.al` | Update existing test assertions; add 7 new tests for calculation type filtering and OnValidate mismatch logic | diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-07-vat-warning-field-plan.md b/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-07-vat-warning-field-plan.md deleted file mode 100644 index 6f0ec1ae8a..0000000000 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-07-vat-warning-field-plan.md +++ /dev/null @@ -1,1189 +0,0 @@ -# VAT Rate Mismatch Inline Warning Field — Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Replace the page-level VAT rate mismatch notification with a persisted boolean field and inline warning column on the E-Document Purchase Draft Subform, following the PO matching warning pattern. - -**Architecture:** Add `[BC] VAT Rate Mismatch` boolean field to `E-Document Purchase Line`, set it during Prepare Draft when resolution fails, re-evaluate it on user edits via `OnValidate`, and display it as a per-line warning column with conditional visibility on the draft subform. Remove all notification infrastructure for VAT mismatch. - -**Tech Stack:** AL (Business Central), E-Document Core framework, VAT Posting Setup table (325) - -**Spec:** `src/Apps/W1/EDocument/App/src/Processing/Import/docs/2026-04-03-vat-warning-field-design.md` - ---- - -### Task 1: Add `[BC] VAT Rate Mismatch` field and update `[BC] VAT Prod. Posting Group` triggers - -**Files:** -- Modify: `src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al:212-217` - -- [ ] **Step 1: Add field 111 and update field 110 with OnValidate and OnLookup** - -Replace the current field 110 block (lines 212–217): - -```al - field(110; "[BC] VAT Prod. Posting Group"; Code[20]) - { - Caption = 'VAT Prod. Posting Group'; - ToolTip = 'Specifies the VAT product posting group resolved from the extracted VAT rate.'; - TableRelation = "VAT Product Posting Group"; - } -``` - -With: - -```al - field(110; "[BC] VAT Prod. Posting Group"; Code[20]) - { - Caption = 'VAT Prod. Posting Group'; - ToolTip = 'Specifies the VAT product posting group resolved from the extracted VAT rate.'; - TableRelation = "VAT Product Posting Group"; - - trigger OnValidate() - var - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - Vendor: Record Vendor; - VATPostingSetup: Record "VAT Posting Setup"; - begin - if "[BC] VAT Prod. Posting Group" = '' then begin - "[BC] VAT Rate Mismatch" := true; - exit; - end; - if not EDocumentPurchaseHeader.Get("E-Document Entry No.") then - exit; - if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then - exit; - if VATPostingSetup.Get(Vendor."VAT Bus. Posting Group", "[BC] VAT Prod. Posting Group") then begin - if not (VATPostingSetup."VAT Calculation Type" in - [VATPostingSetup."VAT Calculation Type"::"Normal VAT", - VATPostingSetup."VAT Calculation Type"::"Reverse Charge VAT"]) - then - exit; - "[BC] VAT Rate Mismatch" := VATPostingSetup."VAT %" <> "VAT Rate"; - end else - "[BC] VAT Rate Mismatch" := true; - end; - - trigger OnLookup() - var - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - Vendor: Record Vendor; - VATPostingSetup: Record "VAT Posting Setup"; - begin - if not EDocumentPurchaseHeader.Get("E-Document Entry No.") then - exit; - if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then - exit; - VATPostingSetup.SetRange("VAT Bus. Posting Group", Vendor."VAT Bus. Posting Group"); - VATPostingSetup.SetFilter("VAT Calculation Type", '%1|%2', - VATPostingSetup."VAT Calculation Type"::"Normal VAT", - VATPostingSetup."VAT Calculation Type"::"Reverse Charge VAT"); - if Page.RunModal(Page::"VAT Posting Setup", VATPostingSetup) = Action::LookupOK then - Validate("[BC] VAT Prod. Posting Group", VATPostingSetup."VAT Prod. Posting Group"); - end; - } - field(111; "[BC] VAT Rate Mismatch"; Boolean) - { - Caption = 'VAT Rate Mismatch'; - ToolTip = 'Specifies whether the VAT Product Posting Group could not be resolved from the extracted VAT rate.'; - } -``` - -The `using Microsoft.Finance.VAT.Setup;` and `using Microsoft.Purchases.Vendor;` are already present at lines 14 and 20 of the table file — no new usings needed. - -- [ ] **Step 2: Compile** - -Run: `al compile` for the E-Document app. -Expected: Clean compilation, no errors. - -- [ ] **Step 3: Commit** - -```bash -git add src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al -git commit -m "feat: add VAT Rate Mismatch field and OnValidate/OnLookup to VAT Prod. Posting Group - -Field 111 [BC] VAT Rate Mismatch persists whether VAT resolution failed. -OnValidate re-evaluates mismatch by comparing VAT Posting Setup rate -against extracted rate, filtering to Normal/Reverse Charge VAT only. -OnLookup opens VAT Posting Setup filtered by vendor's bus posting group." -``` - ---- - -### Task 2: Update `ResolveVATProductPostingGroups` to set mismatch flag instead of notification - -**Files:** -- Modify: `src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al:188-248` - -- [ ] **Step 1: Replace `ResolveVATProductPostingGroups` and `FindVATProductPostingGroup`** - -Replace the current `ResolveVATProductPostingGroups` procedure (lines 188–232): - -```al - local procedure ResolveVATProductPostingGroups(EDocumentEntryNo: Integer; EDocumentPurchaseHeader: Record "E-Document Purchase Header") - var - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - Vendor: Record Vendor; - EDocumentNotification: Codeunit "E-Document Notification"; - VATBusPostingGroup: Code[20]; - VATRate: Decimal; - LineCount: Integer; - HasUnresolvedVATLines: Boolean; - begin - if EDocumentPurchaseHeader."[BC] Vendor No." = '' then - exit; - if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then - exit; - VATBusPostingGroup := Vendor."VAT Bus. Posting Group"; - if VATBusPostingGroup = '' then - exit; - - EDocumentPurchaseLine.SetRange("E-Document Entry No.", EDocumentEntryNo); - LineCount := EDocumentPurchaseLine.Count(); - if LineCount = 0 then - exit; - - if EDocumentPurchaseLine.FindSet() then - repeat - VATRate := EDocumentPurchaseLine."VAT Rate"; - - // Single-line fallback: compute from header Total VAT - if (VATRate = 0) and (LineCount = 1) and - (EDocumentPurchaseHeader."Total VAT" > 0) and (EDocumentPurchaseHeader."Sub Total" > 0) - then - VATRate := Round((EDocumentPurchaseHeader."Total VAT" / EDocumentPurchaseHeader."Sub Total") * 100, 0.01); - - if VATRate > 0 then begin - EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" := - FindVATProductPostingGroup(VATBusPostingGroup, VATRate); - if EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" = '' then - HasUnresolvedVATLines := true; - EDocumentPurchaseLine.Modify(); - end; - until EDocumentPurchaseLine.Next() = 0; - - if HasUnresolvedVATLines then - EDocumentNotification.AddVATRateMismatchNotification(EDocumentEntryNo); - end; -``` - -With: - -```al - local procedure ResolveVATProductPostingGroups(EDocumentEntryNo: Integer; EDocumentPurchaseHeader: Record "E-Document Purchase Header") - var - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - Vendor: Record Vendor; - VATBusPostingGroup: Code[20]; - VATRate: Decimal; - LineCount: Integer; - begin - if EDocumentPurchaseHeader."[BC] Vendor No." = '' then - exit; - if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then - exit; - VATBusPostingGroup := Vendor."VAT Bus. Posting Group"; - if VATBusPostingGroup = '' then - exit; - - EDocumentPurchaseLine.SetRange("E-Document Entry No.", EDocumentEntryNo); - LineCount := EDocumentPurchaseLine.Count(); - if LineCount = 0 then - exit; - - if EDocumentPurchaseLine.FindSet() then - repeat - VATRate := EDocumentPurchaseLine."VAT Rate"; - - // Single-line fallback: compute from header Total VAT - if (VATRate = 0) and (LineCount = 1) and - (EDocumentPurchaseHeader."Total VAT" > 0) and (EDocumentPurchaseHeader."Sub Total" > 0) - then - VATRate := Round((EDocumentPurchaseHeader."Total VAT" / EDocumentPurchaseHeader."Sub Total") * 100, 0.01); - - if VATRate > 0 then begin - EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" := - FindVATProductPostingGroup(VATBusPostingGroup, VATRate); - EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := - EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" = ''; - EDocumentPurchaseLine.Modify(); - end; - until EDocumentPurchaseLine.Next() = 0; - end; -``` - -- [ ] **Step 2: Replace `FindVATProductPostingGroup` to filter by VAT Calculation Type** - -Replace the current `FindVATProductPostingGroup` procedure (lines 234–248): - -```al - local procedure FindVATProductPostingGroup(VATBusPostingGroup: Code[20]; VATRate: Decimal): Code[20] - var - VATPostingSetup: Record "VAT Posting Setup"; - RoundingTolerance: Decimal; - begin - RoundingTolerance := 0.01; - VATPostingSetup.SetRange("VAT Bus. Posting Group", VATBusPostingGroup); - VATPostingSetup.SetFilter("VAT %", '>=%1&<=%2', VATRate - RoundingTolerance, VATRate + RoundingTolerance); - if VATPostingSetup.Count() = 1 then begin - VATPostingSetup.FindFirst(); - exit(VATPostingSetup."VAT Prod. Posting Group"); - end; - // Zero or multiple matches — return blank to signal resolution failure - exit(''); - end; -``` - -With: - -```al - local procedure FindVATProductPostingGroup(VATBusPostingGroup: Code[20]; VATRate: Decimal): Code[20] - var - VATPostingSetup: Record "VAT Posting Setup"; - RoundingTolerance: Decimal; - begin - RoundingTolerance := 0.01; - VATPostingSetup.SetRange("VAT Bus. Posting Group", VATBusPostingGroup); - VATPostingSetup.SetFilter("VAT Calculation Type", '%1|%2', - VATPostingSetup."VAT Calculation Type"::"Normal VAT", - VATPostingSetup."VAT Calculation Type"::"Reverse Charge VAT"); - VATPostingSetup.SetFilter("VAT %", '>=%1&<=%2', VATRate - RoundingTolerance, VATRate + RoundingTolerance); - if VATPostingSetup.Count() = 1 then begin - VATPostingSetup.FindFirst(); - exit(VATPostingSetup."VAT Prod. Posting Group"); - end; - exit(''); - end; -``` - -- [ ] **Step 3: Remove unused `using Microsoft.eServices.EDocument;` if it was only for notification** - -Check the `using` statements at the top of `PreparePurchaseEDocDraft.Codeunit.al`. The `using Microsoft.eServices.EDocument;` at line 7 is still needed for the `"E-Document"` record type used elsewhere in the codeunit. No using changes needed. - -- [ ] **Step 4: Compile** - -Run: `al compile` for the E-Document app. -Expected: Clean compilation, no errors. - -- [ ] **Step 5: Commit** - -```bash -git add src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al -git commit -m "refactor: set VAT Rate Mismatch flag instead of notification in Prepare Draft - -Replace HasUnresolvedVATLines + notification call with direct -[BC] VAT Rate Mismatch field assignment. Filter FindVATProductPostingGroup -to Normal VAT and Reverse Charge VAT calculation types only." -``` - ---- - -### Task 3: Add VAT warning column to draft subform page - -**Files:** -- Modify: `src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al` - -- [ ] **Step 1: Add the warning field in the repeater** - -After the `"VAT Prod. Posting Group"` field block (after line 78, the closing `}` of that field), insert: - -```al - field(VATWarning; VATWarningCaption) - { - ApplicationArea = All; - Caption = 'VAT warnings'; - Editable = false; - Visible = HasVATWarnings; - StyleExpr = VATWarningStyleExpr; - ToolTip = 'Specifies whether the VAT Product Posting Group could not be resolved from the extracted VAT rate.'; - - trigger OnDrillDown() - begin - ShowVATWarningDetails(); - end; - } -``` - -- [ ] **Step 2: Add page-level variables** - -In the `var` section (line 339), add the new variables. Replace line 339: - -```al - AdditionalColumns, OrderMatchedCaption, MatchWarningsCaption, MatchWarningsStyleExpr : Text; -``` - -With: - -```al - AdditionalColumns, OrderMatchedCaption, MatchWarningsCaption, MatchWarningsStyleExpr, VATWarningCaption, VATWarningStyleExpr : Text; -``` - -In the boolean line (line 341), replace: - -```al - DimVisible1, DimVisible2, HasAdditionalColumns, IsEDocumentMatchedToAnyPOLine, IsLineMatchedToOrderLine, IsLineMatchedToReceiptLine, HasEDocumentOrderMatchWarnings : Boolean; -``` - -With: - -```al - DimVisible1, DimVisible2, HasAdditionalColumns, HasVATWarnings, IsEDocumentMatchedToAnyPOLine, IsLineMatchedToOrderLine, IsLineMatchedToReceiptLine, HasEDocumentOrderMatchWarnings : Boolean; -``` - -- [ ] **Step 3: Add `UpdateVATWarnings()` call to `OnOpenPage` and `OnAfterGetCurrRecord`** - -Replace `OnOpenPage` (lines 344–348): - -```al - trigger OnOpenPage() - begin - SetDimensionsVisibility(); - UpdatePOMatching(); - end; -``` - -With: - -```al - trigger OnOpenPage() - begin - SetDimensionsVisibility(); - UpdatePOMatching(); - UpdateVATWarnings(); - end; -``` - -Replace `OnAfterGetCurrRecord` (lines 355–358): - -```al - trigger OnAfterGetCurrRecord() - begin - UpdatePOMatching(); - end; -``` - -With: - -```al - trigger OnAfterGetCurrRecord() - begin - UpdatePOMatching(); - UpdateVATWarnings(); - end; -``` - -- [ ] **Step 4: Add `UpdateVATWarningForLine()` call to `OnAfterGetRecord`** - -At the end of `OnAfterGetRecord` (after `UpdateMatchWarnings();` at line 369), add: - -```al - UpdateVATWarningForLine(); -``` - -- [ ] **Step 5: Add the three new procedures** - -Add before the closing `}` of the page (before line 573): - -```al - local procedure UpdateVATWarnings() - var - EDocPurchLine: Record "E-Document Purchase Line"; - begin - EDocPurchLine.SetRange("E-Document Entry No.", EDocumentPurchaseHeader."E-Document Entry No."); - EDocPurchLine.SetRange("[BC] VAT Rate Mismatch", true); - HasVATWarnings := not EDocPurchLine.IsEmpty(); - end; - - local procedure UpdateVATWarningForLine() - var - VATGroupNotResolvedLbl: Label 'VAT group not resolved'; - begin - if Rec."[BC] VAT Rate Mismatch" then begin - VATWarningCaption := VATGroupNotResolvedLbl; - VATWarningStyleExpr := 'Ambiguous'; - end else begin - VATWarningCaption := ''; - VATWarningStyleExpr := 'None'; - end; - end; - - local procedure ShowVATWarningDetails() - var - VATWarningDetailLbl: Label 'VAT rate %1% was extracted from the invoice but could not be matched to a single VAT Posting Setup for the vendor. Please select the correct VAT Product Posting Group manually.', Comment = '%1 = VAT rate percentage'; - begin - if not Rec."[BC] VAT Rate Mismatch" then - exit; - Message(VATWarningDetailLbl, Rec."VAT Rate"); - end; -``` - -- [ ] **Step 6: Compile** - -Run: `al compile` for the E-Document app. -Expected: Clean compilation, no errors. - -- [ ] **Step 7: Commit** - -```bash -git add src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al -git commit -m "feat: add inline VAT warning column to draft subform page - -Per-line warning with Ambiguous styling, conditional visibility when -any line has a mismatch, and drill-down showing the extracted VAT rate. -Follows the PO matching warning pattern." -``` - ---- - -### Task 4: Remove VAT notification infrastructure - -**Files:** -- Modify: `src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotificationType.Enum.al` -- Modify: `src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotification.Codeunit.al` - -- [ ] **Step 1: Remove enum value from `EDocumentNotificationType.Enum.al`** - -Remove lines 19–22: - -```al - value(2; "VAT Rate Mismatch") - { - Caption = 'VAT Rate Mismatch'; - } -``` - -- [ ] **Step 2: Remove `AddVATRateMismatchNotification` from `EDocumentNotification.Codeunit.al`** - -Remove lines 39–57 (the entire `AddVATRateMismatchNotification` procedure). - -- [ ] **Step 3: Revert `SendPurchaseDocumentDraftNotifications` filter** - -Replace lines 70–73: - -```al - EDocumentNotification.SetFilter(Type, '%1|%2', - "E-Document Notification Type"::"Vendor Matched By Name Not Address", - "E-Document Notification Type"::"VAT Rate Mismatch"); -``` - -With: - -```al - EDocumentNotification.SetRange(Type, "E-Document Notification Type"::"Vendor Matched By Name Not Address"); -``` - -- [ ] **Step 4: Remove `DismissVATRateMismatchNotification` and `DisableVATRateMismatchNotification`** - -Remove lines 119–145 (both procedures). - -- [ ] **Step 5: Revert `AddActionsToNotification` to only handle vendor notification** - -Replace the current `AddActionsToNotification` (lines 162–181): - -```al - local procedure AddActionsToNotification(var Notification: Notification; EDocumentNotification: Record "E-Document Notification") - var - DismissMsg: Label 'Dismiss'; - DontShowThisAgainMsg: Label 'Don''t show this again.'; - begin - Notification.SetData(EDocumentNotification.FieldName("E-Document Entry No."), Format(EDocumentNotification."E-Document Entry No.")); - Notification.SetData(EDocumentNotification.FieldName(ID), EDocumentNotification.ID); - case EDocumentNotification.Type of - "E-Document Notification Type"::"Vendor Matched By Name Not Address": - begin - Notification.AddAction(DismissMsg, Codeunit::"E-Document Notification", 'DismissVendorMatchedByNameNotAddressNotification'); - Notification.AddAction(DontShowThisAgainMsg, Codeunit::"E-Document Notification", 'DisableVendorMatchedByNameNotAddressNotification'); - end; - "E-Document Notification Type"::"VAT Rate Mismatch": - begin - Notification.AddAction(DismissMsg, Codeunit::"E-Document Notification", 'DismissVATRateMismatchNotification'); - Notification.AddAction(DontShowThisAgainMsg, Codeunit::"E-Document Notification", 'DisableVATRateMismatchNotification'); - end; - end; - end; -``` - -With: - -```al - local procedure AddActionsToNotification(var Notification: Notification; EDocumentNotification: Record "E-Document Notification") - var - DismissMsg: Label 'Dismiss'; - DontShowThisAgainMsg: Label 'Don''t show this again.'; - begin - if EDocumentNotification.Type <> "E-Document Notification Type"::"Vendor Matched By Name Not Address" then - exit; - Notification.SetData(EDocumentNotification.FieldName("E-Document Entry No."), Format(EDocumentNotification."E-Document Entry No.")); - Notification.SetData(EDocumentNotification.FieldName(ID), EDocumentNotification.ID); - Notification.AddAction(DismissMsg, Codeunit::"E-Document Notification", 'DismissVendorMatchedByNameNotAddressNotification'); - Notification.AddAction(DontShowThisAgainMsg, Codeunit::"E-Document Notification", 'DisableVendorMatchedByNameNotAddressNotification'); - end; -``` - -- [ ] **Step 6: Remove `GetVATRateMismatchNotificationId`** - -Remove lines 188–191: - -```al - local procedure GetVATRateMismatchNotificationId(): Guid - begin - exit('d4a7e1c3-5f92-4b8a-ae67-1c3d5f924b8a'); - end; -``` - -- [ ] **Step 7: Compile** - -Run: `al compile` for the E-Document app. -Expected: Clean compilation, no errors. - -- [ ] **Step 8: Commit** - -```bash -git add src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotificationType.Enum.al -git add src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotification.Codeunit.al -git commit -m "refactor: remove VAT Rate Mismatch notification infrastructure - -Notification replaced by persisted [BC] VAT Rate Mismatch field -and inline warning column on draft subform." -``` - ---- - -### Task 5: Update existing tests to assert mismatch flag instead of notification - -**Files:** -- Modify: `src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al:297-415` - -- [ ] **Step 1: Update the successful resolution test** - -In `PreparingPurchaseDraftResolvesVATProductPostingGroupFromLineVATRate` (line 298), after the existing assertion at line 351: - -```al - Assert.AreEqual(VATProductPostingGroup.Code, EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'The VAT Prod. Posting Group should be resolved from the matching VAT Posting Setup.'); -``` - -Add: - -```al - Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be false when resolution succeeds.'); -``` - -- [ ] **Step 2: Rewrite the mismatch test** - -Replace the entire `PreparingPurchaseDraftCreatesNotificationWhenNoMatchingVATSetup` procedure (lines 362–415) with: - -```al - [Test] - procedure PreparingPurchaseDraftSetsVATRateMismatchWhenNoMatchingVATSetup() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - TempEDocImportParameters: Record "E-Doc. Import Parameters"; - Vendor2: Record Vendor; - CompanyInformation: Record "Company Information"; - EDocumentProcessing: Codeunit "E-Document Processing"; - EDocImport: Codeunit "E-Doc. Import"; - begin - // [SCENARIO] When a draft line has a VAT Rate but no matching VAT Posting Setup exists, Prepare Draft leaves the field blank and sets the mismatch flag - Initialize(Enum::"Service Integration"::"Mock"); - LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); - - // [GIVEN] A vendor with a known VAT Bus. Posting Group - CompanyInformation.GetRecordOnce(); - Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; - Vendor2."No." := 'EDOC001'; - Vendor2."VAT Registration No." := 'XXXXXXX001'; - Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; - Vendor2.Insert(); - - // [GIVEN] E-Document purchase header and line with VAT Rate = 99 (no matching setup) - EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseHeader."Vendor VAT Id" := Vendor2."VAT Registration No."; - EDocumentPurchaseHeader.Insert(); - EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseLine.Description := 'Test VAT mismatch'; - EDocumentPurchaseLine."VAT Rate" := 99; - EDocumentPurchaseLine.Insert(); - - // [WHEN] Prepare Draft is run - EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Ready for draft"); - TempEDocImportParameters."Step to Run" := "Import E-Document Steps"::"Prepare draft"; - EDocImport.ProcessIncomingEDocument(EDocument, TempEDocImportParameters); - - // [THEN] The VAT Prod. Posting Group is blank and mismatch flag is set - EDocumentPurchaseLine.SetRecFilter(); - EDocumentPurchaseLine.FindFirst(); - Assert.AreEqual('', EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'The VAT Prod. Posting Group should be blank when no matching VAT Posting Setup exists.'); - Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be true when resolution fails.'); - - // Cleanup - Vendor2.SetRecFilter(); - Vendor2.Delete(); - end; -``` - -- [ ] **Step 3: Remove unused `using` if needed** - -Check whether `"E-Document Notification Type"` enum is still referenced in the test file. If the only usage was in the deleted test, the `using` for it can be removed. The `using Microsoft.Finance.VAT.Setup;` at line 17 is still needed. No changes expected here. - -- [ ] **Step 4: Compile and run tests** - -Run: `al compile` and `al run_tests` for the E-Document test app. -Expected: Both updated tests pass. - -- [ ] **Step 5: Commit** - -```bash -git add src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al -git commit -m "test: update VAT resolution tests to assert mismatch flag instead of notification - -Rename and rewrite the mismatch test to assert [BC] VAT Rate Mismatch -boolean. Add mismatch=false assertion to the successful resolution test." -``` - ---- - -### Task 6: Add tests for VAT Calculation Type filtering in Prepare Draft - -**Files:** -- Modify: `src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al` - -These tests validate that `FindVATProductPostingGroup` only matches Normal VAT and Reverse Charge VAT setups. - -- [ ] **Step 1: Add `PreparingDraftIgnoresFullVATSetupWhenResolvingPostingGroup`** - -Insert after the `PreparingPurchaseDraftSetsVATRateMismatchWhenNoMatchingVATSetup` procedure: - -```al - [Test] - procedure PreparingDraftIgnoresFullVATSetupWhenResolvingPostingGroup() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - TempEDocImportParameters: Record "E-Doc. Import Parameters"; - Vendor2: Record Vendor; - CompanyInformation: Record "Company Information"; - VATPostingSetup2: Record "VAT Posting Setup"; - VATProductPostingGroup: Record "VAT Product Posting Group"; - EDocumentProcessing: Codeunit "E-Document Processing"; - EDocImport: Codeunit "E-Doc. Import"; - LibraryERM: Codeunit "Library - ERM"; - begin - // [SCENARIO] Full VAT setups must not be matched during VAT Posting Group resolution - Initialize(Enum::"Service Integration"::"Mock"); - LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); - - // [GIVEN] A vendor - CompanyInformation.GetRecordOnce(); - Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; - Vendor2."No." := 'EDOC001'; - Vendor2."VAT Registration No." := 'XXXXXXX001'; - Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; - Vendor2.Insert(); - - // [GIVEN] A Full VAT Posting Setup with VAT % = 10 - LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); - VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; - VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; - VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Full VAT"; - VATPostingSetup2."VAT %" := 10; - VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2.Insert(); - - // [GIVEN] E-Document line with VAT Rate = 10 - EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseHeader."Vendor VAT Id" := Vendor2."VAT Registration No."; - EDocumentPurchaseHeader.Insert(); - EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseLine.Description := 'Test Full VAT ignored'; - EDocumentPurchaseLine."VAT Rate" := 10; - EDocumentPurchaseLine.Insert(); - - // [WHEN] Prepare Draft is run - EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Ready for draft"); - TempEDocImportParameters."Step to Run" := "Import E-Document Steps"::"Prepare draft"; - EDocImport.ProcessIncomingEDocument(EDocument, TempEDocImportParameters); - - // [THEN] Full VAT setup is not matched - EDocumentPurchaseLine.SetRecFilter(); - EDocumentPurchaseLine.FindFirst(); - Assert.AreEqual('', EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'Full VAT setups must not be matched.'); - Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be true when only Full VAT setups exist.'); - - // Cleanup - Vendor2.SetRecFilter(); - Vendor2.Delete(); - VATPostingSetup2.SetRecFilter(); - VATPostingSetup2.Delete(); - VATProductPostingGroup.SetRecFilter(); - VATProductPostingGroup.Delete(); - end; -``` - -- [ ] **Step 2: Add `PreparingDraftIgnoresSalesTaxSetupWhenResolvingPostingGroup`** - -```al - [Test] - procedure PreparingDraftIgnoresSalesTaxSetupWhenResolvingPostingGroup() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - TempEDocImportParameters: Record "E-Doc. Import Parameters"; - Vendor2: Record Vendor; - CompanyInformation: Record "Company Information"; - VATPostingSetup2: Record "VAT Posting Setup"; - VATProductPostingGroup: Record "VAT Product Posting Group"; - EDocumentProcessing: Codeunit "E-Document Processing"; - EDocImport: Codeunit "E-Doc. Import"; - LibraryERM: Codeunit "Library - ERM"; - begin - // [SCENARIO] Sales Tax setups must not be matched during VAT Posting Group resolution - Initialize(Enum::"Service Integration"::"Mock"); - LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); - - // [GIVEN] A vendor - CompanyInformation.GetRecordOnce(); - Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; - Vendor2."No." := 'EDOC001'; - Vendor2."VAT Registration No." := 'XXXXXXX001'; - Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; - Vendor2.Insert(); - - // [GIVEN] A Sales Tax Posting Setup with VAT % = 10 - LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); - VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; - VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; - VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Sales Tax"; - VATPostingSetup2."VAT %" := 10; - VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2.Insert(); - - // [GIVEN] E-Document line with VAT Rate = 10 - EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseHeader."Vendor VAT Id" := Vendor2."VAT Registration No."; - EDocumentPurchaseHeader.Insert(); - EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseLine.Description := 'Test Sales Tax ignored'; - EDocumentPurchaseLine."VAT Rate" := 10; - EDocumentPurchaseLine.Insert(); - - // [WHEN] Prepare Draft is run - EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Ready for draft"); - TempEDocImportParameters."Step to Run" := "Import E-Document Steps"::"Prepare draft"; - EDocImport.ProcessIncomingEDocument(EDocument, TempEDocImportParameters); - - // [THEN] Sales Tax setup is not matched - EDocumentPurchaseLine.SetRecFilter(); - EDocumentPurchaseLine.FindFirst(); - Assert.AreEqual('', EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'Sales Tax setups must not be matched.'); - Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be true when only Sales Tax setups exist.'); - - // Cleanup - Vendor2.SetRecFilter(); - Vendor2.Delete(); - VATPostingSetup2.SetRecFilter(); - VATPostingSetup2.Delete(); - VATProductPostingGroup.SetRecFilter(); - VATProductPostingGroup.Delete(); - end; -``` - -- [ ] **Step 3: Add `PreparingDraftResolvesReverseChargeVATPostingGroup`** - -```al - [Test] - procedure PreparingDraftResolvesReverseChargeVATPostingGroup() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - TempEDocImportParameters: Record "E-Doc. Import Parameters"; - Vendor2: Record Vendor; - CompanyInformation: Record "Company Information"; - VATPostingSetup2: Record "VAT Posting Setup"; - VATProductPostingGroup: Record "VAT Product Posting Group"; - EDocumentProcessing: Codeunit "E-Document Processing"; - EDocImport: Codeunit "E-Doc. Import"; - LibraryERM: Codeunit "Library - ERM"; - begin - // [SCENARIO] Reverse Charge VAT setups should be matched during VAT Posting Group resolution - Initialize(Enum::"Service Integration"::"Mock"); - LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); - - // [GIVEN] A vendor - CompanyInformation.GetRecordOnce(); - Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; - Vendor2."No." := 'EDOC001'; - Vendor2."VAT Registration No." := 'XXXXXXX001'; - Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; - Vendor2.Insert(); - - // [GIVEN] A Reverse Charge VAT Posting Setup with VAT % = 20 - LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); - VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; - VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; - VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Reverse Charge VAT"; - VATPostingSetup2."VAT %" := 20; - VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2."Reverse Chrg. VAT Acc." := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2.Insert(); - - // [GIVEN] E-Document line with VAT Rate = 20 - EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseHeader."Vendor VAT Id" := Vendor2."VAT Registration No."; - EDocumentPurchaseHeader.Insert(); - EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseLine.Description := 'Test Reverse Charge resolved'; - EDocumentPurchaseLine."VAT Rate" := 20; - EDocumentPurchaseLine.Insert(); - - // [WHEN] Prepare Draft is run - EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Ready for draft"); - TempEDocImportParameters."Step to Run" := "Import E-Document Steps"::"Prepare draft"; - EDocImport.ProcessIncomingEDocument(EDocument, TempEDocImportParameters); - - // [THEN] Reverse Charge VAT setup is matched - EDocumentPurchaseLine.SetRecFilter(); - EDocumentPurchaseLine.FindFirst(); - Assert.AreEqual(VATProductPostingGroup.Code, EDocumentPurchaseLine."[BC] VAT Prod. Posting Group", 'Reverse Charge VAT setups should be matched.'); - Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'VAT Rate Mismatch should be false when Reverse Charge VAT matches.'); - - // Cleanup - Vendor2.SetRecFilter(); - Vendor2.Delete(); - VATPostingSetup2.SetRecFilter(); - VATPostingSetup2.Delete(); - VATProductPostingGroup.SetRecFilter(); - VATProductPostingGroup.Delete(); - end; -``` - -- [ ] **Step 4: Compile and run tests** - -Run: `al compile` and `al run_tests` for the E-Document test app. -Expected: All three new tests pass. - -- [ ] **Step 5: Commit** - -```bash -git add src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al -git commit -m "test: add tests for VAT Calculation Type filtering in Prepare Draft - -Full VAT and Sales Tax setups are excluded from resolution. -Reverse Charge VAT setups are matched successfully." -``` - ---- - -### Task 7: Add tests for OnValidate mismatch re-evaluation - -**Files:** -- Modify: `src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al` - -These tests validate the `OnValidate` trigger on field 110 `[BC] VAT Prod. Posting Group`. They operate directly on the `E-Document Purchase Line` record without running the full Prepare Draft pipeline. - -- [ ] **Step 1: Add `ValidatingVATProdPostingGroupClearsMismatchWhenRateMatches`** - -```al - [Test] - procedure ValidatingVATProdPostingGroupClearsMismatchWhenRateMatches() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - Vendor2: Record Vendor; - CompanyInformation: Record "Company Information"; - VATPostingSetup2: Record "VAT Posting Setup"; - VATProductPostingGroup: Record "VAT Product Posting Group"; - LibraryERM: Codeunit "Library - ERM"; - begin - // [SCENARIO] OnValidate clears mismatch when selected posting group's VAT % matches the line's VAT Rate - Initialize(Enum::"Service Integration"::"Mock"); - LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); - - // [GIVEN] A vendor - CompanyInformation.GetRecordOnce(); - Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; - Vendor2."No." := 'EDOC001'; - Vendor2."VAT Registration No." := 'XXXXXXX001'; - Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; - Vendor2.Insert(); - - // [GIVEN] A Normal VAT setup with VAT % = 20 - LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); - VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; - VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; - VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Normal VAT"; - VATPostingSetup2."VAT %" := 20; - VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2.Insert(); - - // [GIVEN] A line with VAT Rate = 20 and mismatch = true - EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseHeader."[BC] Vendor No." := Vendor2."No."; - EDocumentPurchaseHeader.Insert(); - EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseLine."VAT Rate" := 20; - EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := true; - EDocumentPurchaseLine.Insert(); - - // [WHEN] User validates the posting group to the matching setup - EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", VATProductPostingGroup.Code); - EDocumentPurchaseLine.Modify(); - - // [THEN] Mismatch is cleared - Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should be false when VAT % matches VAT Rate.'); - - // Cleanup - Vendor2.SetRecFilter(); - Vendor2.Delete(); - VATPostingSetup2.SetRecFilter(); - VATPostingSetup2.Delete(); - VATProductPostingGroup.SetRecFilter(); - VATProductPostingGroup.Delete(); - end; -``` - -- [ ] **Step 2: Add `ValidatingVATProdPostingGroupKeepsMismatchWhenRateDiffers`** - -```al - [Test] - procedure ValidatingVATProdPostingGroupKeepsMismatchWhenRateDiffers() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - Vendor2: Record Vendor; - CompanyInformation: Record "Company Information"; - VATPostingSetup2: Record "VAT Posting Setup"; - VATProductPostingGroup: Record "VAT Product Posting Group"; - LibraryERM: Codeunit "Library - ERM"; - begin - // [SCENARIO] OnValidate keeps mismatch when selected posting group's VAT % differs from VAT Rate - Initialize(Enum::"Service Integration"::"Mock"); - LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); - - // [GIVEN] A vendor - CompanyInformation.GetRecordOnce(); - Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; - Vendor2."No." := 'EDOC001'; - Vendor2."VAT Registration No." := 'XXXXXXX001'; - Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; - Vendor2.Insert(); - - // [GIVEN] A Normal VAT setup with VAT % = 10 (different from line's 20) - LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); - VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; - VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; - VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Normal VAT"; - VATPostingSetup2."VAT %" := 10; - VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2.Insert(); - - // [GIVEN] A line with VAT Rate = 20 and mismatch = true - EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseHeader."[BC] Vendor No." := Vendor2."No."; - EDocumentPurchaseHeader.Insert(); - EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseLine."VAT Rate" := 20; - EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := true; - EDocumentPurchaseLine.Insert(); - - // [WHEN] User validates the posting group to a non-matching setup - EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", VATProductPostingGroup.Code); - EDocumentPurchaseLine.Modify(); - - // [THEN] Mismatch remains true - Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should remain true when VAT % does not match VAT Rate.'); - - // Cleanup - Vendor2.SetRecFilter(); - Vendor2.Delete(); - VATPostingSetup2.SetRecFilter(); - VATPostingSetup2.Delete(); - VATProductPostingGroup.SetRecFilter(); - VATProductPostingGroup.Delete(); - end; -``` - -- [ ] **Step 3: Add `ValidatingVATProdPostingGroupSetsMismatchWhenCleared`** - -```al - [Test] - procedure ValidatingVATProdPostingGroupSetsMismatchWhenCleared() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - begin - // [SCENARIO] OnValidate sets mismatch when posting group is cleared - Initialize(Enum::"Service Integration"::"Mock"); - LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); - - // [GIVEN] A line with VAT Rate = 20, a posting group, and no mismatch - EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseHeader.Insert(); - EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseLine."VAT Rate" := 20; - EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" := 'STANDARD'; - EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := false; - EDocumentPurchaseLine.Insert(); - - // [WHEN] User clears the posting group - EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", ''); - EDocumentPurchaseLine.Modify(); - - // [THEN] Mismatch is set - Assert.IsTrue(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should be true when posting group is cleared.'); - end; -``` - -- [ ] **Step 4: Add `ValidatingVATProdPostingGroupSkipsMismatchForFullVAT`** - -```al - [Test] - procedure ValidatingVATProdPostingGroupSkipsMismatchForFullVAT() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - Vendor2: Record Vendor; - CompanyInformation: Record "Company Information"; - VATPostingSetup2: Record "VAT Posting Setup"; - VATProductPostingGroup: Record "VAT Product Posting Group"; - LibraryERM: Codeunit "Library - ERM"; - begin - // [SCENARIO] OnValidate skips mismatch evaluation for Full VAT — flag stays unchanged - Initialize(Enum::"Service Integration"::"Mock"); - LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); - - // [GIVEN] A vendor - CompanyInformation.GetRecordOnce(); - Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; - Vendor2."No." := 'EDOC001'; - Vendor2."VAT Registration No." := 'XXXXXXX001'; - Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; - Vendor2.Insert(); - - // [GIVEN] A Full VAT setup with VAT % = 0 - LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); - VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; - VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; - VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Full VAT"; - VATPostingSetup2."VAT %" := 0; - VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2.Insert(); - - // [GIVEN] A line with VAT Rate = 5 and mismatch = false - EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseHeader."[BC] Vendor No." := Vendor2."No."; - EDocumentPurchaseHeader.Insert(); - EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseLine."VAT Rate" := 5; - EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := false; - EDocumentPurchaseLine.Insert(); - - // [WHEN] User validates the posting group to the Full VAT setup - EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", VATProductPostingGroup.Code); - EDocumentPurchaseLine.Modify(); - - // [THEN] Mismatch flag is unchanged (still false) — Full VAT skips comparison - Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should remain unchanged for Full VAT calculation type.'); - - // Cleanup - Vendor2.SetRecFilter(); - Vendor2.Delete(); - VATPostingSetup2.SetRecFilter(); - VATPostingSetup2.Delete(); - VATProductPostingGroup.SetRecFilter(); - VATProductPostingGroup.Delete(); - end; -``` - -- [ ] **Step 5: Add `ValidatingVATProdPostingGroupMatchesZeroRate`** - -```al - [Test] - procedure ValidatingVATProdPostingGroupMatchesZeroRate() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - Vendor2: Record Vendor; - CompanyInformation: Record "Company Information"; - VATPostingSetup2: Record "VAT Posting Setup"; - VATProductPostingGroup: Record "VAT Product Posting Group"; - LibraryERM: Codeunit "Library - ERM"; - begin - // [SCENARIO] OnValidate clears mismatch when both VAT Rate and VAT % are 0 - Initialize(Enum::"Service Integration"::"Mock"); - LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); - - // [GIVEN] A vendor - CompanyInformation.GetRecordOnce(); - Vendor2."Country/Region Code" := CompanyInformation."Country/Region Code"; - Vendor2."No." := 'EDOC001'; - Vendor2."VAT Registration No." := 'XXXXXXX001'; - Vendor2."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group"; - Vendor2.Insert(); - - // [GIVEN] A Normal VAT setup with VAT % = 0 (zero-rated) - LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); - VATPostingSetup2."VAT Bus. Posting Group" := Vendor2."VAT Bus. Posting Group"; - VATPostingSetup2."VAT Prod. Posting Group" := VATProductPostingGroup.Code; - VATPostingSetup2."VAT Calculation Type" := VATPostingSetup2."VAT Calculation Type"::"Normal VAT"; - VATPostingSetup2."VAT %" := 0; - VATPostingSetup2."Sales VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2."Purchase VAT Account" := LibraryERM.CreateGLAccountNo(); - VATPostingSetup2.Insert(); - - // [GIVEN] A line with VAT Rate = 0 - EDocumentPurchaseHeader."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseHeader."[BC] Vendor No." := Vendor2."No."; - EDocumentPurchaseHeader.Insert(); - EDocumentPurchaseLine."E-Document Entry No." := EDocument."Entry No"; - EDocumentPurchaseLine."VAT Rate" := 0; - EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := true; - EDocumentPurchaseLine.Insert(); - - // [WHEN] User validates the posting group to the zero-rated setup - EDocumentPurchaseLine.Validate("[BC] VAT Prod. Posting Group", VATProductPostingGroup.Code); - EDocumentPurchaseLine.Modify(); - - // [THEN] Mismatch is cleared — both rates are 0 - Assert.IsFalse(EDocumentPurchaseLine."[BC] VAT Rate Mismatch", 'Mismatch should be false when both VAT Rate and VAT % are 0.'); - - // Cleanup - Vendor2.SetRecFilter(); - Vendor2.Delete(); - VATPostingSetup2.SetRecFilter(); - VATPostingSetup2.Delete(); - VATProductPostingGroup.SetRecFilter(); - VATProductPostingGroup.Delete(); - end; -``` - -- [ ] **Step 6: Compile and run all tests** - -Run: `al compile` and `al run_tests` for the E-Document test app. -Expected: All tests pass — both the updated existing tests and all 8 new tests. - -- [ ] **Step 7: Commit** - -```bash -git add src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al -git commit -m "test: add OnValidate mismatch re-evaluation tests - -Covers: rate match clears mismatch, rate mismatch persists, -clearing group sets mismatch, Full VAT skips comparison, -zero-rate matching works." -``` From f3f27b996fd25da7b13c4de5dccf297648b0d64a Mon Sep 17 00:00:00 2001 From: ventselartur Date: Tue, 14 Apr 2026 14:08:17 +0200 Subject: [PATCH 30/53] add total line to the draft --- .../PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al | 5 ++++- .../Import/Purchase/EDocPurchaseDraftSubform.Page.al | 2 ++ .../Import/Purchase/EDocumentPurchaseDraft.Page.al | 5 +++++ .../Import/Purchase/EDocumentPurchaseHeader.Table.al | 8 ++++++++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al index e232bf6f5a..d1296c8396 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al @@ -81,13 +81,16 @@ codeunit 6125 "Prepare Purchase E-Doc. Draft" implements IProcessStructuredData CopilotLineMatching(EDocument."Entry No"); end; - // Log telemetry and activity sessions Clear(EDocumentPurchaseLine); EDocumentPurchaseLine.SetRange("E-Document Entry No.", EDocument."Entry No"); if EDocumentPurchaseLine.FindSet() then repeat + // Update total line amount on the header + EDocumentPurchaseHeader."Total Line Amount" := Round(EDocumentPurchaseLine.Quantity * EDocumentPurchaseLine."Unit Price" - EDocumentPurchaseLine."Total Discount"); + // Log telemetry and activity sessions EDocImpSessionTelemetry.SetLine(EDocumentPurchaseLine.SystemId); until EDocumentPurchaseLine.Next() = 0; + EDocumentPurchaseHeader.Modify(); // Log all accumulated activity session changes at the end LogAllActivitySessionChanges(EDocActivityLogSession); diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al index 379c22e5e6..2f2f3b105b 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al @@ -424,10 +424,12 @@ page 6183 "E-Doc. Purchase Draft Subform" if not EDocumentPurchaseHeader.Get(Rec."E-Document Entry No.") then exit; EDocumentPurchaseHeader."Sub Total" := 0; + EDocumentPurchaseHeader."Total Line Amount" := 0; TotalEDocPurchaseLine.SetRange("E-Document Entry No.", Rec."E-Document Entry No."); if TotalEDocPurchaseLine.FindSet() then repeat EDocumentPurchaseHeader."Sub Total" += Round(TotalEDocPurchaseLine.Quantity * TotalEDocPurchaseLine."Unit Price", EDocumentImportHelper.GetCurrencyRoundingPrecision(EDocumentPurchaseHeader."Currency Code")) - TotalEDocPurchaseLine."Total Discount"; + EDocumentPurchaseHeader."Total Line Amount" += LineAmount; until TotalEDocPurchaseLine.Next() = 0; EDocumentPurchaseHeader.Total := EDocumentPurchaseHeader."Sub Total" + EDocumentPurchaseHeader."Total VAT" - EDocumentPurchaseHeader."Total Discount"; EDocumentPurchaseHeader.Modify(); diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al index 4b6f7f7f26..215f9c2bee 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al @@ -180,6 +180,11 @@ page 6181 "E-Document Purchase Draft" group("E-Document Details") { ShowCaption = false; + field("Total Line Amount"; EDocumentPurchaseHeader."Total Line Amount") + { + ToolTip = 'Specifies the total of the lines amount values from all lines of the draft.'; + Importance = Promoted; + } field("Amount Excl. VAT"; EDocumentPurchaseHeader."Sub Total") { Caption = 'Amount Excl. VAT'; diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseHeader.Table.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseHeader.Table.al index 335ddc1bae..bdacd4bbba 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseHeader.Table.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseHeader.Table.al @@ -224,6 +224,14 @@ table 6100 "E-Document Purchase Header" Caption = 'Posting Description'; DataClassification = CustomerContent; } + field(39; "Total Line Amount"; Decimal) + { + Caption = 'Total Line Amount'; + DataClassification = CustomerContent; + AutoFormatType = 1; + AutoFormatExpression = Rec."Currency Code"; + Editable = false; + } #endregion Purchase fields #region Business Central Data - Validated fields [101-200] From 8f1d64e2abf6cf76cde3416b0e1e39384f509e25 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Tue, 14 Apr 2026 14:20:39 +0200 Subject: [PATCH 31/53] resolve conflicts --- .../EDocPreparePurchDraft.Codeunit.al | 63 +++++++++++++++++++ .../PreparePurchaseEDocDraft.Codeunit.al | 56 ----------------- 2 files changed, 63 insertions(+), 56 deletions(-) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al index 3f7abb5aea..47a025c588 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al @@ -8,6 +8,7 @@ using Microsoft.eServices.EDocument; using Microsoft.eServices.EDocument.Processing.AI; using Microsoft.eServices.EDocument.Processing.Import.Purchase; using Microsoft.eServices.EDocument.Processing.Interfaces; +using Microsoft.Finance.VAT.Setup; using Microsoft.Foundation.UOM; using Microsoft.Purchases.Document; using Microsoft.Purchases.Vendor; @@ -72,6 +73,9 @@ codeunit 6406 "EDoc Prepare Purch. Draft" EDocumentPurchaseLine.Modify(); until EDocumentPurchaseLine.Next() = 0; + // Resolve VAT Product Posting Groups from extracted VAT rates + ResolveVATProductPostingGroups(EDocument."Entry No", EDocumentPurchaseHeader); + CopilotLineMatching(EDocument."Entry No"); end; @@ -79,8 +83,12 @@ codeunit 6406 "EDoc Prepare Purch. Draft" EDocumentPurchaseLine.SetRange("E-Document Entry No.", EDocument."Entry No"); if EDocumentPurchaseLine.FindSet() then repeat + // Update total line amount on the header + EDocumentPurchaseHeader."Total Line Amount" := Round(EDocumentPurchaseLine.Quantity * EDocumentPurchaseLine."Unit Price" - EDocumentPurchaseLine."Total Discount"); + // Log telemetry and activity sessions EDocImpSessionTelemetry.SetLine(EDocumentPurchaseLine.SystemId); until EDocumentPurchaseLine.Next() = 0; + EDocumentPurchaseHeader.Modify(); LogAllActivitySessionChanges(EDocActivityLogSession); @@ -138,6 +146,61 @@ codeunit 6406 "EDoc Prepare Purch. Draft" ActivityLog.Log(); end; + local procedure ResolveVATProductPostingGroups(EDocumentEntryNo: Integer; EDocumentPurchaseHeader: Record "E-Document Purchase Header") + var + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + Vendor: Record Vendor; + VATRate: Decimal; + LineCount: Integer; + begin + if EDocumentPurchaseHeader."[BC] Vendor No." = '' then + exit; + if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then + exit; + if Vendor."VAT Bus. Posting Group" = '' then + exit; + + EDocumentPurchaseLine.SetRange("E-Document Entry No.", EDocumentEntryNo); + LineCount := EDocumentPurchaseLine.Count(); + if LineCount = 0 then + exit; + + if EDocumentPurchaseLine.FindSet() then + repeat + VATRate := EDocumentPurchaseLine."VAT Rate"; + + // Single-line fallback: compute from header Total VAT + if (VATRate = 0) and (LineCount = 1) and + (EDocumentPurchaseHeader."Total VAT" > 0) and (EDocumentPurchaseHeader."Sub Total" > 0) + then + VATRate := Round((EDocumentPurchaseHeader."Total VAT" / EDocumentPurchaseHeader."Sub Total") * 100, 0.01); + + if VATRate > 0 then begin + EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" := + FindVATProductPostingGroup(Vendor."VAT Bus. Posting Group", VATRate); + EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := + EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" = ''; + EDocumentPurchaseLine.Modify(); + end; + until EDocumentPurchaseLine.Next() = 0; + end; + + local procedure FindVATProductPostingGroup(VATBusPostingGroup: Code[20]; VATRate: Decimal): Code[20] + var + VATPostingSetup: Record "VAT Posting Setup"; + begin + VATPostingSetup.SetRange("VAT Bus. Posting Group", VATBusPostingGroup); + VATPostingSetup.SetFilter("VAT Calculation Type", '%1|%2', + VATPostingSetup."VAT Calculation Type"::"Normal VAT", + VATPostingSetup."VAT Calculation Type"::"Reverse Charge VAT"); + VATPostingSetup.SetRange("VAT %", VATRate); + if VATPostingSetup.Count() = 1 then begin + VATPostingSetup.FindFirst(); + exit(VATPostingSetup."VAT Prod. Posting Group"); + end; + exit(''); + end; + local procedure CopilotLineMatching(EDocumentEntryNo: Integer) var EDocumentPurchaseLine: Record "E-Document Purchase Line"; diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al index b39a59b0c7..ecf0c4a8b6 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al @@ -6,7 +6,6 @@ namespace Microsoft.eServices.EDocument.Processing.Import; using Microsoft.eServices.EDocument; using Microsoft.eServices.EDocument.Processing.Interfaces; -using Microsoft.Finance.VAT.Setup; using Microsoft.Purchases.Vendor; codeunit 6125 "Prepare Purchase E-Doc. Draft" implements IProcessStructuredData @@ -36,59 +35,4 @@ codeunit 6125 "Prepare Purchase E-Doc. Draft" implements IProcessStructuredData begin Vendor := PrepareDraftHelper.GetVendor(EDocument, Customizations); end; - - local procedure ResolveVATProductPostingGroups(EDocumentEntryNo: Integer; EDocumentPurchaseHeader: Record "E-Document Purchase Header") - var - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - Vendor: Record Vendor; - VATRate: Decimal; - LineCount: Integer; - begin - if EDocumentPurchaseHeader."[BC] Vendor No." = '' then - exit; - if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then - exit; - if Vendor."VAT Bus. Posting Group" = '' then - exit; - - EDocumentPurchaseLine.SetRange("E-Document Entry No.", EDocumentEntryNo); - LineCount := EDocumentPurchaseLine.Count(); - if LineCount = 0 then - exit; - - if EDocumentPurchaseLine.FindSet() then - repeat - VATRate := EDocumentPurchaseLine."VAT Rate"; - - // Single-line fallback: compute from header Total VAT - if (VATRate = 0) and (LineCount = 1) and - (EDocumentPurchaseHeader."Total VAT" > 0) and (EDocumentPurchaseHeader."Sub Total" > 0) - then - VATRate := Round((EDocumentPurchaseHeader."Total VAT" / EDocumentPurchaseHeader."Sub Total") * 100, 0.01); - - if VATRate > 0 then begin - EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" := - FindVATProductPostingGroup(Vendor."VAT Bus. Posting Group", VATRate); - EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := - EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" = ''; - EDocumentPurchaseLine.Modify(); - end; - until EDocumentPurchaseLine.Next() = 0; - end; - - local procedure FindVATProductPostingGroup(VATBusPostingGroup: Code[20]; VATRate: Decimal): Code[20] - var - VATPostingSetup: Record "VAT Posting Setup"; - begin - VATPostingSetup.SetRange("VAT Bus. Posting Group", VATBusPostingGroup); - VATPostingSetup.SetFilter("VAT Calculation Type", '%1|%2', - VATPostingSetup."VAT Calculation Type"::"Normal VAT", - VATPostingSetup."VAT Calculation Type"::"Reverse Charge VAT"); - VATPostingSetup.SetRange("VAT %", VATRate); - if VATPostingSetup.Count() = 1 then begin - VATPostingSetup.FindFirst(); - exit(VATPostingSetup."VAT Prod. Posting Group"); - end; - exit(''); - end; } \ No newline at end of file From bc5d54d828f19ee05c93834562aa5e528bd6cb25 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Tue, 14 Apr 2026 14:26:49 +0200 Subject: [PATCH 32/53] fix EDocPurchVATTests.Codeunit.al --- .../Test/src/Processing/EDocPurchVATTests.Codeunit.al | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Apps/W1/EDocument/Test/src/Processing/EDocPurchVATTests.Codeunit.al b/src/Apps/W1/EDocument/Test/src/Processing/EDocPurchVATTests.Codeunit.al index eb6dba9046..b12ec43fec 100644 --- a/src/Apps/W1/EDocument/Test/src/Processing/EDocPurchVATTests.Codeunit.al +++ b/src/Apps/W1/EDocument/Test/src/Processing/EDocPurchVATTests.Codeunit.al @@ -6,7 +6,6 @@ namespace Microsoft.eServices.EDocument.Test; using Microsoft.eServices.EDocument; using Microsoft.eServices.EDocument.Integration; -using Microsoft.eServices.EDocument.Processing; using Microsoft.eServices.EDocument.Processing.Import; using Microsoft.eServices.EDocument.Processing.Import.Purchase; using Microsoft.Finance.Currency; @@ -20,7 +19,7 @@ using Microsoft.Sales.Customer; using System.IO; using System.TestLibraries.Utilities; -codeunit 139896 "E-Doc Purch. VAT Tests" +codeunit 139897 "E-Doc Purch. VAT Tests" { Subtype = Test; TestType = IntegrationTest; From ad40024f77c151a69960d9bbbe4dca0694107749 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Tue, 21 Apr 2026 17:44:47 +0200 Subject: [PATCH 33/53] make total line amount not editable --- .../Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al index 9da7f430c9..bcc6f81126 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al @@ -204,6 +204,7 @@ page 6181 "E-Document Purchase Draft" { ToolTip = 'Specifies the total of the lines amount values from all lines of the draft.'; Importance = Promoted; + Editable = false; } field("Amount Excl. VAT"; EDocumentPurchaseHeader."Sub Total") { @@ -249,6 +250,7 @@ page 6181 "E-Document Purchase Draft" Caption = 'Amount Incl. VAT'; ToolTip = 'Specifies the total amount of the electronic document including VAT.'; Importance = Promoted; + Style = Strong; trigger OnValidate() begin From 3caf30fb0c5c85dbd997db036985d32c78133162 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Fri, 8 May 2026 12:49:46 +0200 Subject: [PATCH 34/53] [E-Document] Substitute Standard Purchase Order with Omit Last Line and VAT Amount Difference options - Add report 6199 "E-Doc Standard Purchase Order" copied from Base Application report 1322 with two new request page options: * Omit last line: skips the last Purchase Line in the printout * VAT Amount Difference: decreases both the total VAT amount and the total amount including VAT in the footer by 0.01 - Add codeunit 6405 "E-Doc Reporting Triggers" subscribing to Reporting Triggers::SubstituteReport to route the standard report to the new one - Bundle the StandardPurchaseOrder.docx Word layout under .resources/Template/Purchases/ - Wire the new objects into the E-Doc. Core - Objects permission set Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Purchases/EDocStandardPurchaseOrder.docx | Bin 0 -> 60895 bytes .../EDocCoreObjects.PermissionSet.al | 5 +- .../EDocReportingTriggers.Codeunit.al | 18 + .../EDocStandardPurchaseOrder.Report.al | 1284 +++++++++++++++++ 4 files changed, 1306 insertions(+), 1 deletion(-) create mode 100644 src/Apps/W1/EDocument/App/.resources/Template/Purchases/EDocStandardPurchaseOrder.docx create mode 100644 src/Apps/W1/EDocument/App/src/Document/EDocReportingTriggers.Codeunit.al create mode 100644 src/Apps/W1/EDocument/App/src/Document/EDocStandardPurchaseOrder.Report.al diff --git a/src/Apps/W1/EDocument/App/.resources/Template/Purchases/EDocStandardPurchaseOrder.docx b/src/Apps/W1/EDocument/App/.resources/Template/Purchases/EDocStandardPurchaseOrder.docx new file mode 100644 index 0000000000000000000000000000000000000000..1296cc2d33b104e58c7f4b1555815a55a0f754aa GIT binary patch literal 60895 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%0D*9=_T*woIH1GIaQEN%@9TJnz%{-(^$MUV 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 '' then + ItemNo := "Vendor Item No."; + + if "Item Reference No." <> '' then + ItemNo := "Item Reference No."; + + FormatDocument.SetPurchaseLine("Purchase Line", FormattedQuanitity, FormattedDirectUnitCost, FormattedVATPct, FormattedLineAmount); + + if FirstLineHasBeenOutput then + Clear(DummyCompanyInfo.Picture); + FirstLineHasBeenOutput := true; + end; + + trigger OnPreDataItem() + var + LastPurchaseLine: Record "Purchase Line"; + begin + FirstLineHasBeenOutput := false; + DummyCompanyInfo.Picture := CompanyInfo.Picture; + + LastPurchaseLineNo := 0; + if OmitLastLine then begin + LastPurchaseLine.SetRange("Document Type", "Purchase Header"."Document Type"); + LastPurchaseLine.SetRange("Document No.", "Purchase Header"."No."); + if LastPurchaseLine.FindLast() then + LastPurchaseLineNo := LastPurchaseLine."Line No."; + end; + end; + } + dataitem(Totals; "Integer") + { + DataItemTableView = sorting(Number) where(Number = const(1)); + column(VATAmountText; TempVATAmountLine.VATAmountText()) + { + } + column(TotalVATAmount; VATAmount) + { + AutoFormatExpression = "Purchase Header"."Currency Code"; + AutoFormatType = 1; + } + column(TotalVATDiscountAmount; -VATDiscountAmount) + { + AutoFormatExpression = "Purchase Header"."Currency Code"; + AutoFormatType = 1; + } + column(TotalVATBaseAmount; VATBaseAmount) + { + AutoFormatExpression = "Purchase Header"."Currency Code"; + AutoFormatType = 1; + } + column(TotalAmountInclVAT; TotalAmountInclVAT) + { + AutoFormatExpression = "Purchase Header"."Currency Code"; + AutoFormatType = 1; + } + column(TotalInclVATText; TotalInclVATText) + { + } + column(TotalExclVATText; TotalExclVATText) + { + } + column(TotalSubTotal; TotalSubTotal) + { + AutoFormatExpression = "Purchase Header"."Currency Code"; + AutoFormatType = 1; + } + column(TotalInvoiceDiscountAmount; TotalInvoiceDiscountAmount) + { + AutoFormatExpression = "Purchase Header"."Currency Code"; + AutoFormatType = 1; + } + column(TotalAmount; TotalAmount) + { + AutoFormatExpression = "Purchase Header"."Currency Code"; + AutoFormatType = 1; + } + column(TotalText; TotalText) + { + } + + trigger OnAfterGetRecord() + var + TempPrepmtPurchLine: Record "Purchase Line" temporary; + begin + FirstLineHasBeenOutput := false; + Clear(TempPurchLine); + Clear(PurchPost); + TempPurchLine.DeleteAll(); + TempVATAmountLine.DeleteAll(); + PurchPost.GetPurchLines("Purchase Header", TempPurchLine, 0); + TempPurchLine.CalcVATAmountLines(0, "Purchase Header", TempPurchLine, TempVATAmountLine); + TempPurchLine.UpdateVATOnLines(0, "Purchase Header", TempPurchLine, TempVATAmountLine); + VATAmount := TempVATAmountLine.GetTotalVATAmount(); + VATBaseAmount := TempVATAmountLine.GetTotalVATBase(); + VATDiscountAmount := + TempVATAmountLine.GetTotalVATDiscount("Purchase Header"."Currency Code", "Purchase Header"."Prices Including VAT"); + TotalAmountInclVAT := TempVATAmountLine.GetTotalAmountInclVAT(); + if VATAmountDifference then begin + TotalAmountInclVAT -= 0.01; + VATAmount -= 0.01; + end; + + TempPrepaymentInvLineBuffer.DeleteAll(); + PurchasePostPrepayments.GetPurchLines("Purchase Header", 0, TempPrepmtPurchLine); + if not TempPrepmtPurchLine.IsEmpty() then begin + PurchasePostPrepayments.GetPurchLinesToDeduct("Purchase Header", TempPurchLine); + if not TempPurchLine.IsEmpty() then + PurchasePostPrepayments.CalcVATAmountLines("Purchase Header", TempPurchLine, TempPrePmtVATAmountLineDeduct, 1); + end; + PurchasePostPrepayments.CalcVATAmountLines("Purchase Header", TempPrepmtPurchLine, TempPrepmtVATAmountLine, 0); + TempPrepmtVATAmountLine.DeductVATAmountLine(TempPrePmtVATAmountLineDeduct); + PurchasePostPrepayments.UpdateVATOnLines("Purchase Header", TempPrepmtPurchLine, TempPrepmtVATAmountLine, 0); + PurchasePostPrepayments.BuildInvLineBuffer("Purchase Header", TempPrepmtPurchLine, 0, TempPrepaymentInvLineBuffer); + PrepmtVATAmount := TempPrepmtVATAmountLine.GetTotalVATAmount(); + PrepmtVATBaseAmount := TempPrepmtVATAmountLine.GetTotalVATBase(); + PrepmtTotalAmountInclVAT := TempPrepmtVATAmountLine.GetTotalAmountInclVAT(); + end; + } + dataitem(VATCounter; "Integer") + { + DataItemTableView = sorting(Number); + column(VATAmtLineVATBase; TempVATAmountLine."VAT Base") + { + AutoFormatExpression = "Purchase Header"."Currency Code"; + AutoFormatType = 1; + } + column(VATAmtLineVATAmt; TempVATAmountLine."VAT Amount") + { + AutoFormatExpression = "Purchase Header"."Currency Code"; + AutoFormatType = 1; + } + column(VATAmtLineLineAmt; TempVATAmountLine."Line Amount") + { + AutoFormatExpression = "Purchase Header"."Currency Code"; + AutoFormatType = 1; + } + column(VATAmtLineInvDiscBaseAmt; TempVATAmountLine."Inv. Disc. Base Amount") + { + AutoFormatExpression = "Purchase Header"."Currency Code"; + AutoFormatType = 1; + } + column(VATAmtLineInvDiscAmt; TempVATAmountLine."Invoice Discount Amount") + { + AutoFormatExpression = "Purchase Header"."Currency Code"; + AutoFormatType = 1; + } + column(VATAmtLineVAT; TempVATAmountLine."VAT %") + { + DecimalPlaces = 0 : 5; + } + column(VATAmtLineVATIdentifier; TempVATAmountLine."VAT Identifier") + { + } + + trigger OnAfterGetRecord() + begin + TempVATAmountLine.GetLine(Number); + end; + + trigger OnPreDataItem() + begin + if VATAmount = 0 then + CurrReport.Break(); + SetRange(Number, 1, TempVATAmountLine.Count); + end; + } + dataitem(VATCounterLCY; "Integer") + { + DataItemTableView = sorting(Number); + column(VALExchRate; VALExchRate) + { + } + column(VALSpecLCYHeader; VALSpecLCYHeader) + { + } + column(VALVATAmountLCY; VALVATAmountLCY) + { + AutoFormatType = 1; + } + column(VALVATBaseLCY; VALVATBaseLCY) + { + AutoFormatType = 1; + } + + trigger OnAfterGetRecord() + begin + TempVATAmountLine.GetLine(Number); + VALVATBaseLCY := + TempVATAmountLine.GetBaseLCY( + "Purchase Header"."Posting Date", "Purchase Header"."Currency Code", "Purchase Header"."Currency Factor"); + VALVATAmountLCY := + TempVATAmountLine.GetAmountLCY( + "Purchase Header"."Posting Date", "Purchase Header"."Currency Code", "Purchase Header"."Currency Factor"); + end; + + trigger OnPreDataItem() + begin + if (not GLSetup."Print VAT specification in LCY") or + ("Purchase Header"."Currency Code" = '') or + (TempVATAmountLine.GetTotalVATAmount() = 0) + then + CurrReport.Break(); + + SetRange(Number, 1, TempVATAmountLine.Count); + + if GLSetup."LCY Code" = '' then + VALSpecLCYHeader := VATAmountSpecificationLbl + LocalCurrentyLbl + else + VALSpecLCYHeader := VATAmountSpecificationLbl + Format(GLSetup."LCY Code"); + + CurrExchRate.FindCurrency("Purchase Header"."Posting Date", "Purchase Header"."Currency Code", 1); + VALExchRate := StrSubstNo(ExchangeRateLbl, CurrExchRate."Relational Exch. Rate Amount", CurrExchRate."Exchange Rate Amount"); + end; + } + dataitem(PrepmtLoop; "Integer") + { + DataItemTableView = sorting(Number) where(Number = filter(1 ..)); + column(PrepmtLineAmount; PrepmtLineAmount) + { + AutoFormatExpression = "Purchase Header"."Currency Code"; + AutoFormatType = 1; + } + column(PrepmtInvBufGLAccNo; TempPrepaymentInvLineBuffer."G/L Account No.") + { + } + column(PrepmtInvBufDesc; TempPrepaymentInvLineBuffer.Description) + { + } + column(TotalInclVATText2; TotalInclVATText) + { + } + column(TotalExclVATText2; TotalExclVATText) + { + } + column(PrepmtInvBufAmt; TempPrepaymentInvLineBuffer.Amount) + { + AutoFormatExpression = "Purchase Header"."Currency Code"; + AutoFormatType = 1; + } + column(PrepmtVATAmountText; TempPrepmtVATAmountLine.VATAmountText()) + { + } + column(PrepmtVATAmount; PrepmtVATAmount) + { + AutoFormatExpression = "Purchase Header"."Currency Code"; + AutoFormatType = 1; + } + column(PrepmtTotalAmountInclVAT; PrepmtTotalAmountInclVAT) + { + AutoFormatExpression = "Purchase Header"."Currency Code"; + AutoFormatType = 1; + } + column(PrepmtVATBaseAmount; PrepmtVATBaseAmount) + { + AutoFormatExpression = "Purchase Header"."Currency Code"; + AutoFormatType = 1; + } + column(PrepmtInvBuDescCaption; PrepmtInvBuDescCaptionLbl) + { + } + column(PrepmtInvBufGLAccNoCaption; PrepmtInvBufGLAccNoCaptionLbl) + { + } + column(PrepaymentSpecCaption; PrepaymentSpecCaptionLbl) + { + } + + trigger OnAfterGetRecord() + begin + if Number = 1 then begin + if not TempPrepaymentInvLineBuffer.Find('-') then + CurrReport.Break(); + end else + if TempPrepaymentInvLineBuffer.Next() = 0 then + CurrReport.Break(); + + if "Purchase Header"."Prices Including VAT" then + PrepmtLineAmount := TempPrepaymentInvLineBuffer."Amount Incl. VAT" + else + PrepmtLineAmount := TempPrepaymentInvLineBuffer.Amount; + end; + } + dataitem(PrepmtVATCounter; "Integer") + { + DataItemTableView = sorting(Number); + column(PrepmtVATAmtLineVATAmt; TempPrepmtVATAmountLine."VAT Amount") + { + AutoFormatExpression = "Purchase Header"."Currency Code"; + AutoFormatType = 1; + } + column(PrepmtVATAmtLineVATBase; TempPrepmtVATAmountLine."VAT Base") + { + AutoFormatExpression = "Purchase Header"."Currency Code"; + AutoFormatType = 1; + } + column(PrepmtVATAmtLineLineAmt; TempPrepmtVATAmountLine."Line Amount") + { + AutoFormatExpression = "Purchase Header"."Currency Code"; + AutoFormatType = 1; + } + column(PrepmtVATAmtLineVAT; TempPrepmtVATAmountLine."VAT %") + { + DecimalPlaces = 0 : 5; + } + column(PrepmtVATAmtLineVATId; TempPrepmtVATAmountLine."VAT Identifier") + { + } + column(PrepymtVATAmtSpecCaption; PrepymtVATAmtSpecCaptionLbl) + { + } + + trigger OnAfterGetRecord() + begin + TempPrepmtVATAmountLine.GetLine(Number); + end; + + trigger OnPreDataItem() + begin + SetRange(Number, 1, TempPrepmtVATAmountLine.Count); + end; + } + dataitem(LetterText; "Integer") + { + DataItemTableView = sorting(Number) where(Number = const(1)); + column(GreetingText; GreetingLbl) + { + } + column(BodyText; BodyLbl) + { + } + column(ClosingText; ClosingLbl) + { + } + } + + trigger OnAfterGetRecord() + begin + FirstLineHasBeenOutput := false; + TotalAmount := 0; + TotalSubTotal := 0; + TotalInvoiceDiscountAmount := 0; + CurrReport.Language := LanguageMgt.GetLanguageIdOrDefault("Language Code"); + CurrReport.FormatRegion := LanguageMgt.GetFormatRegionOrDefault("Format Region"); + FormatAddr.SetLanguageCode("Language Code"); + + FormatAddressFields("Purchase Header"); + FormatDocumentFields("Purchase Header"); + if BuyFromContact.Get("Buy-from Contact No.") then; + if PayToContact.Get("Pay-to Contact No.") then; + + if not IsReportInPreviewMode() then begin + CODEUNIT.Run(CODEUNIT::"Purch.Header-Printed", "Purchase Header"); + if ArchiveDocument then + ArchiveManagement.StorePurchDocument("Purchase Header", LogInteraction); + end; + end; + + trigger OnPreDataItem() + begin + FirstLineHasBeenOutput := false; + end; + } + } + + requestpage + { + SaveValues = true; + + layout + { + area(content) + { + group(Options) + { + Caption = 'Options'; + field(ArchiveDocumentField; ArchiveDocument) + { + ApplicationArea = Suite; + Caption = 'Archive Document'; + ToolTip = 'Specifies whether to archive the order.'; + } + field(LogInteractionField; LogInteraction) + { + ApplicationArea = Suite; + Caption = 'Log Interaction'; + Enabled = LogInteractionEnable; + ToolTip = 'Specifies if you want to log this interaction.'; + } + field(OmitLastLineField; OmitLastLine) + { + ApplicationArea = Suite; + Caption = 'Omit last line'; + ToolTip = 'Specifies whether the last purchase line is excluded from the printed document.'; + } + field(VATAmountDifferenceField; VATAmountDifference) + { + ApplicationArea = Suite; + Caption = 'VAT Amount Difference'; + ToolTip = 'Specifies whether the total VAT amount and the total amount in the footer are each decreased by 0.01.'; + } + } + } + } + + actions + { + } + + trigger OnInit() + begin + InitLogInteraction(); + LogInteractionEnable := LogInteraction; + ArchiveDocument := PurchSetup."Archive Orders"; + end; + } + + rendering + { + layout("EDocStandardPurchaseOrder.docx") + { + Type = Word; + LayoutFile = './.resources/Template/Purchases/EDocStandardPurchaseOrder.docx'; + Caption = 'Standard Purchase Order (Word)'; + Summary = 'The Standard Purchase Order (Word) provides a basic layout.'; + } + } + + labels + { + } + + trigger OnInitReport() + var + IsHandled: Boolean; + begin + GLSetup.Get(); + CompanyInfo.SetAutoCalcFields(Picture); + CompanyInfo.Get(); + PurchSetup.Get(); + + IsHandled := false; + OnInitReportForGlobalVariable(IsHandled, LegalOfficeTxt, LegalOfficeLbl, CustomGiroTxt, CustomGiroLbl); + end; + + trigger OnPostReport() + begin + if LogInteraction and not IsReportInPreviewMode() then + if "Purchase Header".FindSet() then + repeat + SegManagement.LogDocument( + 13, "Purchase Header"."No.", 0, 0, DATABASE::Vendor, "Purchase Header"."Buy-from Vendor No.", + "Purchase Header"."Purchaser Code", '', "Purchase Header"."Posting Description", ''); + until "Purchase Header".Next() = 0; + end; + + var + DummyCompanyInfo: Record "Company Information"; + GLSetup: Record "General Ledger Setup"; + TempPurchLine: Record "Purchase Line" temporary; + CurrExchRate: Record "Currency Exchange Rate"; + LanguageMgt: Codeunit Language; + FormatAddr: Codeunit "Format Address"; + FormatDocument: Codeunit "Format Document"; + PurchPost: Codeunit "Purch.-Post"; + SegManagement: Codeunit SegManagement; + PurchasePostPrepayments: Codeunit "Purchase-Post Prepayments"; + ArchiveManagement: Codeunit ArchiveManagement; + VATNoText: Text; + ReferenceText: Text; + OutputNo: Integer; + DimText: Text[120]; + VATAmount: Decimal; + VATBaseAmount: Decimal; + VATDiscountAmount: Decimal; + TotalAmountInclVAT: Decimal; + VALVATBaseLCY: Decimal; + VALVATAmountLCY: Decimal; + VALSpecLCYHeader: Text[80]; + VALExchRate: Text[50]; + PrepmtVATAmount: Decimal; + PrepmtVATBaseAmount: Decimal; + PrepmtTotalAmountInclVAT: Decimal; + PrepmtLineAmount: Decimal; + AllowInvDisctxt: Text[30]; + CompanyLogoPosition: Integer; + ItemNo: Text; + VATAmountSpecificationLbl: Label 'VAT Amount Specification in '; + LocalCurrentyLbl: Label 'Local Currency'; + ExchangeRateLbl: Label 'Exchange rate: %1/%2', Comment = '%1 = CurrExchRate."Relational Exch. Rate Amount", %2 = CurrExchRate."Exchange Rate Amount"'; + CompanyInfoPhoneNoCaptionLbl: Label 'Phone No.'; + CompanyInfoGiroNoCaptionLbl: Label 'Giro No.'; + CompanyInfoBankNameCaptionLbl: Label 'Bank'; + CompanyInfoBankAccNoCaptionLbl: Label 'Account No.'; + OrderNoCaptionLbl: Label 'Order No.'; + PageCaptionLbl: Label 'Page'; + DocumentDateCaptionLbl: Label 'Document Date'; + DirectUniCostCaptionLbl: Label 'Direct Unit Cost'; + PurchLineLineDiscCaptionLbl: Label 'Discount %'; + VATDiscountAmountCaptionLbl: Label 'Payment Discount on VAT'; + PaymentDetailsCaptionLbl: Label 'Payment Details'; + VendNoCaptionLbl: Label 'Vendor No.'; + ShiptoAddressCaptionLbl: Label 'Ship-to Address'; + PrepmtInvBuDescCaptionLbl: Label 'Description'; + PrepmtInvBufGLAccNoCaptionLbl: Label 'G/L Account No.'; + PrepaymentSpecCaptionLbl: Label 'Prepayment Specification'; + PrepymtVATAmtSpecCaptionLbl: Label 'Prepayment VAT Amount Specification'; + AmountCaptionLbl: Label 'Amount'; + PurchLineInvDiscAmtCaptionLbl: Label 'Invoice Discount Amount'; + SubtotalCaptionLbl: Label 'Subtotal'; + VATAmtLineVATCaptionLbl: Label 'VAT %'; + VATAmtLineVATAmtCaptionLbl: Label 'VAT Amount'; + VATAmtSpecCaptionLbl: Label 'VAT Amount Specification'; + VATIdentifierCaptionLbl: Label 'VAT Identifier'; + VATAmtLineInvDiscBaseAmtCaptionLbl: Label 'Invoice Discount Base Amount'; + VATAmtLineLineAmtCaptionLbl: Label 'Line Amount'; + VALVATBaseLCYCaptionLbl: Label 'VAT Base'; + PricesInclVATtxtLbl: Label 'Prices Including VAT'; + TotalCaptionLbl: Label 'Total'; + PaymentTermsDescCaptionLbl: Label 'Payment Terms'; + ShipmentMethodDescCaptionLbl: Label 'Shipment Method'; + PrepymtTermsDescCaptionLbl: Label 'Prepmt. Payment Terms'; + HomePageCaptionLbl: Label 'Home Page'; + EmailIDCaptionLbl: Label 'Email'; + AllowInvoiceDiscCaptionLbl: Label 'Allow Invoice Discount'; + BuyFromContactPhoneNoLbl: Label 'Buy-from Contact Phone No.'; + BuyFromContactMobilePhoneNoLbl: Label 'Buy-from Contact Mobile Phone No.'; + BuyFromContactEmailLbl: Label 'Buy-from Contact E-Mail'; + PayToContactPhoneNoLbl: Label 'Pay-to Contact Phone No.'; + PayToContactMobilePhoneNoLbl: Label 'Pay-to Contact Mobile Phone No.'; + PayToContactEmailLbl: Label 'Pay-to Contact E-Mail'; + DocumentTitleLbl: Label 'Purchase Order'; + ReceivebyCaptionLbl: Label 'Receive By'; + BuyerCaptionLbl: Label 'Buyer'; + ItemNumberCaptionLbl: Label 'Item No.'; + ItemDescriptionCaptionLbl: Label 'Description'; + ItemQuantityCaptionLbl: Label 'Quantity'; + ItemUnitCaptionLbl: Label 'Unit'; + ItemUnitPriceCaptionLbl: Label 'Unit Price'; + ItemLineAmountCaptionLbl: Label 'Line Amount'; + PricesIncludingVATCaptionLbl: Label 'Prices Including VAT'; + ItemUnitOfMeasureCaptionLbl: Label 'Unit'; + ToCaptionLbl: Label 'To:'; + VendorIDCaptionLbl: Label 'Vendor ID'; + ConfirmToCaptionLbl: Label 'Confirm To'; + PurchOrderCaptionLbl: Label 'PURCHASE ORDER'; + PurchOrderNumCaptionLbl: Label 'Purchase Order Number:'; + PurchOrderDateCaptionLbl: Label 'Purchase Order Date:'; + TaxIdentTypeCaptionLbl: Label 'Tax Ident. Type'; + TotalPriceCaptionLbl: Label 'Total Price'; + InvDiscCaptionLbl: Label 'Invoice Discount:'; + GreetingLbl: Label 'Hello'; + ClosingLbl: Label 'Sincerely'; + BodyLbl: Label 'The purchase order is attached to this message.'; + OrderDateLbl: Label 'Order Date'; + VendorOrderNoLbl: Label 'Vendor Order No.'; + VendorInvoiceNoLbl: Label 'Vendor Invoice No.'; + UnitPriceLbl: Label 'Unit Price (LCY)'; + JobNoLbl: Label 'Project No.'; + JobTaskNoLbl: Label 'Project Task No.'; + PromisedReceiptDateLbl: Label 'Promised Receipt Date'; + RequestedReceiptDateLbl: Label 'Requested Receipt Date'; + ExpectedReceiptDateLbl: Label 'Expected Receipt Date'; + PlannedReceiptDateLbl: Label 'Planned Receipt Date'; + LegalOfficeTxt, LegalOfficeLbl, CustomGiroTxt, CustomGiroLbl : Text; + + protected var + ResponsibilityCenter: Record "Responsibility Center"; + BuyFromContact: Record Contact; + PayToContact: Record Contact; + CompanyInfo: Record "Company Information"; + PurchSetup: Record "Purchases & Payables Setup"; + ShipmentMethod: Record "Shipment Method"; + PaymentTerms: Record "Payment Terms"; + PrepmtPaymentTerms: Record "Payment Terms"; + SalespersonPurchaser: Record "Salesperson/Purchaser"; + TempVATAmountLine: Record "VAT Amount Line" temporary; + TempPrepmtVATAmountLine: Record "VAT Amount Line" temporary; + TempPrepaymentInvLineBuffer: Record "Prepayment Inv. Line Buffer" temporary; + TempPrePmtVATAmountLineDeduct: Record "VAT Amount Line" temporary; + BuyFromAddr: array[8] of Text[100]; + CompanyAddr: array[8] of Text[100]; + VendAddr: array[8] of Text[100]; + ShipToAddr: array[8] of Text[100]; + FormattedQuanitity: Text; + FormattedDirectUnitCost: Text; + FormattedVATPct: Text; + FormattedLineAmount: Text; + PurchaserText: Text[50]; + TotalText: Text[50]; + TotalInclVATText: Text[50]; + TotalExclVATText: Text[50]; + ArchiveDocument: Boolean; + LogInteraction: Boolean; + LogInteractionEnable: Boolean; + OmitLastLine: Boolean; + VATAmountDifference: Boolean; + LastPurchaseLineNo: Integer; + FirstLineHasBeenOutput: Boolean; + TotalSubTotal, TotalAmount, TotalInvoiceDiscountAmount : Decimal; + + procedure InitializeRequest(LogInteractionParam: Boolean) + begin + LogInteraction := LogInteractionParam; + end; + + protected procedure IsReportInPreviewMode(): Boolean + var + MailManagement: Codeunit "Mail Management"; + begin + exit(CurrReport.Preview() or MailManagement.IsHandlingGetEmailBody()); + end; + + local procedure FormatAddressFields(var PurchaseHeader: Record "Purchase Header") + begin + FormatAddr.GetCompanyAddr(PurchaseHeader."Responsibility Center", ResponsibilityCenter, CompanyInfo, CompanyAddr); + FormatAddr.PurchHeaderBuyFrom(BuyFromAddr, PurchaseHeader); + if PurchaseHeader."Buy-from Vendor No." <> PurchaseHeader."Pay-to Vendor No." then + FormatAddr.PurchHeaderPayTo(VendAddr, PurchaseHeader); + FormatAddr.PurchHeaderShipTo(ShipToAddr, PurchaseHeader); + end; + + local procedure FormatDocumentFields(PurchaseHeader: Record "Purchase Header") + begin + FormatDocument.SetTotalLabels(PurchaseHeader."Currency Code", TotalText, TotalInclVATText, TotalExclVATText); + FormatDocument.SetPurchaser(SalespersonPurchaser, PurchaseHeader."Purchaser Code", PurchaserText); + FormatDocument.SetPaymentTerms(PaymentTerms, PurchaseHeader."Payment Terms Code", PurchaseHeader."Language Code"); + FormatDocument.SetPaymentTerms(PrepmtPaymentTerms, PurchaseHeader."Prepmt. Payment Terms Code", PurchaseHeader."Language Code"); + FormatDocument.SetShipmentMethod(ShipmentMethod, PurchaseHeader."Shipment Method Code", PurchaseHeader."Language Code"); + + ReferenceText := FormatDocument.SetText(PurchaseHeader."Your Reference" <> '', CopyStr(PurchaseHeader.FieldCaption("Your Reference"), 1, 80)); + VATNoText := FormatDocument.SetText(PurchaseHeader."VAT Registration No." <> '', CopyStr(PurchaseHeader.FieldCaption("VAT Registration No."), 1, 80)); + + OnAfterFormatDocumentFields(PurchaseHeader); + end; + + local procedure InitLogInteraction() + begin + LogInteraction := SegManagement.FindInteractionTemplateCode(Enum::"Interaction Log Entry Document Type"::"Purch. Ord.") <> ''; + end; + + [IntegrationEvent(true, false)] + local procedure OnAfterFormatDocumentFields(var PurchaseHeader: Record "Purchase Header") + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnInitReportForGlobalVariable(var IsHandled: Boolean; var LegalOfficeTxt: Text; var LegalOfficeLbl: Text; var CustomGiroTxt: Text; var CustomGiroLbl: Text) + begin + end; +} + From c96a46dabda1157b8003e1d02ff5d10e54b653c2 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Fri, 8 May 2026 12:54:22 +0200 Subject: [PATCH 35/53] feat(edoc): add LogVATRateMismatch on E-Document Purchase Line Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Purchase/EDocumentPurchaseLine.Table.al | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al index 4873d3b50a..1766ad294e 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al @@ -21,6 +21,7 @@ using Microsoft.Purchases.Document; using Microsoft.Purchases.History; using Microsoft.Purchases.Vendor; using Microsoft.Utilities; +using System.Log; using System.Reflection; using System.Utilities; @@ -429,4 +430,41 @@ table 6101 "E-Document Purchase Line" exit(GetEDocumentPurchaseHeader().GetBCVendor()); end; + internal procedure LogVATRateMismatch() + var + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + Vendor: Record Vendor; + VATPostingSetup: Record "VAT Posting Setup"; + ActivityLog: Codeunit "Activity Log Builder"; + VATPostingSetupRef: RecordRef; + Reasoning: Text[250]; + VATRateMismatchReasonLbl: Label 'VAT rate %1% extracted from the document could not be matched to a VAT Posting Setup for vendor''s VAT Business Posting Group %2.', Comment = '%1 = extracted VAT rate %, %2 = VAT Bus. Posting Group code'; + VATRateMismatchTitleLbl: Label 'VAT Posting Setup for %1', Comment = '%1 = VAT Bus. Posting Group code'; + begin + if not Rec."[BC] VAT Rate Mismatch" then + exit; + if not EDocumentPurchaseHeader.Get(Rec."E-Document Entry No.") then + exit; + if EDocumentPurchaseHeader."[BC] Vendor No." = '' then + exit; + if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then + exit; + + VATPostingSetup.SetRange("VAT Bus. Posting Group", Vendor."VAT Bus. Posting Group"); + VATPostingSetup.SetFilter("VAT Calculation Type", '%1|%2', + VATPostingSetup."VAT Calculation Type"::"Normal VAT", + VATPostingSetup."VAT Calculation Type"::"Reverse Charge VAT"); + VATPostingSetupRef.GetTable(VATPostingSetup); + + Reasoning := CopyStr(StrSubstNo(VATRateMismatchReasonLbl, Rec."VAT Rate", Vendor."VAT Bus. Posting Group"), 1, MaxStrLen(Reasoning)); + + ActivityLog + .Init(Database::"E-Document Purchase Line", Rec.FieldNo("[BC] VAT Prod. Posting Group"), Rec.SystemId) + .SetExplanation(Reasoning) + .SetType(Enum::"Activity Log Type"::"AL") + .SetReferenceSource(Page::"VAT Posting Setup", VATPostingSetupRef) + .SetReferenceTitle(StrSubstNo(VATRateMismatchTitleLbl, Vendor."VAT Bus. Posting Group")) + .Log(); + end; + } From 0604d4f170a9745dc482aca095ee9de428ff76e9 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Fri, 8 May 2026 13:00:48 +0200 Subject: [PATCH 36/53] feat(edoc): log VAT mismatch from line OnValidate Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Processing/Import/Purchase/EDocumentPurchaseLine.Table.al | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al index 1766ad294e..0f6e38817b 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al @@ -224,6 +224,7 @@ table 6101 "E-Document Purchase Line" begin if "[BC] VAT Prod. Posting Group" = '' then begin "[BC] VAT Rate Mismatch" := true; + LogVATRateMismatch(); exit; end; if not EDocumentPurchaseHeader.Get("E-Document Entry No.") then @@ -239,6 +240,7 @@ table 6101 "E-Document Purchase Line" "[BC] VAT Rate Mismatch" := VATPostingSetup."VAT %" <> "VAT Rate"; end else "[BC] VAT Rate Mismatch" := true; + LogVATRateMismatch(); end; trigger OnLookup() From fb6df3a9f71c4062dabb9fd9ed61fcfb8f240435 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Fri, 8 May 2026 13:02:14 +0200 Subject: [PATCH 37/53] feat(edoc): log VAT mismatch from ResolveVATProductPostingGroups Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al index 6b687e9d3a..b12ed21048 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al @@ -181,6 +181,7 @@ codeunit 6406 "EDoc Prepare Purch. Draft" EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" = ''; EDocumentPurchaseLine.Modify(); + EDocumentPurchaseLine.LogVATRateMismatch(); end; until EDocumentPurchaseLine.Next() = 0; end; From 1d4166414dab159a507f69d94e6e79ad1fa50273 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Mon, 11 May 2026 09:54:05 +0200 Subject: [PATCH 38/53] swamp company and vendor fields in EDocStandardPurchaseOrder.Report.al --- .../EDocStandardPurchaseOrder.Report.al | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/Apps/W1/EDocument/App/src/Document/EDocStandardPurchaseOrder.Report.al b/src/Apps/W1/EDocument/App/src/Document/EDocStandardPurchaseOrder.Report.al index 2316d9e100..8a0d3ba8c7 100644 --- a/src/Apps/W1/EDocument/App/src/Document/EDocStandardPurchaseOrder.Report.al +++ b/src/Apps/W1/EDocument/App/src/Document/EDocStandardPurchaseOrder.Report.al @@ -41,28 +41,28 @@ report 6199 "E-Doc Standard Purchase Order" DataItemTableView = sorting("Document Type", "No.") where("Document Type" = const(Order)); RequestFilterFields = "No.", "Buy-from Vendor No.", "No. Printed"; RequestFilterHeading = 'Standard Purchase - Order'; - column(CompanyAddress1; CompanyAddr[1]) + column(CompanyAddress1; BuyFromAddr[1]) { } - column(CompanyAddress2; CompanyAddr[2]) + column(CompanyAddress2; BuyFromAddr[2]) { } - column(CompanyAddress3; CompanyAddr[3]) + column(CompanyAddress3; BuyFromAddr[3]) { } - column(CompanyAddress4; CompanyAddr[4]) + column(CompanyAddress4; BuyFromAddr[4]) { } - column(CompanyAddress5; CompanyAddr[5]) + column(CompanyAddress5; BuyFromAddr[5]) { } - column(CompanyAddress6; CompanyAddr[6]) + column(CompanyAddress6; BuyFromAddr[6]) { } - column(CompanyAddress7; CompanyAddr[7]) + column(CompanyAddress7; BuyFromAddr[7]) { } - column(CompanyAddress8; CompanyAddr[8]) + column(CompanyAddress8; BuyFromAddr[8]) { } column(CompanyHomePage_Lbl; HomePageCaptionLbl) @@ -74,13 +74,13 @@ report 6199 "E-Doc Standard Purchase Order" column(CompanyEmail_Lbl; EmailIDCaptionLbl) { } - column(CompanyEMail; CompanyInfo."E-Mail") + column(CompanyEMail; BuyFromContact."E-Mail") { } column(CompanyPicture; DummyCompanyInfo.Picture) { } - column(CompanyPhoneNo; CompanyInfo."Phone No.") + column(CompanyPhoneNo; BuyFromContact."Phone No.") { } column(CompanyPhoneNo_Lbl; CompanyInfoPhoneNoCaptionLbl) @@ -131,13 +131,13 @@ report 6199 "E-Doc Standard Purchase Order" column(CompanyRegistrationNumber_Lbl; CompanyInfo.GetRegistrationNumberLbl()) { } - column(CompanyVATRegNo; CompanyInfo.GetVATRegistrationNumber()) + column(CompanyVATRegNo; "VAT Registration No.") { } column(CompanyVATRegNo_Lbl; CompanyInfo.GetVATRegistrationNumberLbl()) { } - column(CompanyVATRegistrationNo; CompanyInfo.GetVATRegistrationNumber()) + column(CompanyVATRegistrationNo; "VAT Registration No.") { } column(CompanyVATRegistrationNo_Lbl; CompanyInfo.GetVATRegistrationNumberLbl()) @@ -230,7 +230,7 @@ report 6199 "E-Doc Standard Purchase Order" column(VATNoText; VATNoText) { } - column(VATRegNo_PurchHeader; "VAT Registration No.") + column(VATRegNo_PurchHeader; CompanyInfo.GetVATRegistrationNumber()) { } column(PurchaserText; PurchaserText) @@ -248,28 +248,28 @@ report 6199 "E-Doc Standard Purchase Order" column(BuyFrmVendNo_PurchHeader; "Buy-from Vendor No.") { } - column(BuyFromAddr1; BuyFromAddr[1]) + column(BuyFromAddr1; CompanyAddr[1]) { } - column(BuyFromAddr2; BuyFromAddr[2]) + column(BuyFromAddr2; CompanyAddr[2]) { } - column(BuyFromAddr3; BuyFromAddr[3]) + column(BuyFromAddr3; CompanyAddr[3]) { } - column(BuyFromAddr4; BuyFromAddr[4]) + column(BuyFromAddr4; CompanyAddr[4]) { } - column(BuyFromAddr5; BuyFromAddr[5]) + column(BuyFromAddr5; CompanyAddr[5]) { } - column(BuyFromAddr6; BuyFromAddr[6]) + column(BuyFromAddr6; CompanyAddr[6]) { } - column(BuyFromAddr7; BuyFromAddr[7]) + column(BuyFromAddr7; CompanyAddr[7]) { } - column(BuyFromAddr8; BuyFromAddr[8]) + column(BuyFromAddr8; CompanyAddr[8]) { } column(BuyFromContactPhoneNoLbl; BuyFromContactPhoneNoLbl) @@ -290,13 +290,13 @@ report 6199 "E-Doc Standard Purchase Order" column(PayToContactEmailLbl; PayToContactEmailLbl) { } - column(BuyFromContactPhoneNo; BuyFromContact."Phone No.") + column(BuyFromContactPhoneNo; CompanyInfo."Phone No.") { } column(BuyFromContactMobilePhoneNo; BuyFromContact."Mobile Phone No.") { } - column(BuyFromContactEmail; BuyFromContact."E-Mail") + column(BuyFromContactEmail; CompanyInfo."E-Mail") { } column(PayToContactPhoneNo; PayToContact."Phone No.") From ddfcd2a9cb4314789f36e173c077affa5dc6d59c Mon Sep 17 00:00:00 2001 From: ventselartur Date: Mon, 11 May 2026 10:10:05 +0200 Subject: [PATCH 39/53] update for LogVATRateMismatch --- .../EDocPreparePurchDraft.Codeunit.al | 2 +- .../Purchase/EDocumentPurchaseLine.Table.al | 24 ++++++++++++------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al index b12ed21048..60d7e0c422 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al @@ -181,7 +181,7 @@ codeunit 6406 "EDoc Prepare Purch. Draft" EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" = ''; EDocumentPurchaseLine.Modify(); - EDocumentPurchaseLine.LogVATRateMismatch(); + EDocumentPurchaseLine.LogVATRateMismatch(Vendor."VAT Bus. Posting Group", VATRate); end; until EDocumentPurchaseLine.Next() = 0; end; diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al index 0f6e38817b..0b21701c84 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al @@ -436,12 +436,6 @@ table 6101 "E-Document Purchase Line" var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; Vendor: Record Vendor; - VATPostingSetup: Record "VAT Posting Setup"; - ActivityLog: Codeunit "Activity Log Builder"; - VATPostingSetupRef: RecordRef; - Reasoning: Text[250]; - VATRateMismatchReasonLbl: Label 'VAT rate %1% extracted from the document could not be matched to a VAT Posting Setup for vendor''s VAT Business Posting Group %2.', Comment = '%1 = extracted VAT rate %, %2 = VAT Bus. Posting Group code'; - VATRateMismatchTitleLbl: Label 'VAT Posting Setup for %1', Comment = '%1 = VAT Bus. Posting Group code'; begin if not Rec."[BC] VAT Rate Mismatch" then exit; @@ -452,20 +446,32 @@ table 6101 "E-Document Purchase Line" if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then exit; - VATPostingSetup.SetRange("VAT Bus. Posting Group", Vendor."VAT Bus. Posting Group"); + LogVATRateMismatch(Vendor."VAT Bus. Posting Group", Rec."VAT Rate"); + end; + + internal procedure LogVATRateMismatch(VendVATBusPostingGroupCode: Code[20]; VATRate: Decimal) + var + VATPostingSetup: Record "VAT Posting Setup"; + ActivityLog: Codeunit "Activity Log Builder"; + VATPostingSetupRef: RecordRef; + Reasoning: Text[250]; + VATRateMismatchReasonLbl: Label 'VAT rate %1% extracted from the document could not be matched to a VAT Posting Setup for vendor''s VAT Business Posting Group %2.', Comment = '%1 = extracted VAT rate %, %2 = VAT Bus. Posting Group code'; + VATRateMismatchTitleLbl: Label 'VAT Posting Setup for %1', Comment = '%1 = VAT Bus. Posting Group code'; + begin + VATPostingSetup.SetRange("VAT Bus. Posting Group", VendVATBusPostingGroupCode); VATPostingSetup.SetFilter("VAT Calculation Type", '%1|%2', VATPostingSetup."VAT Calculation Type"::"Normal VAT", VATPostingSetup."VAT Calculation Type"::"Reverse Charge VAT"); VATPostingSetupRef.GetTable(VATPostingSetup); - Reasoning := CopyStr(StrSubstNo(VATRateMismatchReasonLbl, Rec."VAT Rate", Vendor."VAT Bus. Posting Group"), 1, MaxStrLen(Reasoning)); + Reasoning := CopyStr(StrSubstNo(VATRateMismatchReasonLbl, Rec."VAT Rate", VendVATBusPostingGroupCode), 1, MaxStrLen(Reasoning)); ActivityLog .Init(Database::"E-Document Purchase Line", Rec.FieldNo("[BC] VAT Prod. Posting Group"), Rec.SystemId) .SetExplanation(Reasoning) .SetType(Enum::"Activity Log Type"::"AL") .SetReferenceSource(Page::"VAT Posting Setup", VATPostingSetupRef) - .SetReferenceTitle(StrSubstNo(VATRateMismatchTitleLbl, Vendor."VAT Bus. Posting Group")) + .SetReferenceTitle(StrSubstNo(VATRateMismatchTitleLbl, VendVATBusPostingGroupCode)) .Log(); end; From 35cdfacaf8b86d3417bd0fed57172dc31cf09451 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Mon, 11 May 2026 11:42:23 +0200 Subject: [PATCH 40/53] align EDocStandardPurchaseOrder.Report.al --- .../src/Document/EDocStandardPurchaseOrder.Report.al | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Apps/W1/EDocument/App/src/Document/EDocStandardPurchaseOrder.Report.al b/src/Apps/W1/EDocument/App/src/Document/EDocStandardPurchaseOrder.Report.al index 8a0d3ba8c7..5f81a832c1 100644 --- a/src/Apps/W1/EDocument/App/src/Document/EDocStandardPurchaseOrder.Report.al +++ b/src/Apps/W1/EDocument/App/src/Document/EDocStandardPurchaseOrder.Report.al @@ -131,13 +131,13 @@ report 6199 "E-Doc Standard Purchase Order" column(CompanyRegistrationNumber_Lbl; CompanyInfo.GetRegistrationNumberLbl()) { } - column(CompanyVATRegNo; "VAT Registration No.") + column(CompanyVATRegNo; CompanyInfo.GetVATRegistrationNumber()) { } column(CompanyVATRegNo_Lbl; CompanyInfo.GetVATRegistrationNumberLbl()) { } - column(CompanyVATRegistrationNo; "VAT Registration No.") + column(CompanyVATRegistrationNo; CompanyInfo.GetVATRegistrationNumber()) { } column(CompanyVATRegistrationNo_Lbl; CompanyInfo.GetVATRegistrationNumberLbl()) @@ -230,7 +230,7 @@ report 6199 "E-Doc Standard Purchase Order" column(VATNoText; VATNoText) { } - column(VATRegNo_PurchHeader; CompanyInfo.GetVATRegistrationNumber()) + column(VATRegNo_PurchHeader; "VAT Registration No.") { } column(PurchaserText; PurchaserText) @@ -621,14 +621,14 @@ report 6199 "E-Doc Standard Purchase Order" trigger OnAfterGetRecord() begin - if OmitLastLine and ("Line No." = LastPurchaseLineNo) then - CurrReport.Skip(); - AllowInvDisctxt := Format("Allow Invoice Disc."); TotalSubTotal += "Line Amount"; TotalInvoiceDiscountAmount -= "Inv. Discount Amount"; TotalAmount += Amount; + if OmitLastLine and ("Line No." = LastPurchaseLineNo) then + CurrReport.Skip(); + ItemNo := "No."; if "Vendor Item No." <> '' then From fc4e7aa1894d3bf35d855a56da61ce7e4966d000 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Mon, 11 May 2026 12:03:55 +0200 Subject: [PATCH 41/53] update --- .../EDocPreparePurchDraft.Codeunit.al | 14 ++++- .../Purchase/EDocPurchaseDraftSubform.Page.al | 14 +++-- .../Purchase/EDocumentPurchaseDraft.Page.al | 13 ++++- .../Purchase/EDocumentPurchaseHeader.Table.al | 53 +++++++++++++++++++ .../Purchase/EDocumentPurchaseLine.Table.al | 4 +- 5 files changed, 88 insertions(+), 10 deletions(-) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al index 60d7e0c422..6debcf689d 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al @@ -37,6 +37,8 @@ codeunit 6406 "EDoc Prepare Purch. Draft" IUnitOfMeasureProvider: Interface IUnitOfMeasureProvider; IPurchaseLineProvider: Interface IPurchaseLineProvider; IPurchaseOrderProvider: Interface IPurchaseOrderProvider; + LineAmount: Decimal; + LineVATAmount: Decimal; begin IUnitOfMeasureProvider := EDocImportParameters."Processing Customizations"; IPurchaseLineProvider := EDocImportParameters."Processing Customizations"; @@ -81,13 +83,21 @@ codeunit 6406 "EDoc Prepare Purch. Draft" Clear(EDocumentPurchaseLine); EDocumentPurchaseLine.SetRange("E-Document Entry No.", EDocument."Entry No"); + EDocumentPurchaseHeader."Total Line Amount" := 0; + EDocumentPurchaseHeader."Total Line VAT Amount" := 0; + EDocumentPurchaseHeader."Total Line Amt. Incl. VAT" := 0; if EDocumentPurchaseLine.FindSet() then repeat - // Update total line amount on the header - EDocumentPurchaseHeader."Total Line Amount" := Round(EDocumentPurchaseLine.Quantity * EDocumentPurchaseLine."Unit Price" - EDocumentPurchaseLine."Total Discount"); + // Update total line amounts on the header + LineAmount := Round(EDocumentPurchaseLine.Quantity * EDocumentPurchaseLine."Unit Price" - EDocumentPurchaseLine."Total Discount"); + LineVATAmount := Round(LineAmount * EDocumentPurchaseLine."VAT Rate" / 100); + EDocumentPurchaseHeader."Total Line Amount" += LineAmount; + EDocumentPurchaseHeader."Total Line VAT Amount" += LineVATAmount; + EDocumentPurchaseHeader."Total Line Amt. Incl. VAT" += LineAmount + LineVATAmount; // Log telemetry and activity sessions EDocImpSessionTelemetry.SetLine(EDocumentPurchaseLine.SystemId); until EDocumentPurchaseLine.Next() = 0; + EDocumentPurchaseHeader.LogHeaderLinesMismatch(); EDocumentPurchaseHeader.Modify(); LogAllActivitySessionChanges(EDocActivityLogSession); diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al index 2f2f3b105b..30721d4585 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al @@ -4,7 +4,6 @@ // ------------------------------------------------------------------------------------------------ namespace Microsoft.eServices.EDocument.Processing.Import.Purchase; -using Microsoft.eServices.EDocument; using Microsoft.eServices.EDocument.Processing.Import; using Microsoft.Finance.Dimension; using Microsoft.Inventory.Item.Catalog; @@ -406,8 +405,8 @@ page 6183 "E-Doc. Purchase Draft Subform" local procedure UpdateCalculatedAmounts(UpdateParentRecord: Boolean) var TotalEDocPurchaseLine: Record "E-Document Purchase Line"; - EDocumentImportHelper: Codeunit "E-Document Import Helper"; LineSubtotal: Decimal; + LineVATAmount: Decimal; DiscountExceedsSubtotalErr: Label 'Discount should not exceed the subtotal of the line'; begin LineSubtotal := Rec.Quantity * Rec."Unit Price"; @@ -423,15 +422,20 @@ page 6183 "E-Doc. Purchase Draft Subform" exit; if not EDocumentPurchaseHeader.Get(Rec."E-Document Entry No.") then exit; - EDocumentPurchaseHeader."Sub Total" := 0; EDocumentPurchaseHeader."Total Line Amount" := 0; + EDocumentPurchaseHeader."Total Line VAT Amount" := 0; + EDocumentPurchaseHeader."Total Line Amt. Incl. VAT" := 0; TotalEDocPurchaseLine.SetRange("E-Document Entry No.", Rec."E-Document Entry No."); if TotalEDocPurchaseLine.FindSet() then repeat - EDocumentPurchaseHeader."Sub Total" += Round(TotalEDocPurchaseLine.Quantity * TotalEDocPurchaseLine."Unit Price", EDocumentImportHelper.GetCurrencyRoundingPrecision(EDocumentPurchaseHeader."Currency Code")) - TotalEDocPurchaseLine."Total Discount"; + LineSubtotal := TotalEDocPurchaseLine.Quantity * TotalEDocPurchaseLine."Unit Price"; + LineAmount := LineSubtotal - TotalEDocPurchaseLine."Total Discount"; + LineVATAmount := Round(LineAmount * TotalEDocPurchaseLine."VAT Rate" / 100); EDocumentPurchaseHeader."Total Line Amount" += LineAmount; + EDocumentPurchaseHeader."Total Line VAT Amount" += LineVATAmount; + EDocumentPurchaseHeader."Total Line Amt. Incl. VAT" += LineAmount + LineVATAmount; until TotalEDocPurchaseLine.Next() = 0; - EDocumentPurchaseHeader.Total := EDocumentPurchaseHeader."Sub Total" + EDocumentPurchaseHeader."Total VAT" - EDocumentPurchaseHeader."Total Discount"; + EDocumentPurchaseHeader.LogHeaderLinesMismatch(); EDocumentPurchaseHeader.Modify(); CurrPage.Update(); end; diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al index 4c0dee9e67..bef5fbf759 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al @@ -204,7 +204,16 @@ page 6181 "E-Document Purchase Draft" { ToolTip = 'Specifies the total of the lines amount values from all lines of the draft.'; Importance = Promoted; - Editable = false; + } + field("Total Line VAT Amount"; EDocumentPurchaseHeader."Total Line VAT Amount") + { + ToolTip = 'Specifies the total VAT amount computed from all lines of the draft.'; + Importance = Promoted; + } + field("Total Line Amt. Incl. VAT"; EDocumentPurchaseHeader."Total Line Amt. Incl. VAT") + { + ToolTip = 'Specifies the total amount including VAT computed from all lines of the draft.'; + Importance = Promoted; } field("Amount Excl. VAT"; EDocumentPurchaseHeader."Sub Total") { @@ -780,7 +789,7 @@ page 6181 "E-Document Purchase Draft" EDocumentProcessing: Codeunit "E-Document Processing"; FeatureTelemetry: Codeunit "Feature Telemetry"; GlobalEDocumentHelper: Codeunit "E-Document Helper"; - RecordLinkTxt, StyleStatusTxt, DataCaption: Text; + RecordLinkTxt, StyleStatusTxt, DataCaption : Text; HasErrorsOrWarnings, HasErrors : Boolean; ShowFinalizeDraftAction: Boolean; ShowAnalyzeDocumentAction: Boolean; diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseHeader.Table.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseHeader.Table.al index 082f4aed64..e9d56aaa5e 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseHeader.Table.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseHeader.Table.al @@ -8,6 +8,7 @@ using Microsoft.eServices.EDocument; using Microsoft.eServices.EDocument.Processing.Import; using Microsoft.Purchases.Document; using Microsoft.Purchases.Vendor; +using System.Log; using System.Telemetry; table 6100 "E-Document Purchase Header" @@ -242,6 +243,22 @@ table 6100 "E-Document Purchase Header" AutoFormatExpression = Rec."Currency Code"; Editable = false; } + field(42; "Total Line VAT Amount"; Decimal) + { + Caption = 'Total Line VAT Amount'; + DataClassification = CustomerContent; + AutoFormatType = 1; + AutoFormatExpression = Rec."Currency Code"; + Editable = false; + } + field(43; "Total Line Amt. Incl. VAT"; Decimal) + { + Caption = 'Total Line Amt. Incl. VAT'; + DataClassification = CustomerContent; + AutoFormatType = 1; + AutoFormatExpression = Rec."Currency Code"; + Editable = false; + } #endregion Purchase fields #region Business Central Data - Validated fields [101-200] @@ -298,6 +315,42 @@ table 6100 "E-Document Purchase Header" if Vendor.Get(Rec."[BC] Vendor No.") then; end; + internal procedure LogHeaderLinesMismatch() + var + ActivityLog: Codeunit "Activity Log Builder"; + Reasoning: Text[250]; + SubTotalMismatchReasonLbl: Label 'The document Sub Total %1 does not match the computed Total Line Amount %2.', Comment = '%1 = Sub Total from document, %2 = computed Total Line Amount'; + VATMismatchReasonLbl: Label 'The document Total VAT %1 does not match the computed Total Line VAT Amount %2.', Comment = '%1 = Total VAT from document, %2 = computed Total Line VAT Amount'; + TotalMismatchReasonLbl: Label 'The document Total %1 does not match the computed Total Line Amt. Incl. VAT %2.', Comment = '%1 = Total from document, %2 = computed Total Line Amt. Incl. VAT'; + begin + if (Rec."Sub Total" <> 0) and (Rec."Sub Total" <> Rec."Total Line Amount") then begin + Reasoning := CopyStr(StrSubstNo(SubTotalMismatchReasonLbl, Rec."Sub Total", Rec."Total Line Amount"), 1, MaxStrLen(Reasoning)); + ActivityLog + .Init(Database::"E-Document Purchase Header", Rec.FieldNo("Total Line Amount"), Rec.SystemId) + .SetExplanation(Reasoning) + .SetType(Enum::"Activity Log Type"::"AL") + .Log(); + end; + + if (Rec."Total VAT" <> 0) and (Rec."Total VAT" <> Rec."Total Line VAT Amount") then begin + Reasoning := CopyStr(StrSubstNo(VATMismatchReasonLbl, Rec."Total VAT", Rec."Total Line VAT Amount"), 1, MaxStrLen(Reasoning)); + ActivityLog + .Init(Database::"E-Document Purchase Header", Rec.FieldNo("Total Line VAT Amount"), Rec.SystemId) + .SetExplanation(Reasoning) + .SetType(Enum::"Activity Log Type"::"AL") + .Log(); + end; + + if (Rec.Total <> 0) and (Rec.Total <> Rec."Total Line Amt. Incl. VAT") then begin + Reasoning := CopyStr(StrSubstNo(TotalMismatchReasonLbl, Rec.Total, Rec."Total Line Amt. Incl. VAT"), 1, MaxStrLen(Reasoning)); + ActivityLog + .Init(Database::"E-Document Purchase Header", Rec.FieldNo("Total Line Amt. Incl. VAT"), Rec.SystemId) + .SetExplanation(Reasoning) + .SetType(Enum::"Activity Log Type"::"AL") + .Log(); + end; + end; + internal procedure FeatureName(): Text begin exit('E-Document Matching Assistance'); diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al index 0b21701c84..97e125d9cd 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al @@ -458,6 +458,8 @@ table 6101 "E-Document Purchase Line" VATRateMismatchReasonLbl: Label 'VAT rate %1% extracted from the document could not be matched to a VAT Posting Setup for vendor''s VAT Business Posting Group %2.', Comment = '%1 = extracted VAT rate %, %2 = VAT Bus. Posting Group code'; VATRateMismatchTitleLbl: Label 'VAT Posting Setup for %1', Comment = '%1 = VAT Bus. Posting Group code'; begin + if not Rec."[BC] VAT Rate Mismatch" then + exit; VATPostingSetup.SetRange("VAT Bus. Posting Group", VendVATBusPostingGroupCode); VATPostingSetup.SetFilter("VAT Calculation Type", '%1|%2', VATPostingSetup."VAT Calculation Type"::"Normal VAT", @@ -469,7 +471,7 @@ table 6101 "E-Document Purchase Line" ActivityLog .Init(Database::"E-Document Purchase Line", Rec.FieldNo("[BC] VAT Prod. Posting Group"), Rec.SystemId) .SetExplanation(Reasoning) - .SetType(Enum::"Activity Log Type"::"AL") + .SetType(Enum::"Activity Log Type"::AL) .SetReferenceSource(Page::"VAT Posting Setup", VATPostingSetupRef) .SetReferenceTitle(StrSubstNo(VATRateMismatchTitleLbl, VendVATBusPostingGroupCode)) .Log(); From 39a937948abd8df00ee8e3e06014e6b4e34f86b0 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Mon, 11 May 2026 12:37:25 +0200 Subject: [PATCH 42/53] VAT amount diff --- .../EDocPurchPayablesSetup.PageExt.al | 5 ++ .../EDocPurchPayablesSetup.TableExt.al | 6 ++ .../EDocCreatePurchaseInvoice.Codeunit.al | 1 + .../EDocPurchDocHelper.Codeunit.al | 37 ++++++++++ .../EDocPreparePurchDraft.Codeunit.al | 68 +++++++++++++++++++ .../Purchase/EDocumentPurchaseDraft.Page.al | 42 ++++++++++++ .../Purchase/EDocumentPurchaseHeader.Table.al | 62 ++++++++--------- 7 files changed, 188 insertions(+), 33 deletions(-) diff --git a/src/Apps/W1/EDocument/App/src/Extensions/EDocPurchPayablesSetup.PageExt.al b/src/Apps/W1/EDocument/App/src/Extensions/EDocPurchPayablesSetup.PageExt.al index 08b2a7a822..aa6b7b07c8 100644 --- a/src/Apps/W1/EDocument/App/src/Extensions/EDocPurchPayablesSetup.PageExt.al +++ b/src/Apps/W1/EDocument/App/src/Extensions/EDocPurchPayablesSetup.PageExt.al @@ -21,6 +21,11 @@ pageextension 6162 "E-Doc. Purch. Payables Setup" extends "Purchases & Payables ApplicationArea = All; ToolTip = 'Specifies whether Copilot E-Document line matchings are learned by default (Item References and Text To Account Mappings). This can be overwritten on the matching page.'; } + field("Apply VAT Diff. For Purch EDoc"; Rec."Apply VAT Diff. For Purch EDoc") + { + ApplicationArea = All; + ToolTip = 'Specifies whether VAT difference should be applied when matching incoming E-Document line with Purchase Order line'; + } } } } \ No newline at end of file diff --git a/src/Apps/W1/EDocument/App/src/Extensions/EDocPurchPayablesSetup.TableExt.al b/src/Apps/W1/EDocument/App/src/Extensions/EDocPurchPayablesSetup.TableExt.al index 24013c63c5..142c428dac 100644 --- a/src/Apps/W1/EDocument/App/src/Extensions/EDocPurchPayablesSetup.TableExt.al +++ b/src/Apps/W1/EDocument/App/src/Extensions/EDocPurchPayablesSetup.TableExt.al @@ -21,5 +21,11 @@ tableextension 6162 "E-Doc. Purch. Payables Setup" extends "Purchases & Payables Caption = 'E-Document Learn Copilot Matchings'; DataClassification = SystemMetadata; } + field(6102; "Apply VAT Diff. For Purch EDoc"; Boolean) + { + Caption = 'Apply VAT Diff. For Purch. E-Doc.'; + DataClassification = CustomerContent; + InitValue = true; + } } } diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocCreatePurchaseInvoice.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocCreatePurchaseInvoice.Codeunit.al index 0f2b7905d4..7e92650aa8 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocCreatePurchaseInvoice.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocCreatePurchaseInvoice.Codeunit.al @@ -175,6 +175,7 @@ codeunit 6117 "E-Doc. Create Purchase Invoice" implements IEDocumentFinishDraft, LastReceiptNo := EDocLineByReceipt.ReceiptNo; end; EDocLineByReceipt.Close(); + EDocPurchaseDocumentHelper.ApplyVATDifferenceToLines(PurchaseHeader, EDocumentPurchaseHeader."Applied VAT Amount Diff.", EDocumentPurchaseHeader."Total Line Amount"); PurchaseHeader.Modify(); PurchCalcDiscByType.ApplyInvDiscBasedOnAmt(EDocumentPurchaseHeader."Total Discount", PurchaseHeader); exit(PurchaseHeader); diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocPurchDocHelper.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocPurchDocHelper.Codeunit.al index b57c8579e3..ccc9559a9e 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocPurchDocHelper.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocPurchDocHelper.Codeunit.al @@ -7,6 +7,7 @@ namespace Microsoft.eServices.EDocument.Processing.Import; using Microsoft.eServices.EDocument; using Microsoft.eServices.EDocument.Processing; using Microsoft.eServices.EDocument.Processing.Import.Purchase; +using Microsoft.Finance.Currency; using Microsoft.Finance.Dimension; using Microsoft.Foundation.Attachment; using Microsoft.Purchases.Document; @@ -124,6 +125,42 @@ codeunit 6402 "E-Doc. Purch. Doc. Helper" EDocImpSessionTelemetry.SetBool('Totals Validation', TryValidateDocumentTotals(PurchaseHeader)); end; + procedure ApplyVATDifferenceToLines(PurchaseHeader: Record "Purchase Header"; AppliedVATAmountDiff: Decimal; TotalLineAmount: Decimal) + var + PurchaseLine: Record "Purchase Line"; + Currency: Record Currency; + LineAmount: Decimal; + VATDiffRemainder: Decimal; + VATDiffForLine: Decimal; + begin + if AppliedVATAmountDiff = 0 then + exit; + if TotalLineAmount = 0 then + exit; + + if PurchaseHeader."Currency Code" = '' then + Currency.InitRoundingPrecision() + else + Currency.Get(PurchaseHeader."Currency Code"); + + PurchaseLine.SetRange("Document Type", PurchaseHeader."Document Type"); + PurchaseLine.SetRange("Document No.", PurchaseHeader."No."); + PurchaseLine.SetFilter(Type, '<>%1', PurchaseLine.Type::" "); + if not PurchaseLine.FindSet() then + exit; + + VATDiffRemainder := 0; + repeat + LineAmount := PurchaseLine."Line Amount" - PurchaseLine."Inv. Discount Amount"; + if LineAmount <> 0 then begin + VATDiffForLine := VATDiffRemainder + AppliedVATAmountDiff * LineAmount / TotalLineAmount; + PurchaseLine."VAT Difference" := Round(VATDiffForLine, Currency."Amount Rounding Precision"); + VATDiffRemainder := VATDiffForLine - PurchaseLine."VAT Difference"; + PurchaseLine.Modify(); + end; + until PurchaseLine.Next() = 0; + end; + procedure RevertCreatedDocument(EDocument: Record "E-Document") var PurchaseHeader: Record "Purchase Header"; diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al index 6debcf689d..ca27648e99 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al @@ -8,9 +8,11 @@ using Microsoft.eServices.EDocument; using Microsoft.eServices.EDocument.Processing.AI; using Microsoft.eServices.EDocument.Processing.Import.Purchase; using Microsoft.eServices.EDocument.Processing.Interfaces; +using Microsoft.Finance.GeneralLedger.Setup; using Microsoft.Finance.VAT.Setup; using Microsoft.Foundation.UOM; using Microsoft.Purchases.Document; +using Microsoft.Purchases.Setup; using Microsoft.Purchases.Vendor; using System.Log; @@ -97,6 +99,8 @@ codeunit 6406 "EDoc Prepare Purch. Draft" // Log telemetry and activity sessions EDocImpSessionTelemetry.SetLine(EDocumentPurchaseLine.SystemId); until EDocumentPurchaseLine.Next() = 0; + + ComputeAndApplyVATAmountDifference(EDocumentPurchaseHeader); EDocumentPurchaseHeader.LogHeaderLinesMismatch(); EDocumentPurchaseHeader.Modify(); @@ -246,4 +250,68 @@ codeunit 6406 "EDoc Prepare Purch. Draft" if Codeunit.Run(Codeunit::"E-Doc. Deferral Matching", EDocumentPurchaseLine) then; end; end; + + local procedure ComputeAndApplyVATAmountDifference(var EDocumentPurchaseHeader: Record "E-Document Purchase Header") + var + PurchasesPayablesSetup: Record "Purchases & Payables Setup"; + GeneralLedgerSetup: Record "General Ledger Setup"; + ActivityLog: Codeunit "Activity Log Builder"; + VATAmountDiff: Decimal; + Reasoning: Text[250]; + VATDiffAppliedLbl: Label 'Applied VAT amount difference of %1 to reconcile document Total VAT %2 with computed Total Line VAT Amount %3.', Comment = '%1 = VAT difference, %2 = Total VAT, %3 = Total Line VAT Amount'; + VATDiffSkippedSetupLbl: Label 'VAT amount difference of %1 was not applied because Apply VAT Diff. For Purch. E-Doc. is disabled in Purchases & Payables Setup.', Comment = '%1 = VAT difference'; + VATDiffSkippedAllowLbl: Label 'VAT amount difference of %1 was not applied because Allow VAT Difference is disabled in Purchases & Payables Setup.', Comment = '%1 = VAT difference'; + VATDiffSkippedMaxLbl: Label 'VAT amount difference of %1 was not applied because it exceeds the Max. VAT Difference Allowed of %2 in General Ledger Setup.', Comment = '%1 = VAT difference, %2 = Max. VAT Difference Allowed'; + begin + EDocumentPurchaseHeader."Applied VAT Amount Diff." := 0; + + if (EDocumentPurchaseHeader."Total VAT" = 0) or (EDocumentPurchaseHeader."Total Line VAT Amount" = EDocumentPurchaseHeader."Total VAT") then + exit; + + VATAmountDiff := EDocumentPurchaseHeader."Total VAT" - EDocumentPurchaseHeader."Total Line VAT Amount"; + + if not PurchasesPayablesSetup.Get() then + exit; + + if not PurchasesPayablesSetup."Apply VAT Diff. For Purch EDoc" then begin + Reasoning := CopyStr(StrSubstNo(VATDiffSkippedSetupLbl, VATAmountDiff), 1, MaxStrLen(Reasoning)); + ActivityLog + .Init(Database::"E-Document Purchase Header", EDocumentPurchaseHeader.FieldNo("Applied VAT Amount Diff."), EDocumentPurchaseHeader.SystemId) + .SetExplanation(Reasoning) + .SetType(Enum::"Activity Log Type"::"AL") + .Log(); + exit; + end; + + if not PurchasesPayablesSetup."Allow VAT Difference" then begin + Reasoning := CopyStr(StrSubstNo(VATDiffSkippedAllowLbl, VATAmountDiff), 1, MaxStrLen(Reasoning)); + ActivityLog + .Init(Database::"E-Document Purchase Header", EDocumentPurchaseHeader.FieldNo("Applied VAT Amount Diff."), EDocumentPurchaseHeader.SystemId) + .SetExplanation(Reasoning) + .SetType(Enum::"Activity Log Type"::"AL") + .Log(); + exit; + end; + + if not GeneralLedgerSetup.Get() then + exit; + if Abs(VATAmountDiff) > GeneralLedgerSetup."Max. VAT Difference Allowed" then begin + Reasoning := CopyStr(StrSubstNo(VATDiffSkippedMaxLbl, VATAmountDiff, GeneralLedgerSetup."Max. VAT Difference Allowed"), 1, MaxStrLen(Reasoning)); + ActivityLog + .Init(Database::"E-Document Purchase Header", EDocumentPurchaseHeader.FieldNo("Applied VAT Amount Diff."), EDocumentPurchaseHeader.SystemId) + .SetExplanation(Reasoning) + .SetType(Enum::"Activity Log Type"::"AL") + .Log(); + exit; + end; + + EDocumentPurchaseHeader."Applied VAT Amount Diff." := VATAmountDiff; + + Reasoning := CopyStr(StrSubstNo(VATDiffAppliedLbl, VATAmountDiff, EDocumentPurchaseHeader."Total VAT", EDocumentPurchaseHeader."Total Line VAT Amount"), 1, MaxStrLen(Reasoning)); + ActivityLog + .Init(Database::"E-Document Purchase Header", EDocumentPurchaseHeader.FieldNo("Applied VAT Amount Diff."), EDocumentPurchaseHeader.SystemId) + .SetExplanation(Reasoning) + .SetType(Enum::"Activity Log Type"::"AL") + .Log(); + end; } diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al index bef5fbf759..ea97fb7906 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al @@ -204,16 +204,42 @@ page 6181 "E-Document Purchase Draft" { ToolTip = 'Specifies the total of the lines amount values from all lines of the draft.'; Importance = Promoted; + StyleExpr = LineAmountMismatchStyle; + + trigger OnDrillDown() + begin + Message('TBD'); + end; } field("Total Line VAT Amount"; EDocumentPurchaseHeader."Total Line VAT Amount") { ToolTip = 'Specifies the total VAT amount computed from all lines of the draft.'; Importance = Promoted; + StyleExpr = VATAmountMismatchStyle; + + trigger OnDrillDown() + begin + Message('TBD'); + end; } field("Total Line Amt. Incl. VAT"; EDocumentPurchaseHeader."Total Line Amt. Incl. VAT") { ToolTip = 'Specifies the total amount including VAT computed from all lines of the draft.'; Importance = Promoted; + StyleExpr = TotalMismatchStyle; + + trigger OnDrillDown() + begin + Message('TBD'); + end; + } + field("Applied VAT Amount Diff."; EDocumentPurchaseHeader."Applied VAT Amount Diff.") + { + Caption = 'Applied VAT Amount Diff.'; + ToolTip = 'Specifies the VAT amount difference that was automatically applied to reconcile the document total VAT with the computed line VAT amounts.'; + Importance = Additional; + Editable = false; + Visible = EDocumentPurchaseHeader."Applied VAT Amount Diff." <> 0; } field("Amount Excl. VAT"; EDocumentPurchaseHeader."Sub Total") { @@ -561,6 +587,7 @@ page 6181 "E-Document Purchase Draft" ClearErrorsAndWarnings(); SetStyle(); + UpdateMismatchStyles(); SetPageCaption(); Rec.CalcFields("Import Processing Status"); @@ -604,6 +631,20 @@ page 6181 "E-Document Purchase Draft" end; end; + local procedure UpdateMismatchStyles() + begin + LineAmountMismatchStyle := GetMismatchStyle(EDocumentPurchaseHeader."Line Amount Mismatch"); + VATAmountMismatchStyle := GetMismatchStyle(EDocumentPurchaseHeader."VAT Amount Mismatch"); + TotalMismatchStyle := GetMismatchStyle(EDocumentPurchaseHeader."Total Mismatch"); + end; + + local procedure GetMismatchStyle(IsMismatch: Boolean): Text + begin + if IsMismatch then + exit('Unfavorable'); + exit(''); + end; + local procedure UpdateTotal() begin EDocumentPurchaseHeader.Total := EDocumentPurchaseHeader."Sub Total" - EDocumentPurchaseHeader."Total Discount" + EDocumentPurchaseHeader."Total VAT"; @@ -798,4 +839,5 @@ page 6181 "E-Document Purchase Draft" ProcessingDocumentMsg: Label 'Processing document...'; ResetDraftQst: Label 'All the changes that you may have made on the document draft will be lost. Do you want to continue?'; PageEditable, HasPDFSource, IsCreditMemo : Boolean; + LineAmountMismatchStyle, VATAmountMismatchStyle, TotalMismatchStyle : Text; } diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseHeader.Table.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseHeader.Table.al index e9d56aaa5e..5977ea16fa 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseHeader.Table.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseHeader.Table.al @@ -8,7 +8,6 @@ using Microsoft.eServices.EDocument; using Microsoft.eServices.EDocument.Processing.Import; using Microsoft.Purchases.Document; using Microsoft.Purchases.Vendor; -using System.Log; using System.Telemetry; table 6100 "E-Document Purchase Header" @@ -259,6 +258,32 @@ table 6100 "E-Document Purchase Header" AutoFormatExpression = Rec."Currency Code"; Editable = false; } + field(44; "Applied VAT Amount Diff."; Decimal) + { + Caption = 'Applied VAT Amount Diff.'; + DataClassification = CustomerContent; + AutoFormatType = 1; + AutoFormatExpression = Rec."Currency Code"; + Editable = false; + } + field(45; "Line Amount Mismatch"; Boolean) + { + Caption = 'Line Amount Mismatch'; + DataClassification = SystemMetadata; + Editable = false; + } + field(46; "VAT Amount Mismatch"; Boolean) + { + Caption = 'VAT Amount Mismatch'; + DataClassification = SystemMetadata; + Editable = false; + } + field(47; "Total Mismatch"; Boolean) + { + Caption = 'Total Mismatch'; + DataClassification = SystemMetadata; + Editable = false; + } #endregion Purchase fields #region Business Central Data - Validated fields [101-200] @@ -316,39 +341,10 @@ table 6100 "E-Document Purchase Header" end; internal procedure LogHeaderLinesMismatch() - var - ActivityLog: Codeunit "Activity Log Builder"; - Reasoning: Text[250]; - SubTotalMismatchReasonLbl: Label 'The document Sub Total %1 does not match the computed Total Line Amount %2.', Comment = '%1 = Sub Total from document, %2 = computed Total Line Amount'; - VATMismatchReasonLbl: Label 'The document Total VAT %1 does not match the computed Total Line VAT Amount %2.', Comment = '%1 = Total VAT from document, %2 = computed Total Line VAT Amount'; - TotalMismatchReasonLbl: Label 'The document Total %1 does not match the computed Total Line Amt. Incl. VAT %2.', Comment = '%1 = Total from document, %2 = computed Total Line Amt. Incl. VAT'; begin - if (Rec."Sub Total" <> 0) and (Rec."Sub Total" <> Rec."Total Line Amount") then begin - Reasoning := CopyStr(StrSubstNo(SubTotalMismatchReasonLbl, Rec."Sub Total", Rec."Total Line Amount"), 1, MaxStrLen(Reasoning)); - ActivityLog - .Init(Database::"E-Document Purchase Header", Rec.FieldNo("Total Line Amount"), Rec.SystemId) - .SetExplanation(Reasoning) - .SetType(Enum::"Activity Log Type"::"AL") - .Log(); - end; - - if (Rec."Total VAT" <> 0) and (Rec."Total VAT" <> Rec."Total Line VAT Amount") then begin - Reasoning := CopyStr(StrSubstNo(VATMismatchReasonLbl, Rec."Total VAT", Rec."Total Line VAT Amount"), 1, MaxStrLen(Reasoning)); - ActivityLog - .Init(Database::"E-Document Purchase Header", Rec.FieldNo("Total Line VAT Amount"), Rec.SystemId) - .SetExplanation(Reasoning) - .SetType(Enum::"Activity Log Type"::"AL") - .Log(); - end; - - if (Rec.Total <> 0) and (Rec.Total <> Rec."Total Line Amt. Incl. VAT") then begin - Reasoning := CopyStr(StrSubstNo(TotalMismatchReasonLbl, Rec.Total, Rec."Total Line Amt. Incl. VAT"), 1, MaxStrLen(Reasoning)); - ActivityLog - .Init(Database::"E-Document Purchase Header", Rec.FieldNo("Total Line Amt. Incl. VAT"), Rec.SystemId) - .SetExplanation(Reasoning) - .SetType(Enum::"Activity Log Type"::"AL") - .Log(); - end; + Rec."Line Amount Mismatch" := (Rec."Sub Total" <> 0) and (Rec."Sub Total" <> Rec."Total Line Amount"); + Rec."VAT Amount Mismatch" := (Rec."Total VAT" <> 0) and (Rec."Total VAT" <> Rec."Total Line VAT Amount"); + Rec."Total Mismatch" := (Rec.Total <> 0) and (Rec.Total <> Rec."Total Line Amt. Incl. VAT"); end; internal procedure FeatureName(): Text From 20f51b041fdf5e99805b93d14c5badfdecf345f7 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Mon, 11 May 2026 12:56:17 +0200 Subject: [PATCH 43/53] update the page --- .../Purchase/EDocumentPurchaseDraft.Page.al | 66 ++++++++++--------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al index ea97fb7906..8b952e85cc 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al @@ -200,47 +200,26 @@ page 6181 "E-Document Purchase Draft" group("E-Document Details") { ShowCaption = false; + field("Applied VAT Amount Diff."; EDocumentPurchaseHeader."Applied VAT Amount Diff.") + { + Caption = 'Applied VAT Amount Diff.'; + ToolTip = 'Specifies the VAT amount difference that was automatically applied to reconcile the document total VAT with the computed line VAT amounts.'; + Importance = Additional; + Editable = false; + Visible = ShowApplieVATAmtDiff; + } field("Total Line Amount"; EDocumentPurchaseHeader."Total Line Amount") { ToolTip = 'Specifies the total of the lines amount values from all lines of the draft.'; Importance = Promoted; StyleExpr = LineAmountMismatchStyle; + Editable = false; trigger OnDrillDown() begin Message('TBD'); end; } - field("Total Line VAT Amount"; EDocumentPurchaseHeader."Total Line VAT Amount") - { - ToolTip = 'Specifies the total VAT amount computed from all lines of the draft.'; - Importance = Promoted; - StyleExpr = VATAmountMismatchStyle; - - trigger OnDrillDown() - begin - Message('TBD'); - end; - } - field("Total Line Amt. Incl. VAT"; EDocumentPurchaseHeader."Total Line Amt. Incl. VAT") - { - ToolTip = 'Specifies the total amount including VAT computed from all lines of the draft.'; - Importance = Promoted; - StyleExpr = TotalMismatchStyle; - - trigger OnDrillDown() - begin - Message('TBD'); - end; - } - field("Applied VAT Amount Diff."; EDocumentPurchaseHeader."Applied VAT Amount Diff.") - { - Caption = 'Applied VAT Amount Diff.'; - ToolTip = 'Specifies the VAT amount difference that was automatically applied to reconcile the document total VAT with the computed line VAT amounts.'; - Importance = Additional; - Editable = false; - Visible = EDocumentPurchaseHeader."Applied VAT Amount Diff." <> 0; - } field("Amount Excl. VAT"; EDocumentPurchaseHeader."Sub Total") { Caption = 'Amount Excl. VAT'; @@ -267,6 +246,18 @@ page 6181 "E-Document Purchase Draft" CurrPage.Update(); end; } + field("Total Line VAT Amount"; EDocumentPurchaseHeader."Total Line VAT Amount") + { + ToolTip = 'Specifies the total VAT amount computed from all lines of the draft.'; + Importance = Promoted; + StyleExpr = VATAmountMismatchStyle; + Editable = false; + + trigger OnDrillDown() + begin + Message('TBD'); + end; + } field("Total VAT"; EDocumentPurchaseHeader."Total VAT") { Caption = 'Total VAT'; @@ -280,6 +271,18 @@ page 6181 "E-Document Purchase Draft" CurrPage.Update(); end; } + field("Total Line Amt. Incl. VAT"; EDocumentPurchaseHeader."Total Line Amt. Incl. VAT") + { + ToolTip = 'Specifies the total amount including VAT computed from all lines of the draft.'; + Importance = Promoted; + StyleExpr = TotalMismatchStyle; + Editable = false; + + trigger OnDrillDown() + begin + Message('TBD'); + end; + } field("Amount Incl. VAT"; EDocumentPurchaseHeader.Total) { Caption = 'Amount Incl. VAT'; @@ -551,6 +554,7 @@ page 6181 "E-Document Purchase Draft" MatchesRemovedMsg: Label 'This e-document was matched to purchase order lines, but the matches are no longer consistent with the current data. The matches have been removed'; begin if EDocumentPurchaseHeader.Get(Rec."Entry No") then; + ShowApplieVATAmtDiff := EDocumentPurchaseHeader."Applied VAT Amount Diff." <> 0; if not EDocPOMatching.IsPOMatchConsistent(EDocumentPurchaseHeader) then begin EDocPOMatching.RemoveAllMatchesForEDocument(EDocumentPurchaseHeader); Message(MatchesRemovedMsg); @@ -838,6 +842,6 @@ page 6181 "E-Document Purchase Draft" FinalizeDraftPerformedTxt: Label 'User completed Finalize Draft action.', Locked = true; ProcessingDocumentMsg: Label 'Processing document...'; ResetDraftQst: Label 'All the changes that you may have made on the document draft will be lost. Do you want to continue?'; - PageEditable, HasPDFSource, IsCreditMemo : Boolean; + PageEditable, HasPDFSource, IsCreditMemo, ShowApplieVATAmtDiff : Boolean; LineAmountMismatchStyle, VATAmountMismatchStyle, TotalMismatchStyle : Text; } From 2e8171c48985df7fbb4266b4817b6eb5c3d1b9c1 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Fri, 15 May 2026 11:05:44 +0200 Subject: [PATCH 44/53] revert header to line inconsistency. part 1 --- .../EDocPreparePurchDraft.Codeunit.al | 1 - .../Purchase/EDocPurchaseDraftSubform.Page.al | 1 - .../Purchase/EDocumentPurchaseDraft.Page.al | 52 ------------------- .../Purchase/EDocumentPurchaseHeader.Table.al | 25 --------- 4 files changed, 79 deletions(-) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al index ca27648e99..ebf8801f3f 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al @@ -101,7 +101,6 @@ codeunit 6406 "EDoc Prepare Purch. Draft" until EDocumentPurchaseLine.Next() = 0; ComputeAndApplyVATAmountDifference(EDocumentPurchaseHeader); - EDocumentPurchaseHeader.LogHeaderLinesMismatch(); EDocumentPurchaseHeader.Modify(); LogAllActivitySessionChanges(EDocActivityLogSession); diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al index 30721d4585..35636b31b4 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al @@ -435,7 +435,6 @@ page 6183 "E-Doc. Purchase Draft Subform" EDocumentPurchaseHeader."Total Line VAT Amount" += LineVATAmount; EDocumentPurchaseHeader."Total Line Amt. Incl. VAT" += LineAmount + LineVATAmount; until TotalEDocPurchaseLine.Next() = 0; - EDocumentPurchaseHeader.LogHeaderLinesMismatch(); EDocumentPurchaseHeader.Modify(); CurrPage.Update(); end; diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al index 8b952e85cc..69e7011b5c 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al @@ -208,18 +208,6 @@ page 6181 "E-Document Purchase Draft" Editable = false; Visible = ShowApplieVATAmtDiff; } - field("Total Line Amount"; EDocumentPurchaseHeader."Total Line Amount") - { - ToolTip = 'Specifies the total of the lines amount values from all lines of the draft.'; - Importance = Promoted; - StyleExpr = LineAmountMismatchStyle; - Editable = false; - - trigger OnDrillDown() - begin - Message('TBD'); - end; - } field("Amount Excl. VAT"; EDocumentPurchaseHeader."Sub Total") { Caption = 'Amount Excl. VAT'; @@ -246,18 +234,6 @@ page 6181 "E-Document Purchase Draft" CurrPage.Update(); end; } - field("Total Line VAT Amount"; EDocumentPurchaseHeader."Total Line VAT Amount") - { - ToolTip = 'Specifies the total VAT amount computed from all lines of the draft.'; - Importance = Promoted; - StyleExpr = VATAmountMismatchStyle; - Editable = false; - - trigger OnDrillDown() - begin - Message('TBD'); - end; - } field("Total VAT"; EDocumentPurchaseHeader."Total VAT") { Caption = 'Total VAT'; @@ -271,18 +247,6 @@ page 6181 "E-Document Purchase Draft" CurrPage.Update(); end; } - field("Total Line Amt. Incl. VAT"; EDocumentPurchaseHeader."Total Line Amt. Incl. VAT") - { - ToolTip = 'Specifies the total amount including VAT computed from all lines of the draft.'; - Importance = Promoted; - StyleExpr = TotalMismatchStyle; - Editable = false; - - trigger OnDrillDown() - begin - Message('TBD'); - end; - } field("Amount Incl. VAT"; EDocumentPurchaseHeader.Total) { Caption = 'Amount Incl. VAT'; @@ -591,7 +555,6 @@ page 6181 "E-Document Purchase Draft" ClearErrorsAndWarnings(); SetStyle(); - UpdateMismatchStyles(); SetPageCaption(); Rec.CalcFields("Import Processing Status"); @@ -635,20 +598,6 @@ page 6181 "E-Document Purchase Draft" end; end; - local procedure UpdateMismatchStyles() - begin - LineAmountMismatchStyle := GetMismatchStyle(EDocumentPurchaseHeader."Line Amount Mismatch"); - VATAmountMismatchStyle := GetMismatchStyle(EDocumentPurchaseHeader."VAT Amount Mismatch"); - TotalMismatchStyle := GetMismatchStyle(EDocumentPurchaseHeader."Total Mismatch"); - end; - - local procedure GetMismatchStyle(IsMismatch: Boolean): Text - begin - if IsMismatch then - exit('Unfavorable'); - exit(''); - end; - local procedure UpdateTotal() begin EDocumentPurchaseHeader.Total := EDocumentPurchaseHeader."Sub Total" - EDocumentPurchaseHeader."Total Discount" + EDocumentPurchaseHeader."Total VAT"; @@ -843,5 +792,4 @@ page 6181 "E-Document Purchase Draft" ProcessingDocumentMsg: Label 'Processing document...'; ResetDraftQst: Label 'All the changes that you may have made on the document draft will be lost. Do you want to continue?'; PageEditable, HasPDFSource, IsCreditMemo, ShowApplieVATAmtDiff : Boolean; - LineAmountMismatchStyle, VATAmountMismatchStyle, TotalMismatchStyle : Text; } diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseHeader.Table.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseHeader.Table.al index 5977ea16fa..c24430581f 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseHeader.Table.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseHeader.Table.al @@ -266,24 +266,6 @@ table 6100 "E-Document Purchase Header" AutoFormatExpression = Rec."Currency Code"; Editable = false; } - field(45; "Line Amount Mismatch"; Boolean) - { - Caption = 'Line Amount Mismatch'; - DataClassification = SystemMetadata; - Editable = false; - } - field(46; "VAT Amount Mismatch"; Boolean) - { - Caption = 'VAT Amount Mismatch'; - DataClassification = SystemMetadata; - Editable = false; - } - field(47; "Total Mismatch"; Boolean) - { - Caption = 'Total Mismatch'; - DataClassification = SystemMetadata; - Editable = false; - } #endregion Purchase fields #region Business Central Data - Validated fields [101-200] @@ -340,13 +322,6 @@ table 6100 "E-Document Purchase Header" if Vendor.Get(Rec."[BC] Vendor No.") then; end; - internal procedure LogHeaderLinesMismatch() - begin - Rec."Line Amount Mismatch" := (Rec."Sub Total" <> 0) and (Rec."Sub Total" <> Rec."Total Line Amount"); - Rec."VAT Amount Mismatch" := (Rec."Total VAT" <> 0) and (Rec."Total VAT" <> Rec."Total Line VAT Amount"); - Rec."Total Mismatch" := (Rec.Total <> 0) and (Rec.Total <> Rec."Total Line Amt. Incl. VAT"); - end; - internal procedure FeatureName(): Text begin exit('E-Document Matching Assistance'); From eee3358fdf6704441cf6d94105225db7c4b5f89d Mon Sep 17 00:00:00 2001 From: ventselartur Date: Fri, 15 May 2026 13:00:44 +0200 Subject: [PATCH 45/53] remove remaining referenes to the header to lines inconsistency --- .claude/agents/astred-explorer.md | 1 + .claude/agents/bc-al-env-setup.md | 1 + .claude/agents/bc-dme.md | 1 + .claude/agents/bc-fix-baseline.md | 1 + .claude/agents/bc-fix-implement.md | 1 + .claude/agents/bc-fix-pr.md | 1 + .claude/agents/bc-test-implementor.md | 1 + .claude/agents/page-script-generator.md | 1 + .claude/skills/astred-al-init | 1 + .claude/skills/astred-codegraph | 1 + .claude/skills/astred-diff | 1 + .claude/skills/astred-rebuild | 1 + .claude/skills/astred-search | 1 + .claude/skills/bc-altools | 1 + .claude/skills/bc-appdev-dashboard | 1 + .claude/skills/bc-appdev-monitor | 1 + .claude/skills/bc-delocalize | 1 + .claude/skills/bc-delocalize-inventory | 1 + .claude/skills/bc-fix-bug | 1 + .claude/skills/bc-planbug | 1 + .claude/skills/create-pr | 1 + .claude/skills/page-script | 1 + .claude/skills/page-script-doc | 1 + .claude/skills/page-script-run | 1 + .claude/skills/pr-status | 1 + .claude/skills/repro-steps | 1 + .claude/skills/review-ado-pr | 1 + .claude/skills/review-pr | 1 + .claude/skills/session-retrospective | 1 + .claude/skills/update-pr | 1 + .../EDocCreatePurchaseInvoice.Codeunit.al | 2 +- .../EDocPurchDocHelper.Codeunit.al | 24 +++++++++++++++---- .../EDocPreparePurchDraft.Codeunit.al | 21 +++++++--------- .../Purchase/EDocPurchaseDraftSubform.Page.al | 15 ------------ .../Purchase/EDocumentPurchaseDraft.Page.al | 5 ++-- .../Purchase/EDocumentPurchaseHeader.Table.al | 24 ------------------- 36 files changed, 60 insertions(+), 61 deletions(-) create mode 120000 .claude/agents/astred-explorer.md create mode 120000 .claude/agents/bc-al-env-setup.md create mode 120000 .claude/agents/bc-dme.md create mode 120000 .claude/agents/bc-fix-baseline.md create mode 120000 .claude/agents/bc-fix-implement.md create mode 120000 .claude/agents/bc-fix-pr.md create mode 120000 .claude/agents/bc-test-implementor.md create mode 120000 .claude/agents/page-script-generator.md create mode 120000 .claude/skills/astred-al-init create mode 120000 .claude/skills/astred-codegraph create mode 120000 .claude/skills/astred-diff create mode 120000 .claude/skills/astred-rebuild create mode 120000 .claude/skills/astred-search create mode 120000 .claude/skills/bc-altools create mode 120000 .claude/skills/bc-appdev-dashboard create mode 120000 .claude/skills/bc-appdev-monitor create mode 120000 .claude/skills/bc-delocalize create mode 120000 .claude/skills/bc-delocalize-inventory create mode 120000 .claude/skills/bc-fix-bug create mode 120000 .claude/skills/bc-planbug create mode 120000 .claude/skills/create-pr create mode 120000 .claude/skills/page-script create mode 120000 .claude/skills/page-script-doc create mode 120000 .claude/skills/page-script-run create mode 120000 .claude/skills/pr-status create mode 120000 .claude/skills/repro-steps create mode 120000 .claude/skills/review-ado-pr create mode 120000 .claude/skills/review-pr create mode 120000 .claude/skills/session-retrospective create mode 120000 .claude/skills/update-pr diff --git a/.claude/agents/astred-explorer.md b/.claude/agents/astred-explorer.md new file mode 120000 index 0000000000..c1a977f08d --- /dev/null +++ b/.claude/agents/astred-explorer.md @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/agents/astred-explorer.md \ No newline at end of file diff --git a/.claude/agents/bc-al-env-setup.md b/.claude/agents/bc-al-env-setup.md new file mode 120000 index 0000000000..9dbecab4dc --- /dev/null +++ b/.claude/agents/bc-al-env-setup.md @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/agents/bc-al-env-setup.md \ No newline at end of file diff --git a/.claude/agents/bc-dme.md b/.claude/agents/bc-dme.md new file mode 120000 index 0000000000..36dc649aa9 --- /dev/null +++ b/.claude/agents/bc-dme.md @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/agents/bc-dme.md \ No newline at end of file diff --git a/.claude/agents/bc-fix-baseline.md b/.claude/agents/bc-fix-baseline.md new file mode 120000 index 0000000000..5c2ef6b6ea --- /dev/null +++ b/.claude/agents/bc-fix-baseline.md @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/agents/bc-fix-baseline.md \ No newline at end of file diff --git a/.claude/agents/bc-fix-implement.md b/.claude/agents/bc-fix-implement.md new file mode 120000 index 0000000000..91e381b3d1 --- /dev/null +++ b/.claude/agents/bc-fix-implement.md @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/agents/bc-fix-implement.md \ No newline at end of file diff --git a/.claude/agents/bc-fix-pr.md b/.claude/agents/bc-fix-pr.md new file mode 120000 index 0000000000..b149dd700f --- /dev/null +++ b/.claude/agents/bc-fix-pr.md @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/agents/bc-fix-pr.md \ No newline at end of file diff --git a/.claude/agents/bc-test-implementor.md b/.claude/agents/bc-test-implementor.md new file mode 120000 index 0000000000..d359f7def8 --- /dev/null +++ b/.claude/agents/bc-test-implementor.md @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/agents/bc-test-implementor.md \ No newline at end of file diff --git a/.claude/agents/page-script-generator.md b/.claude/agents/page-script-generator.md new file mode 120000 index 0000000000..e9944c9e4a --- /dev/null +++ b/.claude/agents/page-script-generator.md @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/agents/page-script-generator.md \ No newline at end of file diff --git a/.claude/skills/astred-al-init b/.claude/skills/astred-al-init new file mode 120000 index 0000000000..227b4a7f71 --- /dev/null +++ b/.claude/skills/astred-al-init @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/astred/astred-al-init \ No newline at end of file diff --git a/.claude/skills/astred-codegraph b/.claude/skills/astred-codegraph new file mode 120000 index 0000000000..917687feec --- /dev/null +++ b/.claude/skills/astred-codegraph @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/astred/astred-codegraph \ No newline at end of file diff --git a/.claude/skills/astred-diff b/.claude/skills/astred-diff new file mode 120000 index 0000000000..509c33066d --- /dev/null +++ b/.claude/skills/astred-diff @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/astred/astred-diff \ No newline at end of file diff --git a/.claude/skills/astred-rebuild b/.claude/skills/astred-rebuild new file mode 120000 index 0000000000..f6f7c6c63a --- /dev/null +++ b/.claude/skills/astred-rebuild @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/astred/astred-rebuild \ No newline at end of file diff --git a/.claude/skills/astred-search b/.claude/skills/astred-search new file mode 120000 index 0000000000..bfef8d1b9e --- /dev/null +++ b/.claude/skills/astred-search @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/astred/astred-search \ No newline at end of file diff --git a/.claude/skills/bc-altools b/.claude/skills/bc-altools new file mode 120000 index 0000000000..cfc346c928 --- /dev/null +++ b/.claude/skills/bc-altools @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/aibusinesssolutions/claude/skills/bc-altools \ No newline at end of file diff --git a/.claude/skills/bc-appdev-dashboard b/.claude/skills/bc-appdev-dashboard new file mode 120000 index 0000000000..5684d85025 --- /dev/null +++ b/.claude/skills/bc-appdev-dashboard @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/bc-appdev-dashboard \ No newline at end of file diff --git a/.claude/skills/bc-appdev-monitor b/.claude/skills/bc-appdev-monitor new file mode 120000 index 0000000000..7201de701c --- /dev/null +++ b/.claude/skills/bc-appdev-monitor @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/bc-appdev-monitor \ No newline at end of file diff --git a/.claude/skills/bc-delocalize b/.claude/skills/bc-delocalize new file mode 120000 index 0000000000..e2fe4545bf --- /dev/null +++ b/.claude/skills/bc-delocalize @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/bc-delocalization/bc-delocalize \ No newline at end of file diff --git a/.claude/skills/bc-delocalize-inventory b/.claude/skills/bc-delocalize-inventory new file mode 120000 index 0000000000..9655662122 --- /dev/null +++ b/.claude/skills/bc-delocalize-inventory @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/bc-delocalization/bc-delocalize-inventory \ No newline at end of file diff --git a/.claude/skills/bc-fix-bug b/.claude/skills/bc-fix-bug new file mode 120000 index 0000000000..f8b5115690 --- /dev/null +++ b/.claude/skills/bc-fix-bug @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/bc-fix-bug \ No newline at end of file diff --git a/.claude/skills/bc-planbug b/.claude/skills/bc-planbug new file mode 120000 index 0000000000..126c50e766 --- /dev/null +++ b/.claude/skills/bc-planbug @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/bc-planbug \ No newline at end of file diff --git a/.claude/skills/create-pr b/.claude/skills/create-pr new file mode 120000 index 0000000000..ab3cede611 --- /dev/null +++ b/.claude/skills/create-pr @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/source-control/create-pr \ No newline at end of file diff --git a/.claude/skills/page-script b/.claude/skills/page-script new file mode 120000 index 0000000000..88c2640199 --- /dev/null +++ b/.claude/skills/page-script @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/bc-page-scripting/page-script \ No newline at end of file diff --git a/.claude/skills/page-script-doc b/.claude/skills/page-script-doc new file mode 120000 index 0000000000..b4bd101e57 --- /dev/null +++ b/.claude/skills/page-script-doc @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/bc-page-scripting/page-script-doc \ No newline at end of file diff --git a/.claude/skills/page-script-run b/.claude/skills/page-script-run new file mode 120000 index 0000000000..3d2737a031 --- /dev/null +++ b/.claude/skills/page-script-run @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/bc-page-scripting/page-script-run \ No newline at end of file diff --git a/.claude/skills/pr-status b/.claude/skills/pr-status new file mode 120000 index 0000000000..c56e77dd40 --- /dev/null +++ b/.claude/skills/pr-status @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/pr-status \ No newline at end of file diff --git a/.claude/skills/repro-steps b/.claude/skills/repro-steps new file mode 120000 index 0000000000..a67df98024 --- /dev/null +++ b/.claude/skills/repro-steps @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/aldobriansky/claude/skills/repro-steps \ No newline at end of file diff --git a/.claude/skills/review-ado-pr b/.claude/skills/review-ado-pr new file mode 120000 index 0000000000..2936a6afbf --- /dev/null +++ b/.claude/skills/review-ado-pr @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/aldobriansky/claude/skills/review-ado-pr \ No newline at end of file diff --git a/.claude/skills/review-pr b/.claude/skills/review-pr new file mode 120000 index 0000000000..be47fd912e --- /dev/null +++ b/.claude/skills/review-pr @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/source-control/review-pr \ No newline at end of file diff --git a/.claude/skills/session-retrospective b/.claude/skills/session-retrospective new file mode 120000 index 0000000000..ee2ed43877 --- /dev/null +++ b/.claude/skills/session-retrospective @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/session-retrospective \ No newline at end of file diff --git a/.claude/skills/update-pr b/.claude/skills/update-pr new file mode 120000 index 0000000000..e0cb441376 --- /dev/null +++ b/.claude/skills/update-pr @@ -0,0 +1 @@ +C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/source-control/update-pr \ No newline at end of file diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocCreatePurchaseInvoice.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocCreatePurchaseInvoice.Codeunit.al index 7e92650aa8..eb27462b7a 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocCreatePurchaseInvoice.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocCreatePurchaseInvoice.Codeunit.al @@ -175,7 +175,7 @@ codeunit 6117 "E-Doc. Create Purchase Invoice" implements IEDocumentFinishDraft, LastReceiptNo := EDocLineByReceipt.ReceiptNo; end; EDocLineByReceipt.Close(); - EDocPurchaseDocumentHelper.ApplyVATDifferenceToLines(PurchaseHeader, EDocumentPurchaseHeader."Applied VAT Amount Diff.", EDocumentPurchaseHeader."Total Line Amount"); + EDocPurchaseDocumentHelper.ApplyVATDifferenceToLines(PurchaseHeader, EDocumentPurchaseHeader); PurchaseHeader.Modify(); PurchCalcDiscByType.ApplyInvDiscBasedOnAmt(EDocumentPurchaseHeader."Total Discount", PurchaseHeader); exit(PurchaseHeader); diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocPurchDocHelper.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocPurchDocHelper.Codeunit.al index ccc9559a9e..e47fb8ce8b 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocPurchDocHelper.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocPurchDocHelper.Codeunit.al @@ -125,16 +125,17 @@ codeunit 6402 "E-Doc. Purch. Doc. Helper" EDocImpSessionTelemetry.SetBool('Totals Validation', TryValidateDocumentTotals(PurchaseHeader)); end; - procedure ApplyVATDifferenceToLines(PurchaseHeader: Record "Purchase Header"; AppliedVATAmountDiff: Decimal; TotalLineAmount: Decimal) + procedure ApplyVATDifferenceToLines(PurchaseHeader: Record "Purchase Header"; EDocumentPurchaseHeader: Record "E-Document Purchase Header") var PurchaseLine: Record "Purchase Line"; Currency: Record Currency; LineAmount: Decimal; - VATDiffRemainder: Decimal; - VATDiffForLine: Decimal; + TotalLineAmount, VATDiffRemainder, VATDiffForLine : Decimal; begin - if AppliedVATAmountDiff = 0 then + if EDocumentPurchaseHeader."Applied VAT Amount Diff." = 0 then exit; + + TotalLineAmount := ComputeTotalLineAmount(EDocumentPurchaseHeader."E-Document Entry No."); if TotalLineAmount = 0 then exit; @@ -153,7 +154,7 @@ codeunit 6402 "E-Doc. Purch. Doc. Helper" repeat LineAmount := PurchaseLine."Line Amount" - PurchaseLine."Inv. Discount Amount"; if LineAmount <> 0 then begin - VATDiffForLine := VATDiffRemainder + AppliedVATAmountDiff * LineAmount / TotalLineAmount; + VATDiffForLine := VATDiffRemainder + EDocumentPurchaseHeader."Applied VAT Amount Diff." * LineAmount / TotalLineAmount; PurchaseLine."VAT Difference" := Round(VATDiffForLine, Currency."Amount Rounding Precision"); VATDiffRemainder := VATDiffForLine - PurchaseLine."VAT Difference"; PurchaseLine.Modify(); @@ -176,4 +177,17 @@ codeunit 6402 "E-Doc. Purch. Doc. Helper" Clear(PurchaseHeader."E-Document Link"); PurchaseHeader.Modify(); end; + + local procedure ComputeTotalLineAmount(EDocEntryNo: Integer): Decimal + var + EDocPurchLine: Record "E-Document Purchase Line"; + TotalLineAmount: Decimal; + begin + EDocPurchLine.SetRange("E-Document Entry No.", EDocEntryNo); + if EDocPurchLine.FindSet() then + repeat + TotalLineAmount += Round(EDocPurchLine.Quantity * EDocPurchLine."Unit Price" - EDocPurchLine."Total Discount"); + until EDocPurchLine.Next() = 0; + exit(TotalLineAmount); + end; } diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al index ebf8801f3f..55274e0f71 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al @@ -41,6 +41,7 @@ codeunit 6406 "EDoc Prepare Purch. Draft" IPurchaseOrderProvider: Interface IPurchaseOrderProvider; LineAmount: Decimal; LineVATAmount: Decimal; + TotalLineVATAmount: Decimal; begin IUnitOfMeasureProvider := EDocImportParameters."Processing Customizations"; IPurchaseLineProvider := EDocImportParameters."Processing Customizations"; @@ -85,22 +86,16 @@ codeunit 6406 "EDoc Prepare Purch. Draft" Clear(EDocumentPurchaseLine); EDocumentPurchaseLine.SetRange("E-Document Entry No.", EDocument."Entry No"); - EDocumentPurchaseHeader."Total Line Amount" := 0; - EDocumentPurchaseHeader."Total Line VAT Amount" := 0; - EDocumentPurchaseHeader."Total Line Amt. Incl. VAT" := 0; + TotalLineVATAmount := 0; if EDocumentPurchaseLine.FindSet() then repeat - // Update total line amounts on the header LineAmount := Round(EDocumentPurchaseLine.Quantity * EDocumentPurchaseLine."Unit Price" - EDocumentPurchaseLine."Total Discount"); LineVATAmount := Round(LineAmount * EDocumentPurchaseLine."VAT Rate" / 100); - EDocumentPurchaseHeader."Total Line Amount" += LineAmount; - EDocumentPurchaseHeader."Total Line VAT Amount" += LineVATAmount; - EDocumentPurchaseHeader."Total Line Amt. Incl. VAT" += LineAmount + LineVATAmount; - // Log telemetry and activity sessions + TotalLineVATAmount += LineVATAmount; EDocImpSessionTelemetry.SetLine(EDocumentPurchaseLine.SystemId); until EDocumentPurchaseLine.Next() = 0; - ComputeAndApplyVATAmountDifference(EDocumentPurchaseHeader); + ComputeAndApplyVATAmountDifference(EDocumentPurchaseHeader, TotalLineVATAmount); EDocumentPurchaseHeader.Modify(); LogAllActivitySessionChanges(EDocActivityLogSession); @@ -250,7 +245,7 @@ codeunit 6406 "EDoc Prepare Purch. Draft" end; end; - local procedure ComputeAndApplyVATAmountDifference(var EDocumentPurchaseHeader: Record "E-Document Purchase Header") + local procedure ComputeAndApplyVATAmountDifference(var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; TotalLineVATAmount: Decimal) var PurchasesPayablesSetup: Record "Purchases & Payables Setup"; GeneralLedgerSetup: Record "General Ledger Setup"; @@ -264,10 +259,10 @@ codeunit 6406 "EDoc Prepare Purch. Draft" begin EDocumentPurchaseHeader."Applied VAT Amount Diff." := 0; - if (EDocumentPurchaseHeader."Total VAT" = 0) or (EDocumentPurchaseHeader."Total Line VAT Amount" = EDocumentPurchaseHeader."Total VAT") then + if (EDocumentPurchaseHeader."Total VAT" = 0) or (TotalLineVATAmount = EDocumentPurchaseHeader."Total VAT") then exit; - VATAmountDiff := EDocumentPurchaseHeader."Total VAT" - EDocumentPurchaseHeader."Total Line VAT Amount"; + VATAmountDiff := EDocumentPurchaseHeader."Total VAT" - TotalLineVATAmount; if not PurchasesPayablesSetup.Get() then exit; @@ -306,7 +301,7 @@ codeunit 6406 "EDoc Prepare Purch. Draft" EDocumentPurchaseHeader."Applied VAT Amount Diff." := VATAmountDiff; - Reasoning := CopyStr(StrSubstNo(VATDiffAppliedLbl, VATAmountDiff, EDocumentPurchaseHeader."Total VAT", EDocumentPurchaseHeader."Total Line VAT Amount"), 1, MaxStrLen(Reasoning)); + Reasoning := CopyStr(StrSubstNo(VATDiffAppliedLbl, VATAmountDiff, EDocumentPurchaseHeader."Total VAT", TotalLineVATAmount), 1, MaxStrLen(Reasoning)); ActivityLog .Init(Database::"E-Document Purchase Header", EDocumentPurchaseHeader.FieldNo("Applied VAT Amount Diff."), EDocumentPurchaseHeader.SystemId) .SetExplanation(Reasoning) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al index 35636b31b4..c34fdbcda3 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al @@ -404,9 +404,7 @@ page 6183 "E-Doc. Purchase Draft Subform" local procedure UpdateCalculatedAmounts(UpdateParentRecord: Boolean) var - TotalEDocPurchaseLine: Record "E-Document Purchase Line"; LineSubtotal: Decimal; - LineVATAmount: Decimal; DiscountExceedsSubtotalErr: Label 'Discount should not exceed the subtotal of the line'; begin LineSubtotal := Rec.Quantity * Rec."Unit Price"; @@ -422,19 +420,6 @@ page 6183 "E-Doc. Purchase Draft Subform" exit; if not EDocumentPurchaseHeader.Get(Rec."E-Document Entry No.") then exit; - EDocumentPurchaseHeader."Total Line Amount" := 0; - EDocumentPurchaseHeader."Total Line VAT Amount" := 0; - EDocumentPurchaseHeader."Total Line Amt. Incl. VAT" := 0; - TotalEDocPurchaseLine.SetRange("E-Document Entry No.", Rec."E-Document Entry No."); - if TotalEDocPurchaseLine.FindSet() then - repeat - LineSubtotal := TotalEDocPurchaseLine.Quantity * TotalEDocPurchaseLine."Unit Price"; - LineAmount := LineSubtotal - TotalEDocPurchaseLine."Total Discount"; - LineVATAmount := Round(LineAmount * TotalEDocPurchaseLine."VAT Rate" / 100); - EDocumentPurchaseHeader."Total Line Amount" += LineAmount; - EDocumentPurchaseHeader."Total Line VAT Amount" += LineVATAmount; - EDocumentPurchaseHeader."Total Line Amt. Incl. VAT" += LineAmount + LineVATAmount; - until TotalEDocPurchaseLine.Next() = 0; EDocumentPurchaseHeader.Modify(); CurrPage.Update(); end; diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al index 69e7011b5c..f61e4e3880 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al @@ -206,7 +206,7 @@ page 6181 "E-Document Purchase Draft" ToolTip = 'Specifies the VAT amount difference that was automatically applied to reconcile the document total VAT with the computed line VAT amounts.'; Importance = Additional; Editable = false; - Visible = ShowApplieVATAmtDiff; + Visible = EDocumentPurchaseHeader."Applied VAT Amount Diff." <> 0; } field("Amount Excl. VAT"; EDocumentPurchaseHeader."Sub Total") { @@ -518,7 +518,6 @@ page 6181 "E-Document Purchase Draft" MatchesRemovedMsg: Label 'This e-document was matched to purchase order lines, but the matches are no longer consistent with the current data. The matches have been removed'; begin if EDocumentPurchaseHeader.Get(Rec."Entry No") then; - ShowApplieVATAmtDiff := EDocumentPurchaseHeader."Applied VAT Amount Diff." <> 0; if not EDocPOMatching.IsPOMatchConsistent(EDocumentPurchaseHeader) then begin EDocPOMatching.RemoveAllMatchesForEDocument(EDocumentPurchaseHeader); Message(MatchesRemovedMsg); @@ -791,5 +790,5 @@ page 6181 "E-Document Purchase Draft" FinalizeDraftPerformedTxt: Label 'User completed Finalize Draft action.', Locked = true; ProcessingDocumentMsg: Label 'Processing document...'; ResetDraftQst: Label 'All the changes that you may have made on the document draft will be lost. Do you want to continue?'; - PageEditable, HasPDFSource, IsCreditMemo, ShowApplieVATAmtDiff : Boolean; + PageEditable, HasPDFSource, IsCreditMemo : Boolean; } diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseHeader.Table.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseHeader.Table.al index c24430581f..9a05d6ddd2 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseHeader.Table.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseHeader.Table.al @@ -234,30 +234,6 @@ table 6100 "E-Document Purchase Header" Caption = 'Vendor Invoice No.'; DataClassification = CustomerContent; } - field(41; "Total Line Amount"; Decimal) - { - Caption = 'Total Line Amount'; - DataClassification = CustomerContent; - AutoFormatType = 1; - AutoFormatExpression = Rec."Currency Code"; - Editable = false; - } - field(42; "Total Line VAT Amount"; Decimal) - { - Caption = 'Total Line VAT Amount'; - DataClassification = CustomerContent; - AutoFormatType = 1; - AutoFormatExpression = Rec."Currency Code"; - Editable = false; - } - field(43; "Total Line Amt. Incl. VAT"; Decimal) - { - Caption = 'Total Line Amt. Incl. VAT'; - DataClassification = CustomerContent; - AutoFormatType = 1; - AutoFormatExpression = Rec."Currency Code"; - Editable = false; - } field(44; "Applied VAT Amount Diff."; Decimal) { Caption = 'Applied VAT Amount Diff.'; From 0582b39ab7691c8c080d98ff12d9c1f961381756 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Tue, 19 May 2026 22:46:53 +0200 Subject: [PATCH 46/53] replace direct validation of VAT Product Posting group to the one with context --- .../Import/FinishDraft/EDocPurchDocHelper.Codeunit.al | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocPurchDocHelper.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocPurchDocHelper.Codeunit.al index eb47e53b06..1f2691dcfe 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocPurchDocHelper.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocPurchDocHelper.Codeunit.al @@ -42,7 +42,8 @@ codeunit 6402 "E-Doc. Purch. Doc. Helper" PurchaseLine.Type := EDocumentPurchaseLine."[BC] Purchase Line Type"; ValidateFieldWithContext(PurchaseLine, PurchaseLine.FieldNo("No."), EDocumentPurchaseLine."[BC] Purchase Type No."); if EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" <> '' then - PurchaseLine.Validate("VAT Prod. Posting Group", EDocumentPurchaseLine."[BC] VAT Prod. Posting Group"); + ValidateFieldWithContext( + PurchaseLine, PurchaseLine.FieldNo("VAT Prod. Posting Group"), EDocumentPurchaseLine."[BC] VAT Prod. Posting Group"); if (PurchaseLine.Type = PurchaseLine.Type::"G/L Account") and HasTotalDiscount then ValidateFieldWithContext(PurchaseLine, PurchaseLine.FieldNo("Allow Invoice Disc."), true); PurchaseLine.Description := EDocumentPurchaseLine.Description; @@ -221,7 +222,7 @@ codeunit 6402 "E-Doc. Purch. Doc. Helper" exit; PurchaseHeader.Validate("Posting Date", EDocumentPurchaseHeader."Document Date"); end; - + local procedure ComputeTotalLineAmount(EDocEntryNo: Integer): Decimal var EDocPurchLine: Record "E-Document Purchase Line"; From 2cc30fddc35cc48c48456e05bd89c72659f7887f Mon Sep 17 00:00:00 2001 From: ventselartur Date: Tue, 19 May 2026 23:40:06 +0200 Subject: [PATCH 47/53] removeing unnecessary things --- .../Purchases/EDocStandardPurchaseOrder.docx | Bin 60895 -> 0 bytes .../EDocCoreObjects.PermissionSet.al | 5 +- .../EDocReportingTriggers.Codeunit.al | 18 - .../EDocStandardPurchaseOrder.Report.al | 1284 ----------------- .../Purchase/EDocPurchaseDraftSubform.Page.al | 23 - 5 files changed, 1 insertion(+), 1329 deletions(-) delete mode 100644 src/Apps/W1/EDocument/App/.resources/Template/Purchases/EDocStandardPurchaseOrder.docx delete mode 100644 src/Apps/W1/EDocument/App/src/Document/EDocReportingTriggers.Codeunit.al delete mode 100644 src/Apps/W1/EDocument/App/src/Document/EDocStandardPurchaseOrder.Report.al diff --git a/src/Apps/W1/EDocument/App/.resources/Template/Purchases/EDocStandardPurchaseOrder.docx b/src/Apps/W1/EDocument/App/.resources/Template/Purchases/EDocStandardPurchaseOrder.docx deleted file mode 100644 index 1296cc2d33b104e58c7f4b1555815a55a0f754aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60895 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%0D*9=_T*woIH1GIaQEN%@9TJnz%{-(^$MUV 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 '' then - ItemNo := "Vendor Item No."; - - if "Item Reference No." <> '' then - ItemNo := "Item Reference No."; - - FormatDocument.SetPurchaseLine("Purchase Line", FormattedQuanitity, FormattedDirectUnitCost, FormattedVATPct, FormattedLineAmount); - - if FirstLineHasBeenOutput then - Clear(DummyCompanyInfo.Picture); - FirstLineHasBeenOutput := true; - end; - - trigger OnPreDataItem() - var - LastPurchaseLine: Record "Purchase Line"; - begin - FirstLineHasBeenOutput := false; - DummyCompanyInfo.Picture := CompanyInfo.Picture; - - LastPurchaseLineNo := 0; - if OmitLastLine then begin - LastPurchaseLine.SetRange("Document Type", "Purchase Header"."Document Type"); - LastPurchaseLine.SetRange("Document No.", "Purchase Header"."No."); - if LastPurchaseLine.FindLast() then - LastPurchaseLineNo := LastPurchaseLine."Line No."; - end; - end; - } - dataitem(Totals; "Integer") - { - DataItemTableView = sorting(Number) where(Number = const(1)); - column(VATAmountText; TempVATAmountLine.VATAmountText()) - { - } - column(TotalVATAmount; VATAmount) - { - AutoFormatExpression = "Purchase Header"."Currency Code"; - AutoFormatType = 1; - } - column(TotalVATDiscountAmount; -VATDiscountAmount) - { - AutoFormatExpression = "Purchase Header"."Currency Code"; - AutoFormatType = 1; - } - column(TotalVATBaseAmount; VATBaseAmount) - { - AutoFormatExpression = "Purchase Header"."Currency Code"; - AutoFormatType = 1; - } - column(TotalAmountInclVAT; TotalAmountInclVAT) - { - AutoFormatExpression = "Purchase Header"."Currency Code"; - AutoFormatType = 1; - } - column(TotalInclVATText; TotalInclVATText) - { - } - column(TotalExclVATText; TotalExclVATText) - { - } - column(TotalSubTotal; TotalSubTotal) - { - AutoFormatExpression = "Purchase Header"."Currency Code"; - AutoFormatType = 1; - } - column(TotalInvoiceDiscountAmount; TotalInvoiceDiscountAmount) - { - AutoFormatExpression = "Purchase Header"."Currency Code"; - AutoFormatType = 1; - } - column(TotalAmount; TotalAmount) - { - AutoFormatExpression = "Purchase Header"."Currency Code"; - AutoFormatType = 1; - } - column(TotalText; TotalText) - { - } - - trigger OnAfterGetRecord() - var - TempPrepmtPurchLine: Record "Purchase Line" temporary; - begin - FirstLineHasBeenOutput := false; - Clear(TempPurchLine); - Clear(PurchPost); - TempPurchLine.DeleteAll(); - TempVATAmountLine.DeleteAll(); - PurchPost.GetPurchLines("Purchase Header", TempPurchLine, 0); - TempPurchLine.CalcVATAmountLines(0, "Purchase Header", TempPurchLine, TempVATAmountLine); - TempPurchLine.UpdateVATOnLines(0, "Purchase Header", TempPurchLine, TempVATAmountLine); - VATAmount := TempVATAmountLine.GetTotalVATAmount(); - VATBaseAmount := TempVATAmountLine.GetTotalVATBase(); - VATDiscountAmount := - TempVATAmountLine.GetTotalVATDiscount("Purchase Header"."Currency Code", "Purchase Header"."Prices Including VAT"); - TotalAmountInclVAT := TempVATAmountLine.GetTotalAmountInclVAT(); - if VATAmountDifference then begin - TotalAmountInclVAT -= 0.01; - VATAmount -= 0.01; - end; - - TempPrepaymentInvLineBuffer.DeleteAll(); - PurchasePostPrepayments.GetPurchLines("Purchase Header", 0, TempPrepmtPurchLine); - if not TempPrepmtPurchLine.IsEmpty() then begin - PurchasePostPrepayments.GetPurchLinesToDeduct("Purchase Header", TempPurchLine); - if not TempPurchLine.IsEmpty() then - PurchasePostPrepayments.CalcVATAmountLines("Purchase Header", TempPurchLine, TempPrePmtVATAmountLineDeduct, 1); - end; - PurchasePostPrepayments.CalcVATAmountLines("Purchase Header", TempPrepmtPurchLine, TempPrepmtVATAmountLine, 0); - TempPrepmtVATAmountLine.DeductVATAmountLine(TempPrePmtVATAmountLineDeduct); - PurchasePostPrepayments.UpdateVATOnLines("Purchase Header", TempPrepmtPurchLine, TempPrepmtVATAmountLine, 0); - PurchasePostPrepayments.BuildInvLineBuffer("Purchase Header", TempPrepmtPurchLine, 0, TempPrepaymentInvLineBuffer); - PrepmtVATAmount := TempPrepmtVATAmountLine.GetTotalVATAmount(); - PrepmtVATBaseAmount := TempPrepmtVATAmountLine.GetTotalVATBase(); - PrepmtTotalAmountInclVAT := TempPrepmtVATAmountLine.GetTotalAmountInclVAT(); - end; - } - dataitem(VATCounter; "Integer") - { - DataItemTableView = sorting(Number); - column(VATAmtLineVATBase; TempVATAmountLine."VAT Base") - { - AutoFormatExpression = "Purchase Header"."Currency Code"; - AutoFormatType = 1; - } - column(VATAmtLineVATAmt; TempVATAmountLine."VAT Amount") - { - AutoFormatExpression = "Purchase Header"."Currency Code"; - AutoFormatType = 1; - } - column(VATAmtLineLineAmt; TempVATAmountLine."Line Amount") - { - AutoFormatExpression = "Purchase Header"."Currency Code"; - AutoFormatType = 1; - } - column(VATAmtLineInvDiscBaseAmt; TempVATAmountLine."Inv. Disc. Base Amount") - { - AutoFormatExpression = "Purchase Header"."Currency Code"; - AutoFormatType = 1; - } - column(VATAmtLineInvDiscAmt; TempVATAmountLine."Invoice Discount Amount") - { - AutoFormatExpression = "Purchase Header"."Currency Code"; - AutoFormatType = 1; - } - column(VATAmtLineVAT; TempVATAmountLine."VAT %") - { - DecimalPlaces = 0 : 5; - } - column(VATAmtLineVATIdentifier; TempVATAmountLine."VAT Identifier") - { - } - - trigger OnAfterGetRecord() - begin - TempVATAmountLine.GetLine(Number); - end; - - trigger OnPreDataItem() - begin - if VATAmount = 0 then - CurrReport.Break(); - SetRange(Number, 1, TempVATAmountLine.Count); - end; - } - dataitem(VATCounterLCY; "Integer") - { - DataItemTableView = sorting(Number); - column(VALExchRate; VALExchRate) - { - } - column(VALSpecLCYHeader; VALSpecLCYHeader) - { - } - column(VALVATAmountLCY; VALVATAmountLCY) - { - AutoFormatType = 1; - } - column(VALVATBaseLCY; VALVATBaseLCY) - { - AutoFormatType = 1; - } - - trigger OnAfterGetRecord() - begin - TempVATAmountLine.GetLine(Number); - VALVATBaseLCY := - TempVATAmountLine.GetBaseLCY( - "Purchase Header"."Posting Date", "Purchase Header"."Currency Code", "Purchase Header"."Currency Factor"); - VALVATAmountLCY := - TempVATAmountLine.GetAmountLCY( - "Purchase Header"."Posting Date", "Purchase Header"."Currency Code", "Purchase Header"."Currency Factor"); - end; - - trigger OnPreDataItem() - begin - if (not GLSetup."Print VAT specification in LCY") or - ("Purchase Header"."Currency Code" = '') or - (TempVATAmountLine.GetTotalVATAmount() = 0) - then - CurrReport.Break(); - - SetRange(Number, 1, TempVATAmountLine.Count); - - if GLSetup."LCY Code" = '' then - VALSpecLCYHeader := VATAmountSpecificationLbl + LocalCurrentyLbl - else - VALSpecLCYHeader := VATAmountSpecificationLbl + Format(GLSetup."LCY Code"); - - CurrExchRate.FindCurrency("Purchase Header"."Posting Date", "Purchase Header"."Currency Code", 1); - VALExchRate := StrSubstNo(ExchangeRateLbl, CurrExchRate."Relational Exch. Rate Amount", CurrExchRate."Exchange Rate Amount"); - end; - } - dataitem(PrepmtLoop; "Integer") - { - DataItemTableView = sorting(Number) where(Number = filter(1 ..)); - column(PrepmtLineAmount; PrepmtLineAmount) - { - AutoFormatExpression = "Purchase Header"."Currency Code"; - AutoFormatType = 1; - } - column(PrepmtInvBufGLAccNo; TempPrepaymentInvLineBuffer."G/L Account No.") - { - } - column(PrepmtInvBufDesc; TempPrepaymentInvLineBuffer.Description) - { - } - column(TotalInclVATText2; TotalInclVATText) - { - } - column(TotalExclVATText2; TotalExclVATText) - { - } - column(PrepmtInvBufAmt; TempPrepaymentInvLineBuffer.Amount) - { - AutoFormatExpression = "Purchase Header"."Currency Code"; - AutoFormatType = 1; - } - column(PrepmtVATAmountText; TempPrepmtVATAmountLine.VATAmountText()) - { - } - column(PrepmtVATAmount; PrepmtVATAmount) - { - AutoFormatExpression = "Purchase Header"."Currency Code"; - AutoFormatType = 1; - } - column(PrepmtTotalAmountInclVAT; PrepmtTotalAmountInclVAT) - { - AutoFormatExpression = "Purchase Header"."Currency Code"; - AutoFormatType = 1; - } - column(PrepmtVATBaseAmount; PrepmtVATBaseAmount) - { - AutoFormatExpression = "Purchase Header"."Currency Code"; - AutoFormatType = 1; - } - column(PrepmtInvBuDescCaption; PrepmtInvBuDescCaptionLbl) - { - } - column(PrepmtInvBufGLAccNoCaption; PrepmtInvBufGLAccNoCaptionLbl) - { - } - column(PrepaymentSpecCaption; PrepaymentSpecCaptionLbl) - { - } - - trigger OnAfterGetRecord() - begin - if Number = 1 then begin - if not TempPrepaymentInvLineBuffer.Find('-') then - CurrReport.Break(); - end else - if TempPrepaymentInvLineBuffer.Next() = 0 then - CurrReport.Break(); - - if "Purchase Header"."Prices Including VAT" then - PrepmtLineAmount := TempPrepaymentInvLineBuffer."Amount Incl. VAT" - else - PrepmtLineAmount := TempPrepaymentInvLineBuffer.Amount; - end; - } - dataitem(PrepmtVATCounter; "Integer") - { - DataItemTableView = sorting(Number); - column(PrepmtVATAmtLineVATAmt; TempPrepmtVATAmountLine."VAT Amount") - { - AutoFormatExpression = "Purchase Header"."Currency Code"; - AutoFormatType = 1; - } - column(PrepmtVATAmtLineVATBase; TempPrepmtVATAmountLine."VAT Base") - { - AutoFormatExpression = "Purchase Header"."Currency Code"; - AutoFormatType = 1; - } - column(PrepmtVATAmtLineLineAmt; TempPrepmtVATAmountLine."Line Amount") - { - AutoFormatExpression = "Purchase Header"."Currency Code"; - AutoFormatType = 1; - } - column(PrepmtVATAmtLineVAT; TempPrepmtVATAmountLine."VAT %") - { - DecimalPlaces = 0 : 5; - } - column(PrepmtVATAmtLineVATId; TempPrepmtVATAmountLine."VAT Identifier") - { - } - column(PrepymtVATAmtSpecCaption; PrepymtVATAmtSpecCaptionLbl) - { - } - - trigger OnAfterGetRecord() - begin - TempPrepmtVATAmountLine.GetLine(Number); - end; - - trigger OnPreDataItem() - begin - SetRange(Number, 1, TempPrepmtVATAmountLine.Count); - end; - } - dataitem(LetterText; "Integer") - { - DataItemTableView = sorting(Number) where(Number = const(1)); - column(GreetingText; GreetingLbl) - { - } - column(BodyText; BodyLbl) - { - } - column(ClosingText; ClosingLbl) - { - } - } - - trigger OnAfterGetRecord() - begin - FirstLineHasBeenOutput := false; - TotalAmount := 0; - TotalSubTotal := 0; - TotalInvoiceDiscountAmount := 0; - CurrReport.Language := LanguageMgt.GetLanguageIdOrDefault("Language Code"); - CurrReport.FormatRegion := LanguageMgt.GetFormatRegionOrDefault("Format Region"); - FormatAddr.SetLanguageCode("Language Code"); - - FormatAddressFields("Purchase Header"); - FormatDocumentFields("Purchase Header"); - if BuyFromContact.Get("Buy-from Contact No.") then; - if PayToContact.Get("Pay-to Contact No.") then; - - if not IsReportInPreviewMode() then begin - CODEUNIT.Run(CODEUNIT::"Purch.Header-Printed", "Purchase Header"); - if ArchiveDocument then - ArchiveManagement.StorePurchDocument("Purchase Header", LogInteraction); - end; - end; - - trigger OnPreDataItem() - begin - FirstLineHasBeenOutput := false; - end; - } - } - - requestpage - { - SaveValues = true; - - layout - { - area(content) - { - group(Options) - { - Caption = 'Options'; - field(ArchiveDocumentField; ArchiveDocument) - { - ApplicationArea = Suite; - Caption = 'Archive Document'; - ToolTip = 'Specifies whether to archive the order.'; - } - field(LogInteractionField; LogInteraction) - { - ApplicationArea = Suite; - Caption = 'Log Interaction'; - Enabled = LogInteractionEnable; - ToolTip = 'Specifies if you want to log this interaction.'; - } - field(OmitLastLineField; OmitLastLine) - { - ApplicationArea = Suite; - Caption = 'Omit last line'; - ToolTip = 'Specifies whether the last purchase line is excluded from the printed document.'; - } - field(VATAmountDifferenceField; VATAmountDifference) - { - ApplicationArea = Suite; - Caption = 'VAT Amount Difference'; - ToolTip = 'Specifies whether the total VAT amount and the total amount in the footer are each decreased by 0.01.'; - } - } - } - } - - actions - { - } - - trigger OnInit() - begin - InitLogInteraction(); - LogInteractionEnable := LogInteraction; - ArchiveDocument := PurchSetup."Archive Orders"; - end; - } - - rendering - { - layout("EDocStandardPurchaseOrder.docx") - { - Type = Word; - LayoutFile = './.resources/Template/Purchases/EDocStandardPurchaseOrder.docx'; - Caption = 'Standard Purchase Order (Word)'; - Summary = 'The Standard Purchase Order (Word) provides a basic layout.'; - } - } - - labels - { - } - - trigger OnInitReport() - var - IsHandled: Boolean; - begin - GLSetup.Get(); - CompanyInfo.SetAutoCalcFields(Picture); - CompanyInfo.Get(); - PurchSetup.Get(); - - IsHandled := false; - OnInitReportForGlobalVariable(IsHandled, LegalOfficeTxt, LegalOfficeLbl, CustomGiroTxt, CustomGiroLbl); - end; - - trigger OnPostReport() - begin - if LogInteraction and not IsReportInPreviewMode() then - if "Purchase Header".FindSet() then - repeat - SegManagement.LogDocument( - 13, "Purchase Header"."No.", 0, 0, DATABASE::Vendor, "Purchase Header"."Buy-from Vendor No.", - "Purchase Header"."Purchaser Code", '', "Purchase Header"."Posting Description", ''); - until "Purchase Header".Next() = 0; - end; - - var - DummyCompanyInfo: Record "Company Information"; - GLSetup: Record "General Ledger Setup"; - TempPurchLine: Record "Purchase Line" temporary; - CurrExchRate: Record "Currency Exchange Rate"; - LanguageMgt: Codeunit Language; - FormatAddr: Codeunit "Format Address"; - FormatDocument: Codeunit "Format Document"; - PurchPost: Codeunit "Purch.-Post"; - SegManagement: Codeunit SegManagement; - PurchasePostPrepayments: Codeunit "Purchase-Post Prepayments"; - ArchiveManagement: Codeunit ArchiveManagement; - VATNoText: Text; - ReferenceText: Text; - OutputNo: Integer; - DimText: Text[120]; - VATAmount: Decimal; - VATBaseAmount: Decimal; - VATDiscountAmount: Decimal; - TotalAmountInclVAT: Decimal; - VALVATBaseLCY: Decimal; - VALVATAmountLCY: Decimal; - VALSpecLCYHeader: Text[80]; - VALExchRate: Text[50]; - PrepmtVATAmount: Decimal; - PrepmtVATBaseAmount: Decimal; - PrepmtTotalAmountInclVAT: Decimal; - PrepmtLineAmount: Decimal; - AllowInvDisctxt: Text[30]; - CompanyLogoPosition: Integer; - ItemNo: Text; - VATAmountSpecificationLbl: Label 'VAT Amount Specification in '; - LocalCurrentyLbl: Label 'Local Currency'; - ExchangeRateLbl: Label 'Exchange rate: %1/%2', Comment = '%1 = CurrExchRate."Relational Exch. Rate Amount", %2 = CurrExchRate."Exchange Rate Amount"'; - CompanyInfoPhoneNoCaptionLbl: Label 'Phone No.'; - CompanyInfoGiroNoCaptionLbl: Label 'Giro No.'; - CompanyInfoBankNameCaptionLbl: Label 'Bank'; - CompanyInfoBankAccNoCaptionLbl: Label 'Account No.'; - OrderNoCaptionLbl: Label 'Order No.'; - PageCaptionLbl: Label 'Page'; - DocumentDateCaptionLbl: Label 'Document Date'; - DirectUniCostCaptionLbl: Label 'Direct Unit Cost'; - PurchLineLineDiscCaptionLbl: Label 'Discount %'; - VATDiscountAmountCaptionLbl: Label 'Payment Discount on VAT'; - PaymentDetailsCaptionLbl: Label 'Payment Details'; - VendNoCaptionLbl: Label 'Vendor No.'; - ShiptoAddressCaptionLbl: Label 'Ship-to Address'; - PrepmtInvBuDescCaptionLbl: Label 'Description'; - PrepmtInvBufGLAccNoCaptionLbl: Label 'G/L Account No.'; - PrepaymentSpecCaptionLbl: Label 'Prepayment Specification'; - PrepymtVATAmtSpecCaptionLbl: Label 'Prepayment VAT Amount Specification'; - AmountCaptionLbl: Label 'Amount'; - PurchLineInvDiscAmtCaptionLbl: Label 'Invoice Discount Amount'; - SubtotalCaptionLbl: Label 'Subtotal'; - VATAmtLineVATCaptionLbl: Label 'VAT %'; - VATAmtLineVATAmtCaptionLbl: Label 'VAT Amount'; - VATAmtSpecCaptionLbl: Label 'VAT Amount Specification'; - VATIdentifierCaptionLbl: Label 'VAT Identifier'; - VATAmtLineInvDiscBaseAmtCaptionLbl: Label 'Invoice Discount Base Amount'; - VATAmtLineLineAmtCaptionLbl: Label 'Line Amount'; - VALVATBaseLCYCaptionLbl: Label 'VAT Base'; - PricesInclVATtxtLbl: Label 'Prices Including VAT'; - TotalCaptionLbl: Label 'Total'; - PaymentTermsDescCaptionLbl: Label 'Payment Terms'; - ShipmentMethodDescCaptionLbl: Label 'Shipment Method'; - PrepymtTermsDescCaptionLbl: Label 'Prepmt. Payment Terms'; - HomePageCaptionLbl: Label 'Home Page'; - EmailIDCaptionLbl: Label 'Email'; - AllowInvoiceDiscCaptionLbl: Label 'Allow Invoice Discount'; - BuyFromContactPhoneNoLbl: Label 'Buy-from Contact Phone No.'; - BuyFromContactMobilePhoneNoLbl: Label 'Buy-from Contact Mobile Phone No.'; - BuyFromContactEmailLbl: Label 'Buy-from Contact E-Mail'; - PayToContactPhoneNoLbl: Label 'Pay-to Contact Phone No.'; - PayToContactMobilePhoneNoLbl: Label 'Pay-to Contact Mobile Phone No.'; - PayToContactEmailLbl: Label 'Pay-to Contact E-Mail'; - DocumentTitleLbl: Label 'Purchase Order'; - ReceivebyCaptionLbl: Label 'Receive By'; - BuyerCaptionLbl: Label 'Buyer'; - ItemNumberCaptionLbl: Label 'Item No.'; - ItemDescriptionCaptionLbl: Label 'Description'; - ItemQuantityCaptionLbl: Label 'Quantity'; - ItemUnitCaptionLbl: Label 'Unit'; - ItemUnitPriceCaptionLbl: Label 'Unit Price'; - ItemLineAmountCaptionLbl: Label 'Line Amount'; - PricesIncludingVATCaptionLbl: Label 'Prices Including VAT'; - ItemUnitOfMeasureCaptionLbl: Label 'Unit'; - ToCaptionLbl: Label 'To:'; - VendorIDCaptionLbl: Label 'Vendor ID'; - ConfirmToCaptionLbl: Label 'Confirm To'; - PurchOrderCaptionLbl: Label 'PURCHASE ORDER'; - PurchOrderNumCaptionLbl: Label 'Purchase Order Number:'; - PurchOrderDateCaptionLbl: Label 'Purchase Order Date:'; - TaxIdentTypeCaptionLbl: Label 'Tax Ident. Type'; - TotalPriceCaptionLbl: Label 'Total Price'; - InvDiscCaptionLbl: Label 'Invoice Discount:'; - GreetingLbl: Label 'Hello'; - ClosingLbl: Label 'Sincerely'; - BodyLbl: Label 'The purchase order is attached to this message.'; - OrderDateLbl: Label 'Order Date'; - VendorOrderNoLbl: Label 'Vendor Order No.'; - VendorInvoiceNoLbl: Label 'Vendor Invoice No.'; - UnitPriceLbl: Label 'Unit Price (LCY)'; - JobNoLbl: Label 'Project No.'; - JobTaskNoLbl: Label 'Project Task No.'; - PromisedReceiptDateLbl: Label 'Promised Receipt Date'; - RequestedReceiptDateLbl: Label 'Requested Receipt Date'; - ExpectedReceiptDateLbl: Label 'Expected Receipt Date'; - PlannedReceiptDateLbl: Label 'Planned Receipt Date'; - LegalOfficeTxt, LegalOfficeLbl, CustomGiroTxt, CustomGiroLbl : Text; - - protected var - ResponsibilityCenter: Record "Responsibility Center"; - BuyFromContact: Record Contact; - PayToContact: Record Contact; - CompanyInfo: Record "Company Information"; - PurchSetup: Record "Purchases & Payables Setup"; - ShipmentMethod: Record "Shipment Method"; - PaymentTerms: Record "Payment Terms"; - PrepmtPaymentTerms: Record "Payment Terms"; - SalespersonPurchaser: Record "Salesperson/Purchaser"; - TempVATAmountLine: Record "VAT Amount Line" temporary; - TempPrepmtVATAmountLine: Record "VAT Amount Line" temporary; - TempPrepaymentInvLineBuffer: Record "Prepayment Inv. Line Buffer" temporary; - TempPrePmtVATAmountLineDeduct: Record "VAT Amount Line" temporary; - BuyFromAddr: array[8] of Text[100]; - CompanyAddr: array[8] of Text[100]; - VendAddr: array[8] of Text[100]; - ShipToAddr: array[8] of Text[100]; - FormattedQuanitity: Text; - FormattedDirectUnitCost: Text; - FormattedVATPct: Text; - FormattedLineAmount: Text; - PurchaserText: Text[50]; - TotalText: Text[50]; - TotalInclVATText: Text[50]; - TotalExclVATText: Text[50]; - ArchiveDocument: Boolean; - LogInteraction: Boolean; - LogInteractionEnable: Boolean; - OmitLastLine: Boolean; - VATAmountDifference: Boolean; - LastPurchaseLineNo: Integer; - FirstLineHasBeenOutput: Boolean; - TotalSubTotal, TotalAmount, TotalInvoiceDiscountAmount : Decimal; - - procedure InitializeRequest(LogInteractionParam: Boolean) - begin - LogInteraction := LogInteractionParam; - end; - - protected procedure IsReportInPreviewMode(): Boolean - var - MailManagement: Codeunit "Mail Management"; - begin - exit(CurrReport.Preview() or MailManagement.IsHandlingGetEmailBody()); - end; - - local procedure FormatAddressFields(var PurchaseHeader: Record "Purchase Header") - begin - FormatAddr.GetCompanyAddr(PurchaseHeader."Responsibility Center", ResponsibilityCenter, CompanyInfo, CompanyAddr); - FormatAddr.PurchHeaderBuyFrom(BuyFromAddr, PurchaseHeader); - if PurchaseHeader."Buy-from Vendor No." <> PurchaseHeader."Pay-to Vendor No." then - FormatAddr.PurchHeaderPayTo(VendAddr, PurchaseHeader); - FormatAddr.PurchHeaderShipTo(ShipToAddr, PurchaseHeader); - end; - - local procedure FormatDocumentFields(PurchaseHeader: Record "Purchase Header") - begin - FormatDocument.SetTotalLabels(PurchaseHeader."Currency Code", TotalText, TotalInclVATText, TotalExclVATText); - FormatDocument.SetPurchaser(SalespersonPurchaser, PurchaseHeader."Purchaser Code", PurchaserText); - FormatDocument.SetPaymentTerms(PaymentTerms, PurchaseHeader."Payment Terms Code", PurchaseHeader."Language Code"); - FormatDocument.SetPaymentTerms(PrepmtPaymentTerms, PurchaseHeader."Prepmt. Payment Terms Code", PurchaseHeader."Language Code"); - FormatDocument.SetShipmentMethod(ShipmentMethod, PurchaseHeader."Shipment Method Code", PurchaseHeader."Language Code"); - - ReferenceText := FormatDocument.SetText(PurchaseHeader."Your Reference" <> '', CopyStr(PurchaseHeader.FieldCaption("Your Reference"), 1, 80)); - VATNoText := FormatDocument.SetText(PurchaseHeader."VAT Registration No." <> '', CopyStr(PurchaseHeader.FieldCaption("VAT Registration No."), 1, 80)); - - OnAfterFormatDocumentFields(PurchaseHeader); - end; - - local procedure InitLogInteraction() - begin - LogInteraction := SegManagement.FindInteractionTemplateCode(Enum::"Interaction Log Entry Document Type"::"Purch. Ord.") <> ''; - end; - - [IntegrationEvent(true, false)] - local procedure OnAfterFormatDocumentFields(var PurchaseHeader: Record "Purchase Header") - begin - end; - - [IntegrationEvent(false, false)] - local procedure OnInitReportForGlobalVariable(var IsHandled: Boolean; var LegalOfficeTxt: Text; var LegalOfficeLbl: Text; var CustomGiroTxt: Text; var CustomGiroLbl: Text) - begin - end; -} - diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al index c34fdbcda3..6f79a1c9cc 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al @@ -75,20 +75,6 @@ page 6183 "E-Doc. Purchase Draft Subform" ApplicationArea = All; Lookup = true; } - field(VATWarning; VATWarningCaption) - { - ApplicationArea = All; - Caption = 'VAT warnings'; - Editable = false; - Visible = Rec."[BC] VAT Rate Mismatch"; - StyleExpr = VATWarningStyleExpr; - ToolTip = 'Specifies whether the VAT Product Posting Group could not be resolved from the extracted VAT rate.'; - - trigger OnDrillDown() - begin - ShowVATWarningDetails(); - end; - } field("Item Reference No."; Rec."[BC] Item Reference No.") { ApplicationArea = All; @@ -599,13 +585,4 @@ page 6183 "E-Doc. Purchase Draft Subform" end; end; - local procedure ShowVATWarningDetails() - var - VATWarningDetailLbl: Label 'VAT rate %1% was extracted from the invoice but could not be matched to a single VAT Posting Setup for the vendor. Please select the correct VAT Product Posting Group manually.', Comment = '%1 = VAT rate percentage'; - begin - if not Rec."[BC] VAT Rate Mismatch" then - exit; - Message(VATWarningDetailLbl, Rec."VAT Rate"); - end; - } From 9db4a01fbc44949b3ec69c75327066de4f2ef609 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Tue, 19 May 2026 23:54:44 +0200 Subject: [PATCH 48/53] add resolve VAT product posting group option --- .../EDocPurchPayablesSetup.PageExt.al | 5 ++++ .../EDocPurchPayablesSetup.TableExt.al | 5 ++++ .../EDocPreparePurchDraft.Codeunit.al | 5 ++++ .../Processing/EDocPurchVATTests.Codeunit.al | 23 +++++++++++++++++++ 4 files changed, 38 insertions(+) diff --git a/src/Apps/W1/EDocument/App/src/Extensions/EDocPurchPayablesSetup.PageExt.al b/src/Apps/W1/EDocument/App/src/Extensions/EDocPurchPayablesSetup.PageExt.al index 462797c940..6e67ed6537 100644 --- a/src/Apps/W1/EDocument/App/src/Extensions/EDocPurchPayablesSetup.PageExt.al +++ b/src/Apps/W1/EDocument/App/src/Extensions/EDocPurchPayablesSetup.PageExt.al @@ -31,6 +31,11 @@ pageextension 6162 "E-Doc. Purch. Payables Setup" extends "Purchases & Payables ApplicationArea = All; ToolTip = 'Specifies whether VAT difference should be applied when matching incoming E-Document line with Purchase Order line'; } + field("Resolve VAT Group Purch EDoc"; Rec."Resolve VAT Group Purch EDoc") + { + ApplicationArea = All; + ToolTip = 'Specifies whether to resolve VAT Product Group for purchase lines created from e-documents based on the VAT rate and the vendor''s VAT posting setup. If disabled, the VAT Product Group will not be identified based on the purchase e-document.'; + } } } } \ No newline at end of file diff --git a/src/Apps/W1/EDocument/App/src/Extensions/EDocPurchPayablesSetup.TableExt.al b/src/Apps/W1/EDocument/App/src/Extensions/EDocPurchPayablesSetup.TableExt.al index 1dd4d4e3e7..6a433efeaf 100644 --- a/src/Apps/W1/EDocument/App/src/Extensions/EDocPurchPayablesSetup.TableExt.al +++ b/src/Apps/W1/EDocument/App/src/Extensions/EDocPurchPayablesSetup.TableExt.al @@ -32,5 +32,10 @@ tableextension 6162 "E-Doc. Purch. Payables Setup" extends "Purchases & Payables DataClassification = CustomerContent; InitValue = true; } + field(6104; "Resolve VAT Group Purch EDoc"; Boolean) + { + Caption = 'Resolve VAT Product Group for Purch. E-Doc.'; + DataClassification = CustomerContent; + } } } diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al index 55274e0f71..e9d3c5db10 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al @@ -156,11 +156,16 @@ codeunit 6406 "EDoc Prepare Purch. Draft" local procedure ResolveVATProductPostingGroups(EDocumentEntryNo: Integer; EDocumentPurchaseHeader: Record "E-Document Purchase Header") var + PurchasesPayablesSetup: Record "Purchases & Payables Setup"; EDocumentPurchaseLine: Record "E-Document Purchase Line"; Vendor: Record Vendor; VATRate: Decimal; LineCount: Integer; begin + if not PurchasesPayablesSetup.Get() then + exit; + if not PurchasesPayablesSetup."Resolve VAT Group Purch EDoc" then + exit; if EDocumentPurchaseHeader."[BC] Vendor No." = '' then exit; if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then diff --git a/src/Apps/W1/EDocument/Test/src/Processing/EDocPurchVATTests.Codeunit.al b/src/Apps/W1/EDocument/Test/src/Processing/EDocPurchVATTests.Codeunit.al index b12ec43fec..490c7db126 100644 --- a/src/Apps/W1/EDocument/Test/src/Processing/EDocPurchVATTests.Codeunit.al +++ b/src/Apps/W1/EDocument/Test/src/Processing/EDocPurchVATTests.Codeunit.al @@ -14,6 +14,7 @@ using Microsoft.Finance.VAT.Setup; using Microsoft.Foundation.Company; using Microsoft.Purchases.History; using Microsoft.Purchases.Payables; +using Microsoft.Purchases.Setup; using Microsoft.Purchases.Vendor; using Microsoft.Sales.Customer; using System.IO; @@ -33,6 +34,7 @@ codeunit 139897 "E-Doc Purch. VAT Tests" LibraryEDoc: Codeunit "Library - E-Document"; EDocImplState: Codeunit "E-Doc. Impl. State"; LibraryLowerPermission: Codeunit "Library - Lower Permissions"; + LibrarySetupStorage: Codeunit "Library - Setup Storage"; IsInitialized: Boolean; [Test] @@ -52,6 +54,7 @@ codeunit 139897 "E-Doc Purch. VAT Tests" begin // [SCENARIO] When a draft line has a VAT Rate and a matching VAT Posting Setup exists, Prepare Draft resolves the VAT Prod. Posting Group Initialize(Enum::"Service Integration"::"Mock"); + SetResolveVATProductGroupInPurchSetup(true); LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); // [GIVEN] A vendor with a known VAT Bus. Posting Group @@ -115,6 +118,7 @@ codeunit 139897 "E-Doc Purch. VAT Tests" begin // [SCENARIO] When a draft line has a VAT Rate but no matching VAT Posting Setup exists, Prepare Draft leaves the field blank and sets the mismatch flag Initialize(Enum::"Service Integration"::"Mock"); + SetResolveVATProductGroupInPurchSetup(true); LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); // [GIVEN] A vendor with a known VAT Bus. Posting Group @@ -167,6 +171,7 @@ codeunit 139897 "E-Doc Purch. VAT Tests" begin // [SCENARIO] Full VAT setups must not be matched during VAT Posting Group resolution Initialize(Enum::"Service Integration"::"Mock"); + SetResolveVATProductGroupInPurchSetup(true); LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); // [GIVEN] A vendor @@ -233,6 +238,7 @@ codeunit 139897 "E-Doc Purch. VAT Tests" begin // [SCENARIO] Sales Tax setups must not be matched during VAT Posting Group resolution Initialize(Enum::"Service Integration"::"Mock"); + SetResolveVATProductGroupInPurchSetup(true); LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); // [GIVEN] A vendor @@ -299,6 +305,7 @@ codeunit 139897 "E-Doc Purch. VAT Tests" begin // [SCENARIO] Reverse Charge VAT setups should be matched during VAT Posting Group resolution Initialize(Enum::"Service Integration"::"Mock"); + SetResolveVATProductGroupInPurchSetup(true); LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); // [GIVEN] A vendor @@ -363,6 +370,7 @@ codeunit 139897 "E-Doc Purch. VAT Tests" begin // [SCENARIO] OnValidate clears mismatch when selected posting group's VAT % matches the line's VAT Rate Initialize(Enum::"Service Integration"::"Mock"); + SetResolveVATProductGroupInPurchSetup(true); LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); // [GIVEN] A vendor @@ -422,6 +430,7 @@ codeunit 139897 "E-Doc Purch. VAT Tests" begin // [SCENARIO] OnValidate keeps mismatch when selected posting group's VAT % differs from VAT Rate Initialize(Enum::"Service Integration"::"Mock"); + SetResolveVATProductGroupInPurchSetup(true); LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); // [GIVEN] A vendor @@ -476,6 +485,7 @@ codeunit 139897 "E-Doc Purch. VAT Tests" begin // [SCENARIO] OnValidate sets mismatch when posting group is cleared Initialize(Enum::"Service Integration"::"Mock"); + SetResolveVATProductGroupInPurchSetup(true); LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); // [GIVEN] A line with VAT Rate = 20, a posting group, and no mismatch @@ -509,6 +519,7 @@ codeunit 139897 "E-Doc Purch. VAT Tests" begin // [SCENARIO] OnValidate skips mismatch evaluation for Full VAT — flag stays unchanged Initialize(Enum::"Service Integration"::"Mock"); + SetResolveVATProductGroupInPurchSetup(true); LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); // [GIVEN] A vendor @@ -568,6 +579,7 @@ codeunit 139897 "E-Doc Purch. VAT Tests" begin // [SCENARIO] OnValidate clears mismatch when both VAT Rate and VAT % are 0 Initialize(Enum::"Service Integration"::"Mock"); + SetResolveVATProductGroupInPurchSetup(true); LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); // [GIVEN] A vendor @@ -626,6 +638,7 @@ codeunit 139897 "E-Doc Purch. VAT Tests" Currency: Record Currency; LibraryERM: Codeunit "Library - ERM"; begin + LibrarySetupStorage.Restore(); LibraryLowerPermission.SetOutsideO365Scope(); LibraryVariableStorage.Clear(); Clear(EDocImplState); @@ -661,7 +674,17 @@ codeunit 139897 "E-Doc Purch. VAT Tests" TransformationRule.DeleteAll(); TransformationRule.CreateDefaultTransformations(); + LibrarySetupStorage.SavePurchasesSetup(); IsInitialized := true; end; + + local procedure SetResolveVATProductGroupInPurchSetup(NewResolveVATProductGroup: Boolean) + var + PurchasesPayablesSetup: Record "Purchases & Payables Setup"; + begin + PurchasesPayablesSetup.Get(); + PurchasesPayablesSetup.Validate("Resolve VAT Group Purch EDoc", NewResolveVATProductGroup); + PurchasesPayablesSetup.Modify(); + end; } From 8e9474d17489b1800349e536ae06f7261d77b502 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Wed, 20 May 2026 13:30:20 +0200 Subject: [PATCH 49/53] add new options --- .../EDocPreparePurchDraft.Codeunit.al | 5 ++- .../Purchase/EDocPurchaseDraftSubform.Page.al | 17 +--------- .../Purchase/EDocumentPurchaseLine.Table.al | 31 ++++++++++++++++++- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al index e9d3c5db10..264a3c40e7 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al @@ -194,7 +194,10 @@ codeunit 6406 "EDoc Prepare Purch. Draft" EDocumentPurchaseLine."[BC] VAT Rate Mismatch" := EDocumentPurchaseLine."[BC] VAT Prod. Posting Group" = ''; EDocumentPurchaseLine.Modify(); - EDocumentPurchaseLine.LogVATRateMismatch(Vendor."VAT Bus. Posting Group", VATRate); + if EDocumentPurchaseLine."[BC] VAT Rate Mismatch" then + EDocumentPurchaseLine.LogVATRateMismatch(Vendor."VAT Bus. Posting Group", VATRate) + else + EDocumentPurchaseLine.LogVATRateResolved(Vendor."VAT Bus. Posting Group", VATRate); end; until EDocumentPurchaseLine.Next() = 0; end; diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al index 6f79a1c9cc..9f9a1076d4 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al @@ -335,7 +335,7 @@ page 6183 "E-Doc. Purchase Draft Subform" TempEDocumentPOMatchWarnings: Record "E-Doc PO Match Warning"; EDocPurchaseHistMapping: Codeunit "E-Doc. Purchase Hist. Mapping"; EDocPOMatching: Codeunit "E-Doc. PO Matching"; - AdditionalColumns, OrderMatchedCaption, MatchWarningsCaption, MatchWarningsStyleExpr, VATWarningCaption, VATWarningStyleExpr : Text; + AdditionalColumns, OrderMatchedCaption, MatchWarningsCaption, MatchWarningsStyleExpr : Text; LineAmount: Decimal; DimVisible1, DimVisible2, HasAdditionalColumns, HasVATWarnings, IsEDocumentMatchedToAnyPOLine, IsLineMatchedToOrderLine, IsLineMatchedToReceiptLine, HasEDocumentOrderMatchWarnings : Boolean; HistoryCantBeRetrievedErr: Label 'The purchase invoice that matched historically with this line can''t be opened.'; @@ -368,7 +368,6 @@ page 6183 "E-Doc. Purchase Draft Subform" IsLineMatchedToReceiptLine := EDocPOMatching.IsEDocumentLineMatchedToAnyReceiptLine(EDocumentPurchaseLine); OrderMatchedCaption := IsLineMatchedToOrderLine ? GetSummaryOfMatchedOrders() : ''; UpdateMatchWarnings(); - UpdateVATWarningForLine(); end; internal procedure SetEDocumentPurchaseHeader(EDocPurchHeader: Record "E-Document Purchase Header") @@ -571,18 +570,4 @@ page 6183 "E-Doc. Purchase Draft Subform" EDocPurchLine.SetRange("[BC] VAT Rate Mismatch", true); HasVATWarnings := not EDocPurchLine.IsEmpty(); end; - - local procedure UpdateVATWarningForLine() - var - VATGroupNotResolvedLbl: Label 'VAT group not resolved'; - begin - if Rec."[BC] VAT Rate Mismatch" then begin - VATWarningCaption := VATGroupNotResolvedLbl; - VATWarningStyleExpr := 'Ambiguous'; - end else begin - VATWarningCaption := ''; - VATWarningStyleExpr := 'None'; - end; - end; - } diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al index 97e125d9cd..669ba78d51 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al @@ -240,7 +240,8 @@ table 6101 "E-Document Purchase Line" "[BC] VAT Rate Mismatch" := VATPostingSetup."VAT %" <> "VAT Rate"; end else "[BC] VAT Rate Mismatch" := true; - LogVATRateMismatch(); + if "[BC] VAT Rate Mismatch" then + LogVATRateMismatch(); end; trigger OnLookup() @@ -477,4 +478,32 @@ table 6101 "E-Document Purchase Line" .Log(); end; + internal procedure LogVATRateResolved(VendVATBusPostingGroupCode: Code[20]; VATRate: Decimal) + var + VATPostingSetup: Record "VAT Posting Setup"; + ActivityLog: Codeunit "Activity Log Builder"; + VATPostingSetupRef: RecordRef; + Reasoning: Text[250]; + VATRateResolvedReasonLbl: Label 'VAT rate %1% extracted from the document was matched to VAT Product Posting Group %2 for vendor''s VAT Business Posting Group %3.', Comment = '%1 = extracted VAT rate %, %2 = resolved VAT Prod. Posting Group code, %3 = VAT Bus. Posting Group code'; + VATRateResolvedTitleLbl: Label 'VAT Posting Setup for %1', Comment = '%1 = VAT Bus. Posting Group code'; + begin + if Rec."[BC] VAT Rate Mismatch" then + exit; + VATPostingSetup.SetRange("VAT Bus. Posting Group", VendVATBusPostingGroupCode); + VATPostingSetup.SetFilter("VAT Calculation Type", '%1|%2', + VATPostingSetup."VAT Calculation Type"::"Normal VAT", + VATPostingSetup."VAT Calculation Type"::"Reverse Charge VAT"); + VATPostingSetupRef.GetTable(VATPostingSetup); + + Reasoning := CopyStr(StrSubstNo(VATRateResolvedReasonLbl, VATRate, Rec."[BC] VAT Prod. Posting Group", VendVATBusPostingGroupCode), 1, MaxStrLen(Reasoning)); + + ActivityLog + .Init(Database::"E-Document Purchase Line", Rec.FieldNo("[BC] VAT Prod. Posting Group"), Rec.SystemId) + .SetExplanation(Reasoning) + .SetType(Enum::"Activity Log Type"::AL) + .SetReferenceSource(Page::"VAT Posting Setup", VATPostingSetupRef) + .SetReferenceTitle(StrSubstNo(VATRateResolvedTitleLbl, VendVATBusPostingGroupCode)) + .Log(); + end; + } From 73a031206df3a9a36e8c0b287090aa011be9c5a2 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Wed, 20 May 2026 14:03:40 +0200 Subject: [PATCH 50/53] remove unnecessary changes --- .../Purchase/EDocPurchaseDraftSubform.Page.al | 23 +++++++++---------- .../Purchase/EDocumentPurchaseDraft.Page.al | 1 - .../Purchase/EDocumentPurchaseLine.Table.al | 20 ---------------- 3 files changed, 11 insertions(+), 33 deletions(-) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al index 9f9a1076d4..62abbe3835 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al @@ -4,6 +4,7 @@ // ------------------------------------------------------------------------------------------------ namespace Microsoft.eServices.EDocument.Processing.Import.Purchase; +using Microsoft.eServices.EDocument; using Microsoft.eServices.EDocument.Processing.Import; using Microsoft.Finance.Dimension; using Microsoft.Inventory.Item.Catalog; @@ -337,14 +338,13 @@ page 6183 "E-Doc. Purchase Draft Subform" EDocPOMatching: Codeunit "E-Doc. PO Matching"; AdditionalColumns, OrderMatchedCaption, MatchWarningsCaption, MatchWarningsStyleExpr : Text; LineAmount: Decimal; - DimVisible1, DimVisible2, HasAdditionalColumns, HasVATWarnings, IsEDocumentMatchedToAnyPOLine, IsLineMatchedToOrderLine, IsLineMatchedToReceiptLine, HasEDocumentOrderMatchWarnings : Boolean; + DimVisible1, DimVisible2, HasAdditionalColumns, IsEDocumentMatchedToAnyPOLine, IsLineMatchedToOrderLine, IsLineMatchedToReceiptLine, HasEDocumentOrderMatchWarnings : Boolean; HistoryCantBeRetrievedErr: Label 'The purchase invoice that matched historically with this line can''t be opened.'; trigger OnOpenPage() begin SetDimensionsVisibility(); UpdatePOMatching(); - UpdateVATWarnings(); end; trigger OnNewRecord(BelowxRec: Boolean) @@ -355,7 +355,6 @@ page 6183 "E-Doc. Purchase Draft Subform" trigger OnAfterGetCurrRecord() begin UpdatePOMatching(); - UpdateVATWarnings(); end; trigger OnAfterGetRecord() @@ -389,6 +388,8 @@ page 6183 "E-Doc. Purchase Draft Subform" local procedure UpdateCalculatedAmounts(UpdateParentRecord: Boolean) var + TotalEDocPurchaseLine: Record "E-Document Purchase Line"; + EDocumentImportHelper: Codeunit "E-Document Import Helper"; LineSubtotal: Decimal; DiscountExceedsSubtotalErr: Label 'Discount should not exceed the subtotal of the line'; begin @@ -405,6 +406,13 @@ page 6183 "E-Doc. Purchase Draft Subform" exit; if not EDocumentPurchaseHeader.Get(Rec."E-Document Entry No.") then exit; + EDocumentPurchaseHeader."Sub Total" := 0; + TotalEDocPurchaseLine.SetRange("E-Document Entry No.", Rec."E-Document Entry No."); + if TotalEDocPurchaseLine.FindSet() then + repeat + EDocumentPurchaseHeader."Sub Total" += Round(TotalEDocPurchaseLine.Quantity * TotalEDocPurchaseLine."Unit Price", EDocumentImportHelper.GetCurrencyRoundingPrecision(EDocumentPurchaseHeader."Currency Code")) - TotalEDocPurchaseLine."Total Discount"; + until TotalEDocPurchaseLine.Next() = 0; + EDocumentPurchaseHeader.Total := EDocumentPurchaseHeader."Sub Total" + EDocumentPurchaseHeader."Total VAT" - EDocumentPurchaseHeader."Total Discount"; EDocumentPurchaseHeader.Modify(); CurrPage.Update(); end; @@ -561,13 +569,4 @@ page 6183 "E-Doc. Purchase Draft Subform" if WarningDetails.Length() > 0 then Message(WarningDetails.ToText()); end; - - local procedure UpdateVATWarnings() - var - EDocPurchLine: Record "E-Document Purchase Line"; - begin - EDocPurchLine.SetRange("E-Document Entry No.", EDocumentPurchaseHeader."E-Document Entry No."); - EDocPurchLine.SetRange("[BC] VAT Rate Mismatch", true); - HasVATWarnings := not EDocPurchLine.IsEmpty(); - end; } diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al index f61e4e3880..24de204354 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseDraft.Page.al @@ -252,7 +252,6 @@ page 6181 "E-Document Purchase Draft" Caption = 'Amount Incl. VAT'; ToolTip = 'Specifies the total amount of the electronic document including VAT.'; Importance = Promoted; - Style = Strong; trigger OnValidate() begin diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al index 669ba78d51..750fe1c078 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al @@ -224,7 +224,6 @@ table 6101 "E-Document Purchase Line" begin if "[BC] VAT Prod. Posting Group" = '' then begin "[BC] VAT Rate Mismatch" := true; - LogVATRateMismatch(); exit; end; if not EDocumentPurchaseHeader.Get("E-Document Entry No.") then @@ -240,8 +239,6 @@ table 6101 "E-Document Purchase Line" "[BC] VAT Rate Mismatch" := VATPostingSetup."VAT %" <> "VAT Rate"; end else "[BC] VAT Rate Mismatch" := true; - if "[BC] VAT Rate Mismatch" then - LogVATRateMismatch(); end; trigger OnLookup() @@ -433,23 +430,6 @@ table 6101 "E-Document Purchase Line" exit(GetEDocumentPurchaseHeader().GetBCVendor()); end; - internal procedure LogVATRateMismatch() - var - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - Vendor: Record Vendor; - begin - if not Rec."[BC] VAT Rate Mismatch" then - exit; - if not EDocumentPurchaseHeader.Get(Rec."E-Document Entry No.") then - exit; - if EDocumentPurchaseHeader."[BC] Vendor No." = '' then - exit; - if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then - exit; - - LogVATRateMismatch(Vendor."VAT Bus. Posting Group", Rec."VAT Rate"); - end; - internal procedure LogVATRateMismatch(VendVATBusPostingGroupCode: Code[20]; VATRate: Decimal) var VATPostingSetup: Record "VAT Posting Setup"; From 36d5570503d032ed82d8ce3742ab43e08e9831c8 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Wed, 20 May 2026 14:04:59 +0200 Subject: [PATCH 51/53] remove more changes --- .../src/Document/Notification/EDocumentNotification.Codeunit.al | 1 - .../Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotification.Codeunit.al b/src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotification.Codeunit.al index 496981b729..acefe71c5f 100644 --- a/src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotification.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Document/Notification/EDocumentNotification.Codeunit.al @@ -126,5 +126,4 @@ codeunit 6123 "E-Document Notification" begin exit('bc0d8537-8e8d-4d94-a07a-a5a54c729d2a'); end; - } \ No newline at end of file diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al index ecf0c4a8b6..5fc3e99889 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al @@ -35,4 +35,4 @@ codeunit 6125 "Prepare Purchase E-Doc. Draft" implements IProcessStructuredData begin Vendor := PrepareDraftHelper.GetVendor(EDocument, Customizations); end; -} \ No newline at end of file +} From 13c97d32b0a7176d88a7186a5a8d0bbe3c0fa473 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Wed, 20 May 2026 14:49:59 +0200 Subject: [PATCH 52/53] refactoring --- .../EDocPurchDocHelper.Codeunit.al | 9 ++++++ .../EDocPreparePurchDraft.Codeunit.al | 11 +++---- .../Purchase/EDocumentPurchaseLine.Table.al | 30 +++++++------------ 3 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocPurchDocHelper.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocPurchDocHelper.Codeunit.al index 1f2691dcfe..3c2c8398d1 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocPurchDocHelper.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocPurchDocHelper.Codeunit.al @@ -9,6 +9,7 @@ using Microsoft.eServices.EDocument.Processing; using Microsoft.eServices.EDocument.Processing.Import.Purchase; using Microsoft.Finance.Currency; using Microsoft.Finance.Dimension; +using Microsoft.Finance.VAT.Setup; using Microsoft.Foundation.Attachment; using Microsoft.Purchases.Document; using Microsoft.Purchases.Posting; @@ -223,6 +224,14 @@ codeunit 6402 "E-Doc. Purch. Doc. Helper" PurchaseHeader.Validate("Posting Date", EDocumentPurchaseHeader."Document Date"); end; + procedure SetNormalReverseChargeFilter(var VATPostingSetup: Record "VAT Posting Setup"; VATBusPostingGroup: Code[20]) + begin + VATPostingSetup.SetRange("VAT Bus. Posting Group", VATBusPostingGroup); + VATPostingSetup.SetFilter("VAT Calculation Type", '%1|%2', + VATPostingSetup."VAT Calculation Type"::"Normal VAT", + VATPostingSetup."VAT Calculation Type"::"Reverse Charge VAT"); + end; + local procedure ComputeTotalLineAmount(EDocEntryNo: Integer): Decimal var EDocPurchLine: Record "E-Document Purchase Line"; diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al index 264a3c40e7..89e7d6a358 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/EDocPreparePurchDraft.Codeunit.al @@ -166,9 +166,8 @@ codeunit 6406 "EDoc Prepare Purch. Draft" exit; if not PurchasesPayablesSetup."Resolve VAT Group Purch EDoc" then exit; - if EDocumentPurchaseHeader."[BC] Vendor No." = '' then - exit; - if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then + Vendor := EDocumentPurchaseHeader.GetBCVendor(); + if Vendor."No." = '' then exit; if Vendor."VAT Bus. Posting Group" = '' then exit; @@ -205,11 +204,9 @@ codeunit 6406 "EDoc Prepare Purch. Draft" local procedure FindVATProductPostingGroup(VATBusPostingGroup: Code[20]; VATRate: Decimal): Code[20] var VATPostingSetup: Record "VAT Posting Setup"; + EDocPurchDocHelper: Codeunit "E-Doc. Purch. Doc. Helper"; begin - VATPostingSetup.SetRange("VAT Bus. Posting Group", VATBusPostingGroup); - VATPostingSetup.SetFilter("VAT Calculation Type", '%1|%2', - VATPostingSetup."VAT Calculation Type"::"Normal VAT", - VATPostingSetup."VAT Calculation Type"::"Reverse Charge VAT"); + EDocPurchDocHelper.SetNormalReverseChargeFilter(VATPostingSetup, VATBusPostingGroup); VATPostingSetup.SetRange("VAT %", VATRate); if VATPostingSetup.Count() = 1 then begin VATPostingSetup.FindFirst(); diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al index 750fe1c078..417a15c735 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al @@ -218,7 +218,6 @@ table 6101 "E-Document Purchase Line" trigger OnValidate() var - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; Vendor: Record Vendor; VATPostingSetup: Record "VAT Posting Setup"; begin @@ -226,9 +225,8 @@ table 6101 "E-Document Purchase Line" "[BC] VAT Rate Mismatch" := true; exit; end; - if not EDocumentPurchaseHeader.Get("E-Document Entry No.") then - exit; - if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then + Vendor := Rec.GetBCVendor(); + if Vendor."No." = '' then exit; if VATPostingSetup.Get(Vendor."VAT Bus. Posting Group", "[BC] VAT Prod. Posting Group") then begin if not (VATPostingSetup."VAT Calculation Type" in @@ -243,18 +241,14 @@ table 6101 "E-Document Purchase Line" trigger OnLookup() var - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; Vendor: Record Vendor; VATPostingSetup: Record "VAT Posting Setup"; + EDocPurchDocHelper: Codeunit "E-Doc. Purch. Doc. Helper"; begin - if not EDocumentPurchaseHeader.Get("E-Document Entry No.") then - exit; - if not Vendor.Get(EDocumentPurchaseHeader."[BC] Vendor No.") then + Vendor := Rec.GetBCVendor(); + if Vendor."No." = '' then exit; - VATPostingSetup.SetRange("VAT Bus. Posting Group", Vendor."VAT Bus. Posting Group"); - VATPostingSetup.SetFilter("VAT Calculation Type", '%1|%2', - VATPostingSetup."VAT Calculation Type"::"Normal VAT", - VATPostingSetup."VAT Calculation Type"::"Reverse Charge VAT"); + EDocPurchDocHelper.SetNormalReverseChargeFilter(VATPostingSetup, Vendor."VAT Bus. Posting Group"); if Page.RunModal(Page::"VAT Posting Setup", VATPostingSetup) = Action::LookupOK then Validate("[BC] VAT Prod. Posting Group", VATPostingSetup."VAT Prod. Posting Group"); end; @@ -433,6 +427,7 @@ table 6101 "E-Document Purchase Line" internal procedure LogVATRateMismatch(VendVATBusPostingGroupCode: Code[20]; VATRate: Decimal) var VATPostingSetup: Record "VAT Posting Setup"; + EDocPurchDocHelper: Codeunit "E-Doc. Purch. Doc. Helper"; ActivityLog: Codeunit "Activity Log Builder"; VATPostingSetupRef: RecordRef; Reasoning: Text[250]; @@ -441,10 +436,7 @@ table 6101 "E-Document Purchase Line" begin if not Rec."[BC] VAT Rate Mismatch" then exit; - VATPostingSetup.SetRange("VAT Bus. Posting Group", VendVATBusPostingGroupCode); - VATPostingSetup.SetFilter("VAT Calculation Type", '%1|%2', - VATPostingSetup."VAT Calculation Type"::"Normal VAT", - VATPostingSetup."VAT Calculation Type"::"Reverse Charge VAT"); + EDocPurchDocHelper.SetNormalReverseChargeFilter(VATPostingSetup, VendVATBusPostingGroupCode); VATPostingSetupRef.GetTable(VATPostingSetup); Reasoning := CopyStr(StrSubstNo(VATRateMismatchReasonLbl, Rec."VAT Rate", VendVATBusPostingGroupCode), 1, MaxStrLen(Reasoning)); @@ -461,6 +453,7 @@ table 6101 "E-Document Purchase Line" internal procedure LogVATRateResolved(VendVATBusPostingGroupCode: Code[20]; VATRate: Decimal) var VATPostingSetup: Record "VAT Posting Setup"; + EDocPurchDocHelper: Codeunit "E-Doc. Purch. Doc. Helper"; ActivityLog: Codeunit "Activity Log Builder"; VATPostingSetupRef: RecordRef; Reasoning: Text[250]; @@ -469,10 +462,7 @@ table 6101 "E-Document Purchase Line" begin if Rec."[BC] VAT Rate Mismatch" then exit; - VATPostingSetup.SetRange("VAT Bus. Posting Group", VendVATBusPostingGroupCode); - VATPostingSetup.SetFilter("VAT Calculation Type", '%1|%2', - VATPostingSetup."VAT Calculation Type"::"Normal VAT", - VATPostingSetup."VAT Calculation Type"::"Reverse Charge VAT"); + EDocPurchDocHelper.SetNormalReverseChargeFilter(VATPostingSetup, VendVATBusPostingGroupCode); VATPostingSetupRef.GetTable(VATPostingSetup); Reasoning := CopyStr(StrSubstNo(VATRateResolvedReasonLbl, VATRate, Rec."[BC] VAT Prod. Posting Group", VendVATBusPostingGroupCode), 1, MaxStrLen(Reasoning)); From 63562f70fec5eacd72f2d5f8e07d1e1605f6e7d2 Mon Sep 17 00:00:00 2001 From: ventselartur Date: Wed, 20 May 2026 15:55:58 +0200 Subject: [PATCH 53/53] Remove personal .claude symlinks --- .claude/agents/astred-explorer.md | 1 - .claude/agents/bc-al-env-setup.md | 1 - .claude/agents/bc-dme.md | 1 - .claude/agents/bc-fix-baseline.md | 1 - .claude/agents/bc-fix-implement.md | 1 - .claude/agents/bc-fix-pr.md | 1 - .claude/agents/bc-test-implementor.md | 1 - .claude/agents/page-script-generator.md | 1 - .claude/skills/astred-al-init | 1 - .claude/skills/astred-codegraph | 1 - .claude/skills/astred-diff | 1 - .claude/skills/astred-rebuild | 1 - .claude/skills/astred-search | 1 - .claude/skills/bc-altools | 1 - .claude/skills/bc-appdev-dashboard | 1 - .claude/skills/bc-appdev-monitor | 1 - .claude/skills/bc-delocalize | 1 - .claude/skills/bc-delocalize-inventory | 1 - .claude/skills/bc-fix-bug | 1 - .claude/skills/bc-planbug | 1 - .claude/skills/create-pr | 1 - .claude/skills/page-script | 1 - .claude/skills/page-script-doc | 1 - .claude/skills/page-script-run | 1 - .claude/skills/pr-status | 1 - .claude/skills/repro-steps | 1 - .claude/skills/review-ado-pr | 1 - .claude/skills/review-pr | 1 - .claude/skills/session-retrospective | 1 - .claude/skills/update-pr | 1 - 30 files changed, 30 deletions(-) delete mode 120000 .claude/agents/astred-explorer.md delete mode 120000 .claude/agents/bc-al-env-setup.md delete mode 120000 .claude/agents/bc-dme.md delete mode 120000 .claude/agents/bc-fix-baseline.md delete mode 120000 .claude/agents/bc-fix-implement.md delete mode 120000 .claude/agents/bc-fix-pr.md delete mode 120000 .claude/agents/bc-test-implementor.md delete mode 120000 .claude/agents/page-script-generator.md delete mode 120000 .claude/skills/astred-al-init delete mode 120000 .claude/skills/astred-codegraph delete mode 120000 .claude/skills/astred-diff delete mode 120000 .claude/skills/astred-rebuild delete mode 120000 .claude/skills/astred-search delete mode 120000 .claude/skills/bc-altools delete mode 120000 .claude/skills/bc-appdev-dashboard delete mode 120000 .claude/skills/bc-appdev-monitor delete mode 120000 .claude/skills/bc-delocalize delete mode 120000 .claude/skills/bc-delocalize-inventory delete mode 120000 .claude/skills/bc-fix-bug delete mode 120000 .claude/skills/bc-planbug delete mode 120000 .claude/skills/create-pr delete mode 120000 .claude/skills/page-script delete mode 120000 .claude/skills/page-script-doc delete mode 120000 .claude/skills/page-script-run delete mode 120000 .claude/skills/pr-status delete mode 120000 .claude/skills/repro-steps delete mode 120000 .claude/skills/review-ado-pr delete mode 120000 .claude/skills/review-pr delete mode 120000 .claude/skills/session-retrospective delete mode 120000 .claude/skills/update-pr diff --git a/.claude/agents/astred-explorer.md b/.claude/agents/astred-explorer.md deleted file mode 120000 index c1a977f08d..0000000000 --- a/.claude/agents/astred-explorer.md +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/agents/astred-explorer.md \ No newline at end of file diff --git a/.claude/agents/bc-al-env-setup.md b/.claude/agents/bc-al-env-setup.md deleted file mode 120000 index 9dbecab4dc..0000000000 --- a/.claude/agents/bc-al-env-setup.md +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/agents/bc-al-env-setup.md \ No newline at end of file diff --git a/.claude/agents/bc-dme.md b/.claude/agents/bc-dme.md deleted file mode 120000 index 36dc649aa9..0000000000 --- a/.claude/agents/bc-dme.md +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/agents/bc-dme.md \ No newline at end of file diff --git a/.claude/agents/bc-fix-baseline.md b/.claude/agents/bc-fix-baseline.md deleted file mode 120000 index 5c2ef6b6ea..0000000000 --- a/.claude/agents/bc-fix-baseline.md +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/agents/bc-fix-baseline.md \ No newline at end of file diff --git a/.claude/agents/bc-fix-implement.md b/.claude/agents/bc-fix-implement.md deleted file mode 120000 index 91e381b3d1..0000000000 --- a/.claude/agents/bc-fix-implement.md +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/agents/bc-fix-implement.md \ No newline at end of file diff --git a/.claude/agents/bc-fix-pr.md b/.claude/agents/bc-fix-pr.md deleted file mode 120000 index b149dd700f..0000000000 --- a/.claude/agents/bc-fix-pr.md +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/agents/bc-fix-pr.md \ No newline at end of file diff --git a/.claude/agents/bc-test-implementor.md b/.claude/agents/bc-test-implementor.md deleted file mode 120000 index d359f7def8..0000000000 --- a/.claude/agents/bc-test-implementor.md +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/agents/bc-test-implementor.md \ No newline at end of file diff --git a/.claude/agents/page-script-generator.md b/.claude/agents/page-script-generator.md deleted file mode 120000 index e9944c9e4a..0000000000 --- a/.claude/agents/page-script-generator.md +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/agents/page-script-generator.md \ No newline at end of file diff --git a/.claude/skills/astred-al-init b/.claude/skills/astred-al-init deleted file mode 120000 index 227b4a7f71..0000000000 --- a/.claude/skills/astred-al-init +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/astred/astred-al-init \ No newline at end of file diff --git a/.claude/skills/astred-codegraph b/.claude/skills/astred-codegraph deleted file mode 120000 index 917687feec..0000000000 --- a/.claude/skills/astred-codegraph +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/astred/astred-codegraph \ No newline at end of file diff --git a/.claude/skills/astred-diff b/.claude/skills/astred-diff deleted file mode 120000 index 509c33066d..0000000000 --- a/.claude/skills/astred-diff +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/astred/astred-diff \ No newline at end of file diff --git a/.claude/skills/astred-rebuild b/.claude/skills/astred-rebuild deleted file mode 120000 index f6f7c6c63a..0000000000 --- a/.claude/skills/astred-rebuild +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/astred/astred-rebuild \ No newline at end of file diff --git a/.claude/skills/astred-search b/.claude/skills/astred-search deleted file mode 120000 index bfef8d1b9e..0000000000 --- a/.claude/skills/astred-search +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/astred/astred-search \ No newline at end of file diff --git a/.claude/skills/bc-altools b/.claude/skills/bc-altools deleted file mode 120000 index cfc346c928..0000000000 --- a/.claude/skills/bc-altools +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/aibusinesssolutions/claude/skills/bc-altools \ No newline at end of file diff --git a/.claude/skills/bc-appdev-dashboard b/.claude/skills/bc-appdev-dashboard deleted file mode 120000 index 5684d85025..0000000000 --- a/.claude/skills/bc-appdev-dashboard +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/bc-appdev-dashboard \ No newline at end of file diff --git a/.claude/skills/bc-appdev-monitor b/.claude/skills/bc-appdev-monitor deleted file mode 120000 index 7201de701c..0000000000 --- a/.claude/skills/bc-appdev-monitor +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/bc-appdev-monitor \ No newline at end of file diff --git a/.claude/skills/bc-delocalize b/.claude/skills/bc-delocalize deleted file mode 120000 index e2fe4545bf..0000000000 --- a/.claude/skills/bc-delocalize +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/bc-delocalization/bc-delocalize \ No newline at end of file diff --git a/.claude/skills/bc-delocalize-inventory b/.claude/skills/bc-delocalize-inventory deleted file mode 120000 index 9655662122..0000000000 --- a/.claude/skills/bc-delocalize-inventory +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/bc-delocalization/bc-delocalize-inventory \ No newline at end of file diff --git a/.claude/skills/bc-fix-bug b/.claude/skills/bc-fix-bug deleted file mode 120000 index f8b5115690..0000000000 --- a/.claude/skills/bc-fix-bug +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/bc-fix-bug \ No newline at end of file diff --git a/.claude/skills/bc-planbug b/.claude/skills/bc-planbug deleted file mode 120000 index 126c50e766..0000000000 --- a/.claude/skills/bc-planbug +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/bc-planbug \ No newline at end of file diff --git a/.claude/skills/create-pr b/.claude/skills/create-pr deleted file mode 120000 index ab3cede611..0000000000 --- a/.claude/skills/create-pr +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/source-control/create-pr \ No newline at end of file diff --git a/.claude/skills/page-script b/.claude/skills/page-script deleted file mode 120000 index 88c2640199..0000000000 --- a/.claude/skills/page-script +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/bc-page-scripting/page-script \ No newline at end of file diff --git a/.claude/skills/page-script-doc b/.claude/skills/page-script-doc deleted file mode 120000 index b4bd101e57..0000000000 --- a/.claude/skills/page-script-doc +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/bc-page-scripting/page-script-doc \ No newline at end of file diff --git a/.claude/skills/page-script-run b/.claude/skills/page-script-run deleted file mode 120000 index 3d2737a031..0000000000 --- a/.claude/skills/page-script-run +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/bc-page-scripting/page-script-run \ No newline at end of file diff --git a/.claude/skills/pr-status b/.claude/skills/pr-status deleted file mode 120000 index c56e77dd40..0000000000 --- a/.claude/skills/pr-status +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/pr-status \ No newline at end of file diff --git a/.claude/skills/repro-steps b/.claude/skills/repro-steps deleted file mode 120000 index a67df98024..0000000000 --- a/.claude/skills/repro-steps +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/aldobriansky/claude/skills/repro-steps \ No newline at end of file diff --git a/.claude/skills/review-ado-pr b/.claude/skills/review-ado-pr deleted file mode 120000 index 2936a6afbf..0000000000 --- a/.claude/skills/review-ado-pr +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/aldobriansky/claude/skills/review-ado-pr \ No newline at end of file diff --git a/.claude/skills/review-pr b/.claude/skills/review-pr deleted file mode 120000 index be47fd912e..0000000000 --- a/.claude/skills/review-pr +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/source-control/review-pr \ No newline at end of file diff --git a/.claude/skills/session-retrospective b/.claude/skills/session-retrospective deleted file mode 120000 index ee2ed43877..0000000000 --- a/.claude/skills/session-retrospective +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/session-retrospective \ No newline at end of file diff --git a/.claude/skills/update-pr b/.claude/skills/update-pr deleted file mode 120000 index e0cb441376..0000000000 --- a/.claude/skills/update-pr +++ /dev/null @@ -1 +0,0 @@ -C:/depot/BC-agentic-workspace/ai-instructions/user-private/ventselartur/claude/skills/source-control/update-pr \ No newline at end of file