Synk med Business NXT¶
Foreløpig — til kundeavklaring
Lesekontrakten er verifisert mot et reelt NXT-selskap med representative data (3 391 masterordrer, 2 373 produkter, 33 244 serienummerrader og 332 189 leveransetransaksjoner tilgjengelig for lesing).
Formål og avgrensning¶
ePortal eier masterordre-registeret; NXT eier produkter, priser, ekspedering og leveransedata. Synken har derfor to helt ulike deler:
- Løpende synk — polling, daglig og ved behov.
- Engangsmigrering — de eksisterende masterordrene flyttes inn i ePortal én gang og arkiveres deretter i NXT.
Løpende synk¶
| Datatype | Retning | Brukes til |
|---|---|---|
| Produkter med klassifisering og strukturer | NXT → ePortal | Produktvalg, instrumentklassifisering (type 1, 2 og 10) |
| Priser | NXT → ePortal (live-oppslag ved behov) | Alltid fersk pris i tilbud og ordre |
| Kunder / skip / management | Begge veier | En ny kunde kan opprettes i ePortal og få tilbud; overføres til NXT senest ved første kjøp |
| Salgsordrer ved akseptert tilbud | ePortal → NXT | NXT ekspederer og fakturerer — se Datomotor og ordre til NXT |
| Leveransetransaksjoner | NXT → ePortal | Grunnlaget for datomotoren — se klassifisering |
| Serienumre | NXT → ePortal | Ett instrument per serienummerrad |
flowchart LR
A["Planlagt kjøring<br/>(daglig + ved behov)"] --> B["Les endringer fra NXT<br/>(vannmerke: endringstidspunkt)"]
B --> C["Lagre rådata som<br/>uforanderlig historikk"]
C --> D["Klassifiser transaksjoner"]
D --> E["Oppdater leveransebildet<br/>per masterordre"]
E --> F{"Avvik mot lokalt<br/>akseptert tilstand?"}
F -- "Nei" --> G["Ferdig"]
F -- "Ja" --> H["Trenger gjennomgang<br/>(aldri stille overskriving)"]
Beslutningspunkt — kundesynk i konflikt
Kundedata kan endres i begge systemer mellom synkene. Hvem vinner — per feltgruppe eller per system? Og hvilke utfyllende kundefelter må legges til i NXT når en ePortal-først-kunde overføres? Dette må avklares.
Engangsmigreringen av masterordrer¶
- Alle aktive masterordrer (ordretype 6, unntatt ordrer med Gr7 = 9) i Business leses inn og blir ePortal-masterordrer med tilstand «Importert». Gr7 = 9-ordrer holdes bevisst utenfor importen (filtreres bort på Business-siden ved henting).
- Tilknyttet historikk (tilbud, salgsordrer, leveranser, serienumre) følger med som uforanderlige observasjoner — det er slik datomotoren kan forklare datoer fra dag én.
- Etter migreringen arkiveres masterordrene i NXT. Ingen aktive masterordrer vedlikeholdes i NXT etterpå.
- Migreringen kan kjøres på nytt uten duplikater: hver rad har en stabil identitet (f.eks. selskap + journalnummer + transaksjonsnummer for leveranser). Importen overskriver aldri eksisterende rader — en ny kjøring legger kun til nye ordrer, nye linjer og nye observasjoner.
Importere kun ordrer og linjer (uten observasjoner)¶
Observasjons-hentingen (leveringstransaksjonene som mater datomotoren) skanner
et bredt datovindu og kan mot en full prod-kopi overskride tidsgrensen. Du kan
importere kun ordrene og linjene — som allerede har sine egne datoer fra
NXT — ved å sende includeObservations: false:
POST api/SubscriptionImport/run-nxt
{ "maxOrders": 100, "includeObservations": false }
Da hoppes leveringstransaksjons-skanningen over. Ordrene og linjene kommer inn
med riktige datoer; observasjonene (og datomotorens historiske forklaring fra
leveranser) tas ikke med. Standard er includeObservations: true (full import).
Hva importen setter på ordre og linjer¶
- Kundenavn på ordren — hentes fra aktørregisteret (
wv_Actor.actNamevia kundenummeret); finnes ingen lokal aktør, brukes navnet fra NXT-associaten. Navnet driver masterordre-listens kundenavn-kolonne. - Linjepris og rabatt — NXT-ordrelinjens
priceInCurrency(enhetspris) ogdiscountPercent1(rabatt %). Beløp per linje beregnes i visningen som antall × pris × (1 − rabatt %/100). - Produkt-snapshot per linje — instrumentgruppene (Gruppe 1–6) samt beskrivelse og enhet hentes fra produktet i NXT (samme snapshot som når en linje opprettes manuelt). Feiler produktoppslaget, importeres linjen uten snapshot — importen stopper ikke.
Plassholder-produktnumre¶
Et produktnummer fra NXT er ikke alltid et ekte produkt. Når et produkt tas ut av sortimentet, kan NXT legge et erstatningsmerke i produktnummer-feltet på ordrelinjen og beholde den opprinnelige beskrivelsen. Et slikt merke ser ut som et produktnummer, men peker ikke på ett bestemt instrument — flere ulike instrumenter kan ende opp med samme merke.
Derfor kan hver kunde liste opp de produktnumrene som er plassholdere hos seg. Listen er tom som standard; da oppfører importen seg nøyaktig som før.
Når importen møter et produktnummer som står på listen:
- Produktet slås ikke opp. Linjen får verken varegrupper, beskrivelse eller enhet — det er bedre at feltene står tomme enn at et vilkårlig plassholder- produkts verdier settes på et ekte instrument.
- Verdien lagres nøyaktig slik NXT oppga den. ePortal speiler NXT og finner aldri på et erstatningsnummer.
- Ordren flagges for gjennomgang med en systemkommentar som navngir de berørte linjene. Kommentaren ligger i kommentarfeltet på ordren og blir liggende også etter at gjennomgangen er kvittert ut.
Det samme gjelder leveransene: kommer en leveranse inn med et plassholder- nummer, flagges ordren selv om selve linjen har et ekte produkt.
Merk at ordrer som ble importert før listen ble fylt ut, ikke flagges
tilbake i tid. Etterfyllingen (enrich-nxt) slår heller ikke opp et
plassholder-nummer, men den flagger ingenting.
Et plassholder-nummer brukes heller ikke til å koble noe sammen¶
Å la være å hente produktinformasjon er bare halve jobben. Produktnummeret er også nøkkelen resten av modulen kobler på, og der ville et merke som flere instrumenter deler gjøre reell skade. Står nummeret på listen, behandles det derfor som «ikke et produkt» overalt — og det betyr forskjellige ting alt etter hva stedet gjør:
| Hvor | Hva som skjer | Hvorfor |
|---|---|---|
| Instrumentregisteret | Linjen registreres ikke som instrument | Et instrument som er identifisert med et merke flere andre deler, er verre enn ingen registrering. Ordren står allerede til gjennomgang |
| Retur av instrument | Returen matcher ikke noe instrument | Ellers kunne den pensjonere et helt annet instrument som tilfeldigvis har samme merke. Ubehandlet retur er synlig; feil pensjonering er det ikke |
| Malpakker — forslag | Ingen malpakke foreslås for linjen | «Ingen forslag» leses som ingen forslag. Feil malpakke leses som et svar |
| Malpakker — malavvik | Instrumentet telles som uten produktnummer og sammenlignes ikke mot noen mal. En ordrelinje med merket regnes ikke som dekning for et behov | Tallet «instrumenter uten produktnummer» står allerede i panelet, så instrumentet forsvinner ikke — det står synlig som uavklart. Motsatt vei er verre: to urelaterte linjer med samme merke ville ellers dekke hverandres behov og få ordren til å se komplett ut. Gjelder både panelet og nattjobben som fyller oversikten |
| Prismatrise | Linjen får ingen automatisk pris — prisfeltet står tomt og må fylles ut manuelt | Prisgruppene ePortal ville regnet ut fra, tilhører merkets egen katalogoppføring, ikke instrumentet som faktisk ble levert. En pris hentet derfra er ikke et forsiktig anslag, den er et galt tall — og et tall som fylles inn automatisk og lagres på linjen. Tom pris er samme situasjon som et ukjent produkt gir i dag |
| Overføring av tilbud til salgsordre i NXT | Hele overføringen stoppes før noe sendes, og tilbudet havner blant overføringene som trenger tilsyn med produktnumrene som stoppet det | En salgsordrelinje i NXT er en regnskapspost hos kunden, og et merke der peker ikke på noe produkt. Linjen kan ikke rettes fra ePortal etterpå. Hele tilbudet stoppes framfor å utelate linjen, for en kortere ordre enn kunden har akseptert er en stille feil |
| Abonnementsmotoren | Linjen får sin egen referanse i stedet for produktnummeret | Uten dette ville to helt urelaterte linjer med samme merke smelte sammen til ett abonnement |
| Tilbakelesing fra NXT etter tilbudsoverføring | Linjen parres ikke automatisk, men settes til manuell håndtering | Samme utfall som alle andre uklare produktkoblinger: rapportert og overlatt til et menneske, aldri gjettet |
Dette gjelder også ordrer som ble importert før listen ble fylt ut: koblingen vurderes hver gang, ikke én gang ved import. Fyller du ut listen i dag, er gamle linjer beskyttet fra neste gang de leses.
Slik løser du opp et tilbud som ble stoppet¶
Et tilbud som stoppes på vei til salgsordre er kjent ikke sendt — ingenting er opprettet i Business NXT. Verken selve stoppen eller opprettingen etterpå bruker opp noen av forsøkene tilbudet har igjen. (Hadde tilbudet feilet mot NXT en tidligere gang, teller den ordinære omkjøringen av det feilede forsøket ett forsøk, slik den alltid har gjort — det er omkjøringen som teller, ikke plassholder-stoppen.) Slik kommer du videre:
- Åpne NXT-overføringer som trenger tilsyn fra tilbudslisten. Det stoppede tilbudet står der, og feilteksten navngir produktnumrene som stoppet det.
- Trekk tilbudet tilbake til utkast. Tilbudslinjer kan bare endres mens tilbudet er et utkast, og tilbaketrekking er bare mulig så lenge tilbudet ikke har gitt en masterordre eller en salgsordre.
- Bytt ut plassholder-numrene med de virkelige produktnumrene på linjene.
- Produser tilbudet på nytt, og ta det videre til den statusen det sto i før — sendt eller akseptert. Dette steget er lett å hoppe over, og da stopper du opp: handlingen Overfør til NXT finnes ikke på et utkast. Den vises bare på tilbud som er produsert, sendt, purret eller akseptert, så knappen er rett og slett ikke der før tilbudet er tilbake i en av de statusene.
- Kjør Overfør til NXT på tilbudet igjen. Linjene leses på nytt før noe sendes: er de rettet, går overføringen gjennom; er de ikke det, stoppes den igjen på nøyaktig samme måte, uten at noe sendes og uten at et forsøk brukes opp.
Den planlagte kø-jobben plukker ikke opp et stoppet tilbud
Et stoppet tilbud er tatt ut av overføringskøen med vilje — ellers ville nattjobben forsøkt det samme igjen hver eneste kjøring. Kjøringen der tilbudet ble stoppet rapporteres som feilet, med en forklaring i kjøreloggen, slik at overvåkingen ikke viser grønt mens et tilbud venter på en person. Etter det er det den manuelle handlingen som tar tilbudet videre.
Listen er bundet til skrivestartet, ikke til selve sendingen
Lagrer noen plassholder-listen før en overføring har passert skrivestartet — det ene punktet der overføringen får lov til å skrive til NXT — stanses overføringen der. Ingenting sendes, den blir liggende i køen, ingen forsøk brukes opp, og neste gang den kjøres — nattjobben eller Overfør til NXT — vurderes tilbudslinjene mot den listen som nå gjelder.
Har overføringen allerede passert skrivestartet, fullfører den. Den er da bare ett kall unna, og den ble godkjent mot listen som gjaldt i det øyeblikket den passerte. Et nummer du legger til akkurat da stopper altså ikke den overføringen — den nye listen styrer neste overføring.
Er tilbudet kommet så langt at det verken kan endres eller trekkes tilbake, kan ikke linjen rettes i ePortal i dag. Da må salgsordren opprettes og håndteres direkte i Business NXT.
Innstillingen må kunne leses
Kan ePortal ikke lese innstillingene i det hele tatt — for eksempel under en databasefeil — stopper importen med en feilmelding i stedet for å kjøre videre uten beskyttelse. Det er gjort med vilje: en import som stoppet kan kjøres på nytt, mens en linje som er lagret med feil produktidentitet ikke kan rettes av importen etterpå. At nøkkelen ikke er satt er noe helt annet og er ikke en feil — da er listen bare tom, og alt går som før.
Tekniske detaljer
Innstillingen er nøkkelen Subscription.Nxt.PlaceholderProductNos i
wv_SystemConfiguration (klientdatabasen, altså per kunde). Verdien er en
kommaseparert liste; oppslaget trimmer og ignorerer store/små bokstaver.
Tom eller manglende nøkkel slår av hele mekanismen. Nøkkelen settes av en
systemadministrator via POST api/systemconfiguration — den har foreløpig
ingen egen skjermflate.
Avgjørelsen tas ett sted: PlaceholderProductLogic.IsPlaceholder, med
settet fra IPlaceholderProductCatalog. Katalogen leser nøkkelen
utenom 15-minutters-cachen og med SettingReadFailurePolicy.Throw, og
memoiserer svaret for levetiden til den injiserte instansen (én forespørsel
eller én importkjøring). Uten dette ville en instans som hadde cachet
«ingen plassholdere» kjøre ubeskyttet i inntil et kvarter etter at nøkkelen
ble satt på en annen instans. Registrert Scoped i Program.cs.
Konsumentene: NxtMasterOrderImportService (import, etterfylling og
leveranse-delta), ServiceObjectAutoRegistrarLogic.BuildCandidates /
BuildCandidatesFromOfferLines (instrumentregister og retur),
TemplateProductLogic.ResolveTriggerKey via TemplateProductRepository
(malpakke-triggere), TemplateDeviationService.Project (malavvik, både
panelet og nattjobbens cache — instrument- og ordrelinje-nummer prosjekteres
som fraværende identitet gjennom samme ResolveTriggerKey),
NxtPriceLookupService.ResolvePriceAsync (prismatrise — returnerer
Source = None før kandidatradene i det hele tatt hentes; den eldre
NxtPriceResolutionLogic.GetProductRank står igjen og hindrer at en rad som
er nøklet på merket prises inn på et ekte produkt),
OfferNxtTransferService.SubmitAndFinishAsync (overføring til salgsordre —
utfallet PlaceholderProductBlocked, og TryMarkInFlightAsync kalles
aldri), ScheduleApplyLoadLogic.BuildOfferingReference
(OfferingReference faller tilbake til line:<id>) og
NxtOfferLineMatchLogic.Match (tilbakelesing).
Den stoppede overføringen får sin egen tilstand i overførings-ledgeren:
wv_ExtCache_OfferNxtTransfer.Status = 'PlaceholderBlocked', satt av
MarkPlaceholderBlockedSql, som bare treffer en rad som fortsatt står
Queued. Det er poenget: NeedsReconciliation-setningen guarder bare på
«ikke Completed» og traff dermed også InFlight, så et avslag kunne
overskrive en overføring som allerede var på vei ut — og siden
PersistOperationIdSql guarder på InFlight, forsvant gjenopptaks-
håndtaket til en ordre som kanskje allerede fantes hos kunden. Taper
compare-and-set-en, leser tjenesten raden på nytt og rapporterer den
faktiske tilstanden i stedet for å skrive. Opprettingen er
RequeueFromPlaceholderBlockedSql (PlaceholderBlocked → Queued, i én
setning), og hverken avslaget eller opprettingen rører AttemptCount.
Raden vises via StuckSelectSql, og
MasterOrderNxtTransferDrainAdapter teller utfallet som en feilet post slik
at kjøringen ikke rapporteres vellykket.
Selve skrivestartet er bundet til den listen forespørselen ble bygget på.
IPlaceholderProductCatalog.GetPolicyAsync leser først et stempel — den
lagrede verdien av konfigurasjonsraden, uten tolkning — og deretter selve
listen, og memoiserer de to som én enhet. Stempelet sendes med til
TryMarkInFlightAsync, og MarkInFlightSql leser den autoritative raden på
nytt under samme UPDLOCK, HOLDLOCK som go-live-bryteren allerede tas
under, i samme transaksjon som overgangen Queued → InFlight. Overgangen
skjer bare når stempelet fortsatt stemmer. En lagring som rakk å committe
før lesingen blir dermed sett (stempelet spriker → avslag); en lagring som
er underveis blokkerer skrivestartet og blir sett når den slippes gjennom;
og en lagring som kommer etter lesingen må vente til overgangen har
committet, slik at godkjenningen ligger foran den nye listen. Rekkefølgen
stempel-før-liste er med vilje: en liste som er nyere enn stempelet sitt
feiler lukket, mens motsatt rekkefølge ville godkjent en forespørsel bygget
på en utgått liste. Utfallet av et avslag her er TransferInProgress med
raden fortsatt Queued og AttemptCount urørt. Sammenligningen gjøres med
binær kollasjon (Latin1_General_BIN2): databasens standardkollasjon er
aksent-ufølsom, og en aksent-endring som endrer settet ville da lest som
«uendret».
Selve opprettelsen av tilbud fra masterordre kopierer fortsatt
produktnummeret ordrett — både manuelt (CreateOfferFromMasterLogic) og
automatisk (OfferLifecycleLogic). Et merke kan altså stå på en
tilbudslinje; sperren ligger på overføringen, ikke på opprettelsen. Om
tilbudsopprettelsen i tillegg skal blokkere eller utelate slike linjer, er
en åpen beslutning (MO-148 R3).
Flagget bruker samme mekanisme som MO-091: NeedsReviewReason settes til
PlaceholderProductNo og systemkommentaren lagres som markøren
[[review:PlaceholderProductNo|lines=…|observationLines=…]]. Får ordren
både manglende NXT-dato og plassholder, beholder MissingNxtLineDate
årsakskoden i kolonnen — begge kommentarene skrives uansett.
Berørte kolonner: wv_Subscription_MasterOrderLine.ProductNo og
wv_Subscription_DeliveryObservation.ProductNo (begge uendret i skjema).
Importsammendraget skiller mellom hva kjøringen så og hva den lagret:
PlaceholderProductLinesEncountered /
PlaceholderProductObservationsEncountered teller rader lest fra NXT, mens
PlaceholderProductRowsPersisted (og
PlaceholderProductObservationsPersisted i delta-kjøringen) bare teller
rader databasen bekreftet at den satte inn. En ordre som feilet, eller som
allerede fantes, øker derfor ikke det siste tallet.
Etterfylle allerede importerte ordrer (enrich-nxt)¶
Ordrer som ble importert før feltene over ble en del av importen kan etterfylles uten re-import:
POST api/SubscriptionImport/enrich-nxt
{ "maxOrders": 2100 } // eller { "masterOrderNos": [ ... ] }
Endepunktet leser samme utvalg fra NXT og fyller kun blanke verdier på eksisterende rader:
- Kundenavn — kun når feltet er blankt på ordren.
- Linjepris/rabatt — kun når linjens enhetspris er tom eller 0.
- Instrumentgrupper (+ beskrivelse/enhet) — kun når alle seks gruppene er tomme/0 på linjen.
Manuelt redigerte verdier røres aldri, og endepunktet oppretter aldri nye ordrer eller linjer (ordrer som ikke finnes lokalt telles bare i svaret). Kjøringen er idempotent og kan gjentas. Tilgang styres av samme datatilgang som importen (Masterordre-import, opprette).
Spilleregler som beskytter dataene¶
- Lesing endrer aldri noe i NXT. Skriving skjer kun som salgsordre ved akseptert tilbud, kundeoverføring og selve arkiveringen ved migrering.
- Historikk er uforanderlig. En tidligere levert ordrelinje eller en gammel transaksjon skrives aldri om.
- Avvik gir gjennomgang, ikke overskriving. Hvis NXT-data ikke stemmer med det ePortal allerede har akseptert, flagges saken for manuell gjennomgang.
- Endringstidspunkt brukes kun som vannmerke for å hente nytt — aldri som identitet.
Avvik og feil¶
| Situasjon | Håndtering |
|---|---|
| NXT utilgjengelig | Kjøringen prøves igjen; neste kjøring tar igjen etterslepet via vannmerket |
| Rad med endret innhold for kjent identitet | Registreres som kildeavvik og flagges — den lagrede historikken endres ikke |
| Ufullstendig kobling (f.eks. transaksjon uten gjenkjennbar ordrelinje) | Lagres som uavklart faktum; ingen videre effekt før den er avklart |