Add edit functionality for ThirdParty in OnTheFly.vue and refactor ThirdPartyEdit for consistency.

- Implemented dynamic component rendering in `OnTheFly.vue` for `thirdparty` type based on the `action` (`show`, `edit`, etc.).
- Added `ThirdPartyEdit` component with API integration for editing third parties.
- Introduced `thirdpartyToWriteThirdParty` function in the API for mapping `Thirdparty` to `ThirdPartyWrite` structure.
- Centralized validation handling in `ThirdPartyEdit.vue` using `useViolationList` composable and enhanced template conditions.
- Updated and extended API functions (`patchThirdparty`, etc.) and types to support third-party editing.
This commit is contained in:
2025-10-29 16:29:44 +01:00
parent e107d20bea
commit 5c098a336d
4 changed files with 91 additions and 19 deletions

View File

@@ -54,7 +54,7 @@
></PersonEdit> ></PersonEdit>
</template> </template>
<template #body v-else-if="type === 'thirdparty'"> <template #body v-else-if="type === 'thirdparty' && action === 'show'">
<on-the-fly-thirdparty <on-the-fly-thirdparty
:id="id" :id="id"
:type="type" :type="type"
@@ -69,6 +69,10 @@
</div> </div>
</template> </template>
<template #body v-else-if="type === 'thirdparty' && action === 'edit'">
<ThirdPartyEdit ref="castEditThirdParty" action="edit" :id="id"></ThirdPartyEdit>
</template>
<template #body v-else-if="parent"> <template #body v-else-if="parent">
<on-the-fly-thirdparty <on-the-fly-thirdparty
:parent="parent" :parent="parent"
@@ -131,6 +135,8 @@ import {
THIRDPARTY_ADDCONTACT_TITLE, THIRDPARTY_ADDCONTACT_TITLE,
} from "translator"; } from "translator";
import PersonEdit from "ChillPersonAssets/vuejs/_components/OnTheFly/PersonEdit.vue"; import PersonEdit from "ChillPersonAssets/vuejs/_components/OnTheFly/PersonEdit.vue";
import ThirdPartyEdit from "ChillThirdPartyAssets/vuejs/_components/OnTheFly/ThirdPartyEdit.vue";
import ThirdParty from "ChillThirdPartyAssets/vuejs/_components/OnTheFly/ThirdParty.vue";
// Types // Types
type EntityType = "person" | "thirdparty"; type EntityType = "person" | "thirdparty";
@@ -169,7 +175,9 @@ const emit = defineEmits<{
}>(); }>();
type castEditPersonType = InstanceType<typeof PersonEdit>; type castEditPersonType = InstanceType<typeof PersonEdit>;
type castEditThirdPartyType = InstanceType<typeof ThirdParty>;
const castEditPerson = useTemplateRef<castEditPersonType>('castEditPerson') const castEditPerson = useTemplateRef<castEditPersonType>('castEditPerson')
const castEditThirdParty = useTemplateRef<castEditThirdPartyType>('castEditThirdParty');
const modal = ref<{ showModal: boolean; modalDialogClass: string }>({ const modal = ref<{ showModal: boolean; modalDialogClass: string }>({
showModal: false, showModal: false,
@@ -311,9 +319,15 @@ async function saveAction() {
if (null !== person) { if (null !== person) {
emit("saveFormOnTheFly", {type: props.type, data: person}) emit("saveFormOnTheFly", {type: props.type, data: person})
} }
} else if (props.type === 'thirdparty') {
const thirdParty = await castEditThirdParty.value?.postThirdParty();
if (null !== thirdParty) {
emit("saveFormOnTheFly", {type: props.type, data: thirdParty })
}
} }
} }
defineExpose({ defineExpose({
openModal, openModal,
closeModal, closeModal,

View File

@@ -637,12 +637,12 @@ async function postPerson(): Promise<Person> {
const createdPerson = await createPerson(person); const createdPerson = await createPerson(person);
emit("onPersonCreated", { person: createdPerson }); emit("onPersonCreated", { person: createdPerson });
return createdPerson; return Promise.resolve(createdPerson);
} else if (props.id !== null) { } else if (props.id !== null) {
const updatedPerson = await editPerson(person, props.id); const updatedPerson = await editPerson(person, props.id);
emit("onPersonCreated", { person: updatedPerson }); emit("onPersonCreated", { person: updatedPerson });
return updatedPerson; return Promise.resolve(updatedPerson);
} }
} catch (e: unknown) { } catch (e: unknown) {
if (isValidationException<WritePersonViolationMap>(e)) { if (isValidationException<WritePersonViolationMap>(e)) {
@@ -651,7 +651,7 @@ async function postPerson(): Promise<Person> {
toast.error(trans(PERSON_EDIT_ERROR_WHILE_SAVING)); toast.error(trans(PERSON_EDIT_ERROR_WHILE_SAVING));
} }
} }
throw "impossible case might happen..."; throw "'action' is not create, or edit with a not-null id";
} }
onMounted(() => { onMounted(() => {

View File

@@ -1,7 +1,7 @@
/* /*
* GET a thirdparty by id * GET a thirdparty by id
*/ */
import {Thirdparty, ThirdPartyWrite} from '../../types'; import {isThirdpartyChild, isThirdpartyCompany, isThirdpartyContact, Thirdparty, ThirdPartyWrite} from '../../types';
import {makeFetch} from "ChillMainAssets/lib/api/apiMethods"; import {makeFetch} from "ChillMainAssets/lib/api/apiMethods";
export const getThirdparty = async (id: number) : Promise<Thirdparty> => { export const getThirdparty = async (id: number) : Promise<Thirdparty> => {
@@ -14,6 +14,33 @@ export const getThirdparty = async (id: number) : Promise<Thirdparty> => {
}); });
}; };
export const thirdpartyToWriteThirdParty = (t: Thirdparty): ThirdPartyWrite => {
// Determine kind-specific fields using available type guards
const isCompany = isThirdpartyCompany(t);
const isContact = isThirdpartyContact(t);
const isChild = isThirdpartyChild(t);
return {
type: 'thirdparty',
kind: t.kind,
civility:
(isContact || isChild) && t.civility
? { type: 'chill_main_civility', id: t.civility.id }
: null,
profession: (isContact || isChild) ? (t.profession ?? '') : '',
firstname: isCompany ? '' : (t.firstname ?? ''),
name: isCompany
? (t.nameCompany ?? '')
: (t.name ?? ''),
email: t.email ?? '',
telephone: t.telephone ?? '',
telephone2: t.telephone2 ?? '',
address: null,
comment: isChild ? (t.comment ?? '') : '',
parent: isChild && t.parent ? { type: 'thirdparty', id: t.parent.id } : null,
};
};
export interface WriteThirdPartyViolationMap export interface WriteThirdPartyViolationMap
extends Record<string, Record<string, string>> { extends Record<string, Record<string, string>> {
email: { email: {
@@ -42,7 +69,7 @@ export const createThirdParty = async (body: ThirdPartyWrite) => {
/* /*
* PATCH an existing thirdparty * PATCH an existing thirdparty
*/ */
export const patchThirdparty = async (id: number, body: ThirdPartyWrite) => { export const patchThirdparty = async (id: number, body: ThirdPartyWrite): Promise<Thirdparty> => {
const url = `/api/1.0/thirdparty/thirdparty/${id}.json`; const url = `/api/1.0/thirdparty/thirdparty/${id}.json`;
return makeFetch('PATCH', url, body); return makeFetch('PATCH', url, body);
}; };

View File

@@ -1,13 +1,13 @@
<template> <template>
<div> <div>
<div v-if="parent"> <div v-if="resolvedParent">
<div class="parent-info"> <div class="parent-info">
<i class="fa fa-li fa-hand-o-right"/> <i class="fa fa-li fa-hand-o-right"/>
<b class="me-2">{{ trans(THIRDPARTY_MESSAGES_CHILD_OF) }}</b> <b class="me-2">{{ trans(THIRDPARTY_MESSAGES_CHILD_OF) }}</b>
<span class="chill-entity badge-thirdparty">{{ parent.text }}</span> <span class="chill-entity badge-thirdparty">{{ resolvedParent.text }}</span>
</div> </div>
</div> </div>
<div class="form-floating mb-3" v-else-if="props.action !== 'addContact'"> <div class="form-floating mb-3" v-else-if="props.action === 'create'">
<div class="form-check"> <div class="form-check">
<input <input
class="form-check-input mt-0" class="form-check-input mt-0"
@@ -39,10 +39,10 @@
</label> </label>
</div> </div>
</div> </div>
<div v-else> <div v-else-if="resolvedParent">
<p>Contact de&nbsp;:</p> <p>Contact de&nbsp;:</p>
<third-party-render-box <third-party-render-box
:thirdparty="props.parent" :thirdparty="resolvedParent"
:options="{ :options="{
addInfo: true, addInfo: true,
addEntity: false, addEntity: false,
@@ -304,7 +304,12 @@
import {ref, reactive, computed, onMounted, getCurrentInstance} from 'vue' import {ref, reactive, computed, onMounted, getCurrentInstance} from 'vue'
import ThirdPartyRenderBox from '../Entity/ThirdPartyRenderBox.vue' import ThirdPartyRenderBox from '../Entity/ThirdPartyRenderBox.vue'
import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue"; import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue";
import {createThirdParty, getThirdparty, WriteThirdPartyViolationMap} from '../../_api/OnTheFly' import {
createThirdParty,
getThirdparty, patchThirdparty,
thirdpartyToWriteThirdParty,
WriteThirdPartyViolationMap
} from '../../_api/OnTheFly'
import BadgeEntity from 'ChillMainAssets/vuejs/_components/BadgeEntity.vue' import BadgeEntity from 'ChillMainAssets/vuejs/_components/BadgeEntity.vue'
import {localizeString as _localizeString} from 'ChillMainAssets/lib/localizationHelper/localizationHelper' import {localizeString as _localizeString} from 'ChillMainAssets/lib/localizationHelper/localizationHelper'
import { import {
@@ -324,7 +329,14 @@ import {
createPerson, createPerson,
getCivilities, WritePersonViolationMap, getCivilities, WritePersonViolationMap,
} from "ChillPersonAssets/vuejs/_api/OnTheFly"; } from "ChillPersonAssets/vuejs/_api/OnTheFly";
import {Thirdparty, ThirdpartyCompany, ThirdPartyKind, ThirdPartyWrite} from "../../../types"; import {
isThirdpartyChild,
isThirdpartyCompany,
Thirdparty,
ThirdpartyCompany,
ThirdPartyKind,
ThirdPartyWrite
} from "../../../types";
import {Civility, SetCivility} from "ChillMainAssets/types"; import {Civility, SetCivility} from "ChillMainAssets/types";
import {isValidationException} from "ChillMainAssets/lib/api/apiMethods"; import {isValidationException} from "ChillMainAssets/lib/api/apiMethods";
import {useViolationList} from "ChillMainAssets/vuejs/_composables/violationList"; import {useViolationList} from "ChillMainAssets/vuejs/_composables/violationList";
@@ -373,6 +385,8 @@ const thirdParty = ref<ThirdPartyWrite>({
parent: null, parent: null,
}); });
const originalThirdParty = ref<Thirdparty|null>(null);
const civility = computed<number | null>({ const civility = computed<number | null>({
get: () => { get: () => {
if (thirdParty.value.civility !== null) { if (thirdParty.value.civility !== null) {
@@ -425,6 +439,14 @@ const context = computed(() => {
return ctx return ctx
}) })
const resolvedParent = computed<null|ThirdpartyCompany>(() => {
if (null !== originalThirdParty.value && isThirdpartyChild(originalThirdParty.value)) {
return originalThirdParty.value.parent;
}
return props.parent ?? null;
})
/** /**
* Find the query items to display for suggestion * Find the query items to display for suggestion
*/ */
@@ -465,7 +487,10 @@ onMounted(() => {
}); });
async function loadData(): Promise<void> { async function loadData(): Promise<void> {
if (!props.id) return Promise.resolve() if (!props.id) return Promise.resolve();
const t = await getThirdparty(props.id);
originalThirdParty.value = t;
thirdParty.value = thirdpartyToWriteThirdParty(t);
} }
function submitAddress(payload: { addressId: number }) { function submitAddress(payload: { addressId: number }) {
@@ -494,19 +519,25 @@ function addQuery(query: string) {
const violations = useViolationList<WriteThirdPartyViolationMap>(); const violations = useViolationList<WriteThirdPartyViolationMap>();
async function postThirdParty(): Promise<void> { async function postThirdParty(): Promise<Thirdparty> {
try { try {
const tp = await createThirdParty(thirdParty.value); if (props.action === 'edit' && props.id) {
const tp = await patchThirdparty(props.id, thirdParty.value);
emit('onThirdPartyCreated', {thirdParty: tp}); return Promise.resolve(tp);
} else if (props.action === 'addContact' || props.action === 'create') {
const tp = await createThirdParty(thirdParty.value);
emit('onThirdPartyCreated', {thirdParty: tp});
return Promise.resolve(tp);
}
} catch (e: unknown) { } catch (e: unknown) {
if (isValidationException<WriteThirdPartyViolationMap>(e)) { if (isValidationException<WriteThirdPartyViolationMap>(e)) {
violations.setValidationException(e); violations.setValidationException(e);
} else { } else {
toast.error("An error occurred while creating the third party"); toast.error("An error occurred while creating the third party");
throw e;
} }
throw e;
} }
throw "'action' is not edit with and id, or addContact or create";
} }
</script> </script>