feat(front) : onglet comptabilite prestataire
This commit is contained in:
@@ -127,7 +127,136 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="canAccountingView" #accounting><ComingSoonPlaceholder /></template>
|
||||
<!-- Onglet Comptabilite (present uniquement si accounting.view ; editable si manage). -->
|
||||
<template v-if="canAccountingView" #accounting>
|
||||
<div class="mt-12 flex flex-col gap-6">
|
||||
<div class="bg-white py-4 pl-[28px] pr-[60px] shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]">
|
||||
<div class="grid grid-cols-4 gap-x-[44px] gap-y-4">
|
||||
<MalioInputText
|
||||
v-model="accounting.siren"
|
||||
:label="t('technique.providers.form.accounting.siren')"
|
||||
:mask="SIREN_MASK"
|
||||
:readonly="accountingReadonly"
|
||||
:required="true"
|
||||
:error="accountingErrors.errors.siren"
|
||||
/>
|
||||
<MalioInputText
|
||||
v-model="accounting.accountNumber"
|
||||
:label="t('technique.providers.form.accounting.accountNumber')"
|
||||
:readonly="accountingReadonly"
|
||||
:required="true"
|
||||
:error="accountingErrors.errors.accountNumber"
|
||||
/>
|
||||
<MalioSelect
|
||||
:model-value="accounting.tvaModeIri"
|
||||
:options="referentials.tvaModes.value"
|
||||
:label="t('technique.providers.form.accounting.tvaMode')"
|
||||
:readonly="accountingReadonly"
|
||||
empty-option-label=""
|
||||
:required="true"
|
||||
:error="accountingErrors.errors.tvaMode"
|
||||
@update:model-value="(v: string | number | null) => accounting.tvaModeIri = v === null ? null : String(v)"
|
||||
/>
|
||||
<MalioInputText
|
||||
v-model="accounting.nTva"
|
||||
:label="t('technique.providers.form.accounting.nTva')"
|
||||
:readonly="accountingReadonly"
|
||||
:required="true"
|
||||
:error="accountingErrors.errors.nTva"
|
||||
/>
|
||||
<MalioSelect
|
||||
:model-value="accounting.paymentDelayIri"
|
||||
:options="referentials.paymentDelays.value"
|
||||
:label="t('technique.providers.form.accounting.paymentDelay')"
|
||||
:readonly="accountingReadonly"
|
||||
empty-option-label=""
|
||||
:required="true"
|
||||
:error="accountingErrors.errors.paymentDelay"
|
||||
@update:model-value="(v: string | number | null) => accounting.paymentDelayIri = v === null ? null : String(v)"
|
||||
/>
|
||||
<MalioSelect
|
||||
:model-value="accounting.paymentTypeIri"
|
||||
:options="referentials.paymentTypes.value"
|
||||
:label="t('technique.providers.form.accounting.paymentType')"
|
||||
:readonly="accountingReadonly"
|
||||
empty-option-label=""
|
||||
:required="true"
|
||||
:error="accountingErrors.errors.paymentType"
|
||||
@update:model-value="onPaymentTypeChange"
|
||||
/>
|
||||
<!-- Banque : visible et obligatoire seulement si VIREMENT (RG-3.07). -->
|
||||
<MalioSelect
|
||||
v-if="isBankRequired"
|
||||
:model-value="accounting.bankIri"
|
||||
:options="referentials.banks.value"
|
||||
:label="t('technique.providers.form.accounting.bank')"
|
||||
:readonly="accountingReadonly"
|
||||
empty-option-label=""
|
||||
:required="true"
|
||||
:error="accountingErrors.errors.bank"
|
||||
@update:model-value="(v: string | number | null) => accounting.bankIri = v === null ? null : String(v)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Blocs RIB — affiches uniquement si type de reglement = LCR (RG-3.08). -->
|
||||
<div
|
||||
v-for="(rib, index) in visibleRibs"
|
||||
:key="index"
|
||||
class="relative bg-white py-4 pl-[28px] pr-[60px] shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]"
|
||||
>
|
||||
<MalioButtonIcon
|
||||
v-if="!accountingReadonly && visibleRibs.length > 1"
|
||||
icon="mdi:delete-outline"
|
||||
variant="ghost"
|
||||
button-class="absolute top-3 right-3"
|
||||
v-bind="{ ariaLabel: t('technique.providers.form.accounting.removeRib') }"
|
||||
@click="askRemoveRib(index)"
|
||||
/>
|
||||
<div class="grid grid-cols-4 gap-x-[44px] gap-y-4">
|
||||
<MalioInputText
|
||||
v-model="rib.label"
|
||||
:label="t('technique.providers.form.accounting.ribLabel')"
|
||||
:readonly="accountingReadonly"
|
||||
:required="true"
|
||||
:error="ribErrors[index]?.label"
|
||||
/>
|
||||
<MalioInputText
|
||||
v-model="rib.bic"
|
||||
:label="t('technique.providers.form.accounting.ribBic')"
|
||||
:readonly="accountingReadonly"
|
||||
:required="true"
|
||||
:error="ribErrors[index]?.bic"
|
||||
/>
|
||||
<MalioInputText
|
||||
v-model="rib.iban"
|
||||
:label="t('technique.providers.form.accounting.ribIban')"
|
||||
:readonly="accountingReadonly"
|
||||
:required="true"
|
||||
:error="ribErrors[index]?.iban"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!accountingReadonly" class="flex justify-center gap-6">
|
||||
<MalioButton
|
||||
v-if="isRibRequired"
|
||||
variant="secondary"
|
||||
icon-name="mdi:add-bold"
|
||||
icon-position="left"
|
||||
:label="t('technique.providers.form.accounting.addRib')"
|
||||
:disabled="!canAddRib"
|
||||
@click="addRib"
|
||||
/>
|
||||
<MalioButton
|
||||
variant="primary"
|
||||
:label="t('technique.providers.form.submit')"
|
||||
:disabled="tabSubmitting"
|
||||
@click="onSubmitAccounting"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MalioTabList>
|
||||
|
||||
<!-- Modal de confirmation generique (suppression d'un bloc contact). -->
|
||||
@@ -158,8 +287,15 @@
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useProviderReferentials, type RefOption } from '~/modules/technique/composables/useProviderReferentials'
|
||||
import { useProviderForm } from '~/modules/technique/composables/useProviderForm'
|
||||
import {
|
||||
isBankRequiredForPaymentType,
|
||||
isRibRequiredForPaymentType,
|
||||
} from '~/modules/technique/utils/forms/providerAccounting'
|
||||
import { extractApiErrorMessage } from '~/shared/utils/api'
|
||||
|
||||
// Masque SIREN : 9 chiffres (la normalisation finale reste serveur).
|
||||
const SIREN_MASK = '#########'
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
@@ -200,6 +336,16 @@ const {
|
||||
addAddress,
|
||||
removeAddress,
|
||||
submitAddresses,
|
||||
accounting,
|
||||
ribs,
|
||||
accountingErrors,
|
||||
ribErrors,
|
||||
accountingReadonly,
|
||||
setPaymentType,
|
||||
canAddRib,
|
||||
addRib,
|
||||
removeRib,
|
||||
submitAccounting,
|
||||
} = useProviderForm()
|
||||
|
||||
/** Retour vers le repertoire prestataires (fleche d'en-tete). */
|
||||
@@ -282,6 +428,43 @@ function askRemoveAddress(index: number): void {
|
||||
askConfirm(t('technique.providers.form.confirmDelete.address'), () => removeAddress(index))
|
||||
}
|
||||
|
||||
// ── Onglet Comptabilite ───────────────────────────────────────────────────────
|
||||
// Code stable du type de reglement selectionne (pour RG-3.07 / RG-3.08).
|
||||
const selectedPaymentTypeCode = computed(() =>
|
||||
referentials.paymentTypes.value.find(p => p.value === accounting.paymentTypeIri)?.code ?? null,
|
||||
)
|
||||
const isBankRequired = computed(() => isBankRequiredForPaymentType(selectedPaymentTypeCode.value))
|
||||
const isRibRequired = computed(() => isRibRequiredForPaymentType(selectedPaymentTypeCode.value))
|
||||
|
||||
// Les blocs RIB ne sont affiches que pour une LCR (RG-3.08).
|
||||
const visibleRibs = computed(() => isRibRequired.value ? ribs.value : [])
|
||||
|
||||
/** Changement de type de reglement : propage les RG inter-champs (banque / RIB). */
|
||||
function onPaymentTypeChange(value: string | number | null): void {
|
||||
const iri = value === null ? null : String(value)
|
||||
const code = referentials.paymentTypes.value.find(p => p.value === iri)?.code ?? null
|
||||
setPaymentType(iri, isBankRequiredForPaymentType(code), isRibRequiredForPaymentType(code))
|
||||
}
|
||||
|
||||
function askRemoveRib(index: number): void {
|
||||
askConfirm(t('technique.providers.form.confirmDelete.rib'), () => removeRib(index))
|
||||
}
|
||||
|
||||
/** Valide l'onglet Comptabilite ; toast de succes si l'onglet a ete finalise. */
|
||||
async function onSubmitAccounting(): Promise<void> {
|
||||
const ok = await submitAccounting(
|
||||
isBankRequired.value,
|
||||
isRibRequired.value,
|
||||
error => toast.error({
|
||||
title: t('technique.providers.toast.error'),
|
||||
message: apiErrorMessage(error),
|
||||
}),
|
||||
)
|
||||
if (ok) {
|
||||
toast.success({ title: t('technique.providers.toast.updateSuccess') })
|
||||
}
|
||||
}
|
||||
|
||||
// ── Modal de confirmation generique ─────────────────────────────────────────
|
||||
const confirmModal = reactive({
|
||||
open: false,
|
||||
@@ -320,5 +503,9 @@ const tabs = computed(() => tabKeys.value.map((key, index) => ({
|
||||
onMounted(() => {
|
||||
// Echec du chargement des referentiels non bloquant : les selects restent vides.
|
||||
referentials.loadMain().catch(() => {})
|
||||
// Referentiels comptables charges uniquement si l'onglet est accessible.
|
||||
if (canAccountingView.value) {
|
||||
referentials.loadAccounting().catch(() => {})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user