Gå til innhold

Leveringsadresse-velger (delivery-address-picker)

Gjenbrukbar, presentasjonell inline-velger for leveringsadresse på masterordre- og tilbudshodet (MO-098). Velgeren lar brukeren enten hente en adresse fra kunden (aktørens hovedadresse pluss aktørens ekstra adresserader) eller skrive en annen adresse, med et valgfritt «Lagre også på kundekortet». I tillegg til selve adressen (inkl. en 4. adresselinje) viser velgeren alltid en «Kontakt & referanser»-gruppe med ti gjenbrukbare-med-per-ordre-overstyring leveringsinstruks-felt — kontaktperson, kontakt-telefon/-e-post, merknad, referansenummer, deres ref, PO-nummer og tre valgfrie «Ekstra info»-felt (MO-098-EXT).

Komponenten er ren og skrivebeskyttet: den utleder aldri en aktør selv, den skriver aldri til server, og den emitterer kun et resultat. Verten (host) eier lagringen — masterordren via PUT api/SubscriptionMasterOrder/{id}/delivery-address, tilbudet via PUT api/CrmOffer/{id}/delivery-address. Serveren avleder det tillatte aktør-settet på nytt og validerer valget; klientens actorId er kun et display-hint.

Tatt i bruk i masterordre-detaljens «Rediger ordrehode»-skuff og i tilbudsdetaljen (se Tilbudsflyt).


Feature-tabell

Komponent Selector Hovedfunksjon Import
DeliveryAddressPickerComponent app-delivery-address-picker Inline velger: hent adresse fra kunden (hovedadresse + wv_ActorAdr-rader) eller skriv en annen; valgfri «lagre på kundekortet» Standalone

Kildefiler

Del Sti
Komponent ui/ePortal.ui/src/app/components/shared/delivery-address-picker/delivery-address-picker.component.ts
Mal ui/ePortal.ui/src/app/components/shared/delivery-address-picker/delivery-address-picker.component.html
Rene hjelpere/typer ui/ePortal.ui/src/app/components/shared/delivery-address-picker/delivery-address-picker.helpers.ts
Tester ui/ePortal.ui/src/app/components/shared/delivery-address-picker/delivery-address-picker.component.spec.ts, delivery-address-picker.helpers.spec.ts
Snapshot-type DeliveryAddressSnapshot + DeliveryAddressSourceKind i ui/ePortal.ui/src/app/shared/models.ts

Komponenten er standalone — importer klassen direkte i vertens imports. Den bruker ng-select for aktør-/adressenedtrekkene og land-velgeren (app-country-select) for Land-feltet.


Bruk

<app-delivery-address-picker
  [actors]="derivedActors"
  [actorId]="selectedActorId"
  [value]="masterOrder.deliveryAddress"
  [disabled]="saving"
  (valueChange)="onDeliveryChange($event)">
</app-delivery-address-picker>

Verten sender inn det server-avledede aktør-settet (actors), gjeldende adresse- snapshot (value), lytter på valueChange og bygger skrive-nyttelasten selv. Velgeren skriver aldri direkte.


Inputs

Input Type Standard Beskrivelse
actors DeliveryActorOption[] [] Det server-avledede tillatte kilde-aktør-settet (skip/kunde, fakturakunde, forvaltning). Tom liste skjuler «hent fra kunden»-modus. Velgeren utleder aldri en aktør selv.
actorId string \| null undefined Gjeldende valgte kilde-aktør (et display-hint). Faller tilbake til første aktør i actors.
value DeliveryAddressSnapshot \| null undefined Snapshotet velgeren seedes fra.
disabled boolean false Deaktiverer alle kontroller (f.eks. mens verten lagrer).

Outputs

Output Type Beskrivelse
valueChange EventEmitter<DeliveryAddressPickerResult> Emitteres ved hver endring så verten kan holde utkastet i synk.

Modelltyper

// shared/models.ts
type DeliveryAddressSourceKind = 'None' | 'ActorMain' | 'ActorAddress' | 'Manual';

interface DeliveryAddressSnapshot {
  name: string | null;
  line1: string | null;
  line2: string | null;
  line3: string | null;
  line4: string | null;             // MO-098-EXT — 4. adresselinje, fryser ubetinget som line1-3
  postalCode: string | null;
  city: string | null;
  countryCode: string | null;       // ISO 3166-1 alpha-2
  sourceKind: DeliveryAddressSourceKind;
  sourceAddressId: string | null;   // wv_ActorAdr.actaID — kun for ActorAddress
  // MO-098-EXT: ti gjenbrukbare-med-per-ordre-overstyring leveringsinstruks-metadatafelt —
  // forhåndsfylt fra den valgte wv_ActorAdr-raden, fritt overstyrbare per ordre, verdien på
  // skjemaet ved lagring fryses inn (se «Oppførsel» under).
  contactPerson: string | null;
  contactPhone: string | null;
  contactEmail: string | null;
  note: string | null;
  extraInfo1: string | null;
  extraInfo2: string | null;
  extraInfo3: string | null;
  referenceNumber: string | null;
  theirRef: string | null;
  poNumber: string | null;
}

