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:261
|
||||||||||||||||
|
(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:187
|
||||||||||||||||||||
|
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>
|
| 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:122
|
|
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:203
|
|
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,
);
}
}