KeyBalance Opdateringer

Genereret: 2026-09-15 16:01:02

Beskrivelse
#1541 - 0-fakturaer danner nu Debitor-/Kreditorposter + div. småforbedringer (15-09-26 16:00)
0-fakturaer (bruttobeløb 0) blev hidtil undertrykt og dannede hverken debitor- eller kreditorpost. De danner nu poster som alle andre fakturaer. Derudover en række mindre forbedringer.

0-fakturaer danner nu Debitor-/Kreditorposter


`FILE/SFaktura.PTD` (trigger `DSAMLEPOST`) og `FILE/IFaktura.PTD` (trigger `KSAMLEPOST`): `Brutto`-betingelsen er fjernet fra begge triggeres `CanExecute`.

```diff
  • CanExecute( Firma.AutoBogførSalgsFaktura & SFaktura.Brutto & SFaktura.NoTrigger == false & ... )
+ CanExecute( Firma.AutoBogførSalgsFaktura & SFaktura.NoTrigger == false & ... )
  • CanExecute( Firma.AutoBogførIndkøbsFaktura & IFaktura.Brutto & IFaktura.NoTrigger == false )
+ CanExecute( Firma.AutoBogførIndkøbsFaktura & IFaktura.NoTrigger == false )
```

`Brutto` er falsk ved beløb 0, så samleposten blev slet ikke dannet — fakturaen fandtes kun i fakturajournalen og var usynlig i reskontroen. Øvrige betingelser er uændrede.

Dagssaldo i bankafstemning


  • `FILE/BankkontiSaldo.ptd`: nyt felt `BankPostBevægDag` — sum for **den aktuelle dag** alene (`[UltDato]` i stedet for `[..UltDato]`). Det eksisterende akkumulerede `BankPostBevæg` har fået prompt "Bank Bevægelse" → **"Bank Bevægelse AKK"**, så de to ikke forveksles.
  • `TASK/BankPost_Saldi.ptd`: kolonnen viser nu dagsfeltet, så dagens bevægelse kan aflæses direkte pr. dato.

Valuta ved manuel import af bankposter


`TASK/Import_BankPost_Generisk.PTD`: `Valuta` blev ikke sat ved generisk import, hvilket gav poster uden valuta på valutabankkonti. Den hentes nu fra bankkontoens finanskonto via `lookup(Finans.Valuta;1;lookup(Bankkonti.Kontonr;1;#BankKonto))`. `if`-vagten sikrer, at feltet kun sættes når kontoen faktisk har en valuta — konti i systemvaluta røres ikke.

BS — tilføj ekstra bilag


  • `TASK/BS_Bilag_TilfojExBilag.PTD`: filnavnet er nu `<BSNummer>_<nextnum("BS_ExtraBilag")>_<filnavn>`. Uden tælleren fik to bilag med samme oprindelige filnavn på samme BS-nummer identisk destinationsnavn, og det ene overskrev det andet.
  • `TASK/BS_Bilag_ExtraBilag_OpenFil.PTD`: `FileNameExpr` tog før altid `"../" + DokNavn`, hvilket pegede forkert hvis stien allerede indeholdt BS-mappen. Der tjekkes nu med `strleft(DokNavn;strlen(FSetup.BS_Path)) == FSetup.BS_Path`, og `BS_Path` indsættes kun når den mangler.

Sordrekort værksted


`TASK/SORDREKORT_VÆRKS.PTD`:

  • Memofelterne "Opgave (Extern beskrivelse)" og "Intern tekst" er flyttet til en **ny fane "Tekster"** nederst, placeret side om side (`Direction = LeftRight`) og væsentligt bredere (800/700 mod ca. 200 før), så længere værkstedsnotater kan læses uden scroll.
  • Ordrelinjetabellen er rykket ned (`y` 600 → 1201) for at give plads.
  • `actSORDRE_Dokumenter` omdøbt "Old - Dokumenter" → **"Dokumenter v1"**.
  • Rettet indrykning to steder — ingen funktionel ændring.

Valgfrie tekster på arbejdskort


`TASK/UARBEJDSKORT.PTD`:

  • Ny parameter `TekstVlg` (`BChList`): **Begge** (0, default), **Kun Intern** (1), **Kun Extern** (2).
  • Overskrifter og memofelter styres nu af `Enable()` ud fra valget. "Intern tekst" var før hårdkodet `Enable( 0 )` — altså aldrig med på udskriften; den kan nu vælges til.
  • Blokhøjde 500 → 700 og teksthøjde 300 → 400, så overskrifterne ikke beskæres.
  • Nyt `LINK` fra `SOrdreLinje` til `SOrdre_Mobile` via `SOrdre.AktivSOM`.
  • Dummy-linjens `Løbenr` ændret 10 → 100001, så den ikke kolliderer med rigtige ordrelinjenumre.

Til reviewer: diff-støj i SFaktura.PTD


Filen fremstår med ca. 4.500 ændrede linjer, fordi den er EOL-normaliseret CRLF → LF ved commit (`core.autocrlf`). Indholdet er uændret bortset fra `CanExecute`-linjen ovenfor og nogle kommentarer, hvor efterstillet whitespace er brudt om på ny linje. **Slå "ignorer whitespace" til** — så er ændringen 17 linjer.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

#1514 - WEB_API_TokenGrp: AuthMode EntraID Intern / Statisk + EntraID Intern, DefaultUser required for External modes (13-09-26 19:38)

What


`FILE/WEB_API_TokenGrp.PTD` only.

  • `AuthMode` choice list gains **5 "EntraID Intern"** (Entra JWT; the UPN must match `Bruger.Email`, the call runs as that user) and **6 "Statisk + EntraID Intern"** (static token in `Authorization` + JWT in `X-KB-User-Token`, same identity rule).
  • Labels 1-4 (Statisk, EntraID, Enten, Begge) are untouched - choice-list values cannot be renamed. Their hint text now spells out that EntraID, Enten and Begge bind the JWT to an **external** identity and run the call as Default bruger / the token's Login, and that the bound user's rights apply in every EntraID mode.
  • `DefaultUser` is now `Required`, enterable and shown for EntraID, Enten and Begge. Its hint text is rewritten; the old one carried corrupted characters (`n�r`, `pr�senteret`).

Why


The server (Keybalance2018 branch `feature/apiv3-license-filter`) binds every Entra-authenticated APIv3 call to a real Bruger and enforces that user's standard access controls. External modes run as `DefaultUser`, so a group in one of those modes without it is skipped at config build. This PR makes the PTD enforce the same rule at save time and offers the two Internal modes.

Notes


  • Stored values 1-4 are unchanged; existing groups keep working.
  • `WEB_API_TokenAdgang.Kategori` (Swagger grouping) is deliberately **not** in this PR.
  • Edited at byte level to preserve the file's mixed encodings; passes the grammar stage of `KB.PTD.Parser`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
#1479 - Brug TSAkt i stedet for HRTimer (08-09-26 19:18)
Brug TSAkt i stedet for HRTimer
#1489 - Aktivitetsteksten blev hentet fra den forkerte fil og blankede feltet (08-09-26 11:23)

Resumé


I `OpdaterJobFørDag` slår opslaget aktivitetsteksten op i **`TSAktivitet`** i stedet for **`TSBCAktivitet`** — `TSBC` er faldet ud af en kopi af linjen.

Nøgle 1 i `TSAktivitet` er `KEY Kørselnr`, hvis første segment er det ulong `Kørselnr`. Opslaget søger altså på et *kørselsnummer* med et *ydelsesnummer*. Resultatet er enten en tom tekst eller en vilkårlig fremmed aktivitets tekst — hver gang `Bemærk1` er tom.

```
  • TSAktivitet2.Tekst = lookup( TSAktivitet.Tekst;1; TSAktivitet2.VNummer);
+ TSAktivitet2.Tekst = lookup( TSBCAktivitet.Tekst;1; TSAktivitet2.VNummer);
```

Den korrekte form står i samme fil 141 linjer længere oppe, i søster-tasken `OpdaterJobFørSammeDag` (linje 172), og i forældre-tasken (linje 94). `TSBCAktivitet` nøgle 1 er `KEY I_ID` med ét segment, `Navn`, `BString(10)` — samme type og længde som `VNummer`.

Fejlen har vist sig i Sentry som **Compare failed** med `TSAktivitet:Kørselnr` (B_ULONG) mod et streng-udtryk, bruger SUSANNE S, licens BTM.

Overflødig tildeling fjernet

`TSAktivitet2.Tekst = TSBCF.Bemærk1;` stod umiddelbart før `if TSBCF.Bemærk1 then TSAktivitet2.Tekst = TSBCF.Bemærk1`. Den er formentlig grunden til at fejlen har kunnet ligge upåagtet: teksten så rigtig ud så længe `Bemærk1` var udfyldt. Blokken er nu identisk med søster-taskens.

Filer


  • `TASK/TSBCUpdateJobs.PTD` — rettet opslag, fjernet dobbelt tildeling.

Filen er samtidig konverteret fra **cp1252 til UTF-8**. Det er de øvrige 19 linjer i diffen — rene `æ ø å`, ingen indholdsændring. Linjeskift (CRLF) er uændrede.