// delivery-address-picker.helpers.ts
interface DeliveryActorOption { actorId: string; name: string; role?: string; }

interface DeliveryAddressPickerResult {
  snapshot: DeliveryAddressSnapshot;
  actorId: string | null;           // display-hint (null for ren manuell entry)
  saveToCustomerCard: boolean;
}

Oppførsel

  • To modus: hent fra kunden (card) og skriv en annen adresse (manual). Uten aktører i actors starter velgeren i manuell modus.
  • Adresseoppslag: for valgt aktør leser velgeren aktørens hovedadresse (wv_Actor) og aktørens ekstra adresserader (wv_ActorAdr) via ett CRM-lesekall (CrmCustomerService.getCustomerById). Lesing er lov mot det åpne CRM-endepunktet; kortskriving går alltid via vertens gatede ordre-/tilbudsendepunkt.
  • Provenance (sourceKind): hovedadressen gir ActorMain (uten adresse-id), en kortrad gir ActorAddress (med sourceAddressId = actaID), fritekst gir Manual. Tom tilstand er None.
  • «Fra kundekortet»-chip: vises når sourceKind ∈ {ActorMain, ActorAddress}ikke basert på sourceAddressId != null (hovedadressen er kort-hentet, men har ingen id). Hjelperen isCardSourced() innkapsler regelen.
  • Lagre også på kundekortet: en avkrysning (standard AV). Flagget emitteres i resultatet; verten utfører kortskrivingen inne i den autoriserte ordre-/tilbuds- skrivingen — aldri fra velgeren.
  • Sikkerhet: actorId i resultatet er kun et hint. Serveren avleder det tillatte aktør-settet på nytt fra den persisterte entiteten og avviser alt utenfor settet med Forbidden. Et sourceAddressId valideres mot den avledede aktøren.
  • Land: Land-feltet bruker app-country-select. Snapshotet lagrer ISO alpha-2; en kortverdi i alpha-3 (wv_ActorAdr.actaCountry char(3)) løses opp mot alpha-2, ellers faller den til tom (en gal landkode er verre enn ingen).
  • Adressesøk (Google, valgfritt): øverst i «skriv en annen adresse»-modus vises app-address-autocomplete — søk på en adresse, et sted eller et firmanavn hvor som helst i verden. Et treff fyller ut manualDraft.name / line1 / line2 / postalCode / city / countryCode (ren field-for-field-erstatning, slik at et nytt søk ikke etterlater stale verdier fra forrige treff) og går gjennom den vanlige onManualChange()-emit-stien — snapshotet forblir Manual-provenance, og «Kontakt & referanser»-metadata-gruppen røres aldri av et adressesøk. countryCode er alpha-2 på begge sider (AutocompleteAddress og denne velgerens land-felt), så ingen alpha-3-konvertering trengs her. Komponenten skjuler seg selv når adressesøk ikke er aktivert for tenanten (fail-safe — kun ren manuell inntasting).
  • Adresselinje 3/4 (MO-098-EXT): «skriv en annen adresse»-modus har fire adresselinje- felt (Navn, Adresselinje 1-4, Postnummer + Sted, Land) — linje 4 fryser ubetinget som linje 1-3, uten egen gjenbruk/overstyring-historie.
  • «Kontakt & referanser» (MO-098-EXT): en egen fieldset viser alle ti gjenbrukbare leveringsinstruks-metadatafelt — kontaktperson, kontakt-telefon, kontakt-e-post, merknad, referansenummer, deres ref og PO-nummer alltid synlige; tre generiske «Ekstra info»-felt bak en kollapset-som-standard bryter (showExtraInfo/toggleExtraInfo()) siden de fleste leveranser ikke trenger dem. Gruppen vises uavhengig av adressemodus (card eller manual) — feltene er bundet til manualDrafts metadata-del, som komponenten alltid holder pre-fylt fra den sist plukkede kortraden (onAddressChange kaller extractMetadata() på nytt for hvert kortvalg). Verdien som står i feltet ved lagring — enten kortets opprinnelige verdi eller brukerens overstyring — er det som fryses inn i snapshotet (currentSnapshot() slår alltid sammen adresse-delen fra valgt kilde med metadata-delen fra manualDraft, uansett modus). Et nytt kortvalg erstatter hele metadata-arbeidskopien med det nye kortets standing-verdier, og forkaster en eventuell tidligere overstyring for forrige kort — bytter du kort på nytt, starter overstyringen fra blanke ark igjen.

