File

src/app/features/location/location.datatype.ts

Extends

DefaultDatatype

Index

Properties
Methods

Methods

getExportColumns
getExportColumns(schemaField: EntitySchemaField)
Inherited from DefaultDatatype
Defined in DefaultDatatype:37

Export the human-readable location string (default column) plus separate columns for the individual address parts (street, house number, postcode, city) so they can be used flexibly for mail merge and similar processes (see #3715).

The address parts are read from the flat top-level fields, which transformToObjectFormat / importMapFunction populate via enrichGeoLocation (copying geoLookup.address up, folding village/town into city and stringifying postcode). Every value reaching export is therefore already enriched, so no geoLookup.address fallback is needed.

Parameters :
Name Type Optional
schemaField EntitySchemaField No
Returns : ExportColumnMapping[]
Async importMapFunction
importMapFunction(val: any, schemaField?: any, additional?: LocationImportConfig)
Inherited from DefaultDatatype
Parameters :
Name Type Optional
val any No
schemaField any Yes
additional LocationImportConfig Yes
transformToObjectFormat
transformToObjectFormat(value: GeoLocation)
Inherited from DefaultDatatype
Defined in DefaultDatatype:85
Parameters :
Name Type Optional
value GeoLocation No
Returns : GeoLocation
Async anonymize
anonymize(value: EntityType, schemaField: EntitySchemaField, parent: any)
Inherited from DefaultDatatype

(Partially) anonymize to "retain-anonymized" for reporting purposes without personal identifiable information.

Parameters :
Name Type Optional Description
value EntityType No

The original value to be anonymized

schemaField EntitySchemaField No
parent any No
Returns : Promise<any>
Static detectAllFieldsInEntity
detectAllFieldsInEntity(entityOrType: Entity | EntityConstructor, dataTypes: string | string[])
Inherited from DefaultDatatype

Detect all fields of the given datatype(s) in an entity's schema.

Parameters :
Name Type Optional Description
entityOrType Entity | EntityConstructor No

An entity instance or entity constructor to inspect.

dataTypes string | string[] No

One or more datatype identifiers to match against.

Returns : literal type[]

Array of matching fields with their id and schema definition.

Static detectFieldInEntity
detectFieldInEntity(entityOrType: Entity | EntityConstructor, dataTypes: string | string[])
Inherited from DefaultDatatype
Defined in DefaultDatatype:95

Detect the first field of the given datatype(s) in an entity's schema.

Scans the schema for a field whose dataType matches one of the provided strings and returns its property name.

Subclasses typically override this without the extra dataTypes parameter, forwarding their own relevant datatype identifiers.

Parameters :
Name Type Optional Description
entityOrType Entity | EntityConstructor No

An entity instance or entity constructor to inspect.

dataTypes string | string[] No

One or more datatype identifiers to match against.

Returns : string | undefined

The field name of the first matching field, or undefined if none is found.

Async importMatchField
importMatchField(schemaField: EntitySchemaField, columns: ColumnImportInput[], importProcessingContext?: ImportProcessingContext)
Inherited from DefaultDatatype

Map all columns mapped to one target field to the value(s) stored there.

ImportService calls this once per target field per row with every column mapped to that field. The default handles trimming and array splitting and delegates each individual value to importMapFunction. Datatypes that need to consider several columns together (e.g. matching an entity by more than one property) override this.

(isArray=true), or undefined if nothing was mapped

Parameters :
Name Type Optional Description
schemaField EntitySchemaField No

the target property the value(s) are imported into

columns ColumnImportInput[] No

every column mapped to this field, with its raw cell value

importProcessingContext ImportProcessingContext Yes

shared context across columns and rows

Returns : Promise<unknown>

the value to store: a single value (isArray=false) or array (isArray=true), or undefined if nothing was mapped

normalizeSchemaField
normalizeSchemaField(schemaField: EntitySchemaField)
Inherited from DefaultDatatype

Return the (potentially adjusted) schema field for this datatype.

Called when schema fields are set up (e.g. from config), allowing the datatype to normalize or fill in required settings.

Override this in a subclass to enforce constraints (e.g. always setting isArray: true).

Parameters :
Name Type Optional Description
schemaField EntitySchemaField No

The current schema field definition

Returns : EntitySchemaField

The schema field to use (default: unchanged)

sortValue
sortValue(_fieldValue: EntityType)
Inherited from DefaultDatatype

Return a comparable primitive for sorting this field's value in a list column. Return undefined to fall through to the default sort logic. Override this in datatypes that store arrays or complex objects where the raw value cannot be sorted meaningfully.

Parameters :
Name Type Optional
_fieldValue EntityType No
Returns : number | string | undefined
transformToDatabaseFormat
transformToDatabaseFormat(value: EntityType, schemaField?: EntitySchemaField, parent?: Entity)
Inherited from DefaultDatatype

Transformation function taking a value in the format that is used in entity instances and returning the value in the format used in database objects.

Example :
     This can be set as a parameter to the `@DatabaseField()` annotation in Entity classes.
Parameters :
Name Type Optional Description
value EntityType No

The value (in Entity format) to be transformed

schemaField EntitySchemaField Yes

The EntitySchemaField configuration providing details of how the value should be transformed. This can be set as a parameter to the @DatabaseField() annotation in Entity classes.

parent Entity Yes

The full entity instance this value is part of (e.g. to allow cross-related transformations)

Returns : DBType

Properties

Static dataType
Type : string
Default value : "location"
Inherited from DefaultDatatype
Defined in DefaultDatatype:19
editComponent
Type : string
Default value : "EditLocation"
Inherited from DefaultDatatype
Defined in DefaultDatatype:22
importConfigComponent
Type : string
Default value : "LocationImportConfig"
Inherited from DefaultDatatype
Defined in DefaultDatatype:24
Static label
Type : string
Default value : $localize`:datatype-label:location (address + map)`
Inherited from DefaultDatatype
Defined in DefaultDatatype:20
viewComponent
Type : string
Default value : "ViewLocation"
Inherited from DefaultDatatype
Defined in DefaultDatatype:23
Readonly importAllowsMultiMapping
Type : boolean
Default value : false
Inherited from DefaultDatatype

Whether this datatype allows multiple values to be mapped to the same entity field during import.

import { Injectable, inject } from "@angular/core";
import { lastValueFrom } from "rxjs";
import {
  DefaultDatatype,
  ExportColumnMapping,
} from "../../core/entity/default-datatype/default.datatype";
import { GeoLocation } from "./geo-location";
import { OpenStreetMapsSearchResult, GeoService } from "./geo.service";
import { LocationImportConfig } from "./location-import-config/location-import-config.component";
import { EntitySchemaField } from "../../core/entity/schema/entity-schema-field";

@Injectable()
export class LocationDatatype extends DefaultDatatype<
  GeoLocation,
  GeoLocation
> {
  private geoService = inject(GeoService);

  static override dataType = "location";
  static override label: string = $localize`:datatype-label:location (address + map)`;

  override editComponent = "EditLocation";
  override viewComponent = "ViewLocation";
  override importConfigComponent = "LocationImportConfig";

  /**
   * Export the human-readable location string (default column) plus separate
   * columns for the individual address parts (street, house number, postcode, city)
   * so they can be used flexibly for mail merge and similar processes (see #3715).
   *
   * The address parts are read from the flat top-level fields, which
   * {@link transformToObjectFormat} / {@link importMapFunction} populate via
   * `enrichGeoLocation` (copying `geoLookup.address` up, folding village/town
   * into city and stringifying postcode). Every value reaching export is
   * therefore already enriched, so no `geoLookup.address` fallback is needed.
   */
  override getExportColumns(
    schemaField: EntitySchemaField,
  ): ExportColumnMapping<GeoLocation>[] {
    if (!schemaField.label) {
      return [];
    }

    const label = schemaField.label;
    const part = (
      keySuffix: string,
      partLabel: string,
      pick: (value: GeoLocation) => string | undefined,
    ): ExportColumnMapping<GeoLocation> => ({
      keySuffix,
      label: `${label} (${partLabel})`,
      resolveValue: (value) => (value ? pick(value) : undefined),
    });

    return [
      {
        keySuffix: "",
        label,
        // return the whole object so the CSV/XLSX transformation extracts the locationString
        resolveValue: (value) => value,
      },
      part(
        "_street",
        $localize`:Location export column:street`,
        (value) => value.road,
      ),
      part(
        "_house_number",
        $localize`:Location export column:house number`,
        (value) => value.house_number,
      ),
      part(
        "_postcode",
        $localize`:Location export column:postcode`,
        (value) => value.postcode,
      ),
      part(
        "_city",
        $localize`:Location export column:city`,
        (value) => value.city,
      ),
    ];
  }

  override transformToObjectFormat(value: GeoLocation): GeoLocation {
    if (typeof value !== "object") {
      // until we have an extended location datatype that includes a custom address addition field, discard invalid values (e.g. in case datatype was changed)
      return undefined;
    }

    // migrate from legacy format
    if (
      !value.hasOwnProperty("locationString") &&
      !value.hasOwnProperty("geoLookup")
    ) {
      value = {
        geoLookup: value as unknown as OpenStreetMapsSearchResult,
      };
    }

    // fix errors from broken migrations
    while (value?.geoLookup && "geoLookup" in value.geoLookup) {
      value.geoLookup = (
        value.geoLookup as { geoLookup: OpenStreetMapsSearchResult }
      ).geoLookup;
    }

    if (!value.locationString) {
      value.locationString = value.geoLookup?.display_name ?? "";
    }

    return this.geoService.enrichGeoLocation(value);
  }

  override async importMapFunction(
    val: any,
    schemaField?: any,
    additional?: LocationImportConfig,
  ): Promise<GeoLocation> {
    if (!val) {
      return undefined;
    }

    let geoResults: OpenStreetMapsSearchResult[] | undefined;
    if (!additional?.skipAddressLookup) {
      try {
        geoResults = await lastValueFrom(this.geoService.lookup(val));
      } catch {
        geoResults = undefined;
      }
    }

    return this.geoService.enrichGeoLocation({
      locationString: val,
      geoLookup: geoResults ? geoResults[0] : undefined,
    });
  }
}

results matching ""

    No results matching ""