Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cde2c4fbb7 | ||
| 5552d98935 | |||
|
|
9e67a5e289 | ||
| 92289f9cb2 | |||
|
|
59418f2c66 | ||
| e1c9e25187 | |||
| 0b22574932 | |||
|
|
9115699f96 | ||
| 178b4e4eee | |||
|
|
fbc8365405 | ||
| 4561467532 | |||
| c441420522 | |||
| ba9ea2de71 | |||
|
|
961fa63f3d | ||
| bebfabcacc | |||
|
|
e208bcd893 | ||
| 3fe0bbf71e |
@@ -1,4 +1,11 @@
|
|||||||
security:
|
security:
|
||||||
|
# Hiérarchie des rôles : ADMIN inclut BUREAU qui inclut USER.
|
||||||
|
# Ajouter un nouveau rôle = ajouter une ligne ici (et son équivalent côté
|
||||||
|
# front dans utils/roles.ts).
|
||||||
|
role_hierarchy:
|
||||||
|
ROLE_BUREAU: ROLE_USER
|
||||||
|
ROLE_ADMIN: ROLE_BUREAU
|
||||||
|
|
||||||
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
|
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
|
||||||
password_hashers:
|
password_hashers:
|
||||||
App\Entity\User: 'auto'
|
App\Entity\User: 'auto'
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
parameters:
|
parameters:
|
||||||
app.version: '0.0.92'
|
app.version: '0.0.99'
|
||||||
|
|||||||
@@ -18,13 +18,15 @@ export interface UseBovineColumnsOptions {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Définition partagée des colonnes des tableaux bovins (inventory + case).
|
* Définition partagée des colonnes des tableaux bovins (inventory + case).
|
||||||
* Variants distincts pour chaque écran et chaque rôle (admin/user) afin de
|
* 4 variants : avec/sans colonnes prix × inventory/case.
|
||||||
* pouvoir ajuster les largeurs indépendamment.
|
*
|
||||||
|
* Les colonnes Prix/kg et Prix total sont visibles pour les rôles BUREAU
|
||||||
|
* et ADMIN (BUREAU hérite ses droits price-visibility, ADMIN hérite de BUREAU).
|
||||||
*/
|
*/
|
||||||
export const useBovineColumns = (options: UseBovineColumnsOptions = {}) => {
|
export const useBovineColumns = (options: UseBovineColumnsOptions = {}) => {
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
|
|
||||||
const adminColumnsInventory: BovineColumn[] = [
|
const withPricesInventory: BovineColumn[] = [
|
||||||
{ key: 'nationalNumber', label: 'N° National', width: '80px' },
|
{ key: 'nationalNumber', label: 'N° National', width: '80px' },
|
||||||
{ key: 'workNumber', label: 'N° Travail', width: '60px' },
|
{ key: 'workNumber', label: 'N° Travail', width: '60px' },
|
||||||
{ key: 'sex', label: 'Sexe', width: '70px' },
|
{ key: 'sex', label: 'Sexe', width: '70px' },
|
||||||
@@ -38,7 +40,7 @@ export const useBovineColumns = (options: UseBovineColumnsOptions = {}) => {
|
|||||||
{ key: 'finalPrice', label: 'Prix total', width: '80px' }
|
{ key: 'finalPrice', label: 'Prix total', width: '80px' }
|
||||||
]
|
]
|
||||||
|
|
||||||
const userColumnsInventory: BovineColumn[] = [
|
const withoutPricesInventory: BovineColumn[] = [
|
||||||
{ key: 'nationalNumber', label: 'N° National', width: '80px' },
|
{ key: 'nationalNumber', label: 'N° National', width: '80px' },
|
||||||
{ key: 'workNumber', label: 'N° Travail', width: '60px' },
|
{ key: 'workNumber', label: 'N° Travail', width: '60px' },
|
||||||
{ key: 'sex', label: 'Sexe', width: '70px' },
|
{ key: 'sex', label: 'Sexe', width: '70px' },
|
||||||
@@ -50,7 +52,7 @@ export const useBovineColumns = (options: UseBovineColumnsOptions = {}) => {
|
|||||||
{ key: 'arrivalDate', label: 'Entrée le', width: '90px' }
|
{ key: 'arrivalDate', label: 'Entrée le', width: '90px' }
|
||||||
]
|
]
|
||||||
|
|
||||||
const adminColumnsCase: BovineColumn[] = [
|
const withPricesCase: BovineColumn[] = [
|
||||||
{ key: 'nationalNumber', label: 'N° National', width: '110px' },
|
{ key: 'nationalNumber', label: 'N° National', width: '110px' },
|
||||||
{ key: 'workNumber', label: 'N° Travail', width: '85px' },
|
{ key: 'workNumber', label: 'N° Travail', width: '85px' },
|
||||||
{ key: 'sex', label: 'Sexe', width: '90px' },
|
{ key: 'sex', label: 'Sexe', width: '90px' },
|
||||||
@@ -62,7 +64,7 @@ export const useBovineColumns = (options: UseBovineColumnsOptions = {}) => {
|
|||||||
{ key: 'finalPrice', label: 'Prix total', width: '105px' }
|
{ key: 'finalPrice', label: 'Prix total', width: '105px' }
|
||||||
]
|
]
|
||||||
|
|
||||||
const userColumnsCase: BovineColumn[] = [
|
const withoutPricesCase: BovineColumn[] = [
|
||||||
{ key: 'nationalNumber', label: 'N° National', width: '130px' },
|
{ key: 'nationalNumber', label: 'N° National', width: '130px' },
|
||||||
{ key: 'workNumber', label: 'N° Travail', width: '100px' },
|
{ key: 'workNumber', label: 'N° Travail', width: '100px' },
|
||||||
{ key: 'sex', label: 'Sexe', width: '110px' },
|
{ key: 'sex', label: 'Sexe', width: '110px' },
|
||||||
@@ -73,10 +75,13 @@ export const useBovineColumns = (options: UseBovineColumnsOptions = {}) => {
|
|||||||
]
|
]
|
||||||
|
|
||||||
const columns = computed<BovineColumn[]>(() => {
|
const columns = computed<BovineColumn[]>(() => {
|
||||||
if (options.variant === 'case') {
|
const isCase = options.variant === 'case'
|
||||||
return auth.isAdmin ? adminColumnsCase : userColumnsCase
|
const seePrice = auth.isBureau
|
||||||
|
|
||||||
|
if (isCase) {
|
||||||
|
return seePrice ? withPricesCase : withoutPricesCase
|
||||||
}
|
}
|
||||||
return auth.isAdmin ? adminColumnsInventory : userColumnsInventory
|
return seePrice ? withPricesInventory : withoutPricesInventory
|
||||||
})
|
})
|
||||||
|
|
||||||
return { columns }
|
return { columns }
|
||||||
|
|||||||
27
frontend/middleware/admin-guard.global.ts
Normal file
27
frontend/middleware/admin-guard.global.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { useAuthStore } from '~/stores/auth'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Garde-fou global : empêche les utilisateurs non-admin d'accéder aux pages
|
||||||
|
* sous /admin/*. Renvoie vers la home pour les utilisateurs authentifiés
|
||||||
|
* non-admin, et vers /login pour les non authentifiés.
|
||||||
|
*
|
||||||
|
* L'API back rejette de toute façon les actions admin avec un 403, mais ce
|
||||||
|
* middleware évite l'affichage des pages vides / en erreur quand un user
|
||||||
|
* tape directement l'URL /admin/...
|
||||||
|
*/
|
||||||
|
export default defineNuxtRouteMiddleware(async (to) => {
|
||||||
|
if (!to.path.startsWith('/admin')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const auth = useAuthStore()
|
||||||
|
await auth.ensureSession()
|
||||||
|
|
||||||
|
if (!auth.isAuthenticated) {
|
||||||
|
return navigateTo('/login')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!auth.isAdmin) {
|
||||||
|
return navigateTo('/')
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -32,6 +32,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Type de bovin' })
|
||||||
|
|
||||||
import {createBovin, getBovin, updateBovin} from "~/services/bovine-type";
|
import {createBovin, getBovin, updateBovin} from "~/services/bovine-type";
|
||||||
import type {BovineTypeData, BovinFormData} from "~/services/dto/bovine-type-data";
|
import type {BovineTypeData, BovinFormData} from "~/services/dto/bovine-type-data";
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|||||||
@@ -38,6 +38,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Types de bovins' })
|
||||||
|
|
||||||
import type { BovineTypeData } from '~/services/dto/bovine-type-data'
|
import type { BovineTypeData } from '~/services/dto/bovine-type-data'
|
||||||
import { useAuthStore } from '~/stores/auth'
|
import { useAuthStore } from '~/stores/auth'
|
||||||
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
||||||
|
|||||||
@@ -44,6 +44,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Transporteur' })
|
||||||
|
|
||||||
import {createCarrier, getCarrier, updateCarrier} from "~/services/carrier";
|
import {createCarrier, getCarrier, updateCarrier} from "~/services/carrier";
|
||||||
import type {CarrierData, CarrierFormData} from "~/services/dto/carrier-data";
|
import type {CarrierData, CarrierFormData} from "~/services/dto/carrier-data";
|
||||||
import {computed} from "vue";
|
import {computed} from "vue";
|
||||||
|
|||||||
@@ -34,6 +34,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Transporteurs' })
|
||||||
|
|
||||||
import type { CarrierData } from '~/services/dto/carrier-data'
|
import type { CarrierData } from '~/services/dto/carrier-data'
|
||||||
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
||||||
|
|
||||||
|
|||||||
@@ -96,6 +96,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Client' })
|
||||||
|
|
||||||
import {computed, reactive, ref, watch} from "vue"
|
import {computed, reactive, ref, watch} from "vue"
|
||||||
import {createCustomer, getCustomer, updateCustomer} from "~/services/customer"
|
import {createCustomer, getCustomer, updateCustomer} from "~/services/customer"
|
||||||
import type {CustomerData, CustomerFormData, CustomerPayload} from "~/services/dto/customer-data"
|
import type {CustomerData, CustomerFormData, CustomerPayload} from "~/services/dto/customer-data"
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Adresse client' })
|
||||||
|
|
||||||
import type { AddressData, AddressPayload } from "~/services/address"
|
import type { AddressData, AddressPayload } from "~/services/address"
|
||||||
import { createAddress, getAddress, updateAddress } from "~/services/address"
|
import { createAddress, getAddress, updateAddress } from "~/services/address"
|
||||||
import { getCustomer, updateCustomer } from "~/services/customer"
|
import { getCustomer, updateCustomer } from "~/services/customer"
|
||||||
|
|||||||
@@ -44,6 +44,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Clients' })
|
||||||
|
|
||||||
import type { CustomerData } from '~/services/dto/customer-data'
|
import type { CustomerData } from '~/services/dto/customer-data'
|
||||||
import { useAuthStore } from '~/stores/auth'
|
import { useAuthStore } from '~/stores/auth'
|
||||||
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
||||||
|
|||||||
@@ -97,6 +97,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Fournisseur' })
|
||||||
|
|
||||||
import {computed, reactive, ref, watch} from "vue"
|
import {computed, reactive, ref, watch} from "vue"
|
||||||
import {createSupplier, getSupplier, updateSupplier} from "~/services/supplier"
|
import {createSupplier, getSupplier, updateSupplier} from "~/services/supplier"
|
||||||
import type {SupplierData, SupplierFormData, SupplierPayload} from "~/services/dto/supplier-data"
|
import type {SupplierData, SupplierFormData, SupplierPayload} from "~/services/dto/supplier-data"
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Adresse fournisseur' })
|
||||||
|
|
||||||
import type {AddressData, AddressPayload} from "~/services/address";
|
import type {AddressData, AddressPayload} from "~/services/address";
|
||||||
import {createAddress, getAddress, updateAddress} from "~/services/address";
|
import {createAddress, getAddress, updateAddress} from "~/services/address";
|
||||||
import {getSupplier, updateSupplier} from "~/services/supplier";
|
import {getSupplier, updateSupplier} from "~/services/supplier";
|
||||||
|
|||||||
@@ -44,6 +44,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Fournisseurs' })
|
||||||
|
|
||||||
import type { SupplierData } from '~/services/dto/supplier-data'
|
import type { SupplierData } from '~/services/dto/supplier-data'
|
||||||
import { useAuthStore } from '~/stores/auth'
|
import { useAuthStore } from '~/stores/auth'
|
||||||
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
||||||
|
|||||||
@@ -74,6 +74,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Utilisateur' })
|
||||||
|
|
||||||
import { computed, reactive, ref, watch } from 'vue'
|
import { computed, reactive, ref, watch } from 'vue'
|
||||||
import { ROLE } from '~/utils/constants'
|
import { ROLE } from '~/utils/constants'
|
||||||
import { createUser, updateUser, getUser } from '~/services/auth'
|
import { createUser, updateUser, getUser } from '~/services/auth'
|
||||||
|
|||||||
@@ -63,6 +63,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Utilisateurs' })
|
||||||
|
|
||||||
import type { UserData } from '~/services/dto/user-data'
|
import type { UserData } from '~/services/dto/user-data'
|
||||||
import { ROLE } from '~/utils/constants'
|
import { ROLE } from '~/utils/constants'
|
||||||
import { useAuthStore } from '~/stores/auth'
|
import { useAuthStore } from '~/stores/auth'
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Accueil' })
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-wrap justify-center pb-16 gap-12">
|
<div class="flex flex-wrap justify-center pb-16 gap-12">
|
||||||
|
|||||||
@@ -69,6 +69,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Bovins' })
|
||||||
|
|
||||||
import { createBovine, getBovine, updateBovine } from '~/services/bovine'
|
import { createBovine, getBovine, updateBovine } from '~/services/bovine'
|
||||||
import type { BovinePayload } from '~/services/dto/bovine-data'
|
import type { BovinePayload } from '~/services/dto/bovine-data'
|
||||||
import type { SupplierData } from '~/services/dto/supplier-data'
|
import type { SupplierData } from '~/services/dto/supplier-data'
|
||||||
|
|||||||
@@ -80,6 +80,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Bâtiments' })
|
||||||
|
|
||||||
import type {BuildingData} from "~/services/dto/building-data"
|
import type {BuildingData} from "~/services/dto/building-data"
|
||||||
import type {BuildingLayoutData} from "~/services/dto/building-layout-data"
|
import type {BuildingLayoutData} from "~/services/dto/building-layout-data"
|
||||||
import type {BuildingCasePositionData} from "~/services/dto/building-case-position-data"
|
import type {BuildingCasePositionData} from "~/services/dto/building-case-position-data"
|
||||||
|
|||||||
@@ -130,6 +130,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Cases' })
|
||||||
|
|
||||||
import type { BuildingCaseData } from '~/services/dto/building-case-data'
|
import type { BuildingCaseData } from '~/services/dto/building-case-data'
|
||||||
import type { BovineData } from '~/services/dto/bovine-data'
|
import type { BovineData } from '~/services/dto/bovine-data'
|
||||||
import { useAuthStore } from '~/stores/auth'
|
import { useAuthStore } from '~/stores/auth'
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<h1 class="font-bold text-3xl uppercase text-primary-500">Inventaire bovins</h1>
|
<h1 class="font-bold text-3xl uppercase text-primary-500">Inventaire bovins</h1>
|
||||||
<span class="text-lg text-slate-500">({{ totalItems }} bovin{{ totalItems > 1 ? 's' : '' }})</span>
|
<span class="text-lg text-slate-500">({{ totalItems }} bovin{{ totalItems > 1 ? 's' : '' }})</span>
|
||||||
<div
|
<div
|
||||||
v-if="auth.isAdmin"
|
v-if="auth.isBureau"
|
||||||
class="bg-primary-500 p-1 rounded-md flex items-center cursor-pointer hover:opacity-80"
|
class="bg-primary-500 p-1 rounded-md flex items-center cursor-pointer hover:opacity-80"
|
||||||
:class="exporting ? 'cursor-not-allowed opacity-60' : ''"
|
:class="exporting ? 'cursor-not-allowed opacity-60' : ''"
|
||||||
title="Exporter en Excel"
|
title="Exporter en Excel"
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
v-if="auth.isAdmin"
|
v-if="auth.isBureau"
|
||||||
type="button"
|
type="button"
|
||||||
:disabled="syncing"
|
:disabled="syncing"
|
||||||
class="inline-flex items-center justify-center text-xl text-white uppercase bg-primary-500 h-[50px] px-6 rounded hover:opacity-80 gap-2 disabled:cursor-not-allowed disabled:opacity-60"
|
class="inline-flex items-center justify-center text-xl text-white uppercase bg-primary-500 h-[50px] px-6 rounded hover:opacity-80 gap-2 disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
@@ -147,6 +147,8 @@
|
|||||||
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Inventaire' })
|
||||||
|
|
||||||
import type { BovineData } from '~/services/dto/bovine-data'
|
import type { BovineData } from '~/services/dto/bovine-data'
|
||||||
import type { InventoryExportFilters } from '~/components/inventory/inventory-export-modal.vue'
|
import type { InventoryExportFilters } from '~/components/inventory/inventory-export-modal.vue'
|
||||||
import { useAuthStore } from '~/stores/auth'
|
import { useAuthStore } from '~/stores/auth'
|
||||||
|
|||||||
@@ -53,6 +53,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Connexion' })
|
||||||
|
|
||||||
import type { UserData } from '~/services/dto/user-data'
|
import type { UserData } from '~/services/dto/user-data'
|
||||||
import { getUsers } from '~/services/auth'
|
import { getUsers } from '~/services/auth'
|
||||||
import { useAuthStore } from '~/stores/auth'
|
import { useAuthStore } from '~/stores/auth'
|
||||||
|
|||||||
@@ -54,6 +54,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Réception' })
|
||||||
|
|
||||||
import { useReceptionStore } from '~/stores/reception'
|
import { useReceptionStore } from '~/stores/reception'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useWorkflowSteps } from '~/composables/useWorkflowSteps'
|
import { useWorkflowSteps } from '~/composables/useWorkflowSteps'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex items-center justify-start gap-10">
|
<div class="flex items-center justify-start gap-10">
|
||||||
<Icon @click="router.push('/')" name="gg:arrow-left-o" size="44" class="cursor-pointer text-primary-500"/>
|
<Icon @click="router.push('/')" name="gg:arrow-left-o" size="44" class="cursor-pointer text-primary-500"/>
|
||||||
<h1 class="text-3xl font-bold uppercase text-primary-500">listes des réceptions finie</h1>
|
<h1 class="text-3xl font-bold uppercase text-primary-500">liste des réceptions finies</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="px-[86px]">
|
<div class="px-[86px]">
|
||||||
@@ -73,6 +73,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Validation réception' })
|
||||||
|
|
||||||
import type { ReceptionData } from '~/services/dto/reception-data'
|
import type { ReceptionData } from '~/services/dto/reception-data'
|
||||||
import type { ReceptionTypeData } from '~/services/dto/reception-type-data'
|
import type { ReceptionTypeData } from '~/services/dto/reception-type-data'
|
||||||
import { getReceptionTypeList } from '~/services/reception-type'
|
import { getReceptionTypeList } from '~/services/reception-type'
|
||||||
|
|||||||
@@ -226,6 +226,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Modifier réception' })
|
||||||
|
|
||||||
import { usePdfPrinter } from '#imports'
|
import { usePdfPrinter } from '#imports'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import UpdateBovin from '~/components/reception/update-bovin.vue'
|
import UpdateBovin from '~/components/reception/update-bovin.vue'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex items-center justify-start gap-10">
|
<div class="flex items-center justify-start gap-10">
|
||||||
<Icon @click="router.push('/')" name="gg:arrow-left-o" size="44" class="cursor-pointer text-primary-500"/>
|
<Icon @click="router.push('/')" name="gg:arrow-left-o" size="44" class="cursor-pointer text-primary-500"/>
|
||||||
<h1 class="text-3xl font-bold uppercase text-primary-500">listes des réceptions en attente</h1>
|
<h1 class="text-3xl font-bold uppercase text-primary-500">liste des réceptions en attente</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="px-[86px]">
|
<div class="px-[86px]">
|
||||||
@@ -72,6 +72,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Réceptions en attente' })
|
||||||
|
|
||||||
import type { ReceptionData } from '~/services/dto/reception-data'
|
import type { ReceptionData } from '~/services/dto/reception-data'
|
||||||
import type { ReceptionTypeData } from '~/services/dto/reception-type-data'
|
import type { ReceptionTypeData } from '~/services/dto/reception-type-data'
|
||||||
import { deleteReception } from '~/services/reception'
|
import { deleteReception } from '~/services/reception'
|
||||||
|
|||||||
@@ -125,6 +125,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Scanner' })
|
||||||
|
|
||||||
import { ref, computed, nextTick, onMounted, watch } from 'vue'
|
import { ref, computed, nextTick, onMounted, watch } from 'vue'
|
||||||
import { useBarcodeScanner } from '~/composables/useBarcodeScanner'
|
import { useBarcodeScanner } from '~/composables/useBarcodeScanner'
|
||||||
import { createBovine } from '~/services/bovine'
|
import { createBovine } from '~/services/bovine'
|
||||||
|
|||||||
@@ -51,6 +51,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Expédition' })
|
||||||
|
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useShipmentStore } from '~/stores/shipment'
|
import { useShipmentStore } from '~/stores/shipment'
|
||||||
import { useWorkflowSteps } from '~/composables/useWorkflowSteps'
|
import { useWorkflowSteps } from '~/composables/useWorkflowSteps'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex items-center justify-start gap-10">
|
<div class="flex items-center justify-start gap-10">
|
||||||
<Icon @click="router.push('/')" name="gg:arrow-left-o" size="44" class="cursor-pointer text-primary-500"/>
|
<Icon @click="router.push('/')" name="gg:arrow-left-o" size="44" class="cursor-pointer text-primary-500"/>
|
||||||
<h1 class="text-3xl font-bold uppercase text-primary-500">listes des expéditions finie</h1>
|
<h1 class="text-3xl font-bold uppercase text-primary-500">liste des expéditions finies</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="px-[86px]">
|
<div class="px-[86px]">
|
||||||
@@ -71,6 +71,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Validation expédition' })
|
||||||
|
|
||||||
import type { ShipmentData } from '~/services/dto/shipment-data'
|
import type { ShipmentData } from '~/services/dto/shipment-data'
|
||||||
import type { ShipmentTypeData } from '~/services/dto/shipment-type-data'
|
import type { ShipmentTypeData } from '~/services/dto/shipment-type-data'
|
||||||
import { getShipmentTypeList } from '~/services/shipment-type'
|
import { getShipmentTypeList } from '~/services/shipment-type'
|
||||||
|
|||||||
@@ -197,6 +197,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Modifier expédition' })
|
||||||
|
|
||||||
import { usePdfPrinter } from '#imports'
|
import { usePdfPrinter } from '#imports'
|
||||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||||
import UpdateWeight from '~/components/commun/update-weight.vue'
|
import UpdateWeight from '~/components/commun/update-weight.vue'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex items-center justify-start gap-10">
|
<div class="flex items-center justify-start gap-10">
|
||||||
<Icon @click="router.push('/')" name="gg:arrow-left-o" size="44" class="cursor-pointer text-primary-500"/>
|
<Icon @click="router.push('/')" name="gg:arrow-left-o" size="44" class="cursor-pointer text-primary-500"/>
|
||||||
<h1 class="text-3xl font-bold uppercase text-primary-500">listes des expéditions en attente</h1>
|
<h1 class="text-3xl font-bold uppercase text-primary-500">liste des expéditions en attente</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="px-[86px]">
|
<div class="px-[86px]">
|
||||||
@@ -84,6 +84,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Expéditions en attente' })
|
||||||
|
|
||||||
import type { ShipmentData } from '~/services/dto/shipment-data'
|
import type { ShipmentData } from '~/services/dto/shipment-data'
|
||||||
import type { ShipmentTypeData } from '~/services/dto/shipment-type-data'
|
import type { ShipmentTypeData } from '~/services/dto/shipment-type-data'
|
||||||
import { deleteShipment } from '~/services/shipment'
|
import { deleteShipment } from '~/services/shipment'
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {defineStore} from 'pinia'
|
|||||||
import type {UserData} from '~/services/dto/user-data'
|
import type {UserData} from '~/services/dto/user-data'
|
||||||
import {getCurrentUser, createUser, updateUser, login, logout} from '~/services/auth'
|
import {getCurrentUser, createUser, updateUser, login, logout} from '~/services/auth'
|
||||||
import type {UserPayload} from "~/services/dto/user-data";
|
import type {UserPayload} from "~/services/dto/user-data";
|
||||||
import {ROLE} from '~/utils/constants'
|
import {userHasRole} from '~/utils/roles'
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', {
|
export const useAuthStore = defineStore('auth', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
@@ -12,7 +12,9 @@ export const useAuthStore = defineStore('auth', {
|
|||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
isAuthenticated: (state) => Boolean(state.user),
|
isAuthenticated: (state) => Boolean(state.user),
|
||||||
isAdmin: (state) => Boolean(state.user?.roles?.includes(ROLE[0].value))
|
hasRole: (state) => (role: string): boolean => userHasRole(state.user?.roles, role),
|
||||||
|
isAdmin: (state) => userHasRole(state.user?.roles, 'ROLE_ADMIN'),
|
||||||
|
isBureau: (state) => userHasRole(state.user?.roles, 'ROLE_BUREAU')
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
clearSession() {
|
clearSession() {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export const MERCHANDISE_TYPE_CODES = {
|
|||||||
|
|
||||||
export const ROLE = [
|
export const ROLE = [
|
||||||
{ label: 'Administrateur', value: 'ROLE_ADMIN' },
|
{ label: 'Administrateur', value: 'ROLE_ADMIN' },
|
||||||
|
{ label: 'Bureau', value: 'ROLE_BUREAU' },
|
||||||
{ label: 'Utilisateur', value: 'ROLE_USER' }
|
{ label: 'Utilisateur', value: 'ROLE_USER' }
|
||||||
]
|
]
|
||||||
export const SUPPLIER_CODE = {
|
export const SUPPLIER_CODE = {
|
||||||
|
|||||||
38
frontend/utils/roles.ts
Normal file
38
frontend/utils/roles.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Hiérarchie des rôles côté front. Doit rester synchronisée avec
|
||||||
|
* `role_hierarchy` dans config/packages/security.yaml côté back.
|
||||||
|
*
|
||||||
|
* Pour ajouter un nouveau rôle :
|
||||||
|
* 1. Ajouter une entrée ici (son rôle parent dans la chaîne)
|
||||||
|
* 2. Ajouter `ROLE_X: ROLE_Y` dans security.yaml côté back
|
||||||
|
* 3. Ajouter le rôle dans `ROLE` (utils/constants.ts) pour le form admin
|
||||||
|
*/
|
||||||
|
export const ROLE_HIERARCHY: Record<string, string[]> = {
|
||||||
|
ROLE_ADMIN: ['ROLE_BUREAU'],
|
||||||
|
ROLE_BUREAU: ['ROLE_USER'],
|
||||||
|
ROLE_USER: []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne l'ensemble des rôles effectifs en expansant la hiérarchie.
|
||||||
|
* Ex : ['ROLE_ADMIN'] → Set { 'ROLE_ADMIN', 'ROLE_BUREAU', 'ROLE_USER' }.
|
||||||
|
*/
|
||||||
|
export const expandRoles = (roles: string[]): Set<string> => {
|
||||||
|
const expanded = new Set<string>(roles)
|
||||||
|
const visit = (role: string): void => {
|
||||||
|
const parents = ROLE_HIERARCHY[role] ?? []
|
||||||
|
for (const parent of parents) {
|
||||||
|
if (!expanded.has(parent)) {
|
||||||
|
expanded.add(parent)
|
||||||
|
visit(parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const r of roles) visit(r)
|
||||||
|
return expanded
|
||||||
|
}
|
||||||
|
|
||||||
|
export const userHasRole = (userRoles: string[] | null | undefined, role: string): boolean => {
|
||||||
|
if (!userRoles || userRoles.length === 0) return false
|
||||||
|
return expandRoles(userRoles).has(role)
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ use App\State\Bovin\BovineInventoryExportProvider;
|
|||||||
description: "Retourne un fichier XLSX listant tous les bovins actifs (exitedAt IS NULL) triés par date de naissance croissante, avec colorisation des lignes selon l'âge.",
|
description: "Retourne un fichier XLSX listant tous les bovins actifs (exitedAt IS NULL) triés par date de naissance croissante, avec colorisation des lignes selon l'âge.",
|
||||||
tags: ['Bovines'],
|
tags: ['Bovines'],
|
||||||
),
|
),
|
||||||
security: "is_granted('ROLE_ADMIN')",
|
security: "is_granted('ROLE_BUREAU')",
|
||||||
output: false,
|
output: false,
|
||||||
provider: BovineInventoryExportProvider::class,
|
provider: BovineInventoryExportProvider::class,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use App\State\Bovin\BovineSyncInventoryProcessor;
|
|||||||
description: 'Upsert des bovins par numéro national ; marque comme sortis ceux absents de la réponse EDNOTIF.',
|
description: 'Upsert des bovins par numéro national ; marque comme sortis ceux absents de la réponse EDNOTIF.',
|
||||||
tags: ['Bovines'],
|
tags: ['Bovines'],
|
||||||
),
|
),
|
||||||
security: "is_granted('ROLE_ADMIN')",
|
security: "is_granted('ROLE_BUREAU')",
|
||||||
input: false,
|
input: false,
|
||||||
output: self::class,
|
output: self::class,
|
||||||
processor: BovineSyncInventoryProcessor::class,
|
processor: BovineSyncInventoryProcessor::class,
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class Bovine
|
|||||||
|
|
||||||
#[ORM\Column(type: 'float', nullable: true)]
|
#[ORM\Column(type: 'float', nullable: true)]
|
||||||
#[Groups(['bovine:read', 'bovine:write', 'building_case:read'])]
|
#[Groups(['bovine:read', 'bovine:write', 'building_case:read'])]
|
||||||
#[ApiProperty(security: "is_granted('ROLE_ADMIN')")]
|
#[ApiProperty(security: "is_granted('ROLE_BUREAU')")]
|
||||||
private ?float $pricePerKg = null;
|
private ?float $pricePerKg = null;
|
||||||
|
|
||||||
#[ORM\Column(type: 'date_immutable', nullable: true)]
|
#[ORM\Column(type: 'date_immutable', nullable: true)]
|
||||||
@@ -177,7 +177,7 @@ class Bovine
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[Groups(['bovine:read', 'building_case:read'])]
|
#[Groups(['bovine:read', 'building_case:read'])]
|
||||||
#[ApiProperty(security: "is_granted('ROLE_ADMIN')")]
|
#[ApiProperty(security: "is_granted('ROLE_BUREAU')")]
|
||||||
public function getFinalPrice(): ?float
|
public function getFinalPrice(): ?float
|
||||||
{
|
{
|
||||||
if (null === $this->receivedWeight || null === $this->pricePerKg) {
|
if (null === $this->receivedWeight || null === $this->pricePerKg) {
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
|||||||
),
|
),
|
||||||
new Delete(
|
new Delete(
|
||||||
requirements: ['id' => '\d+'],
|
requirements: ['id' => '\d+'],
|
||||||
|
security: "is_granted('ROLE_ADMIN')",
|
||||||
),
|
),
|
||||||
new Get(
|
new Get(
|
||||||
uriTemplate: '/receptions/weigh',
|
uriTemplate: '/receptions/weigh',
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
|||||||
),
|
),
|
||||||
new Delete(
|
new Delete(
|
||||||
requirements: ['id' => '\d+'],
|
requirements: ['id' => '\d+'],
|
||||||
|
security: "is_granted('ROLE_ADMIN')",
|
||||||
),
|
),
|
||||||
new Get(
|
new Get(
|
||||||
uriTemplate: '/shipments/weigh',
|
uriTemplate: '/shipments/weigh',
|
||||||
|
|||||||
@@ -91,6 +91,22 @@ final readonly class BuildingCaseWeightsReportProvider implements ProviderInterf
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
usort($rows, static function (array $a, array $b): int {
|
||||||
|
$aw = (string) ($a['workNumber'] ?? '');
|
||||||
|
$bw = (string) ($b['workNumber'] ?? '');
|
||||||
|
if ('' === $aw && '' === $bw) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if ('' === $aw) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if ('' === $bw) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int) $aw <=> (int) $bw;
|
||||||
|
});
|
||||||
|
|
||||||
$monthHeaders = $this->buildMonthHeaders($firstArrivalDate, $headerBreedCode);
|
$monthHeaders = $this->buildMonthHeaders($firstArrivalDate, $headerBreedCode);
|
||||||
|
|
||||||
$dompdf = new Dompdf();
|
$dompdf = new Dompdf();
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
.sheet { width: auto; }
|
.sheet { width: auto; }
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin: 8px 0 16px 0;
|
margin: 0 0 8px 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@@ -139,10 +139,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.main .sub-title {
|
.main .sub-title {
|
||||||
font-size: 16px;
|
font-size: 13px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0;
|
letter-spacing: 0;
|
||||||
padding: 8px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main .base {
|
.main .base {
|
||||||
@@ -203,61 +203,61 @@
|
|||||||
<h1 style="color: red; text-align: center; width: 100%; font-size: 36px">
|
<h1 style="color: red; text-align: center; width: 100%; font-size: 36px">
|
||||||
Arrivage du {{ firstArrivalDate ?? '-' }}
|
Arrivage du {{ firstArrivalDate ?? '-' }}
|
||||||
</h1>
|
</h1>
|
||||||
<table style="width:100%; border-collapse:collapse; table-layout:fixed; margin-bottom: 16px">
|
<table style="width:100%; border-collapse:collapse; table-layout:fixed; margin-bottom: 4px">
|
||||||
|
<colgroup>
|
||||||
|
{# 28 colonnes ≈ 3.571% chacune #}
|
||||||
|
{% for _ in 0..27 %}<col style="width:3.571%">{% endfor %}
|
||||||
|
</colgroup>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="width:40%; vertical-align:top; padding-right:2mm; border:0;">
|
<td style="border:0; text-align:left; font-weight:700; font-size: 18px;" colspan="4">PROVENANCE</td>
|
||||||
<table style="width:100%; border-collapse:collapse; table-layout:fixed;">
|
|
||||||
<tr>
|
|
||||||
<td style="border: 0; height: 20px"></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td style="font-weight:700; text-align: left; border: none; font-size: 24px">CASE N° {{ buildingCase.caseNumber ?? '' }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td style="width:60%; vertical-align:top; padding-left:2mm; border:0;">
|
{# Paire 1 : chiffre + case vide #}
|
||||||
<table class="header-right-free" style="width:100%; border-collapse:collapse; table-layout:fixed;">
|
<td style="border:1px solid #2b2b2b; text-align:center; font-weight:700; font-size: 11px; padding:0;">1</td>
|
||||||
<tr>
|
<td style="border:1px solid #2b2b2b;"></td>
|
||||||
<td style="border:0; text-align:center; font-weight:700; height: 20px;" colspan="5"></td>
|
<td style="border:0;"></td>
|
||||||
<td style="border:0;" colspan="2"></td>
|
{# Paire 2 #}
|
||||||
<td style="border:1px solid #2b2b2b; text-align:center; font-weight:700; height: 20px;">1</td>
|
<td style="border:1px solid #2b2b2b; text-align:center; font-weight:700; font-size: 11px; padding:0;">2</td>
|
||||||
<td style="border:0; height: 20px;"></td>
|
<td style="border:1px solid #2b2b2b;"></td>
|
||||||
<td style="border:1px solid #2b2b2b; text-align:center; font-weight:700; height: 20px;">2</td>
|
<td style="border:0;"></td>
|
||||||
<td style="border:0; height: 20px;"></td>
|
{# Paire 3 #}
|
||||||
<td style="border:1px solid #2b2b2b; text-align:center; font-weight:700; height: 20px;">3</td>
|
<td style="border:1px solid #2b2b2b; text-align:center; font-weight:700; font-size: 11px; padding:0;">3</td>
|
||||||
<td style="border:0; height: 20px;"></td>
|
<td style="border:1px solid #2b2b2b;"></td>
|
||||||
<td style="border:1px solid #2b2b2b; text-align:center; font-weight:700; height: 20px;">4</td>
|
<td style="border:0;"></td>
|
||||||
<td style="border:0;" colspan="2"></td>
|
{# Paire 4 #}
|
||||||
</tr>
|
<td style="border:1px solid #2b2b2b; text-align:center; font-weight:700; font-size: 11px; padding:0;">4</td>
|
||||||
<tr>
|
<td style="border:1px solid #2b2b2b;"></td>
|
||||||
<td style="border:0; text-align:left; font-weight:700; font-size: 24px; width:40%; height: 20px;" colspan="5">PROVENANCE</td>
|
|
||||||
<td style="border:0;" colspan="2"></td>
|
{# Espacement entre PROVENANCE et RACE (1 col, RACE commence plus tôt) #}
|
||||||
<td style="border:1px solid #2b2b2b;"></td>
|
<td style="border:0;"></td>
|
||||||
<td style="border:0;"></td>
|
|
||||||
<td style="border:1px solid #2b2b2b;"></td>
|
{# Bloc RACE #}
|
||||||
<td style="border:0;"></td>
|
<td style="border:0; text-align:left; font-weight:700; font-size: 18px;" colspan="2">RACE</td>
|
||||||
<td style="border:1px solid #2b2b2b;"></td>
|
<td style="border:1px solid #2b2b2b; text-align:center; font-weight:700;" colspan="2">LIMOUSIN</td>
|
||||||
<td style="border:0;"></td>
|
<td style="border:1px solid #2b2b2b;"></td>
|
||||||
<td style="border:1px solid #2b2b2b;"></td>
|
<td style="border:0;"></td>
|
||||||
<td style="border: 0; width: 20%;" colspan="2"></td>
|
<td style="border:1px solid #2b2b2b; text-align:center; font-weight:700;" colspan="2">CHAROLAIS</td>
|
||||||
</tr>
|
<td style="border:1px solid #2b2b2b;"></td>
|
||||||
<tr>
|
<td style="border:0;"></td>
|
||||||
<td style="border: 0; height: 20px" colspan="16"></td>
|
<td style="border:1px solid #2b2b2b; text-align:center; font-weight:700;">AUTRE</td>
|
||||||
</tr>
|
<td style="border:1px solid #2b2b2b;"></td>
|
||||||
<tr>
|
</tr>
|
||||||
<td style="border: 0; text-align:left; font-weight:700; font-size: 24px" colspan="3">RACE</td>
|
</table>
|
||||||
<td style="border:1px solid #2b2b2b; text-align:center; font-weight:700;" colspan="3">LIMOUSIN</td>
|
|
||||||
<td style="border:1px solid #2b2b2b; text-align:center; font-weight:700;" colspan="1"></td>
|
<table style="width:auto; border-collapse:collapse; margin-bottom: 8px; margin-top: 8px">
|
||||||
<td style="border: 0; text-align:center; font-weight:700;" colspan="1"></td>
|
<tr>
|
||||||
<td style="border:1px solid #2b2b2b; text-align:center; font-weight:700;" colspan="3">CHAROLAIS</td>
|
<td style="border:0; text-align:left; font-weight:700; font-size: 18px; padding-right: 8px;">BATIMENT N°</td>
|
||||||
<td style="border:1px solid #2b2b2b; text-align:center; font-weight:700;" colspan="1"></td>
|
<td style="border:1px solid #2b2b2b; width: 22px; height: 22px;"></td>
|
||||||
<td style="border: 0; text-align:center; font-weight:700;" colspan="1"></td>
|
<td style="border:0; width: 22px;"></td>
|
||||||
<td style="border:1px solid #2b2b2b; text-align:center; font-weight:700;" colspan="2">Autre</td>
|
<td style="border:1px solid #2b2b2b; width: 22px; height: 22px;"></td>
|
||||||
<td style="border:1px solid #2b2b2b; text-align:center; font-weight:700;" colspan="1"></td>
|
<td style="border:0; width: 22px;"></td>
|
||||||
</tr>
|
<td style="border:1px solid #2b2b2b; width: 22px; height: 22px;"></td>
|
||||||
</table>
|
<td style="border:0; width: 32px;"></td>
|
||||||
</td>
|
<td style="border:0; text-align:left; font-weight:700; font-size: 18px; padding-right: 8px;">CASE N°</td>
|
||||||
|
<td style="border:1px solid #2b2b2b; width: 22px; height: 22px;"></td>
|
||||||
|
<td style="border:0; width: 22px;"></td>
|
||||||
|
<td style="border:1px solid #2b2b2b; width: 22px; height: 22px;"></td>
|
||||||
|
<td style="border:0; width: 22px;"></td>
|
||||||
|
<td style="border:1px solid #2b2b2b; width: 22px; height: 22px;"></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@@ -267,30 +267,29 @@
|
|||||||
<table class="main">
|
<table class="main">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th rowspan="4" class="head-big" style="width:5%">N° de<br>travail</th>
|
{% for month in monthHeaders|default([])|reverse %}
|
||||||
<th rowspan="4" class="head-big" style="width:5%">N° de<br>travail</th>
|
|
||||||
<th rowspan="4" class="head-big head-big-weight" style="width:4%">Poids<br>(kg)</th>
|
|
||||||
<th rowspan="4" class="head-big" style="width:7%">Date de<br>naissance</th>
|
|
||||||
|
|
||||||
{% for month in monthHeaders|default([]) %}
|
|
||||||
<th class="month" style="width:6.58%">{{ month.name }}</th>
|
<th class="month" style="width:6.58%">{{ month.name }}</th>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
<th rowspan="4" class="head-big" style="width:7%">Date de<br>naissance</th>
|
||||||
|
<th rowspan="4" class="head-big head-big-weight" style="width:4%">Poids<br>(kg)</th>
|
||||||
|
<th rowspan="4" class="head-big" style="width:5%">N° de<br>travail</th>
|
||||||
|
<th rowspan="4" class="head-big" style="width:5%">N° de<br>travail</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
{% for month in monthHeaders|default([]) %}
|
{% for month in monthHeaders|default([])|reverse %}
|
||||||
<th class="days">{{ month.days }}</th>
|
<th class="days">{{ month.days }}</th>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th class="days">Foin</th>
|
|
||||||
<th class="days">Foin</th>
|
|
||||||
<th colspan="{{ monthHeaders|length -2 }}" class="sub-title">POIDS PAR MOIS</th>
|
<th colspan="{{ monthHeaders|length -2 }}" class="sub-title">POIDS PAR MOIS</th>
|
||||||
|
<th class="days">Foin</th>
|
||||||
|
<th class="days">Foin</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
{% for month in monthHeaders|default([]) %}
|
{% for month in monthHeaders|default([])|reverse %}
|
||||||
<th class="base">
|
<th class="base">
|
||||||
{% if month.baseValue is defined %}
|
{% if month.baseValue is defined %}
|
||||||
{{ month.baseValue|round(0, 'common') }} kg
|
{{ month.baseValue|round(0, 'common') }} kg
|
||||||
@@ -303,27 +302,28 @@
|
|||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{# 11 lignes comme dans ton code (0..10) #}
|
{# 13 lignes comme dans ton code (0..12) #}
|
||||||
{% for i in 0..12 %}
|
{% for i in 0..12 %}
|
||||||
{% set row = rows[i] ?? null %}
|
{% set row = rows[i] ?? null %}
|
||||||
{% set baseWeight = row ? (row.receivedWeight ?? null) : null %}
|
{% set baseWeight = row ? (row.receivedWeight ?? null) : null %}
|
||||||
|
|
||||||
<tr class="data-row">
|
<tr class="data-row">
|
||||||
<td class="row-work"></td>
|
{% for idx in 0..(monthCount > 0 ? monthCount - 1 : 0) %}
|
||||||
<td class="row-work">{{ row ? (row.workNumber ?? '') : '' }}</td>
|
{% set reversedIdx = (monthCount - 1) - idx %}
|
||||||
<td class="row-weight">{{ baseWeight ?? '' }}</td>
|
{% set projectedWeight = row and row.projectedWeights is defined ? (row.projectedWeights[reversedIdx] ?? null) : null %}
|
||||||
|
<td class="row-month"{% if reversedIdx < 4 %} style="background:#e0e0e0;"{% endif %}>
|
||||||
|
{{ projectedWeight is not null ? projectedWeight|round(0, 'common') : '' }}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
<td class="row-birth">
|
<td class="row-birth">
|
||||||
{% if row and row.birthDate %}
|
{% if row and row.birthDate %}
|
||||||
{% set birthParts = row.birthDate|split('/') %}
|
{% set birthParts = row.birthDate|split('/') %}
|
||||||
{{ birthParts|length == 3 ? birthParts[1] ~ '/' ~ birthParts[2] : row.birthDate }}
|
{{ birthParts|length == 3 ? birthParts[1] ~ '/' ~ birthParts[2] : row.birthDate }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
{% for idx in 0..(monthCount > 0 ? monthCount - 1 : 0) %}
|
<td class="row-weight">{{ baseWeight ?? '' }}</td>
|
||||||
{% set projectedWeight = row and row.projectedWeights is defined ? (row.projectedWeights[idx] ?? null) : null %}
|
<td class="row-work">{{ row ? (row.workNumber ?? '') : '' }}</td>
|
||||||
<td class="row-month"{% if loop.index0 < 4 %} style="background:#e0e0e0;"{% endif %}>
|
<td class="row-work"></td>
|
||||||
{{ projectedWeight is not null ? projectedWeight|round(0, 'common') : '' }}
|
|
||||||
</td>
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -331,41 +331,89 @@
|
|||||||
<!-- =========================
|
<!-- =========================
|
||||||
FOOTER (traitements / vaccins)
|
FOOTER (traitements / vaccins)
|
||||||
========================= -->
|
========================= -->
|
||||||
<table class="footer" style="border-collapse:collapse; margin-top: 32px">
|
<table style="width:100%; border:0; border-collapse:collapse; table-layout:fixed; margin-top: 12px">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="height: 20px; border: 0" colspan="4"></td>
|
<td style="border:0; padding:0; width:49%; vertical-align:top;">
|
||||||
<td style="font-weight: 700" colspan="2">Grippe</td>
|
<table class="footer" style="border-collapse:collapse; width:100%; table-layout:fixed;">
|
||||||
<td style="font-weight: 700" colspan="2">Protivity</td>
|
<tr>
|
||||||
</tr>
|
<td style="font-weight: 700; height: 20px" colspan="10">Traitements</td>
|
||||||
<tr>
|
</tr>
|
||||||
<td style="height: 20px">Date</td>
|
<tr>
|
||||||
<td>Antibiotique</td>
|
<td style="height: 20px" colspan="2">Date</td>
|
||||||
<td>Date</td>
|
<td colspan="2"></td>
|
||||||
<td>Antero</td>
|
<td>Dose</td>
|
||||||
<td>Date</td>
|
<td colspan="5">Observation</td>
|
||||||
<td>Intranasale</td>
|
</tr>
|
||||||
<td>Date</td>
|
<tr>
|
||||||
<td>Rappel 30 jours</td>
|
<td style="height: 20px" colspan="2"></td>
|
||||||
</tr>
|
<td colspan="2">Grippe</td>
|
||||||
<tr>
|
<td></td>
|
||||||
<td style="height: 20px"></td>
|
<td colspan="5"></td>
|
||||||
<td></td>
|
</tr>
|
||||||
<td></td>
|
<tr>
|
||||||
<td></td>
|
<td style="height: 20px" colspan="2"></td>
|
||||||
<td></td>
|
<td colspan="2">Antéro</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
<td colspan="5"></td>
|
||||||
<td></td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<td style="height: 20px" colspan="2"></td>
|
||||||
<td style="height: 20px"></td>
|
<td colspan="2">Antibiotiques</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
<td colspan="5"></td>
|
||||||
<td></td>
|
</tr>
|
||||||
<td></td>
|
<tr>
|
||||||
<td></td>
|
<td style="height: 20px" colspan="2"></td>
|
||||||
<td></td>
|
<td colspan="2">Déparasitage</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
|
<td colspan="5"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="height: 20px" colspan="2"></td>
|
||||||
|
<td colspan="2"></td>
|
||||||
|
<td></td>
|
||||||
|
<td colspan="5"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
<td style="border:0; padding:0; width:2%;"></td>
|
||||||
|
<td style="border:0; padding:0; width:49%; vertical-align:top;">
|
||||||
|
<table class="footer" style="border-collapse:collapse; width:100%; table-layout:fixed;">
|
||||||
|
<tr>
|
||||||
|
<td style="font-weight: 700; height: 20px" colspan="10">Rappel</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="height: 20px" colspan="2">Date</td>
|
||||||
|
<td>Dose</td>
|
||||||
|
<td colspan="7">Observation</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="height: 20px" colspan="2"></td>
|
||||||
|
<td></td>
|
||||||
|
<td colspan="7"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="height: 20px" colspan="2"></td>
|
||||||
|
<td></td>
|
||||||
|
<td colspan="7"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="height: 20px" colspan="2"></td>
|
||||||
|
<td></td>
|
||||||
|
<td colspan="7"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="height: 20px" colspan="2"></td>
|
||||||
|
<td></td>
|
||||||
|
<td colspan="7"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="height: 20px" colspan="2"></td>
|
||||||
|
<td></td>
|
||||||
|
<td colspan="7"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user