src/app/features/attendance/model/attendance.datatype.ts
Datatype for attendance tracking on any entity.
Use this as dataType: "attendance" with isArray: true on an entity field
to store an array of AttendanceItem objects, each referencing a participant entity.
The allowed entity types for the participant field can be overridden via
the field's additional config, e.g.:
{
"dataType": "attendance",
"isArray": true,
"additional": {
"participant": { "dataType": "entity", "additional": ["Child", "School"] }
}
}
Properties |
|
Methods |
| Static detectAllFieldsInEntity | ||||||
detectAllFieldsInEntity(entityOrType: Entity | EntityConstructor)
|
||||||
|
Inherited from
DefaultDatatype
|
||||||
|
Defined in
DefaultDatatype:188
|
||||||
|
Detect all
Parameters :
Returns :
literal type[]
|
| Static detectFieldInEntity | ||||||
detectFieldInEntity(entityOrType: Entity | EntityConstructor)
|
||||||
|
Inherited from
DefaultDatatype
|
||||||
|
Defined in
DefaultDatatype:178
|
||||||
|
Parameters :
Returns :
string | undefined
|
| getExportColumns | ||||||
getExportColumns(schemaField: EntitySchemaField)
|
||||||
|
Inherited from
DefaultDatatype
|
||||||
|
Defined in
DefaultDatatype:51
|
||||||
|
Parameters :
Returns :
ExportColumnMapping[]
|
| normalizeSchemaField | ||||||
normalizeSchemaField(schemaField: EntitySchemaField)
|
||||||
|
Inherited from
DefaultDatatype
|
||||||
|
Defined in
DefaultDatatype:44
|
||||||
|
Parameters :
Returns :
EntitySchemaField
|
| sortValue | ||||||
sortValue(fieldValue: AttendanceItem[])
|
||||||
|
Inherited from
DefaultDatatype
|
||||||
|
Defined in
DefaultDatatype:91
|
||||||
|
Returns the attendance percentage (0–1) for use as the sort key in list columns. Calculated as present / (present + absent), ignoring participants with IGNORE status (e.g. Excused). Returns -1 if no participants have a countable status so empty attendance ("-") sorts before a real 0% value.
Parameters :
Returns :
number
|
| transformToDatabaseFormat | |||||||||
transformToDatabaseFormat(value: EntityType, schemaField?: EntitySchemaField)
|
|||||||||
|
Inherited from
DefaultDatatype
|
|||||||||
|
Defined in
DefaultDatatype:92
|
|||||||||
|
Parameters :
Returns :
DBType
|
| transformToObjectFormat | |||||||||
transformToObjectFormat(value: DBType, schemaField?: EntitySchemaField)
|
|||||||||
|
Inherited from
DefaultDatatype
|
|||||||||
|
Defined in
DefaultDatatype:103
|
|||||||||
|
Parameters :
Returns :
EntityType
|
| Async anonymize | ||||||||||||||||
anonymize(value: EntityType, schemaField: EntitySchemaField, parent: any)
|
||||||||||||||||
|
Inherited from
DefaultDatatype
|
||||||||||||||||
|
Defined in
DefaultDatatype:360
|
||||||||||||||||
|
(Partially) anonymize to "retain-anonymized" for reporting purposes without personal identifiable information.
Parameters :
Returns :
Promise<any>
|
| Async importMapFunction | ||||||||||||||||||||
importMapFunction(val: any, schemaField: EntitySchemaField, additional?: any, importProcessingContext?: any)
|
||||||||||||||||||||
|
Inherited from
DefaultDatatype
|
||||||||||||||||||||
|
Defined in
DefaultDatatype:199
|
||||||||||||||||||||
|
The function used to map values from the import data to values in the entities to be created. to share information across processing of multiple columns and rows.
Parameters :
Returns :
Promise<EntityType>
|
| Async importMatchField | ||||||||||||||||
importMatchField(schemaField: EntitySchemaField, columns: ColumnImportInput[], importProcessingContext?: ImportProcessingContext)
|
||||||||||||||||
|
Inherited from
DefaultDatatype
|
||||||||||||||||
|
Defined in
DefaultDatatype:223
|
||||||||||||||||
|
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 :
Returns :
Promise<unknown>
the value to store: a single value (isArray=false) or array (isArray=true), or undefined if nothing was mapped |
| Static Readonly dataType |
Type : string
|
Default value : "attendance"
|
|
Inherited from
DefaultDatatype
|
|
Defined in
DefaultDatatype:36
|
| editComponent |
Type : string
|
Default value : "EditAttendance"
|
|
Inherited from
DefaultDatatype
|
|
Defined in
DefaultDatatype:41
|
| embeddedType |
Type : unknown
|
Default value : AttendanceItem
|
|
Inherited from
SchemaEmbedDatatype
|
|
Defined in
SchemaEmbedDatatype:39
|
| Static label |
Type : string
|
Default value : $localize`:datatype-label:attendance (participants with status)`
|
|
Inherited from
DefaultDatatype
|
|
Defined in
DefaultDatatype:37
|
| viewComponent |
Type : string
|
Default value : "DisplayAttendance"
|
|
Inherited from
DefaultDatatype
|
|
Defined in
DefaultDatatype:42
|
| Protected Readonly schemaService |
Type : unknown
|
Default value : inject(EntitySchemaService)
|
|
Inherited from
SchemaEmbedDatatype
|
|
Defined in
SchemaEmbedDatatype:70
|
| Readonly importAllowsMultiMapping |
Type : boolean
|
Default value : false
|
|
Inherited from
DefaultDatatype
|
|
Defined in
DefaultDatatype:134
|
|
Whether this datatype allows multiple values to be mapped to the same entity field during import. |
| Optional importConfigComponent |
Type : string
|
|
Inherited from
DefaultDatatype
|
|
Defined in
DefaultDatatype:302
|
|
A component to be rendered inline to configure the import transformation (e.g. defining a format or value mapping). The component receives inputs:
|
import { inject, Injectable } from "@angular/core";
import { SchemaEmbedDatatype } from "#src/app/core/basic-datatypes/schema-embed/schema-embed.datatype";
import { AttendanceItem } from "./attendance-item";
import { AttendanceLogicalStatus } from "./attendance-status";
import { EntitySchemaField } from "#src/app/core/entity/schema/entity-schema-field";
import { Entity, EntityConstructor } from "#src/app/core/entity/model/entity";
import { EventAttendanceMapDatatype } from "../deprecated/event-attendance-map.datatype";
import { EntityMapperService } from "#src/app/core/entity/entity-mapper/entity-mapper.service";
import {
ExportColumnMapping,
DefaultDatatype,
} from "#src/app/core/entity/default-datatype/default.datatype";
/**
* Datatype for attendance tracking on any entity.
*
* Use this as `dataType: "attendance"` with `isArray: true` on an entity field
* to store an array of {@link AttendanceItem} objects, each referencing a participant entity.
*
* The allowed entity types for the `participant` field can be overridden via
* the field's `additional` config, e.g.:
* ```json
* {
* "dataType": "attendance",
* "isArray": true,
* "additional": {
* "participant": { "dataType": "entity", "additional": ["Child", "School"] }
* }
* }
* ```
*/
@Injectable()
export class AttendanceDatatype extends SchemaEmbedDatatype {
private readonly entityMapper = inject(EntityMapperService);
static override readonly dataType = "attendance";
static override label: string = $localize`:datatype-label:attendance (participants with status)`;
override embeddedType = AttendanceItem;
override editComponent = "EditAttendance";
override viewComponent = "DisplayAttendance";
override normalizeSchemaField(
schemaField: EntitySchemaField,
): EntitySchemaField {
// attendance always requires isArray
return { ...schemaField, isArray: true };
}
override getExportColumns(
schemaField: EntitySchemaField,
): ExportColumnMapping[] {
if (!schemaField.label) {
return [];
}
return [
{
keySuffix: "",
label: schemaField.label + " (participation details)",
resolveValue: async (value: AttendanceItem[]) => {
const attendance = Array.isArray(value) ? value : [];
const participantCache = new Map<string, Promise<string>>();
const details = await Promise.all(
attendance.map((attendanceItem) =>
this.toParticipationDetails(attendanceItem, participantCache),
),
);
return details.join(", ");
},
},
{
keySuffix: "_participant_count",
label: schemaField.label + " (number of participants)",
resolveValue: (value: AttendanceItem[]) => {
const attendance = Array.isArray(value) ? value : [];
return attendance.length;
},
},
];
}
/**
* Returns the attendance percentage (0–1) for use as the sort key in list columns.
* Calculated as present / (present + absent), ignoring participants with IGNORE status (e.g. Excused).
* Returns -1 if no participants have a countable status so empty attendance ("-")
* sorts before a real 0% value.
*/
override sortValue(fieldValue: AttendanceItem[]): number {
const items = Array.isArray(fieldValue) ? fieldValue : [];
let present = 0;
let counted = 0;
for (const item of items) {
const countAs = item.status?.countAs;
if (countAs === AttendanceLogicalStatus.PRESENT) {
present++;
counted++;
} else if (countAs === AttendanceLogicalStatus.ABSENT) {
counted++;
}
}
return counted > 0 ? present / counted : -1;
}
private async toParticipationDetails(
attendanceItem: AttendanceItem,
participantCache: Map<string, Promise<string>>,
): Promise<string> {
const participant = await this.getParticipantReadable(
attendanceItem,
participantCache,
);
const statusLabel = this.getStatusLabel(attendanceItem);
if (participant && statusLabel) {
return `${participant} (${statusLabel})`;
}
if (participant) {
return participant;
}
if (statusLabel) {
return statusLabel;
}
return "";
}
private async getParticipantReadable(
attendanceItem: AttendanceItem,
participantCache: Map<string, Promise<string>>,
): Promise<string> {
const participantId = attendanceItem?.participant;
if (!participantId) {
return "";
}
const cachedResult = participantCache.get(participantId);
if (cachedResult) {
return cachedResult;
}
const readableResultPromise = this.entityMapper
.load(Entity.extractTypeFromId(participantId), participantId)
.then((entity) => entity.toString())
.catch(() => "<not_found>");
participantCache.set(participantId, readableResultPromise);
return readableResultPromise;
}
private getStatusLabel(attendanceItem: AttendanceItem): string {
const status = attendanceItem?.status;
if (!status) {
return "";
}
if (typeof status === "string") {
return status;
}
if (typeof status === "object" && "label" in status) {
return status.label ?? "";
}
return "";
}
private static readonly ATTENDANCE_DATATYPES = [
AttendanceDatatype.dataType,
EventAttendanceMapDatatype.dataType,
];
/** @override Detects the first `attendance` or legacy `event-attendance-map` field in the entity schema. */
static override detectFieldInEntity(
entityOrType: Entity | EntityConstructor,
): string | undefined {
return DefaultDatatype.detectFieldInEntity(
entityOrType,
AttendanceDatatype.ATTENDANCE_DATATYPES,
);
}
/** Detect all `attendance` or legacy `event-attendance-map` fields in the entity schema. */
static override detectAllFieldsInEntity(
entityOrType: Entity | EntityConstructor,
): { fieldId: string; schemaField: EntitySchemaField }[] {
return DefaultDatatype.detectAllFieldsInEntity(
entityOrType,
AttendanceDatatype.ATTENDANCE_DATATYPES,
);
}
}