Add support for person identifiers workflow: update PersonEdit component, API methods, and modals for identifier handling during person creation. Adjust related types for improved consistency.

This commit is contained in:
2025-09-16 10:00:29 +02:00
parent 10cd0f2ccc
commit 6e9ed7f79f
6 changed files with 132 additions and 48 deletions

View File

@@ -64,6 +64,8 @@ const props = withDefaults(defineProps<CreateComponentConfig>(), {
action: "create",
query: "",
});
const type = ref<CreatableEntityType | null>(null);
const radioType = computed<CreatableEntityType | null>({
@@ -74,12 +76,14 @@ const radioType = computed<CreatableEntityType | null>({
},
});
type PersonEditComponent = InstanceType<typeof PersonEdit>;
type AnyComponentInstance =
| InstanceType<typeof OnTheFlyPerson>
| InstanceType<typeof OnTheFlyThirdparty>
| null;
const castPerson = ref<AnyComponentInstance>(null);
const castPerson = ref<PersonEditComponent>(null);
const castThirdparty = ref<AnyComponentInstance>(null);
onMounted(() => {
@@ -100,31 +104,13 @@ const containsPerson = computed<boolean>(() => {
return props.allowedTypes.includes("person");
});
// Types for data structures coming from child components are not declared in TS yet.
// We conservatively type them as any to preserve runtime behavior while enabling TS in this component.
function castDataByType(): any {
switch (radioType.value) {
case "person":
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (castPerson.value as any)?.$data?.person;
case "thirdparty": {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let data: any = (castThirdparty.value as any)?.$data?.thirdparty;
if (data && data.address !== undefined && data.address !== null) {
data.address = { id: data.address.address_id };
} else if (data) {
data.address = null;
}
return data;
}
default:
throw Error("Invalid type of entity");
}
function save(): void {
castPerson.value.postPerson();
}
defineExpose({
castDataByType,
});
defineExpose({save});
</script>
<style lang="css" scoped>

View File

@@ -2,11 +2,25 @@
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import Create from "ChillMainAssets/vuejs/OnTheFly/components/Create.vue";
import { CreateComponentConfig } from "ChillMainAssets/types";
import {trans, SAVE} from "translator";
import {useTemplateRef} from "vue";
const emit = defineEmits<(e: "close") => void>();
const props = defineProps<CreateComponentConfig>();
const modalDialogClass = { "modal-xl": true, "modal-scrollable": true };
type CreateComponentType = InstanceType<typeof Create>
const create = useTemplateRef<CreateComponentType>("create");
function save(): void {
console.log('save from CreateModal');
create.value?.save();
}
defineExpose({save})
</script>
<template>
@@ -22,12 +36,16 @@ const modalDialogClass = { "modal-xl": true, "modal-scrollable": true };
<template #body-head>
<div class="modal-body">
<Create
ref="create"
:allowedTypes="props.allowedTypes"
:action="props.action"
:query="props.query"
></Create>
</div>
</template>
<template #footer>
<button class="btn btn-save" type="button" @click.prevent="save">{{ trans(SAVE) }}</button>
</template>
</modal>
</teleport>
</template>

View File

@@ -18,9 +18,14 @@ import { Calendar } from "../../../ChillCalendarBundle/Resources/public/types";
import Person from "./vuejs/_components/OnTheFly/Person.vue";
export interface AltName {
label: TranslatableString;
labels: TranslatableString;
key: string;
}
export interface AltNameWrite {
key: string;
value: string;
}
export interface Person {
id: number;
type: "person";
@@ -44,14 +49,20 @@ export interface Person {
current_residential_addresses: Address[];
}
export interface PersonIdentifierWrite {
type: "person_identifier";
definition_id: number;
value: object;
}
/**
* Person representation to create or update a Person
*/
export interface PersonEdit {
export interface PersonWrite {
type: "person";
firstName: string;
lastName: string;
altNames: AltName[];
altNames: AltNameWrite[];
// address: number | null;
birthdate: DateTimeCreate | null;
deathdate: DateTimeCreate | null;
@@ -61,6 +72,7 @@ export interface PersonEdit {
gender: SetGender | null;
center: SetCenter | null;
civility: SetCivility | null;
identifiers: PersonIdentifierWrite[];
}
export interface AccompanyingPeriod {
@@ -408,6 +420,14 @@ export interface SearchOptions {
};
}
export interface PersonIdentifierWorker {
type: "person_identifier_worker";
definition_id: number;
engine: string;
label: TranslatableString;
isActive: boolean;
}
export class MakeFetchException extends Error {
sta: number;
txt: string;

View File

@@ -1,6 +1,7 @@
import { fetchResults, makeFetch } from "ChillMainAssets/lib/api/apiMethods";
import { Center, Civility, Gender } from "ChillMainAssets/types";
import { AltName, Person } from "ChillPersonAssets/types";
import {AltName, Person, PersonIdentifierWorker, PersonWrite} from "ChillPersonAssets/types";
import person from "ChillPersonAssets/vuejs/_components/OnTheFly/Person.vue";
/*
* GET a person by id
@@ -31,3 +32,10 @@ export const getGenders = async (): Promise<Gender[]> =>
export const getCentersForPersonCreation = async (): Promise<{showCenters: boolean; centers: Center[];}> =>
makeFetch("GET", "/api/1.0/person/creation/authorized-centers", null);
export const getPersonIdentifiers = async (): Promise<PersonIdentifierWorker[]> =>
fetchResults("/api/1.0/person/identifiers/workers");
export const createPerson = async (person: PersonWrite): Promise<Person> => {
return makeFetch("POST", "/api/1.0/person/person.json", person);
}

View File

@@ -35,7 +35,7 @@ const { person, isCut = false, addAge = true } = toRefs(props);
const altNameLabel = computed(() => {
if (!person.value.altNames) return "";
return person.value.altNames.map((a: AltName) => a.label).join("");
return person.value.altNames.map((a: AltName) => a.labels).join("");
});
const altNameKey = computed(() => {

View File

@@ -56,10 +56,26 @@
<input
class="form-control form-control-lg"
:id="a.key"
:value="personAltNamesLabels[i]"
@input="onAltNameInput"
:name="'label_'+a.key"
value=""
@input="onAltNameInput($event, a.key)"
/>
<label :for="a.key">{{ localizeString(a.labels) }}</label>
<label :for="'label_'+a.key">{{ localizeString(a.labels) }}</label>
</div>
<div
v-for="worker in config.identifiers"
:key="worker.definition_id"
class="form-floating mb-3"
>
<input
class="form-control form-control-lg"
type="text"
:name="'worker_'+worker.definition_id"
:placeholder="localizeString(worker.label)"
@input="onIdentifierInput($event, worker.definition_id)"
/>
<label :for="'worker_'+worker.definition_id">{{ localizeString(worker.label) }}</label>
</div>
<div class="form-floating mb-3">
@@ -181,11 +197,12 @@ import { ref, reactive, computed, onMounted } from "vue";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue";
import {
createPerson,
getCentersForPersonCreation,
getCivilities,
getGenders,
getPerson,
getPersonAltNames,
getPersonAltNames, getPersonIdentifiers,
} from "../../_api/OnTheFly";
import {
trans,
@@ -209,7 +226,14 @@ import {
Gender,
DateTimeCreate,
} from "ChillMainAssets/types";
import {AltName, PersonEdit} from "ChillPersonAssets/types";
import {
AltName,
Person,
PersonWrite,
PersonIdentifierWorker,
type Suggestion,
type EntitiesOrMe
} from "ChillPersonAssets/types";
interface PersonEditComponentConfig {
@@ -224,7 +248,13 @@ const props = withDefaults(defineProps<PersonEditComponentConfig>(), {
type: "TODO",
});
const person = reactive<PersonEdit>({
const emit = defineEmits<{
(e: "onPersonCreated", payload: { person: Person }): void;
}>();
defineExpose({postPerson});
const person = reactive<PersonWrite>({
type: "person",
firstName: "",
lastName: "",
@@ -238,6 +268,7 @@ const person = reactive<PersonEdit>({
gender: null,
center: null,
civility: null,
identifiers: [],
});
const config = reactive<{
@@ -245,11 +276,13 @@ const config = reactive<{
civilities: Civility[];
centers: Center[];
genders: Gender[];
identifiers: PersonIdentifierWorker[];
}>({
altNames: [],
civilities: [],
centers: [],
genders: [],
identifiers: [],
});
const showCenters = ref(false);
@@ -350,9 +383,9 @@ const center = computed({
},
});
const personAltNamesLabels = computed(() =>
person.altNames.map((a) => (a ? a.label : "")),
);
/**
* Find the query items to display for suggestion
*/
const queryItems = computed(() => {
const words: null|string[] = props.query ? props.query.split(" ") : null;
@@ -387,21 +420,30 @@ function checkErrors() {
async function loadData() {
if (props.id !== undefined && props.id !== null) {
const person = await getPerson(props.id);
}
}
/*
function onAltNameInput(event: Event) {
function onAltNameInput(event: Event, key: string): void {
const target = event.target as HTMLInputElement;
const key = target.id;
const label = target.value;
const updateAltNames = person.altNames.filter((a) => a.key !== key);
updateAltNames.push({ key, label });
person.altNames = updateAltNames;
const value = target.value;
const updateAltNamesKey = person.altNames.findIndex((a) => a.key === key);
if (-1 === updateAltNamesKey) {
person.altNames.push({key, value})
} else {
person.altNames[updateAltNamesKey].value = value;
}
}
*/
function onIdentifierInput(event: Event, definition_id: number): void {
const target = event.target as HTMLInputElement;
const value = target.value;
const updateIdentifierKey = person.identifiers.findIndex((w) => w.definition_id === definition_id);
if (-1 === updateIdentifierKey) {
person.identifiers.push({type: "person_identifier", definition_id, value: { content: value }})
} else {
person.identifiers[updateIdentifierKey].value = {content: value}
}
}
function addQueryItem(field: "lastName" | "firstName", queryItem: string) {
switch (field) {
@@ -422,6 +464,13 @@ function submitNewAddress(payload: { addressId: number }) {
// person.addressId = payload.addressId;
}
async function postPerson(): Promise<void> {
console.log('postPerson');
const createdPerson = await createPerson(person);
emit('onPersonCreated', {person: createdPerson});
}
onMounted(() => {
getPersonAltNames().then((altNames) => {
config.altNames = altNames;
@@ -432,6 +481,9 @@ onMounted(() => {
getGenders().then((genders) => {
config.genders = genders;
});
getPersonIdentifiers().then((identifiers) => {
config.identifiers = identifiers;
});
if (props.action !== "create") {
loadData();
} else {