Test


  • [ ] Kør `TSBCUpdateJobs` på en registrering hvor `Bemærk1` er tom, og som rammer `OpdaterJobFørDag`-grenen (dagen før, ikke samme dag)
  • [ ] `TSAktivitet.Tekst` får aktivitetens tekst fra `TSBCAktivitet` i stedet for at blive blank
  • [ ] Kontrollér at en registrering *med* `Bemærk1` stadig får bemærkningen som tekst
  • [ ] Sentry-eventen `Compare failed / TSAktivitet:Kørselnr` holder op

🤖 Generated with [Claude Code](https://claude.com/claude-code)
#1424 - Forbedring af CRM kundeemne-kort i klient og app (08-09-26 09:58)
Forbedring af kundeemne-kortet, så det både i klienten og i app'en (WE) fremstår
som en færdig funktion frem for en demo.

Klient — `TASK/CRM_Map.PTD`


  • Dialogtitel rettet fra "KundeEmne Map Demo" til "KundeEmne Kort".
  • Pin-action omdøbt fra `actKEKort` til `actKUNDEEMNEKORT`, med teksten
"Vis emne" / "Viser alle oplysninger om emnet" i stedet for "Vis KE" /
"Viser alle oplysninger om kontoen".
  • Tilføjet `Options = [ CreateAction ]`, så handlingen også ligger i
handlingslisten og ikke kun udløses af selve pin'en.
  • Tilføjet F5-genvej (`Event { WVEvent { VK = App.VK_F5 } }`) på linje med
øvrige "vis kort"-handlinger i systemet.

App/WE — ny `TASK/CRM_Map_WE.ptd`


  • Ny task `CRM_Map_WE` med titlen "KundeEmner på Kort", bygget over samme
struktur som klientens `CRM_Map`: subtask `Points` på `KundeEmne`, filtreret
på at `AdrLat`/`AdrLng` er sat (`FromTo( [0,01..] )`).
  • Pin viser navn, adresse, postnr./by og sælger.
  • Pin-action `actKUNDEEMNEKORT` åbner `CRM_KundeEmneKort_WE` med
`KundeEmne.Kontonr` som parameter.

Menu og handlingsliste


  • `WVActionList/ActionList.PTD`: ny `actCRM_Map_WE` ("KundeEmner, Kort") med
`TaskRef = CRM_Map_WE`.
  • `WVMainMenu/WE_ALT.PTD`: menupunktet er indsat to steder, begge gange lige
efter `actCRM_AktivitetKal_WE`. Det ene sted erstatter det demo-/test-punkt,
der pegede på `actTEST_DebitorMap2_WE`.

Licens/modul — `MODULEMAIN/MODULES.PTD`


  • `CRM_Map_WE` er tilføjet til `MODULE CRM`s `TaskList` ved siden af klientens
`CRM_Map`, så app-menupunktet kommer og går sammen med CRM-licensen.
  • Bevidst **ikke** tilføjet under `MODULE KB2018`, hvor `CRM_Map` også står:
KB2018 sikrede oprindeligt, at tasken slet ikke blev vist i den gamle klient
(som ikke understøtter kontrollen). Da ribbon-menuen i dag kun kører i den
nye klient og hovedmenuen mest i den gamle, er den gating ikke relevant
længere.

Bemærkninger


  • `actTEST_DebitorMap2_WE` og `TASK/TEST_DebitorMap2_WE.ptd` er bevaret — actionen
er fortsat refereret to andre steder i `WE_ALT.PTD`, så der er ingen døde
referencer.
  • Begge `TaskRef` er verificeret: `KUNDEEMNEKORT` og `CRM_KundeEmneKort_WE`
findes begge i `TASK/`.

#1437 - Div forbedringer: POSuid på debitor, mailvalg ved nyt password, BC-ID fallback (08-09-26 09:52)
En blanding af mindre forbedringer og rettelser på tværs af debitorstamdata,
FlexPos-integrationen, bankfileksport, brugeradministration og bilagsgodkendelse.

---

Debitor — nyt felt POSuid


`FILE/Debitor.PTD`

  • Nyt felt **POSuid** (BString, længde 100) på debitoren, så en debitor kan bære
det UID, kassesystemet identificerer kunden med.
  • Ny nøgle **POSuid** med segmenterne `POSuid` + `Kontonr`, så feltet kan bruges
direkte som opslagsnøgle. `Kontonr` som andet segment gør nøglen unik, også
hvis flere debitorer skulle ende med samme POSuid.

FlexPos — UID flyttet fra Bemærk3 til POSuid


`TASK/FlexPosAPI_Customer.PTD`

FlexPos-kundens UID blev hidtil gemt i `Debitor.Bemærk3` — et generelt notatfelt,
der ligger frit redigerbart på debitorkortet sammen med Bemærk1 og Bemærk2.
Et forkert tastetryk dér kunne afkoble debitoren fra sin FlexPos-kunde.

UID'et genereres og gemmes nu i `Debitor.POSuid`, som både er dedikeret til
formålet og har en nøgle. `Bemærk3` er dermed frigivet til sit egentlige brug.

FlexPos — find debitor på POSuid


`TASK/FlexPosApi_SalgBon.PTD`

Kæden, der finder debitoren til en bon, har hidtil kun kunnet slå op i
`FlexPos_Customer`. Rammer `Customer_UID` ikke en kunde dér, blev bonnen sendt
videre til afdelingens standarddebitor, selvom kunden godt kunne findes i
KeyBalance.

Opslagsrækkefølgen er nu:

1. `FlexPos_Customer.ID1` på `Customer_UID` (uændret)
2. **`Debitor.POSuid` på `Customer_UID`** (ny)
3. `FlexPos_CUnit_ID` på ClientID + Unit_ID
4. `FlexPos_Setup.DKontonr` som sidste udvej

Fil-varianten `FlexPos_SalgBon` er bevidst ikke rørt — FlexPos uden API er
under udfasning, og ny funktionalitet lægges kun i API-varianten.

Nyt password — vælg hvordan mailen sendes


`TASK/Bruger_NytPassword.ptd`

  • Ny parameter **SendeType** med valgene *Send direkte* og *Send via Outlook*.
  • Tasken har nu to `SMTPSENDMAIL`-blokke styret af `ShouldExecute`:
direkte SMTP-afsendelse, eller `MailType = Mapi`.

Med Outlook-valget popper mailen op i et åbent Outlook-vindue i stedet for at
blive sendt afsted med det samme. Dermed kan koden læses på skærmen og
videregives ad anden vej — fx til den, der skal instruere brugeren — inden
mailen eventuelt sendes.

Tidligere kørte den eneste blok altid (`ShouldExecute( 1 )`).

Bankfileksport — Id-tag faldt tilbage til tom værdi


`TASK/EXPORT_BANK_ISO20022_Fil.PTD`

`Id`-tagget under afsenderidentifikationen blev udfyldt ud fra bankcentralen
(BANKDATA/SDC → `BC_Funktion`, BEC → `00` + SE-nr). Var det pågældende felt ikke
udfyldt — eller var bankcentralen en anden — returnerede beregningen ingenting,
og filen kom afsted med et tomt `Id`, som banken afviser.

Betingelserne kræver nu, at værdien rent faktisk findes, og der er tilføjet en
generel fallback til firmaets SE-nr, så tagget altid får indhold.

Bilagsgodkendelse — versalufølsom sammenligning


`TASK/BS_GodkendEgneUdv.PTD`

`CanEnter` sammenlignede `BS_Bilag_Godkend.Godkender` direkte med `user()`.
Er godkenderen registreret med anden bogstavskasse end brugerens login, kunne
brugeren ikke åbne sine egne bilag. Sammenligningen er nu pakket i `toupper()`
i begge sider.

Bemærk: gruppeopslaget i samme udtryk
(`lookup(Medarbejder.BS_GodkGrp;1;user()) == BS_Bilag_Godkend.Godkender`)
er stadig versalfølsomt — det er bevidst ikke rørt her, da det sammenligner en
godkendergruppe og ikke et brugernavn.

---

Til gennemsyn — eksisterende data


Flytningen af FlexPos-UID fra `Bemærk3` til `POSuid` rammer kun nye debitorer
af sig selv. Debitorer, der **allerede** er synkroniseret til FlexPos, har deres
UID liggende i `Bemærk3` og har tom `POSuid`. Kører `FlexPosAPI_Customer` på
dem, får de tildelt et nyt guid og oprettes som **nye** kunder i FlexPos ved
siden af de eksisterende.

Inden dette rulles ud på en installation med data i FlexPos, skal eksisterende
værdier derfor kopieres fra `Bemærk3` til `POSuid`.

#1454 - Outlook Plugin: skrivbare tasks til KundeEmne og KontaktPerson (01-09-26 20:16)

Formål


KB Outlook Plugin (Standard V1) kunne indtil nu kun læse stamdata på
KundeEmne og KontaktPerson via `API_OP_S1_KE` og `API_OP_S1_KP`.
Denne PR tilføjer to skrivbare søster-tasks, så stamdata kan rettes
direkte fra Outlook uden at skifte over i klienten.

TASK/API_OP_S1_KERW.ptd (ny)


Skrivbar task på `KundeEmne` — `TaskMode = Update`.

Kolonner: Kontonr, Navn, Adr1, PostNr, ByNavn, Land, Tlf, EmailDomæne.

Modsat `API_OP_S1_KE` er der ingen subtasks (Debitorpost/SFaktura),
da RW-tasken kun bruges til redigering af selve stamkortet.

TASK/API_OP_S1_KPRW.ptd (ny)


Skrivbar task på `KontaktPerson` — `TaskMode = Update`.

Kolonner: Kontonr, Initial, Navn, Titel, Email, Tlf.

`Initial` er med her, men ikke i læse-tasken `API_OP_S1_KP`. Feltet er
andet nøglesegment i primærnøglen `Kunde` (Kontonr + Initial), jf.
`FILE/KontaktPerson.PTD`, og skal derfor kunne sættes ved oprettelse.

Bemærkninger


  • Dialogens `Title` i `API_OP_S1_KPRW` var kopieret med fra KERW og stod
som `"KundeEmne"`. Rettet til `"KontaktPerson"`.
  • `API_OP_S1_KPRW` har bevidst ikke læse-taskens `SelectList` med
`Fratrådt = false`, så også fratrådte kontaktpersoner kan rettes.
  • Taskene indgår ikke i hovedmenuen og registreres derfor ikke i MODULES,
på linje med de eksisterende `API_OP_S1_*`-tasks.

#1452 - Bankafstemning: gem original bankpost + korrekt valutaindlaesning fra MIB (01-09-26 19:21)

Forbedring af bankafstemning


Original bankpost gemmes ved indlæsning, og valutaoplysninger indlæses nu korrekt fra MIB.

FILE/BankPostAfstem.PTD


  • Nyt felt `OriginalRecord` (BString, 15000) til at gemme den rå post, som den kom
fra banken (json/xml). Gør det muligt bagefter at se præcis hvad banken leverede,
når en afstemning ikke går op, uden at skulle hente posten igen.

TASK/Bank_MIB_TransHent.ptd


  • Rettet tagnavne i MIB-svaret fra camelCase til snake_case:
`currencyExchange[0].exchangeRate` → `currency_exchange[0].exchange_rate`.
De gamle navne matchede ikke MIB's faktiske JSON, så kursen blev aldrig fundet
og org.valuta-felterne forblev tomme.
  • `OrgValuta` læses nu fra `currency_exchange[0].source_currency` (med array-indeks)
og sættes inde i kurs-betingelsen. Den tidligere sti
`currencyExchange.sourceCurrency` manglede indeks og kunne derfor ikke resolve.
  • `BeløbOrgVal` ganges med 100, så beregningen følger kurskonventionen pr. 100
enheder, som `KursOrgVal` bruges med i resten af systemet.
  • Fjernet dobbelt debug-`okmsg()` på samme kurs, så der kun logges én gang.
  • Ny EXPRESSION i AfterRecord gemmer hele transaktionen i `OriginalRecord`.

TASK/Bank_Ng_TransHent.PTD


  • Ny EXPRESSION i AfterRecord gemmer hele transaktionen (`transactions.booked[n]`)
i `OriginalRecord`, svarende til MIB.

Bemærk til senere


`Bank_Ng_TransHent` bruger fortsat de gamle camelCase-tagnavne og ganger ikke med
100 ved beregning af `BeløbOrgVal`. Det er ikke ændret her, da denne PR kun
retter MIB-siden, men det bør efterprøves mod Nordeas faktiske svar.

#1451 - Udvidelse af promptexpr() hintexpr() taget fra Vitrex (01-09-26 18:20)
#1448 - Oversigt valuta sum / hvis ikke standard valuta eller "". (01-09-26 18:18)
#1379 - Peppol BIS 3: reelle EDI-ID'er og bankkonto pr. valuta, adresser på ISO20022 og Outlook API-backend (27-08-26 19:56)
Fire områder i denne PR: Peppol-eksporten er lagt om til de reelle EDI-ID'er og bankkonto pr. valuta, Debitor leverer nu rigtige Peppol-scheme-koder, modtageradressen kommer med på alle ISO20022-betalingsformater, og første step af API-backenden til Outlook-pluginnet er på plads.

PEPPOL BIS 3 (TASK/UDSKRIVFAKTURA_PEPPOL_BIS_3.ptd)


  • **Køber-ID kommer nu fra kundekortet.** `$IVSchemeVal`/`$IVSchemeId` slås op i `Debitor.EDIReeltID` og `Debitor.EDIReeltIDArt` i stedet for den lokale EAN/SE_nr-logik med hårdkodede scheme-koder pr. land. Den gamle kode sendte bl.a. det forældede 9901 for DK, og fallback-grenen havde identisk `then`/`else`.
  • **Afsender-ID uden landepræfiks.** `cbc:EndpointID`, `cac:PartyIdentification/cbc:ID` og `cac:PartyLegalEntity/cbc:CompanyID` sendes nu som rent org.nr., hvilket er det scheme 0007 (SE) og 0192 (NO) forventer. Landekoden lægges i stedet på `cac:PartyTaxScheme/cbc:CompanyID` (BT-31), hvor den hører til. Testet mod svensk validering.
  • **Ny IBAN-betalingsblok.** `cac:PaymentMeans` med kode 30 (credit transfer), `cbc:PaymentID`, IBAN, kontonavn og SWIFT. De fire gamle NO-varianter (`KontoNO`, `IBANNO_STD`, `IBANNO`, `KontoNO_ARK`) er sat til `Enable( 0 )`.
  • **Bankkonto findes ud fra valuta.** Alle opslag i Bankkonti bruger nu nøglen `Bankkonti.BTextValuta` i stedet for primærnøglen (Navn), så kontoen vælges ud fra fakturaens valuta og ikke bankkontoens navn — som feltets hjælpetekst også beskriver.
  • **Faktura og kreditnota er bragt i sync.** Begge grene har nu samme IBAN-blok og samme landekode på VAT-nummeret; tidligere afveg kreditnotaen på begge punkter.

Filen er reformateret af editoren undervejs, så langt de fleste diff-linjer er whitespace og klammer omkring enkelt-statement `if`'er uden funktionel betydning.

Debitor: Peppol-scheme-koder (FILE/Debitor.PTD)


  • `EDIReeltIDArt` returnerer nu rigtige koder for EHF3-, PEPPOL3- og OIOUBL3-kunder: **0184** for DK og **0007** for SE, hvor der før stod `"xxx"`.
  • `EDIReeltID` returnerer CVR ved 0184, og ved 0007 renses det svenske org.nr for bindestreger og `SE`-præfiks.

Også her er resten af diffen reformatering; de reelle ændringer er de to `Init`-udtryk.

ISO20022: modtageradresse på alle formater (TASK/EXPORT_BANK_ISO20022_Fil.PTD)


Gade, postnummer og bynavn på modtageren blev kun udfyldt ved IBAN-betalinger — linjerne var udkommenteret i de øvrige grene. De er nu aktive for **FIK/Giro**, **Overførsel** og **SWIFT** også, så adressen følger med uanset betalingsform.

Outlook-plugin: API-backend, første step (TASK/API_OutLook_KE.ptd, TASK/API_OutLook_KP.ptd)


To nye tasks der eksponerer de data pluginnet skal vise:

  • **API_OutLook_KE** — KundeEmne med navn, adresse, land, SE-nr., telefon, e-mail og e-maildomæne. Subtasks henter kundens fakturaer (`SalgKøbProd` 0 og 10..29) med dato, valuta, netto og brutto, samt debitorposter med dato, tekst, beløb i valuta og åbent beløb.
  • **API_OutLook_KP** — aktive kontaktpersoner (`Fratrådt = false`) med initialer, navn, e-mail, titel samt fastnet- og mobilnummer.

Test


`KBRebuild -IPTD` på alle fem ændrede filer kompilerer uden fejl (`done OK`). Peppol-outputtet er kontrolleret mod svensk validering.

#1334 - Tilføj Azure DevOps MCP-server til Claude Code (25-08-26 11:06)
Giver Claude Code direkte adgang til PR'er, kommentartråde og build-logs, så
fejlsøgning på en PR ikke kræver manuel kopiering fra webfladen.

Baggrunden er arbejdet med PR 1332: hele undersøgelsen af hvorfor review-bottens
JSON blev postet råt måtte foregå uden adgang til selve PR'en, fordi hverken
`az devops` eller anonym REST kunne læse den.

Hvad der tilføjes


`.mcp.json` i repo-roden med den officielle server `@azure-devops/mcp` (v2.9.0)
mod organisationen KeyBalance, samt en linje i `CLAUDE.md` der nævner serveren.

Valg der er truffet


**Kun tre domæner slået til:** `core`, `repositories` og `pipelines`. Serveren
loader som standard alle domæner (også work-items, wiki, search, test-plans og
advanced-security), og hvert værktøjs schema fylder i konteksten i hver eneste
session. De tre giver de 18 værktøjer vi reelt bruger — herunder
`repo_pull_request_thread` og `pipelines_build_log`. Listen kan udvides ved at
rette `args`.

**Interaktivt browser-login (serverens standard).** Ingen PAT gemmes i repoet,
og hver udvikler logger ind som sig selv første gang serveren bruges.
`-a azcli` blev fravalgt: token fra en almindelig `az login` afvises af
organisationen med TF400813.

**Ingen hemmeligheder i konfigurationen.** Eneste `env` er `ado_mcp_project`,
som blot er projektnavnet, så værktøjerne ikke behøver spørge.

For dem der henter ændringen


Serveren starter ikke af sig selv. Projekt-scopede `.mcp.json`-servere kræver
eksplicit godkendelse ved opstart af Claude Code — det er den normale
sikkerhedsspærre for MCP-konfiguration der kommer med et repo. Efter
godkendelse åbner første værktøjskald et browser-login mod
`dev.azure.com/KeyBalance`.

Verifikation


`initialize`-handshake mod serveren svarer (Azure DevOps MCP Server 2.9.0), og
de tre domæner parses som forventet. Efterfølgende brugt i praksis til at læse
review-trådene på PR 1332 og svare på dem.
#1408 - Navngivning FakturaTil (25-08-26 10:06)
#1384 - Div småting fra opgradering (21-08-26 15:11)
Div småting fra opgradering
#1332 - Claude PR review: robust JSON-parsing i stedet for raa dump i PR'en (14-08-26 12:17)
Claude PR review: robust JSON-parsing i stedet for raa dump i PR'en

Nogle PR'er (fx 1313 og 1328) fik hele review-JSON'en postet raat ind i
PR-kommentaren i stedet for den formaterede opsummering.

Aarsagen var oprydningen foer ConvertFrom-Json: den strippede kun en
markdown-fence der laa PRAECIS i start og slut af svaret. Skrev modellen
en indledning ("Her er reviewet:") eller en afsluttende bemaerkning, slog
parsingen fejl, og fallbacken postede $raw direkte. Samme udfald ved
afkortede svar og ved trailing comma. Reproduceret under PowerShell 5.1.

Vaerst af alt gjorde fallbacken "exit 0" med en lukket traad: ingen
inline-fund, ingen impact-vurdering, og PR'en gled igennem porten uden at
nogen havde set paa den.

Aendringer:

  • Get-ReviewJson: proever flere kandidater - hele svaret, indhold i
```json-fences uanset placering, og foerste balancerede {...}-blok med
en scanner der ignorerer klammer inde i strenge. Trailing comma
ryddes kun op som sidste udvej, saa strenge ikke korrumperes.
  • Invoke-Claude: --output-format json giver en fast envelope, saa stoej
paa CLI'ens stdout ikke kan blande sig ind i review-teksten. Logger
samtidig subtype, is_error, stop_reason og afviste tool-kald.
  • Ét reparationsforsoeg med haiku naar udtraekket fejler - daekker
afkortede svar, som ingen udtraeksregel kan redde.
  • Uparsbart svar dumpes ikke laengere raat. Der postes en kort besked i
en AKTIV traad, saa PR'en holdes tilbage til manuel gennemgang. Det
raa svar vises afkortet i en kodeblok og ligger som artefakt.
  • Get-Content laeser eksplicit med -Encoding UTF8.
  • prompt.md: bed om maks. 12 findings og korte bodies for at mindske
risikoen for afkortede svar.

Verificeret under Windows PowerShell 5.1: alle steps parser, 10
udtraeks-cases giver forventet resultat, og et rigtigt CLI-kald med
prosa foer og efter fencen parses nu korrekt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#1328 - Små rettelser Indkøb / bilag (14-08-26 10:34)
Kontroller flyttet til task, der kan udvides - på sigt evt. ændres til system opsætning.
Kørt og testet hos Helsinge (m. Gitte).
#1322 - Div småting opsamlet ved opgradering (14-08-26 07:59)
Div småting opsamlet ved opgradering
#1313 - ISO20022 PAIN.001 export som manuel fil (uden BankConnect) + 2026 adresseformat (13-08-26 11:27)

ISO20022 PAIN.001 export som manuel fil (uden BankConnect)


Hovedformålet er at gøre det muligt at danne en ISO20022 PAIN.001-betalingsfil **manuelt** — dvs. filen gemmes lokalt på pc'en og indlæses selv i netbanken — i stedet for kun at kunne sendes via BankConnect. Samtidig er adresseblokkene bragt på det format bankerne kræver fra 2026 (struktureret adresse).

EXPORT_BANK_ISO20022_Fil
  • Ny parameter `Lokalt` (BBool). Når den er sat, skrives filen client-side (`FilesServerSide` returnerer false) i stedet for på serveren. `Path` og `Fil` er nu synlige i startdialogen.
  • Formularlog er omlagt: `FormularerLog` oprettes nu via ny subtask `CreateFormuarLog` i AfterLast i stedet for via `Links`/`LinkMode = Create`. Logteksten bygges i `$FormularTxt` (MsgId, kontrolantal, kontrolbeløb).
  • Ved manuel kørsel (uden indgående `#FormularLøbenr`) oprettes formularen som afsluttet med teksten `(Lukket) Betalingsforslag - ISO20022 PAIN01 Fil`, og `KBHYdre.ErOverførtTilBank` sættes.
  • Rettet `ShouldExecute( !#FormularLøbenr )` → `!$FormularLøbenr` på kaldet til `CreateFormuar`.

**Adresser (2026-format):**
  • `$ModtAdr1`/`$ModtAdr2` er erstattet af struktureret `$ModtGade`, `$ModtPostNr`, `$ModtByNavn` → `PstCd`, `TwnNm`, `Ctry`, `AdrLine`.
  • Modtagerens `PstlAdr` udfyldes kun hvis adressen faktisk findes (`Enable( $ModtByNavn )`), og kun for udenlandske betalingsformer (IBAN og Swift). For FIK/Giro og indenlandsk overførsel udelades adresseblokken.
  • Afsender (`InitgPty` / debitor) får nu fuld `PstlAdr` med `PstCd`, `TwnNm`, `Ctry` og `AdrLine` fra FSetup/Firma. Den overflødige `PstlAdr` under bankens `FinInstnId` er fjernet begge steder.

**Valuta:**
  • `$BetalValISO` slås nu op som `lookup(Valuta.ISO4217_A3; 1; KBetForHistLinier.Valuta)` i stedet for at bruge den interne valutakode direkte — filen indeholdt før en kode banken ikke kunne godtage.

KBetForHistHeadDlg
  • Ny handling **"> ISO20022 (FIL)"**, der kalder `EXPORT_BANK_ISO20022_Fil` med path `Export\Bank\`, filnavn `<løbenr>.xml` og `Lokalt = true`. `CanExecute` blokerer, hvis betalingsforslaget allerede er eksporteret.

EXPORT_TILBANK
  • ISO20022 (format 20) er fjernet fra det gamle CSV-eksportvalg sammen med parameteren `FileMappe` — ISO20022 køres nu enten via BankConnect eller via den nye filhandling. Beskrivelsen præciserer, at de gamle CSV-formater er på vej ud hos bankerne.

EXPORT_BANK_ISO20022_Samlet
  • Sender `false` med som ny `Lokalt`-parameter, så BankConnect-flowet uændret gemmer serverside.

Øvrige rettelser i denne branch
  • **Import_BankConnect_camt_053_001**: fakturanr udtrækkes nu efter samme strategi som FIK og accepterer både `71`- og `75`-præfiks; udtrækket er forenklet til `substring()`.
  • **MailF_SendAbn**: tomme eller ugyldige medarbejder-mailadresser falder tilbage til firmaets e-mail (`emailcheck`), i stedet for at fejle med "ingen modtager".
  • **BS_AF_IOrdreTotal**: bedre logtekst med brutto-beløb fra BS_Bilag og indkøbsordre.
  • Rettet manglende afsluttende `}` i den nye WVAction, som ville have brudt buildet.

#1311 - Rettelser fra Lasses noter (13-08-26 10:01)
#1316 - Smårettelser, init på user() i userformular (13-08-26 09:56)
Smårettelser, init på user() i userformular
UDSKRIVFØLGESEDDEL_FAKTURA
Helt tossede lookups på ordrelinje, ordre
#1305 - Mindre rettelse til Varebilag til ordre (12-08-26 10:49)
#1291 - Reguleringslog + små rettelser (fundet fra Helsinge) (10-08-26 12:48)
#1287 - Registrér hvem der har bestilt en pakke-klipning (09-08-26 16:50)

Hvorfor


Alle konsulenter starter pipelinen med **samme PAT**, så Azure DevOps krediterer hver eneste kørsel til den der ejer tokenet. Oplysningen om hvem der faktisk trykkede på knappen fandtes kun i Keybalance — og gik tabt.

Hvad


Ny parameter **`RequestedBy`** ("Bestilt af"), som `KB_PkgKlip` udfylder med `user()`.

Værdien lander tre steder:

| Sted | Hvorfor |
|---|---|
| `CreatedBy` på den nye katalogrække | synligt i UI'et, feltet findes allerede |
| `MANIFEST.json` | payloaden bærer selv sporet, uforanderligt |
| Byggeloggen | ved fejlsøgning |

Hvorfor `CreatedBy` og ikke et nyt felt


Feltet er der allerede og vises på pakke-kortet. Dets `Init(user())` er bare ubrugelig her: oprettes rækken over api2, er `user()` api2-kontoen — altså den samme på hver eneste pakke der nogensinde klippes. Nu overskrives den med den rigtige.

Bemærk


Parameteren er valgfri (`default: ''`), så en kørsel startet direkte fra Azure DevOps-UI'et virker uændret — den får bare ingen attribution.

Den tilhørende PTD-ændring (`KB_PkgCatalogOpret` udstiller `CreatedBy`, `KB_PkgKlip` sender `user()`) ligger i admin-PTD'en uden for git, jf. §4.0 i modellen.

Test


  • [ ] Klip en pakke fra Keybalance og bekræft at `Oprettet af` viser konsulentens initialer
  • [ ] Bekræft at `RequestedBy` står i `MANIFEST.json`
  • [ ] Bekræft at en kørsel startet fra Azure DevOps-UI'et stadig virker uden parameteren
#1282 - Send blob-SAS gennem miljøet i stedet for --sas-token (08-08-26 20:42)

Fejlen


Uploaden fejlede med `NoAuthenticationInformation`, efterfulgt af en stribe mærkelige linjer:

```
'st' is not recognized as an internal or external command
'se' is not recognized as an internal or external command
'sig' is not recognized as an internal or external command
```

Det er **SAS-tokenets query-parametre der bliver eksekveret som kommandoer**.

Årsagen


`az` er `az.cmd` på Windows, så argumenterne genparses af `cmd.exe` — og dér er `&` en kommandoseparator. Tokenet blev derfor hugget over ved første `&`:

```
--sas-token "sp=racwl&st=...&sig=..."
^ cmd deler her
```

az fik ingen credential overhovedet, og resten (`st=...`, `se=...`, `sig=...`) blev forsøgt kørt som programmer.

Det kan ikke løses med bedre citering i PowerShell — opdelingen sker *efter* PowerShell er færdig med linjen.

Rettelsen


az læser `AZURE_STORAGE_ACCOUNT` og `AZURE_STORAGE_SAS_TOKEN` direkte fra miljøet, så værdien aldrig rører en kommandolinje:

```yaml
env:
AZURE_STORAGE_ACCOUNT: kbupdate
AZURE_STORAGE_SAS_TOKEN: $(BlobWriteSas)
```

`--account-name` og `--sas-token` er dermed fjernet fra kaldet. Sidegevinst: hemmeligheden står ikke længere i procesargumenterne.

Bedre fejlbesked


Katalogrækken oprettes før uploaden, så en fejlet upload efterlader en pakke uden payload. Beskeden nævner nu pakkenummeret der skal ryddes op, i stedet for kun at tale om blob-mappen.

Status på kørslen der fejlede


Den nåede at oprette **pakke 10 med 2 objektlinjer** før uploaden fejlede. Den pakke har ingen payload og bør slettes før næste kørsel.

Test


  • [x] Årsagen identificeret ud fra `'sig' is not recognized`-linjerne
  • [ ] Pipelinen kørt igennem uden DryRun
  • [ ] `Package<nr>/` findes i blob med HOTFIX og MANIFEST.json
  • [ ] `SourceTag` stemplet på pakken
#1281 - Vis api2's fejlbesked i stedet for kun statuskoden (08-08-26 20:06)

Problemet


Første rigtige kørsel (uden DryRun) fejlede med:

```
Invoke-RestMethod : The remote server returned an error: (406) Not Acceptable.
```

Og intet andet. api2 lægger forklaringen i svarets **body**, men `Invoke-RestMethod` kaster og smider den væk.

Kørte man samme kald med curl, stod der:

```
Row not valid - Denne række findes allerede!
(PackageId: KB_PkgCatalog:PackageId=6)
```

Altså årsagen sort på hvidt — nummerserien uddeler et id der allerede er taget — men intet af det nåede byggeloggen.

Rettelsen


Alle seks api2-kald går nu gennem en lille wrapper der læser response-streamen og hænger serverens besked på fejlen:

```powershell
catch {
$resp = $_.Exception.Response
if ($resp) { $detail = (New-Object System.IO.StreamReader($resp.GetResponseStream())).ReadToEnd() }
$msg = "$What failed ($Method): $($_.Exception.Message)"
if ($detail) { $msg += " -- server said: $detail" }
throw $msg
}
```

Hvert kald får en `-What`-tekst, så det fremgår hvilket skridt der fejlede ("Creating the package", "Copying TASK X onto package 57", …).

**URL'en er bevidst udeladt** af fejlbeskeden — api2-tokenet står i dens sti.

Verifikation


Kørt mod det endpoint der faktisk fejlede:

```
Creating the package failed (Post): (406) Ikke acceptabel.
-- server said: Row not valid - Denne række findes allerede!
(PackageId: KB_PkgCatalog:PackageId=6)
```

Det er beskeden der manglede.

Bemærk


Selve 406-fejlen er ikke en fejl i pipelinen: nummerserien for `PackageId` er ude af trit med kataloget og skal sættes frem forbi den højeste eksisterende pakke. Denne PR sørger blot for at den slags siger sig selv næste gang.

Test


  • [x] Wrapper verificeret mod live-endpointet
  • [ ] Pipelinen kørt igennem uden DryRun efter at nummerserien er rettet
#1279 - Fix: objektlisten blev ikke læst korrekt i PowerShell 5.1 (08-08-26 19:26)

Fejlen


Første kørsel af pakke-klipperen fejlede med:

```
##[warning]FILE TASK Vare VAREKORT: not found at 20260804.2 - skipped
None of the listed objects exist at 20260804.2
```

Bemærk at **to objekters værdier står sammenkædet i én advarsel**. Løkken kørte altså kun én gang, med hele listen bundet til `$o`, så `$o.ObjType` returnerede `@("FILE","TASK")` — der interpoleres til `"FILE TASK"`.

Årsagen


`@(Get-Content ... | ConvertFrom-Json)` gør ikke det den ser ud til på agenten. Den kører **Windows PowerShell 5.1** (`WindowsPowerShell\v1.0\powershell.exe`), som sender hele det parsede array videre som ét pipeline-element. Array-subexpressionen pakker det derfor ind i et 1-element-array der indeholder arrayet.

Rettelsen


Tildel først, konvertér bagefter — det opfører sig ens i 5.1 og 7:

```powershell
$parsed = Get-Content $f -Raw | ConvertFrom-Json
$list = @($parsed)
```

Samme mønster tre steder: objektlisten i `Build payload`, i `Create package and copy the object list`, og payload-rækkerne i `Write manifest`.

Dertil et formtjek efter indlæsning, så en fremtidig ændring i deserialiseringen fejler med en tydelig besked frem for stille at iterere over det forkerte:

```powershell
if ($list.Count -gt 0 -and -not $list[0].PSObject.Properties['ObjType']) { throw ... }
```

Verifikation


Reproduceret mod Windows PowerShell 5.1 med samme data som i den fejlede kørsel:

| Form | Resultat |
|---|---|
| `@(... \| ConvertFrom-Json)` | `Count = 1`, `ObjType = "FILE TASK"` |
| `$parsed = ...; @($parsed)` | `Count = 2`, rækkerne intakte |

Den øverste er nøjagtig den streng der stod i byggeloggen.

Test


  • [x] Kvirken reproduceret og rettelsen verificeret lokalt i PowerShell 5.1
  • [ ] Pipelinen kørt igen med DryRun
  • [ ] Objekterne findes og lander i payloaden
#1278 - Pipeline: klip PTD-pakke fra objektliste og to baseline-tags (08-08-26 10:40)

Formål


Producent-siden af pakke-distributionen. Indtil nu skulle hver payload samles i hånden i blob storage — det skalerer ikke, og en håndlavet mappe er ikke reproducerbar, hvilket ellers er hele grundlaget modellen hviler på.

Pipelinen tager en **kildepakkes objektliste** plus **to baseline-tags** og udgiver de af listens objekter der er ændret imellem dem.

`trigger: none` — filen starter ikke noget af sig selv.

Arbejdsgangen


Du angiver kun hvilken pakke du kopierer listen fra, et navn, og hvilket tag du udgiver til. `BaseTag` kommer af sig selv fra kildens `SourceTag`, så der er intet tag-bogholderi at holde styr på fra måned til måned.

```
læs kilde → validér tags → byg payload
↓ (første centrale skrivning først her)
opret pakke → kopiér liste → manifest

