feat(transport) : onglet Qualimat accessible dès le départ, recherche réactive au nom, sélection remplit le formulaire (ERP-166)
This commit is contained in:
@@ -558,7 +558,6 @@
|
|||||||
},
|
},
|
||||||
"qualimat": {
|
"qualimat": {
|
||||||
"empty": "Aucun transporteur QUALIMAT trouvé.",
|
"empty": "Aucun transporteur QUALIMAT trouvé.",
|
||||||
"continue": "Continuer",
|
|
||||||
"columns": {
|
"columns": {
|
||||||
"name": "Nom",
|
"name": "Nom",
|
||||||
"address": "Adresse",
|
"address": "Adresse",
|
||||||
|
|||||||
@@ -162,8 +162,10 @@ describe('useCarrierForm', () => {
|
|||||||
// RG-4.13 : réaffiche le nom normalisé (UPPERCASE) renvoyé par le serveur.
|
// RG-4.13 : réaffiche le nom normalisé (UPPERCASE) renvoyé par le serveur.
|
||||||
expect(form.main.name).toBe('TRANSPORTS ACME')
|
expect(form.main.name).toBe('TRANSPORTS ACME')
|
||||||
expect(form.mainLocked.value).toBe(true)
|
expect(form.mainLocked.value).toBe(true)
|
||||||
expect(form.activeTab.value).toBe('qualimat')
|
// L'onglet Qualimat était déjà accessible (saisie assistée) ; le POST
|
||||||
expect(form.unlockedIndex.value).toBe(0)
|
// déverrouille Adresses (index 1) et bascule dessus.
|
||||||
|
expect(form.activeTab.value).toBe('addresses')
|
||||||
|
expect(form.unlockedIndex.value).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('buildMainPayload : omet certificationType vide, garde isChartered', () => {
|
it('buildMainPayload : omet certificationType vide, garde isChartered', () => {
|
||||||
@@ -213,8 +215,9 @@ describe('useCarrierForm', () => {
|
|||||||
expect(CARRIER_TAB_KEYS).toEqual(['qualimat', 'addresses', 'contacts', 'prices'])
|
expect(CARRIER_TAB_KEYS).toEqual(['qualimat', 'addresses', 'contacts', 'prices'])
|
||||||
const form = useCarrierForm()
|
const form = useCarrierForm()
|
||||||
expect(form.tabKeys.value).toEqual(['qualimat', 'addresses', 'contacts', 'prices'])
|
expect(form.tabKeys.value).toEqual(['qualimat', 'addresses', 'contacts', 'prices'])
|
||||||
// Tous verrouillés tant que le formulaire principal n'est pas validé.
|
// L'onglet Qualimat (index 0) est accessible dès le départ (saisie assistée) ;
|
||||||
expect(form.unlockedIndex.value).toBe(-1)
|
// Adresses / Contacts / Prix restent verrouillés jusqu'au POST principal.
|
||||||
|
expect(form.unlockedIndex.value).toBe(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('completeTab : déverrouille/avance, et signale le dernier onglet du flux', () => {
|
it('completeTab : déverrouille/avance, et signale le dernier onglet du flux', () => {
|
||||||
|
|||||||
@@ -76,8 +76,10 @@ export function useCarrierForm() {
|
|||||||
|
|
||||||
// ── Onglets : ordre + gating progressif ───────────────────────────────────
|
// ── Onglets : ordre + gating progressif ───────────────────────────────────
|
||||||
const tabKeys = ref<string[]>([...CARRIER_TAB_KEYS])
|
const tabKeys = ref<string[]>([...CARRIER_TAB_KEYS])
|
||||||
// Index du dernier onglet déverrouillé (-1 tant que le transporteur n'est pas créé).
|
// Index du dernier onglet déverrouillé. L'onglet Qualimat (index 0) est la saisie
|
||||||
const unlockedIndex = ref(-1)
|
// assistée du formulaire principal : accessible DÈS LE DÉPART (≠ Adresses /
|
||||||
|
// Contacts / Prix, déverrouillés seulement après le POST principal).
|
||||||
|
const unlockedIndex = ref(0)
|
||||||
const activeTab = ref<string>(CARRIER_TAB_KEYS[0])
|
const activeTab = ref<string>(CARRIER_TAB_KEYS[0])
|
||||||
// Onglets validés (passent en lecture seule).
|
// Onglets validés (passent en lecture seule).
|
||||||
const validated = reactive<Record<string, boolean>>({})
|
const validated = reactive<Record<string, boolean>>({})
|
||||||
@@ -194,9 +196,10 @@ export function useCarrierForm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /carriers (groupe `carrier:write:main`). Pré-check front (nom), puis
|
* POST /carriers (groupe `carrier:write:main`). Pré-check front, puis création.
|
||||||
* création. Au succès : verrouille le bloc principal, déverrouille le 1er onglet
|
* Au succès : verrouille le bloc principal, déverrouille l'onglet Adresses et
|
||||||
* et bascule sur « Qualimat ». Retourne true si créé, false sinon.
|
* bascule dessus (l'onglet Qualimat, saisie assistée, était déjà accessible).
|
||||||
|
* Retourne true si créé, false sinon.
|
||||||
*/
|
*/
|
||||||
async function submitMain(): Promise<boolean> {
|
async function submitMain(): Promise<boolean> {
|
||||||
if (mainSubmitting.value) return false
|
if (mainSubmitting.value) return false
|
||||||
@@ -217,8 +220,9 @@ export function useCarrierForm() {
|
|||||||
main.certificationType = created.certificationType ?? main.certificationType
|
main.certificationType = created.certificationType ?? main.certificationType
|
||||||
|
|
||||||
mainLocked.value = true
|
mainLocked.value = true
|
||||||
unlockedIndex.value = 0
|
// Déverrouille l'onglet suivant (Adresses, index 1) et bascule dessus.
|
||||||
activeTab.value = tabKeys.value[0] ?? CARRIER_TAB_KEYS[0]
|
unlockedIndex.value = Math.max(unlockedIndex.value, 1)
|
||||||
|
activeTab.value = tabKeys.value[1] ?? CARRIER_TAB_KEYS[1]
|
||||||
toast.success({ title: t('transport.carriers.toast.createSuccess') })
|
toast.success({ title: t('transport.carriers.toast.createSuccess') })
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,15 +167,6 @@
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</MalioDataTable>
|
</MalioDataTable>
|
||||||
|
|
||||||
<div v-if="!isValidated('qualimat')" class="flex justify-center">
|
|
||||||
<MalioButton
|
|
||||||
variant="primary"
|
|
||||||
:label="t('transport.carriers.form.qualimat.continue')"
|
|
||||||
:disabled="carrierId === null"
|
|
||||||
@click="onContinueQualimat"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -216,7 +207,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
|
import { debounce } from '~/shared/utils/debounce'
|
||||||
import { useCarrierForm } from '~/modules/transport/composables/useCarrierForm'
|
import { useCarrierForm } from '~/modules/transport/composables/useCarrierForm'
|
||||||
import { useQualimatSearch, type QualimatCarrierRow } from '~/modules/transport/composables/useQualimatSearch'
|
import { useQualimatSearch, type QualimatCarrierRow } from '~/modules/transport/composables/useQualimatSearch'
|
||||||
|
|
||||||
@@ -240,7 +232,6 @@ if (!can('transport.carriers.manage')) {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
main,
|
main,
|
||||||
carrierId,
|
|
||||||
mainLocked,
|
mainLocked,
|
||||||
mainSubmitting,
|
mainSubmitting,
|
||||||
mainErrors,
|
mainErrors,
|
||||||
@@ -251,10 +242,8 @@ const {
|
|||||||
tabKeys,
|
tabKeys,
|
||||||
activeTab,
|
activeTab,
|
||||||
unlockedIndex,
|
unlockedIndex,
|
||||||
isValidated,
|
|
||||||
submitMain,
|
submitMain,
|
||||||
applyQualimatSelection,
|
applyQualimatSelection,
|
||||||
completeTab,
|
|
||||||
} = useCarrierForm()
|
} = useCarrierForm()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -335,17 +324,20 @@ const tabs = computed(() => tabKeys.value.map((key, index) => ({
|
|||||||
const placeholderTabs = computed(() => tabKeys.value.filter(key => key !== 'qualimat'))
|
const placeholderTabs = computed(() => tabKeys.value.filter(key => key !== 'qualimat'))
|
||||||
|
|
||||||
// ── Saisie assistee QUALIMAT (onglet Qualimat) ───────────────────────────────
|
// ── Saisie assistee QUALIMAT (onglet Qualimat) ───────────────────────────────
|
||||||
const qualimatLoaded = ref(false)
|
|
||||||
const confirmOpen = ref(false)
|
const confirmOpen = ref(false)
|
||||||
const pendingRow = ref<QualimatCarrierRow | null>(null)
|
const pendingRow = ref<QualimatCarrierRow | null>(null)
|
||||||
|
|
||||||
// Chargement quand l'onglet Qualimat devient actif : la recherche est branchée sur
|
// Le datatable QUALIMAT est filtré par le NOM saisi dans le formulaire principal
|
||||||
// le NOM saisi dans le formulaire principal (RG-4.01) — pas de champ dédié.
|
// (RG-4.01) — pas de champ de recherche dédié. Re-filtrage debouncé à chaque frappe,
|
||||||
watch(activeTab, (tab) => {
|
// plus un chargement initial au montage (liste active complète si le nom est vide).
|
||||||
if (tab === 'qualimat' && !qualimatLoaded.value) {
|
const filterQualimatByName = debounce((term: string) => {
|
||||||
qualimatLoaded.value = true
|
void qualimatSetFilters({ search: term })
|
||||||
void qualimatSetFilters({ search: main.name })
|
}, 300)
|
||||||
}
|
|
||||||
|
watch(() => main.name, term => filterQualimatByName(term))
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
void qualimatSetFilters({ search: main.name })
|
||||||
})
|
})
|
||||||
|
|
||||||
/** Adresse QUALIMAT condensee pour la colonne « Adresse » (voie · CP · ville). */
|
/** Adresse QUALIMAT condensee pour la colonne « Adresse » (voie · CP · ville). */
|
||||||
@@ -406,11 +398,6 @@ async function confirmIntegrate(): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** « Continuer » : valide l'onglet Qualimat et avance a l'onglet Adresses. */
|
|
||||||
function onContinueQualimat(): void {
|
|
||||||
completeTab('qualimat')
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Retour vers le repertoire transporteurs (fleche d'en-tete). */
|
/** Retour vers le repertoire transporteurs (fleche d'en-tete). */
|
||||||
function goBack(): void {
|
function goBack(): void {
|
||||||
router.push('/carriers')
|
router.push('/carriers')
|
||||||
|
|||||||
Reference in New Issue
Block a user