|
| 1 | +# Business Logic |
| 2 | + |
| 3 | +## Export pipeline |
| 4 | + |
| 5 | +Every export follows the same sequence regardless of document type: |
| 6 | + |
| 7 | +```mermaid |
| 8 | +flowchart TD |
| 9 | + A[Record Export Buffer triggers codeunit] --> B[Load Setup + resolve format enum] |
| 10 | + B --> C{Validate posted document} |
| 11 | + C -->|Fail| ERR[Error raised -- export aborted] |
| 12 | + C -->|Pass| D[Initialize XmlPort with RecordRef + Format] |
| 13 | + D --> E[XmlPort iterates headers via PEPPOL Posted Document Iterator] |
| 14 | + E --> F[Convert posted record to Sales Header buffer] |
| 15 | + F --> G[Extract all UBL data sections via 8 provider interfaces] |
| 16 | + G --> H[XmlPort iterates lines via same iterator] |
| 17 | + H --> I[Convert each posted line to Sales Line buffer] |
| 18 | + I --> J[Extract line-level UBL data] |
| 19 | + J --> K[Serialize to UBL XML on OutStream] |
| 20 | + K --> L[Write XML to Record Export Buffer."File Content"] |
| 21 | +``` |
| 22 | + |
| 23 | +### Entry points |
| 24 | + |
| 25 | +There are 4 export codeunits, each bound to `Record Export Buffer`: |
| 26 | + |
| 27 | +| Codeunit | Source table | Format field | |
| 28 | +|----------|-------------|--------------| |
| 29 | +| Exp. Sales Inv. PEPPOL30 (37206) | Sales Invoice Header | Sales Format | |
| 30 | +| Exp. Sales CrM. PEPPOL30 (37205) | Sales Cr.Memo Header | Sales Format | |
| 31 | +| Exp. Serv.Inv. PEPPOL30 (37212) | Service Invoice Header | Service Format | |
| 32 | +| Exp. Serv.CrM. PEPPOL30 (37211) | Service Cr.Memo Header | Service Format | |
| 33 | + |
| 34 | +All four follow the identical pattern: get record from buffer, validate, |
| 35 | +generate XML. The service codeunits reuse the same `Sales Invoice - PEPPOL30` |
| 36 | +XmlPort (not a typo -- service documents are converted to Sales Header |
| 37 | +buffers before the XmlPort sees them). |
| 38 | + |
| 39 | +### The "Sales Header as universal buffer" trick |
| 40 | + |
| 41 | +`PEPPOL30 Common.ConvertPostedHeaderToSalesHeader()` is the conversion |
| 42 | +hub. It uses `TransferFields` for sales documents and |
| 43 | +`PEPPOL30.TransferHeaderToSalesHeader()` (RecordRef field-by-field copy) |
| 44 | +for service documents. The document type field is explicitly set after |
| 45 | +transfer: |
| 46 | + |
| 47 | +- Invoice headers -> `Document Type::Invoice` |
| 48 | +- Credit memo headers -> `Document Type::"Credit Memo"` |
| 49 | + |
| 50 | +For service lines, `MapServiceLineTypeToSalesLineType()` maps service |
| 51 | +line types to their sales equivalents. Service types that have no |
| 52 | +direct sales match (Cost, G/L Account) map to `Sales Line Type::"G/L Account"`. |
| 53 | + |
| 54 | +## Validation |
| 55 | + |
| 56 | +Validation runs **before** XML generation and will abort the export with |
| 57 | +an error if any check fails. |
| 58 | + |
| 59 | +```mermaid |
| 60 | +flowchart TD |
| 61 | + V1[ValidatePostedDocument] --> V2{Document type?} |
| 62 | + V2 -->|Sales Invoice| V3[TransferFields to Sales Header] |
| 63 | + V2 -->|Sales Cr.Memo| V3 |
| 64 | + V2 -->|Service Invoice| V4[RecRefTransferFields to Sales Header] |
| 65 | + V2 -->|Service Cr.Memo| V4 |
| 66 | + V4 --> V3 |
| 67 | + V3 --> V5[CheckSalesDocument - header-level] |
| 68 | + V5 --> V6[CheckSalesDocumentLine - per line] |
| 69 | +
|
| 70 | + V5 --> H1[Currency code = 3 chars] |
| 71 | + V5 --> H2[Company Info: name, address, country, GLN or VAT] |
| 72 | + V5 --> H3[Bill-to: name, address, city, post code, country] |
| 73 | + V5 --> H4[Customer: GLN or VAT Reg No.] |
| 74 | + V5 --> H5[Ship-to address complete] |
| 75 | + V5 --> H6[Due date, Your Reference filled] |
| 76 | + V5 --> H7[Bank: IBAN or account + branch + SWIFT] |
| 77 | + V5 --> H8[Credit memo: Applies-to Doc. No. if type is Invoice] |
| 78 | +
|
| 79 | + V6 --> L1[UoM has international standard code] |
| 80 | + V6 --> L2[Non-blank lines have description] |
| 81 | + V6 --> L3[VAT Prod. Posting Group filled] |
| 82 | + V6 --> L4[Tax Category checks] |
| 83 | + V6 --> L5[Negative unit price confirmation] |
| 84 | +
|
| 85 | + L4 --> TC1{Tax Category} |
| 86 | + TC1 -->|S| TC2[VAT % must be > 0] |
| 87 | + TC1 -->|Z,E,AE,K,G| TC3[VAT % must be 0] |
| 88 | + TC1 -->|O| TC4[VAT % = 0 + single breakdown only] |
| 89 | +``` |
| 90 | + |
| 91 | +### Validation gotchas |
| 92 | + |
| 93 | +- **Service validation delegates to Sales.** `PEPPOL30 Serv. Validation Impl` |
| 94 | + converts service records to sales records and calls the sales validation |
| 95 | + impl. Shipment Date is set to Posting Date (services don't have a |
| 96 | + shipment date). |
| 97 | + |
| 98 | +- **Country codes must have 2-char ISO codes.** The check reads |
| 99 | + `Country/Region."ISO Code"` and errors if not exactly 2 characters. |
| 100 | + |
| 101 | +- **GLN-or-VAT is enforced** for both Company Information (supplier) and |
| 102 | + Customer (buyer). Missing both is a hard error. |
| 103 | + |
| 104 | +## Tax category mapping |
| 105 | + |
| 106 | +The PEPPOL tax categories come from `VAT Posting Setup."Tax Category"`: |
| 107 | + |
| 108 | +| Code | Meaning | VAT % rule | |
| 109 | +|------|---------|------------| |
| 110 | +| S | Standard rate | Must be > 0% | |
| 111 | +| Z | Zero rated | Must be 0% | |
| 112 | +| E | Exempt from tax | Must be 0% | |
| 113 | +| AE | VAT reverse charge | Must be 0% | |
| 114 | +| K | EEA intra-community | Must be 0% | |
| 115 | +| G | Free export, not charged | Must be 0% | |
| 116 | +| O | Outside scope of VAT | Must be 0%, max 1 breakdown | |
| 117 | + |
| 118 | +## Monetary totals calculation |
| 119 | + |
| 120 | +`GetLegalMonetaryInfo` in `PEPPOL30 Impl.` computes: |
| 121 | + |
| 122 | +- **LineExtensionAmount** = VAT Base + Invoice Discount Amount (pre-discount line total) |
| 123 | +- **TaxExclusiveAmount** = VAT Base - Payment Discount Amount |
| 124 | +- **TaxInclusiveAmount** = Amount Including VAT - Payment Discount Amount |
| 125 | +- **AllowanceTotalAmount** = Invoice Discount Amount + Payment Discount Amount |
| 126 | +- **PayableRoundingAmount** = difference from rounding line (or fractional cents) |
| 127 | +- **PayableAmount** = Amount Including VAT + rounding - Payment Discount (rounded to 0.01) |
| 128 | + |
| 129 | +## Installation and upgrade |
| 130 | + |
| 131 | +`PEPPOL30 Initialize` (Install subtype) registers 6 electronic document |
| 132 | +format entries in the `Electronic Document Format` table: |
| 133 | + |
| 134 | +- Sales Invoice export, Sales Credit Memo export, Sales Validation |
| 135 | +- Service Invoice export, Service Credit Memo export, Service Validation |
| 136 | + |
| 137 | +The upgrade codeunit re-runs the same registration with an upgrade tag |
| 138 | +(`MS-121225-PEPPOL1P-APP-INSTALL`). A `Company-Initialize` event |
| 139 | +subscriber also ensures formats exist in new companies. |
0 commit comments