i18n

Alle brukerrettede tekster ligger under Subscription.delivery* i både no.json og en.json (blokken er en-speilet). Sentrale nøkler:

"Subscription": {
  "deliveryPickFromCustomer":     "Velg adresse fra kunden",
  "deliveryUseOtherAddress":      "Bruk annen adresse",
  "deliverySaveToCustomerCard":   "Lagre også på kundekortet",
  "deliveryFromCustomerCard":     "Fra kundekortet",
  "deliveryMainAddress":          "Hovedadresse",
  "deliveryName":                 "Navn / mottaker",
  "deliveryLine1":                "Adresselinje 1",
  "deliveryLine2":                "Adresselinje 2",
  "deliveryLine3":                "Adresselinje 3",
  "deliveryLine4":                "Adresselinje 4",
  "deliveryPostalCode":           "Postnummer",
  "deliveryCity":                 "Sted",
  "deliveryCountry":              "Land",
  // MO-098-EXT — «Kontakt & referanser»-fieldsettet
  "deliveryContactSection":       "Kontakt & referanser",
  "deliveryFieldOverrideHint":    "Forhåndsfylles fra adressen, kan overstyres for denne ordren. Lagres på adressen hvis «lagre på kundekortet» er på.",
  "deliveryContactPerson":        "Kontaktperson",
  "deliveryContactPhone":         "Kontakt-tlf",
  "deliveryContactEmail":         "Kontakt-e-post",
  "deliveryNote":                 "Merknad",
  "deliveryReferenceNumber":      "Referansenummer",
  "deliveryTheirRef":             "Deres ref",
  "deliveryPoNumber":             "PO-nummer",
  "deliveryExtraInfoToggleShow":  "Vis ekstra info",
  "deliveryExtraInfoToggleHide":  "Skjul ekstra info",
  "deliveryExtraInfoSection":     "Ekstra info",
  "deliveryExtraInfo1":           "Ekstra info 1",
  "deliveryExtraInfo2":           "Ekstra info 2",
  "deliveryExtraInfo3":           "Ekstra info 3"
}

Tilgjengelighet og mobil

  • Nedtrekkene bruker ng-select med appendTo="body" — tastaturnavigasjon og søk som standard, og de klippes ikke av skuff-/modal-scrolling.
  • Land-feltet arver tilgjengeligheten fra app-country-select (flagg er dekorativt, landnavnet er den tilgjengelige teksten).

Kjente fallgruver

Fallgruve Riktig mønster
La velgeren skrive til server Den er ren/skrivebeskyttet. Verten lagrer via ordre-/tilbudsendepunktet og bygger nyttelasten fra valueChange.
Stole på klientens actorId Det er kun et display-hint. Serveren avleder aktør-settet på nytt og avviser Forbidden utenfor settet.
Vise «Fra kundekortet» ut fra sourceAddressId != null Bruk sourceKind ∈ {ActorMain, ActorAddress} (isCardSourced()) — hovedadressen er kort-hentet uten id.
Sette sourceAddressId for hovedadresse/manuell sourceAddressId settes kun for ActorAddress. Null for ActorMain/Manual/None.
Sende inn hele kundens aktørliste Send kun det server-avledede settet; velgeren skal ikke la brukeren velge en aktør utenfor det.
Anta at «Kontakt & referanser»-feltene bevarer en overstyring på tvers av kortvalg De erstattes fullt ut ved hvert nytt kortvalg (onAddressChange re-pre-fyller fra det nye kortets standing-verdier) — en overstyring for forrige kort forkastes.

Verifiseringspunkter

  1. Åpne en masterordre → «Rediger ordrehode»: seksjonen «Leveringsadresse» viser velgeren; velg hovedadressen — «Fra kundekortet»-chip vises.
  2. Klikk «Bruk annen adresse», skriv en adresse og lagre — snapshotet lagres på ordren uten å endre kundekortet (chip forsvinner).
  3. Kryss av «Lagre også på kundekortet» og lagre — adressen dukker opp som en wv_ActorAdr-rad på kunden, og snapshotet får ActorAddress-provenance.
  4. Velg en kortadresse, åpne «Kontakt & referanser» og bekreft at feltene forhånds- fylles fra kortraden; overstyr ett felt (f.eks. PO-nummer) og lagre — snapshotet fryser overstyringen, ikke kortets opprinnelige verdi.
  5. Skriv en verdi i «Ekstra info 1» bak «Vis ekstra info»-bryteren og lagre, åpne redigeringen på nytt — verdien er der (showExtraInfo er ren UI-tilstand og starter alltid kollapset ved en fersk åpning, uavhengig av om feltet har en verdi).

Relaterte sider