feat(transport) : onglet adresses transporteur (ERP-167)
This commit is contained in:
@@ -170,7 +170,44 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Adresses / Contacts / Prix : contenu aux tickets suivants. -->
|
||||
<!-- Onglet Adresses (ERP-167) : un bloc par adresse + BAN. RG-4.05
|
||||
préremplissage si QUALIMAT ; RG-4.07 pas de Valider si QUALIMAT. -->
|
||||
<template #addresses>
|
||||
<div class="mt-12 flex flex-col gap-6">
|
||||
<CarrierAddressBlock
|
||||
v-for="(address, index) in addresses"
|
||||
:key="index"
|
||||
:model-value="address"
|
||||
:country-options="countryOptions"
|
||||
:removable="isRowRemovable(addresses, index)"
|
||||
:readonly="isQualimat || isValidated('addresses')"
|
||||
:errors="addressErrors[index]"
|
||||
@update:model-value="(v) => addresses[index] = v"
|
||||
@remove="askRemoveAddress(index)"
|
||||
@degraded="onAddressDegraded"
|
||||
/>
|
||||
<!-- RG-4.07 : pas de bouton Valider pour un transporteur QUALIMAT
|
||||
(adresse copiée et persistée automatiquement). -->
|
||||
<div v-if="!isQualimat && !isValidated('addresses')" class="flex justify-center gap-6">
|
||||
<MalioButton
|
||||
variant="secondary"
|
||||
icon-name="mdi:add-bold"
|
||||
icon-position="left"
|
||||
:label="t('transport.carriers.form.address.add')"
|
||||
:disabled="!canAddAddress"
|
||||
@click="addAddress"
|
||||
/>
|
||||
<MalioButton
|
||||
variant="primary"
|
||||
:label="t('transport.carriers.form.submit')"
|
||||
:disabled="tabSubmitting || carrierId === null"
|
||||
@click="onSubmitAddresses"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Contacts / Prix : contenu aux tickets suivants. -->
|
||||
<template
|
||||
v-for="key in placeholderTabs"
|
||||
:key="key"
|
||||
@@ -203,12 +240,37 @@
|
||||
/>
|
||||
</template>
|
||||
</MalioModal>
|
||||
|
||||
<!-- Modal de confirmation de suppression (bloc adresse). -->
|
||||
<MalioModal v-model="deleteConfirm.open" modal-class="max-w-md">
|
||||
<template #header>
|
||||
<h2 class="text-[24px] font-bold">{{ t('transport.carriers.form.confirmDelete.title') }}</h2>
|
||||
</template>
|
||||
<p>{{ t('transport.carriers.form.confirmDelete.message') }}</p>
|
||||
<template #footer>
|
||||
<MalioButton
|
||||
variant="secondary"
|
||||
button-class="flex-1"
|
||||
:label="t('transport.carriers.form.confirmDelete.cancel')"
|
||||
@click="deleteConfirm.open = false"
|
||||
/>
|
||||
<MalioButton
|
||||
variant="danger"
|
||||
button-class="flex-1"
|
||||
:label="t('transport.carriers.form.confirmDelete.confirm')"
|
||||
@click="runDeleteConfirm"
|
||||
/>
|
||||
</template>
|
||||
</MalioModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { debounce } from '~/shared/utils/debounce'
|
||||
import { extractApiErrorMessage } from '~/shared/utils/api'
|
||||
import { isRowRemovable } from '~/shared/utils/collectionRow'
|
||||
import CarrierAddressBlock from '~/modules/transport/components/CarrierAddressBlock.vue'
|
||||
import { useCarrierForm } from '~/modules/transport/composables/useCarrierForm'
|
||||
import { useQualimatSearch, type QualimatCarrierRow } from '~/modules/transport/composables/useQualimatSearch'
|
||||
|
||||
@@ -218,6 +280,7 @@ interface SelectOption {
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
const api = useApi()
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
const { can } = usePermissions()
|
||||
@@ -232,16 +295,26 @@ if (!can('transport.carriers.manage')) {
|
||||
|
||||
const {
|
||||
main,
|
||||
carrierId,
|
||||
mainLocked,
|
||||
mainSubmitting,
|
||||
tabSubmitting,
|
||||
mainErrors,
|
||||
isLiot,
|
||||
isQualimat,
|
||||
certificationReadonly,
|
||||
showCharteredFields,
|
||||
showDischarge,
|
||||
tabKeys,
|
||||
activeTab,
|
||||
unlockedIndex,
|
||||
isValidated,
|
||||
addresses,
|
||||
addressErrors,
|
||||
canAddAddress,
|
||||
addAddress,
|
||||
removeAddress,
|
||||
submitAddresses,
|
||||
submitMain,
|
||||
applyQualimatSelection,
|
||||
} = useCarrierForm()
|
||||
@@ -335,8 +408,80 @@ const tabs = computed(() => tabKeys.value.map((key, index) => ({
|
||||
disabled: index > unlockedIndex.value,
|
||||
})))
|
||||
|
||||
// Onglets dont le contenu arrive aux tickets suivants (tout sauf Qualimat).
|
||||
const placeholderTabs = computed(() => tabKeys.value.filter(key => key !== 'qualimat'))
|
||||
// Onglets dont le contenu arrive aux tickets suivants (Contacts / Prix).
|
||||
const placeholderTabs = computed(() => tabKeys.value.filter(key => key !== 'qualimat' && key !== 'addresses'))
|
||||
|
||||
// ── Onglet Adresses (ERP-167) ────────────────────────────────────────────────
|
||||
// Pays : France garantie en tete meme si /countries echoue (resilience), pour
|
||||
// rester preselectionnable par defaut sur chaque adresse (RG-4.05).
|
||||
const countryOptions = ref<SelectOption[]>([{ value: 'France', label: 'France' }])
|
||||
|
||||
/** Charge le referentiel pays (/api/countries) ; conserve France par defaut si echec. */
|
||||
async function loadCountries(): Promise<void> {
|
||||
try {
|
||||
const data = await api.get<{ member?: { name: string }[] }>(
|
||||
'/countries',
|
||||
{ pagination: 'false' },
|
||||
{ headers: { Accept: 'application/ld+json' }, toast: false },
|
||||
)
|
||||
const list = (data.member ?? []).map(c => ({ value: c.name, label: c.name }))
|
||||
countryOptions.value = list.some(c => c.value === 'France')
|
||||
? list
|
||||
: [{ value: 'France', label: 'France' }, ...list]
|
||||
}
|
||||
catch {
|
||||
// Reste sur le fallback France (non bloquant).
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadCountries().catch(() => {})
|
||||
})
|
||||
|
||||
// Avertissement unique quand l'autocompletion d'adresse bascule en degrade.
|
||||
const addressDegradedNotified = ref(false)
|
||||
|
||||
function onAddressDegraded(): void {
|
||||
if (addressDegradedNotified.value) {
|
||||
return
|
||||
}
|
||||
addressDegradedNotified.value = true
|
||||
toast.warning({
|
||||
title: t('transport.carriers.toast.error'),
|
||||
message: t('transport.carriers.form.address.degraded'),
|
||||
})
|
||||
}
|
||||
|
||||
/** Message d'erreur affichable (toast) extrait d'une erreur API — jamais undefined. */
|
||||
function apiErrorMessage(error: unknown): string {
|
||||
const data = (error as { response?: { _data?: unknown } })?.response?._data
|
||||
return extractApiErrorMessage(data) || t('transport.carriers.toast.error')
|
||||
}
|
||||
|
||||
/** Valide l'onglet Adresses (POST/PATCH par ligne ; avance gere par le composable). */
|
||||
async function onSubmitAddresses(): Promise<void> {
|
||||
const ok = await submitAddresses(error => toast.error({
|
||||
title: t('transport.carriers.toast.error'),
|
||||
message: apiErrorMessage(error),
|
||||
}))
|
||||
if (ok) {
|
||||
toast.success({ title: t('transport.carriers.toast.addressSaved') })
|
||||
}
|
||||
}
|
||||
|
||||
// Modal de confirmation de suppression (bloc adresse).
|
||||
const deleteConfirm = reactive({ open: false, action: null as null | (() => void) })
|
||||
|
||||
function askRemoveAddress(index: number): void {
|
||||
deleteConfirm.action = () => { void removeAddress(index) }
|
||||
deleteConfirm.open = true
|
||||
}
|
||||
|
||||
function runDeleteConfirm(): void {
|
||||
deleteConfirm.action?.()
|
||||
deleteConfirm.action = null
|
||||
deleteConfirm.open = false
|
||||
}
|
||||
|
||||
// ── Saisie assistee QUALIMAT (onglet Qualimat) ───────────────────────────────
|
||||
const confirmOpen = ref(false)
|
||||
@@ -417,8 +562,18 @@ function goBack(): void {
|
||||
router.push('/carriers')
|
||||
}
|
||||
|
||||
/** Valide le formulaire principal (POST /carriers ; bascule geree par le composable). */
|
||||
/**
|
||||
* Valide le formulaire principal (POST /carriers ; bascule geree par le composable).
|
||||
* RG-4.07 : pour un transporteur QUALIMAT, l'adresse copiee est persistee
|
||||
* automatiquement (pas de bouton Valider dans l'onglet Adresses).
|
||||
*/
|
||||
async function onSubmitMain(): Promise<void> {
|
||||
await submitMain()
|
||||
const ok = await submitMain()
|
||||
if (ok && isQualimat.value) {
|
||||
await submitAddresses(error => toast.error({
|
||||
title: t('transport.carriers.toast.error'),
|
||||
message: apiErrorMessage(error),
|
||||
}))
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user