upload → stempl SourceTag
```

Fejler noget i de tre første trin, er der intet oprettet centralt. `SourceTag` stemples allersidst, fordi det er dét den næste klipning fortsætter fra — en pakke uden publiceret payload må ikke hævde at være nået dertil.

Detaljer værd at kende


  • **`fetchDepth: 0` er påkrævet.** Azure checker ud shallow som standard, og en shallow klon kan ikke diffe to tags — den fejler eller, værre, rapporterer for lidt.
  • **`git archive`, ikke checkout eller tekstkopi.** PTDSAMLET blander Windows-1252, UTF-8 og ASCII. Alt der læser en fil som tekst og skriver den tilbage ødelægger encoding og normaliserer linjeskift. `git archive` streamer de gemte blobs byte-for-byte — verificeret identisk med `git show`.
  • **Case-kollision fejler kørslen.** Git skelner mellem store og små bogstaver, Windows gør ikke. Repoet har et sådant par i dag (`TASK/Dash_Chart_DebForfald.PTD` og `.ptd`), og uden vagten ville den ene tavst kunne overskrive den anden i en payload.
  • **Listen føres videre i sin helhed**, ikke beskåret til det der blev sendt — ellers ville det stående sæt skrumpe for hver udgivelse. Hvad der faktisk blev sendt står i `MANIFEST.json` inde i payloaden, sammen med `MergeMode` per objekt.
  • **`--overwrite false` ved upload.** En publiceret payload er uforanderlig; et andet forsøg skal fejle højlydt frem for at omskrive noget kunder allerede kan have installeret.
  • **Slettede objekter** kan ikke udtrykkes i en payload (importen kan kun tilføje og overskrive) — de logges som advarsel.

Opsætning


To secret-variabler på pipelinen: `PkgApiToken` (api2-token) og `BlobWriteSas` (write-SAS til `ptdreleases`).

Test


  • [x] Diff- og arkiv-logikken verificeret lokalt mod rigtige tags (`20260803.6` → `20260804.2`, 6 ændrede filer)
  • [x] `git archive` giver byte-identisk indhold
  • [x] Case-kollisionen i repoet fundet og afvist af vagten
  • [ ] Pipelinen kørt i Azure (YAML-syntaks og opgavenavne er ikke valideret herfra)
  • [ ] DryRun-kørsel giver forventet payload i build-artifacten
  • [ ] Fuld kørsel opretter pakke, uploader og stempler `SourceTag`
#1277 - SFaktura: beskyt DB% med KOSTPRIS + indsnævr arkivoversigtens bilagstyper (07-08-26 15:46)

Formål


De to ændringer der blev holdt ude af MCP-PR'en (#1276), fordi de ikke har med MCP at gøre. De er indbyrdes uafhængige og kan skilles, hvis I foretrækker det.

1. `FILE/SFaktura.PTD` — rettighedslæk på dækningsgrad


`FILEFIELD DB1` (dækningsbidrag i kr.) er beskyttet af `RightsGroup = "KOSTPRIS"`. `DGrad` (DB %) var ikke — men beregnes som:

```
Init( SFaktura.DB1 * 100 / SFaktura.NettoDKR )
```

Altså præcis samme oplysning udtrykt som procent. Brugere uden KOSTPRIS-rettighed kunne se dækningsgraden, selvom kronebeløbet var skjult. `DGrad` får nu samme `RightsGroup`.

Mønsteret følger huset — `RightsGroup` på FILEFIELD-niveau bruges bl.a. i `FILE/BS_Bilag_VareLinjer.PTD` med samme indrykning.

```diff
FILEFIELD DGrad {
BaseType = BDouble
TypeRef = Beløb
+ RightsGroup = "KOSTPRIS"
Details { TYPEDETAIL {
Prompt = "DB %"
```

Diffen er **én tilføjet linje, nul slettede**. Tidligere udgave af denne ændring optrådte som en omskrivning af alle 2252 linjer, fordi filens blandede linjeskift var blevet normaliseret. Filen har `CRLF` de fleste steder, men **bare `CR`** på en række `// AfterRecord` / `// DoTask`-kommentarlinjer. Den profil er bevaret uændret her, så kun den reelle ændring er i diffen. Normaliseringen bør tages for sig, hvis den ønskes.

2. `TASK/SFAKTURA_ALLE.PTD` — arkivoversigtens udvalg


```diff
SelectField = SalgKøbProd
  • FromTo( [0;3..29] )
+ FromTo( [0;10..29] )
```

Type **3–9 udgår** af "Salgsfaktura - Arkiv".

⚠️ **Bør bekræftes ved review:** der findes ingen `TYPE`/enum for `SalgKøbProd` i repoet — feltet er en rå `BUShort` med hinttekst "Er Ordren salg/Indkøb eller produktion". Værdirummet er kun dokumenteret implicit gennem andre tasks: `0` = salgsfaktura (`SFAKTURA`), `10` = abonnement (`SFAKTURA_ABONNEMENT`), `12` = maskine (`SFAKTURA_MASKIN`). Hvad 3–9 dækker, og om de bevidst skal ud af arkivoversigten, kan jeg ikke afgøre ud fra koden.

Forhold til #1276


Begge PR'er rører `TASK/SFAKTURA_ALLE.PTD` — #1276 tilføjer `Scope = UseMCP` (linje 5), denne ændrer `FromTo` (linje 7). Jeg har simuleret fletningen med `git merge-tree`: **ingen konflikt**, resultatet får korrekt begge ændringer. Rækkefølgen de merges i er uden betydning.

Test


  • [ ] `KBRebuild.exe regnskab -S -IPTD ptdsamlet/file/SFaktura` oversætter
  • [ ] Bruger **uden** KOSTPRIS-rettighed kan ikke se DB % nogen steder hvor `SFaktura.DGrad` vises
  • [ ] Bruger **med** KOSTPRIS-rettighed ser DB % som før
  • [ ] Bekræft at bilagstype 3–9 skal ud af "Salgsfaktura - Arkiv"
  • [ ] Arkivoversigten viser stadig abonnements- og maskinfakturaer (10 og 12 er inden for det nye interval)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
#1276 - MCP: Scope = UseMCP på standard-tasks + MCP-pakke til tidlig udrulning (07-08-26 15:42)

Formål


Åbner 1034 TASK-definitioner for MCP-serveren, og lægger samtidig en kopi-pakke i `EXTRA_PTD`, så udvalgte kunder kan få funktionaliteten **før** næste standardopdatering.

Standard — permanent


  • **1031 tasks** får `Scope = UseMCP` tilføjet.
  • **3 dashboard-tasks** får `UseMCP` føjet til deres eksisterende Scope-liste: `Dash_Chart_Oms`, `Dash_Chart_DebSaldo`, `DASH_DEBITOR_Forfald` (fx `Scope = [ UseDashboard, UseMCP ]`).

Pakke til tidlig udrulning — midlertidig


20 tasks er kopieret til `EXTRA_PTD/TASK/MCP_<navn>.PTD`:

`ALSTATISTIK`, `DEBITOR`, `DTRANSAKTIONER`, `DVSTATISTIK`, `FKONTOPLAN`, `FTRANSAKTIONER`, `IOLSTATISTIK`, `KREDITOR`, `KTRANSAKTIONER`, `LVSTATISTIK`, `MASKINE`, `MTRANSAKTIONER`, `OLSTATISTIK`, `SAG`, `SFAKTURA`, `SFAKTURA_ALLE`, `SOLSTATISTIK`, `STLSTATISTIK`, `VARER`, `VTRANSAKTIONER`

I hver kopi er **kun to linjer** ændret:

1. `TASK <navn> {` → `TASK MCP_<navn> {`
2. Den selv-refererende `TaskRef = <navn>` → `TaskRef = MCP_<navn>`

Bevidst urørt: fremmed-`TaskRef` (fx `DEBITORKORT`, `DPOST`, `SAGSKORT`) peger på tasks der ikke pakkes med; `FileId`/`FileRef`, `RightsGroup` og `Hint`/`Title`-tekster indeholder samme ord, men er ikke task-navne.

Verificeret: hver kopi er præcis 8 bytes større end originalen (2 × `MCP_`), `diff` viser nøjagtigt 2 linjer, og original-encoding (ISO-8859-1 / UTF-8 pr. fil) samt CRLF er bevaret.

⚠️ `EXTRA_PTD/TASK` er uden for autobyg, så **kopierne oversættes ikke af pipelinen og fanges ikke af PR-validering**. De skal bygges manuelt hos kunden. Kopierne er et udrulningsvehikel, ikke en varig parallel-version — når standarden er ude, er de redundante og bør fjernes.

Holdt bevidst udenfor


  • **`_WE`-tasks (172 filer)** rullet tilbage. Webklienten tages i en separat PR.
  • **`FILE/SFaktura.PTD`** rullet tilbage. Den indeholdt en `RightsGroup = "KOSTPRIS"`-tilføjelse uden relation til MCP, og filens blandede linjeskift (bare `CR` på en del `// AfterRecord` / `// DoTask`-kommentarlinjer) var normaliseret til CRLF — det gjorde diffen til en omskrivning af alle 2252 linjer for én reel ændring. Bør tages for sig, evt. med line-ending-normaliseringen som separat commit.
  • **`SFAKTURA_ALLE`**: `FromTo` rullet tilbage til `[0;3..29]`. Ændringen til `[0;10..29]` indsnævrer hvilke bilagstyper oversigten viser — reel adfærdsændring, hører ikke her.

Derudover er `Scope = UseMCP` i `IOLSTATISTIK.PTD` rykket ind med 2 spaces som i alle øvrige filer (stod i kolonne 0).

Test


  • [ ] Fuldt byg går igennem med `Scope = UseMCP` på 1034 tasks
  • [ ] MCP-serveren lister de nye tasks via `get_available_tasks`
  • [ ] De 3 dashboard-tasks virker stadig på dashboardet (Scope-listen er udvidet, ikke erstattet)
  • [ ] `KBRebuild.exe regnskab -S -IPTD ptdsamlet/extra_ptd/task/MCP_<navn>` oversætter for de 20 kopier — bemærk at pipelinen **ikke** dækker dem
  • [ ] En MCP_-kopi kan åbnes side om side med sin original uden navnekollision

🤖 Generated with [Claude Code](https://claude.com/claude-code)
#1273 - Pakke-ledger: kundeside-filer til PTD-distribution (07-08-26 10:26)

Formål


Første skridt mod at udsende PTD-funktionalitet til kunderne som nummererede, tildelte pakker i stedet for ét `PTDVersionNY`-nummer. Disse to FILEs er kundesiden af modellen.

Design: `docs/ptd-distribution-model.md` i Keybalance2018 (separat PR).

Filer


**Beholdt — skal ud til kunderne**

  • `FILE/KB_PkgLedger.ptd` — én række pr. pakke tildelt denne KBA. Kunden ejer resultatet (status, forsøg, fejltekst); tildelingen kommer fra centralen.
  • `FILE/KB_PkgAncestor.ptd` — indeks over ancestor-fil-træet, dvs. den seneste upstream-udgave vi har leveret af hvert objekt. Det er "Original"-siden i 3-vejs-fletningen.

**Slettet — kun til os selv**

  • `FILE/KB_PkgAssign.ptd`
  • `FILE/KB_PkgCatalog.ptd`
  • `FILE/KB_PkgCatalogObj.ptd`

De tre er centrale administrationstabeller. De er indlæst i vores egen centrale Keybalance og må aldrig havne hos en kunde — så slipper kundens ressourcesystem også for tabeller det ikke bruger.

Ændringer i denne PR


  • **Tid-felter** på tidsstemplerne: `LastAttemptTime`, `AppliedAtTime`. `BDate` alene mister rækkefølgen når flere pakker lander samme dag. Følger husmønsteret `BDate` + `BTime` (jf. `Ordre_Log` Dato/Tid).
  • **`PTDSource`-rod** på ancestor-træet (`<respath>\PTDSource\Ancestor\...`), så det er ét træ blandt søskende. Giver plads til et fremtidigt `Local\`-træ uden at skulle lave om på noget kunderne allerede har liggende. Kun kommentaren er rørt — `RelPath`-værdier er uændrede.
  • Sletning af de tre centrale filer.

Test


  • [ ] `KBRebuild.exe regnskab -S -IPTD ptdsamlet/file/KB_PkgLedger` oversætter
  • [ ] `KBRebuild.exe regnskab -S -IPTD ptdsamlet/file/KB_PkgAncestor` oversætter
  • [ ] Fuldt byg går igennem uden referencer til de tre slettede filer
  • [ ] `BChList` (Status) og `BTime` (Tid-felterne) ser rigtige ud i en tabelvisning

Bemærk


Ingen kode bruger tabellerne endnu — de er tomme definitioner. Selve mekanikken ligger i Phase 0 af `KBAutoU` (skitseret, ikke bygget), så denne PR kan ikke ændre adfærd for nogen kunde.
#1253 - :102 udenfor string() (04-08-26 16:39)
:102 udenfor string()
#1255 - Nulstilling af BSBilag ved kørsel på flere bankposter (04-08-26 16:38)
Nulstiller $BSBilag i BeforeRecord i BankPost_Afstem_TilKladde, så en tidligere bankposts BS-nummer ikke bæres videre til næste post, når kun én af flere bankposter har angivet BS.
#1168 - Beregn rabat på oio fra AAB (bruttopris) (03-08-26 14:05)
Beregn rabat på oio fra AAB (bruttopris)
#1249 - Kopiér salgsordre: åbn korrekt kort + fix af Fkey-tabel (03-08-26 13:46)
Retter to fejl relateret til kopiering af salgsordre (SORDREKOPIER):
#924 - Salgsgebyrer grundlæggende forbedret. Nu burde alle beregnes og printes korrekt! (03-08-26 13:17)
Louise:
Tilføjelse af gebyr på udskrifter

Lasse
Nu er gebyrgrupper lagt om til virtuelle felter
Der er opfundet samlet gebyr som er med på ordrekort og bruges i beregningerne.
Så bør alt være VARE + SamletGebyrFørMoms + SamletGebyrEfterMoms + Fragt - Rabat
Ved fakturering flyttes fra Auto felterne (de nye virtuelle) til at sammenlægges med de manuelle.

Ordre, FakKladde og OrdreMulti er rettet til med gebyrer.

Momsgrundlag bør have review. Jeg mener den er korrekt MEN check gerne.
NettoDKK er ikke bare Netto*kurs - er det fordi at kursen ganges på helt nede på ordrelinjen eller hvordan?
#1188 - Små rettelse Chart (2nd level CarlJ) (03-08-26 11:03)
#1247 - KeyBalanceWF2GitBuild.bat: hent compileren hvor den bliver bygget (03-08-26 08:53)

Baggrund


Batten hentede assemblies fra `..\..\KeybalanceWF`, det gamle WinForms-træ. De fire filer den skulle forny — `KRuntime.dll`, `KB.PTD.Parser.dll`, `KB.PTD.Grammar.dll` og `KBRebuild` — bliver ikke bygget der længere. De kommer fra Keybalance2018.

Et tørløb (`xcopy /L`) viser hvad linjen faktisk gjorde:

```
gammel: EXTDLLS\FileHelpers.dll, EXTDLLS\SecuredClient.dll 2 filer
ny: de seks filer fra KBRebuild\bin\Release 6 filer
```

Den har altså **aldrig** fornyet compileren. Det eneste den nåede var at overskrive to EXTDLLS-afhængigheder med kopier fra 2022, fordi `/S` rekurserer og `/U` rammer ethvert filnavn der tilfældigvis også findes i destinationen.

Det skete i praksis i dag: en utilsigtet kørsel byttede `FileHelpers.dll` fra 146.944 til 208.896 bytes — væk fra den version værktøjskæden i PR 1243 er verificeret med. Uden varsel, og kun synligt fordi `git status` fangede den.

Ændringen


```diff
xcopy /U /S ..\..\New\Def\* Def\ /Y
-xcopy /U /S ..\..\KeybalanceWF\* KeybalanceWF\ /Y
+xcopy /U ..\..\Keybalance2018\KBRebuild\bin\Release\net10.0-windows\* KeybalanceWF\ /Y
```

Kilden peger nu på det build der faktisk producerer filerne, og `/S` er væk fra den linje, så xcopy ikke kan grave i undermapper. `/U` bevares, så kun de seks filer der allerede ligger i mappen fornys, og `EXTDLLS` bliver ladt i fred — præcis det sæt der blev lagt op i hånden i PR 1243.

Def-linjen er urørt. Kommentaren over linjerne forklarer hvorfor de fire assemblies skal komme fra ét og samme build: `KB.PTD.Parser` slår felter op med refleksion i KRuntimes structs, så parser og runtime kan ikke fornys hver for sig uden at modellen og tabellen driver fra hinanden.

Det her er stadig en genvej


Den rigtige løsning er at udgive værktøjskæden som pipeline-artefakt i stedet for indcheckede binære filer, så der slet ikke er kopier at holde ved lige. Mønstret findes allerede i PTD-pipelinen, som henter `programEmpty` som pipeline-ressource. Det står noteret sammen med embedding af `objects.tab` i KRuntime i Keybalance2018s `docs/REFACTORING-BACKLOG.md`.

Test


  • [ ] Kør batten fra `_TOOLS` og se at kun de seks filer i `KeybalanceWF\` opdateres
  • [ ] `git status` bagefter: `EXTDLLS` og `EXTDLLS86` skal være urørte

🤖 Generated with [Claude Code](https://claude.com/claude-code)
#1248 - Barcode og vare forbedringer (03-08-26 08:23)
Barcode og vare forbedringer
#1243 - Delt udskrifts-skabelon: 16 udskrifter styres nu fra ét sted (BlockRef/ElementRef + PostLinje) (31-07-26 13:50)

Formål


Elementer der er placeret på en formular kan nu rettes centralt via skabelonen `Faktura`, uden at rette i de enkelte PTD'er. 16 udskrifter er med.

Runtime slår op på `(Template, BlockRef, ElementRef)` — ikke rapportnavn. Ét ref = én placering på alle udskrifter.

Omfang


**Salg (3):** UDSKRIVFAKTURA, UDSKRIVORDRE, UDSKRIVTILBUD
**Indkøb + kladde (10):** IUDSKRIVFAKTURA, IUDSKRIVORDRE, IUDSKRIVFAKKLADDE, IUDSKRIVFORESP, IUDSKRIVFØLGESEDDEL, IUDSKRIVMODTAGSEDDEL, IUDSKRIVREKV, IUDSKRIVOrdreRykker, UDSKRIVFAKKLADDE, UDSKRIVTILBUDSPEC
**Debitor (3):** DKONTOUDTOG, UDSKRIVAABNEKTOG, UDSKRIVRYKKER

Lønudskrifterne er holdt udenfor, da de udgår.

Navngivning


  • **Indholds-ref** hvor elementet står samme sted med samme betydning. Feltet må gerne være forskelligt: `FakturaNr` bærer fakturanr/ordrenr/tilbudsnr — de deler slot.
  • **Positions-ref for adresser** (`Left*`/`Right*`/`Mid*`/`Lev*`): betaler står til venstre på en faktura, til højre på en ordre. Da ét ref kun kan have én position, navngives adresserne efter position, ikke efter part.
  • **Ét `FirmaHoved`-bånd (3900)** overalt. `FirmaHovedTom` er væk; brevhoved-elementerne gates individuelt med hver fils **egen** betingelse (`Forms.BrevH`, `Enable(1)` eller `Enable(0)`). Auditeret mod originalen — adfærden er bevaret i alle filer.
  • **Bånd-højder** hører i skabelonen, ikke i PTD'en.

Ny arketype: PostLinje


Posteringstabellen på debitorudskrifterne (bilag, datoer, beløb) er en anden tabel end fakturaens varelinje. At tvinge den ind i `VLinje` gav semantisk forkerte mapninger og op til 284 mm skred.

`PostLinje` har 8 kolonne-slots. Kun par der aldrig optræder i samme dokument deler position: `Udlign`/`RykNr`, `Valuta`/`Aabent`, `Saldo`/`Rente`. Kolonneoverskrifterne (`Post*Txt` i `Hoved`) har samme Left/Width som deres kolonne, så overskrift og data ikke kan glide fra hinanden. Dokumenteret i `_TOOLS/Templates/POSTLINJE.md`.

Bevidst uden ref: rykkerens totalblok (Forfalden saldo/Rente/Gebyr er ikke fakturaens Netto/Moms/Total), aldringstabellerne (kun båndet får `Total` for `BetID`) og carry-over-båndene, hvis højde og saldo-position ikke passer.

Risiko: hvad sker der uden skabelonrækker?


**15 af 16 udskrifter printer præcis som før.** Alle koordinat- og højdelinjer er sammenlignet før/efter (encoding neutraliseret): 13 filer har kun den slettede `FirmaHovedTom`-linje, som er højde-neutral. Eneste ægte undtagelse er UDSKRIVTILBUD, hvor `TekstLinje`-båndet er 1 mm lavere.

Refs er inerte uden en skabelonrække — `FindTemplateInfo` returnerer false, og PTD'ens egne koordinater gælder. Risikoen ligger derfor i **importen**, ikke i PTD-ændringerne. Slettes rækkerne, falder alt tilbage til koordinaterne.

Filer


  • 16 PTD-udskrifter (også konverteret fra Windows-1252 til UTF-8, verificeret tabsfrit)
  • `_TOOLS/Templates/faktura-template.csv` — 248 rækker, nu med `BlockType` og `Sort` så den visuelle editor kan lave korrekt header/detail/footer-layout
  • `_TOOLS/Templates/regenerate-template.ps1` — genskaber CSV'en; bærer `PostLinje` som autoritativ definition, da koordinaterne **er** standarden. Advarer hvis et bånd mangler sin båndrække (editoren fejler ellers)
  • `_TOOLS/Templates/POSTLINJE.md`, `README.md`

Test


  • [ ] Kompilér de 16 PTD'er
  • [ ] Print faktura/ordre/tilbud **uden** at importere skabelonen — skal se uændret ud
  • [ ] Ryd `Faktura`-rækkerne og importér `_TOOLS/Templates/faktura-template.csv`
  • [ ] Print igen: brevpapirets top skal flugte på alle 16 (brevhovedet er drift 0)
  • [ ] Test med og uden brevhoved (`Forms.BrevH`) — brevhovedet må kun tegnes når flaget er sat
  • [ ] **Rykkeren skifter kolonnerækkefølge** (BILAG først, FORFALD næstsidst) — det er den tilsigtede forening, men skal ses efter
  • [ ] DKONTOUDTOG: linjehøjde 400 → 500 (retter at elementerne i forvejen var 500 høje i et 400-højt bånd)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
#1219 - L.Saldo + mps udskrift linjeskift (30-07-26 18:30)
#1242 - Saldooversigter: totalerne summeres nu fra de linjer der faktisk printes (30-07-26 12:43)

Resumé


De fire saldooversigter tog deres totaler fra kontofelterne `SaldoDKR` / `ForfaldentDkr`, mens de printede linjer var et filtreret udsnit — kun forfaldne poster, eller kun poster der var åbne pr. en given dato. Total og linjer kunne derfor ikke stemme.

Nu summeres der undervejs: hver linje lægger sit eget åbne beløb til `$LinjerSaldo` (og til `$LinjerForfaldent` hvis den er forfalden), delsummen pr. konto printes fra de to variabler, og hovedtotalen bygges af delsummerne og nulstilles før næste konto.

**Tre ting værd at fremhæve:**

1. **Akkumuleringen i `_DATO`-varianterne er pakket i samme betingelse som `REPORT PosterList`s `ShouldExecute`.** Uden den blev en linje der netter til nul i valuta talt med i DKK — og da DKK-beløbet er `ÅbentBeløbDkr + $TotalDKR` med to forskellige kurser, lagde en fuldt udlignet valutalinje sin kursdifference oveni totalen uden en synlig linje at forklare den med. Samtidig springes footer-snapshottet over for en linje der ikke printes, så delsum og hovedtotal kunne drive fra hinanden.

2. **Forfaldstesten er strammet fra `<=` til `<` pr.-datoen,** så den matcher definitionen af `ForfaldentDkr`, der opgør over `[..max(DatoAfgrænsning) - 1D]`. En post der forfalder præcis på pr.-datoen regnes altså ikke længere som forfalden. Rettet både i subtaskens `CanEnter` (hvad der printes) og i akkumuleringen (hvad der summeres) — og tilsvarende mod `$$BogføringsDato` i ikke-dato-varianterne, så hele familien har samme definition.

3. **I `KSALDOOVERSIGT.PTD` skifter forfaldent-totalen reelt grundlag:** den kom fra `Kreditor.ForfaldentDkr`, som opgør over `max(Kreditor.DatoAfgrænsning)` — men `DatoAfgrænsning` bliver aldrig sat i den task. Nu er det eksplicit "forfald før bogføringsdato" summeret over de linjer der faktisk printes.

Placeringen af akkumuleringen inde i `AfterRecord` er uden betydning for delsummen: `KTask.OnLeave()` afvikler hele `AfterRecord`-listen, og først derefter kaldes `PrintFooters()`, som snapshotter footer-værdierne. Sidste linje kommer altid med.

Filer


| Fil | Ændring |
|---|---|
| `TASK/DSALDOOVERSIGT.PTD` | Linjesummer + `< $$BogføringsDato` i `CanEnter` og akkumulering |
| `TASK/DSALDOOVERSIGT_DATO.PTD` | Linjesummer, guard mod `ShouldExecute`, `< #PrDato` begge steder |
| `TASK/KSALDOOVERSIGT.PTD` | Samme port som debitor-varianten (ingen `KunForfaldne`, så kun akkumuleringen) |
| `TASK/KSALDOOVERSIGT_DATO.PTD` | Fuld port af `_DATO`-mønsteret inkl. guard |

`DSALDOOVERSIGT-STYLE2025.PTD` er urørt — den bruger `Options = [ Style2025 ]` med automatiske kolonnetotaler.

Testplan


  • [ ] `_DATO` med "Kun forf.poster": en post der forfalder præcis på pr.-datoen falder nu ud af både linjer og totaler
  • [ ] Valutakonto med en faktura udlignet efter pr.-datoen: delsummen "Åbent - DKR" flugter med summen af de printede linjer
  • [ ] Hovedtotalen flugter med summen af delsummerne på tværs af flere konti
  • [ ] `KSALDOOVERSIGT` (ikke-dato): saldo-totalen er uændret, forfaldent-tallet kan flytte sig (se punkt 3)
  • [ ] Konto med kun én linje: delsummen kommer stadig korrekt med

🤖 Generated with [Claude Code](https://claude.com/claude-code)
#1241 - Warnings - lookup - fejl (30-07-26 11:28)
Enkelte fejl i Flexpos lookup, fejl i lookup
#1239 - RTEXT - CalText/TextLayer opryddet (30-07-26 11:09)
RTEXT - CalText/TextLayer opryddet
Der kan ikke være CalcText og TextLayer i samme RTEXT
Vise CalcText med konstante strings er blevet til Text og TextLayer bevaret.

#1189 - Ret bemærkning på godkendte bilag (hvis de ligger og afventer noget) (22-07-26 12:02)
#1186 - Running page-balance carry-over in tilbud/ordre/fakkladde printouts (10-07-26 06:38)

Summary


Ports the "Overført fra/til side" running-balance mechanism from **UDSKRIVFAKTURA** to **UDSKRIVTILBUD**, **UDSKRIVORDRE** and **UDSKRIVFAKKLADDE**, so multi-page printouts carry the accumulated net amount across page breaks.

In each report's `UDSKRIV` subtask:
  • `LøbendeSaldo` / `LøbendeSaldo2` local vars (Beløb) added next to `SideStart`.
  • Both reset to 0 in the header record's `AfterRecord`.
  • Accumulated per line in the `Linjer` subtask's `AfterRecord`: `LøbendeSaldo` = running total incl. the current line, `LøbendeSaldo2` = total before it (carried from the previous page).
  • `Header2` (`Show = AllButFirst`): "Overført fra forrige side" + `LøbendeSaldo2`.
  • `Footer2` (`Show = AllButLast`, `ForceShow`): "Overført til næste side" + `LøbendeSaldo`.

Mirrors the existing, proven pattern in UDSKRIVFAKTURA exactly.

Files

  • `TASK/UDSKRIVTILBUD.PTD`
  • `TASK/UDSKRIVORDRE.PTD` (Windows-1252 — encoding preserved)
  • `TASK/UDSKRIVFAKKLADDE.PTD` (Windows-1252 — encoding preserved)

Test plan

  • [ ] Print a **tilbud**, an **ordre** and a **fakturakladde** that span more than one page → "Overført til næste side" appears at the bottom of each page with the page's running total, and "Overført fra forrige side" appears at the top of the next page with the same amount.
  • [ ] Single-page printouts are unchanged (Header2/Footer2 must not appear — `AllButFirst`/`AllButLast`).
  • [ ] Totals at the bottom (NETTO/MOMS/TOTAL) are unchanged.
#1166 - CRM - kører nu på KundeEmne, knappen virker, interface er det samme (09-07-26 08:19)
CRM - kører nu på KundeEmne, knappen virker, interface er det samme
#1178 - Trigger, ikke alias, men fil (09-07-26 07:30)
Trigger, ikke alias, men fil
I Select - FromTo
#1169 - Claude PR review: inline kontekst og diff i prompten for hurtigere review (08-07-26 10:41)

Baggrund

#1173 - Fejl i Vis Tilbud (08-07-26 10:03)
Fejl i Vis Tilbud
Ingen parametre overhovedet - kopieret fra SORDREKORT
#1171 - Feature bedre graf over omsætning resultat (08-07-26 10:02)
Feature bedre graf over omsætning resultat