feat : creer une nouvelle expedtion (WIP)

This commit is contained in:
2026-02-10 16:16:05 +01:00
parent 42c5a3b2d2
commit bffdc88701
19 changed files with 1083 additions and 125 deletions

View File

@@ -0,0 +1,544 @@
<template>
<form @submit.prevent="validate">
<div class="grid grid-cols-2 items-start gap-y-8 gap-x-40 mb-16">
<h1 class="font-bold text-5xl uppercase col-start-1 row-start-1">Éxpedition</h1>
<UiSelect
id="shipment-user"
label="Nom de l'utisateur"
:options="users.map((user) => ({
value: String(user.id),
label: user.username
}))"
:loading="isLoadingUsers"
wrapper-class="col-start-1 row-start-2"
/>
<UiDateInput
id="shipment-date"
v-model="form.shipmentDate"
label="Date du jour"
wrapper-class="col-start-1 row-start-3"
/>
<div class="col-start-1 row-start-4">
<label class="font-bold uppercase text-xl mb-2 block">
Type d'expédition
</label>
<div class="grid grid-cols-2 gap-x-8">
<div
v-for="type in bovineShipment"
:key="type.id"
class="mt-8 flex flex-row gap-6"
>
<UiNumberInput
:label="type.label"
v-model="bovineQuantities[String(type.id)]"
:placeholder="0"
:min="0"
:max="10"
/>
</div>
</div>
</div>
<UiSelect
id="reception-driver"
v-model="form.customerId"
label="Client"
:options="customers.map((customer) => ({
value: String(customer.id),
label: customer.label
}))"
:loading="isLoadingCustomers"
wrapper-class="col-start-1 row-start-5"
/>
<UiSelect
id="reception-driver"
v-model="form.addressId"
:options="customerAddresses.map((address) => ({
value: String(address.id),
label: address.fullAddress
}))"
:disabled="isLoadingCustomers || customerAddresses.length === 0"
label="Adresse"
wrapper-class="col-start-2 row-start-1"
/>
<UiSelect
id="reception-driver"
v-model="form.truckId"
label="Camion"
:options="trucks.map((truck) => ({
value: String(truck.id),
label: truck.name
}))"
:loading="isLoadingTrucks"
wrapper-class="col-start-2 row-start-2"
/>
<UiSelect
id="reception-driver"
v-model="form.carrierId"
label="Transporteur"
:options="carriers.map((carrier) => ({
value: String(carrier.id),
label: carrier.name
}))"
wrapper-class="col-start-2 row-start-3"
/>
<UiSelect
id="reception-driver"
v-model="form.driverId"
label="Nom du chauffeur si LIOT"
:options="filteredDrivers.map((driver) => ({
value: String(driver.id),
label: driver.name
}))"
:loading="isLoadingDrivers"
wrapper-class="col-start-2 row-start-4"
/>
<div v-if="!isLiotCarrier" class="col-start-2 row-start-5">
<UiLicensePlateInput
v-model="form.licencePlate"
v-model:allowAny="allowAnyLicensePlate"
/>
</div>
<!-- Immatriculation (LIOT) -->
<UiSelect
v-if="isLiotCarrier"
id="reception-vehicle"
v-model="form.vehicleId"
label="Immatriculation"
:options="filteredVehicles.map((vehicle) => ({
value: String(vehicle.id),
label: vehicle.plate
}))"
:loading="isLoadingVehicles"
:disabled="isLoadingVehicles || filteredVehicles.length === 0"
wrapper-class="col-start-2 row-start-5"
/>
</div>
<div class="flex justify-center">
<button
type="submit"
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] justify-self-end"
>Valider
</button>
</div>
</form>
</template>
<script setup lang="ts">
import type {UserData} from '~/services/dto/user-data'
import type {CustomerData} from '~/services/dto/customer-data'
import type {TruckData} from '~/services/dto/truck-data'
import type {CarrierData} from '~/services/dto/carrier-data'
import type {DriverData} from '~/services/dto/driver-data'
import type {VehicleData} from '~/services/dto/vehicle-data'
import {getUsers} from '~/services/auth'
import {getCustomerList} from '~/services/customer'
import {getTruckList} from '~/services/truck'
import {getCarrierList} from '~/services/carrier'
import {getVehicleList} from '~/services/vehicle'
import {getDriverList} from '~/services/driver'
import type {ShipmentFormData} from '~/services/dto/shipment-data'
import {SUPLLIER_CODE} from "~/utils/constants"
import {useAuthStore} from '~/stores/auth'
import {useShipmentStore} from '~/stores/shipment'
import {ref} from "vue";
import type {ShipmentTypeData} from "~/services/dto/shipment-type-data";
import {getShipmentTypeList} from "~/services/shipment-type";
import {
createShipmentBovine,
deleteShipmentBovine,
getBovinShipmentList,
updateShipmentBovine
} from "~/services/bovin-shipment";
const users = ref<UserData[]>([])
const customers = ref<CustomerData[]>([])
const trucks = ref<TruckData[]>([])
const carriers = ref<CarrierData[]>([])
const drivers = ref<DriverData[]>([])
const vehicles = ref<VehicleData[]>([])
const isLoadingUsers = ref(false)
const isLoadingCustomers = ref(false)
const isLoadingTrucks = ref(false)
const isLoadingCarriers = ref(false)
const isHydrating = ref(false)
const isLoadingVehicles = ref(false)
const allowAnyLicensePlate = ref(false)
const isLoadingDrivers = ref(false)
const authStore = useAuthStore()
const shipmentStore = useShipmentStore()
const router = useRouter()
const bovineQuantities = ref<Record<string, number | null>>({})
const bovineShipment = ref<ShipmentTypeData[]>([])
// Transporteur sélectionné dans le formulaire
const selectedCarrier = computed(() =>
carriers.value.find((carrier) => String(carrier.id) === form.carrierId) ?? null
)
const isLiotCarrier = computed(() => selectedCarrier.value?.code === SUPLLIER_CODE.LIOT)
const form = reactive<ShipmentFormData>({
userId: '',
shipmentDate: new Date().toISOString().slice(0, 10),
customerId: '',
addressId: '',
truckId: '',
carrierId: '',
driverId: '',
vehicleId: '',
licencePlate: '',
})
const customerAddresses = computed(() => {
const customerId = Number(form.customerId)
if (!Number.isFinite(customerId)) {
return []
}
return customers.value.find((customer) => customer.id === customerId)?.addresses ?? []
})
const filteredDrivers = computed<DriverData[]>(() => {
if (!form.carrierId) {
return []
}
return drivers.value.filter((driver) => String(driver.carrier?.id) === form.carrierId)
})
const filteredVehicles = computed<VehicleData[]>(() => {
if (!form.carrierId) {
return []
}
return vehicles.value.filter(
(vehicle) =>
String(vehicle.carrier?.id) === form.carrierId &&
(!form.truckId || String(vehicle.truck?.id) === form.truckId)
)
})
const loadUsers = async () => {
isLoadingUsers.value = true
try {
users.value = await getUsers()
} finally {
isLoadingUsers.value = false
}
}
const loadShipmentType = async () => {
isLoadingUsers.value = true
try {
bovineShipment.value = await getShipmentTypeList()
} finally {
isLoadingUsers.value = false
}
}
const loadCustomers = async () => {
isLoadingCustomers.value = true
try {
customers.value = await getCustomerList()
} finally {
isLoadingCustomers.value = false
}
}
const loadTrucks = async () => {
isLoadingTrucks.value = true
try {
trucks.value = await getTruckList()
} finally {
isLoadingTrucks.value = false
}
}
const loadCarriers = async () => {
isLoadingCarriers.value = true
try {
carriers.value = await getCarrierList()
} finally {
isLoadingCarriers.value = false
}
}
const loadVehicles = async () => {
isLoadingVehicles.value = true
try {
vehicles.value = await getVehicleList()
} finally {
isLoadingVehicles.value = false
}
}
const loadDrivers = async () => {
isLoadingDrivers.value = true
try {
drivers.value = await getDriverList()
} finally {
isLoadingDrivers.value = false
}
}
// On met le user connecté par défaut dans le select
const setDefaultUser = () => {
if (form.userId) {
return
}
if (authStore.user?.id) {
form.userId = String(authStore.user.id)
}
}
onMounted(async () => {
await loadShipmentType()
await loadUsers()
await loadCustomers()
await loadTrucks()
await loadCarriers()
await loadVehicles()
await loadDrivers()
await authStore.ensureSession()
setDefaultUser()
})
watch(
() => shipmentStore.current,
(shipment) => {
if (!shipment || !shipment.bovinShipments) {
bovineQuantities.value = {}
return
}
const next: Record<string, number | null> = {}
for (const entry of shipment.bovinShipments) {
const typeId = entry.shipmentType?.id
if (!typeId) {
continue
}
next[String(typeId)] = entry.nbBovinSend ?? null
}
bovineQuantities.value = next
},
{immediate: true}
)
// Ajuste driver/vehicle quand le transporteur change (logique LIOT)
watch(
() => [form.customerId, customers.value],
() => {
if (!form.customerId) {
form.addressId = ''
return
}
if (!form.addressId && customerAddresses.value.length === 1) {
form.addressId = String(customerAddresses.value[0].id)
return
}
if (!form.addressId) {
return
}
const matches = customerAddresses.value.some(
(address) => String(address.id) === form.addressId
)
if (!matches) {
form.addressId = ''
}
},
{immediate: true}
)
// Valide/auto-sélectionne le véhicule selon camion + transporteur (LIOT)
watch(
() => form.carrierId,
() => {
if (isHydrating.value) {
return
}
if (!form.carrierId) {
form.driverId = ''
form.vehicleId = ''
return
}
if (!isLiotCarrier.value) {
form.driverId = ''
form.vehicleId = ''
return
}
if (filteredDrivers.value.length === 1) {
form.driverId = String(filteredDrivers.value[0].id)
}
if (filteredVehicles.value.length === 1) {
form.vehicleId = String(filteredVehicles.value[0].id)
}
},
{immediate: true}
)
// Récupère la plaque depuis le véhicule choisi (LIOT)
watch(
() => [form.truckId, form.carrierId, vehicles.value],
() => {
if (!isLiotCarrier.value) {
return
}
if (filteredVehicles.value.length === 1) {
form.vehicleId = String(filteredVehicles.value[0].id)
return
}
if (!form.vehicleId) {
return
}
const matches = filteredVehicles.value.some(
(vehicle) => String(vehicle.id) === form.vehicleId
)
if (!matches) {
form.vehicleId = ''
}
},
{immediate: true}
)
// Auto-renseigne le véhicule si la plaque correspond (LIOT)
watch(
() => [form.vehicleId, form.carrierId, vehicles.value],
() => {
if (!isLiotCarrier.value) {
return
}
if (isHydrating.value) {
return
}
const selected = filteredVehicles.value.find(
(vehicle) => String(vehicle.id) === form.vehicleId
)
if (selected) {
form.licencePlate = selected.plate
allowAnyLicensePlate.value = false
}
}
)
watch(
() => [form.licencePlate, form.carrierId, vehicles.value],
() => {
if (!isLiotCarrier.value || form.vehicleId) {
return
}
const match = filteredVehicles.value.find(
(vehicle) => vehicle.plate === form.licencePlate
)
if (match) {
form.vehicleId = String(match.id)
}
}
)
const buildDesiredBovinShipments = () => {
return bovineShipment.value
.map((type) => {
const raw = bovineQuantities.value[String(type.id)]
const quantity = raw === null || raw === undefined ? 0 : Number(raw)
return {
type,
quantity: Number.isFinite(quantity) ? Math.max(0, Math.trunc(quantity)) : 0
}
})
.filter((entry) => entry.quantity > 0)
}
const syncBovinShipments = async (shipmentId: number) => {
const shipmentIri = `/api/shipments/${shipmentId}`
const existing = await getBovinShipmentList(shipmentIri)
const desired = buildDesiredBovinShipments()
const desiredByTypeId = new Map<number, number>()
for (const entry of desired) {
desiredByTypeId.set(entry.type.id, entry.quantity)
}
for (const entry of existing) {
const rawType = entry.shipmentType
let typeId: number | null = null
if (rawType && typeof rawType === 'object' && 'id' in rawType) {
typeId = Number((rawType as { id: number }).id)
} else if (typeof rawType === 'string') {
const match = rawType.match(/\/shipment_types\/(\\d+)$/)
typeId = match ? Number(match[1]) : null
}
if (!typeId) {
continue
}
const desiredQuantity = desiredByTypeId.get(typeId)
if (!desiredQuantity) {
await deleteShipmentBovine(entry.id)
continue
}
if (entry.nbBovinSend !== desiredQuantity) {
await updateShipmentBovine(entry.id, {nbBovinSend: desiredQuantity})
}
desiredByTypeId.delete(typeId)
}
for (const [typeId, quantity] of desiredByTypeId.entries()) {
await createShipmentBovine({
shipment: shipmentIri,
shipmentType: `/api/shipment_types/${typeId}`,
nbBovinSend: quantity
})
}
}
// Valide le formulaire et crée/met à jour l'expédition
const validate = async () => {
const normalizedLicensePlate = form.licencePlate.trim()
const normalizedShipmentDate = form.shipmentDate.trim()
const normalizedCustomerId = form.customerId.trim()
const normalizedTruckId = form.truckId.trim()
const normalizedCarrierId = form.carrierId.trim()
const customerIri = normalizedCustomerId
? `/api/customers/${normalizedCustomerId}`
: null
const truckIri = normalizedTruckId
? `/api/trucks/${normalizedTruckId}`
: null
const carrierIri = normalizedCarrierId
? `/api/carriers/${normalizedCarrierId}`
: null
const payload = {
licencePlate: normalizedLicensePlate,
shipmentDate: normalizedShipmentDate,
customer: customerIri,
truck: truckIri,
carrier: carrierIri
}
if (!shipmentStore.current) {
const created = await shipmentStore.createShipment({
currentStep: 1,
...payload
})
if (created) {
await syncBovinShipments(created.id)
await router.push(`/shipment/${created.id}`)
}
return
}
const nextStep = shipmentStore.current.currentStep + 1
await shipmentStore.updateShipment(shipmentStore.current.id, {
currentStep: nextStep,
...payload
})
await syncBovinShipments(shipmentStore.current.id)
}
</script>

View File

@@ -3,7 +3,7 @@
<label
v-if="label"
:for="id"
class="text-xl text-bold flex items-center gap-2"
class="text-xl text-bold flex items-center"
:class="labelClass"
>
<span
@@ -11,7 +11,8 @@
{{ label }}
</span>
<span
v-if="code" class="text-neutral-600">
v-if="code"
class="text-neutral-600">
({{ code }})
</span>
</label>
@@ -24,7 +25,7 @@
:step="step"
:disabled="disabled"
v-bind="attrs"
class="border-b border-black text-xl bg-transparent w-48"
class="border-b border-black text-xl bg-transparent w-12"
:class="[
isEmpty ? 'text-neutral-400' : 'text-black',
disabled ? 'cursor-not-allowed' : 'cursor-text',

View File

@@ -2,7 +2,8 @@ export enum StepLabel {
Reception = 'Réception',
GrossWeighing = 'Pesée à plein',
Selection = 'Sélection réceptionnées',
TareWeighing = 'Pesée à vide'
TareWeighing = 'Pesée à vide',
Shipment = 'Expédition',
}
export const RECEPTION_STEP_LABELS = [
@@ -11,3 +12,9 @@ export const RECEPTION_STEP_LABELS = [
StepLabel.Selection,
StepLabel.TareWeighing
]
export const SHIPMENT_STEP_LABELS = [
StepLabel.Shipment,
StepLabel.TareWeighing,
StepLabel.GrossWeighing,
]

View File

@@ -14,6 +14,13 @@
"update": "Impossible de mettre à jour la réception.",
"weigh": "Impossible de récupérer la pesée."
},
"shipment": {
"list": "Impossible de récupérer la liste des éxpeditions.",
"fetch": "Impossible de récupérer l'éxpeditions.",
"create": "Impossible de créer l'éxpeditions.",
"update": "Impossible de mettre à jour l'éxpeditions.",
"weigh": "Impossible de récupérer la pesée."
},
"receptionType": {
"list": "Impossible de récupérer la liste des types de réception."
},
@@ -50,7 +57,6 @@
"fetch": "Impossible de récupérer les données du transporteur",
"update": "Impossible de mettre à jour le transporteur",
"create": "Impossible de créer le transporteur"
},
"driver": {
"list": "Impossible de récupérer la liste des chauffeurs."
@@ -70,6 +76,9 @@
"reception": {
"update": "Réception mise à jour avec succès."
},
"shipment": {
"update": "Éxpedition mise à jour avec succès."
},
"auth": {
"update": "Utilisateur mis à jour avec succès.",
"create": "Utilisateur créé avec succès.",

View File

@@ -3,7 +3,7 @@
<template>
<div class="flex flex-wrap justify-center mt-8 gap-8 mb-8 md:mb-0">
<card-link label="NOUVELLE RÉCEPTION" link="/reception" iconName="mdi:truck-outline" />
<card-link label="NOUVELLE EXPÉDITION" link="/" iconName="mdi:truck-fast-outline" />
<card-link label="NOUVELLE EXPÉDITION" link="/shipment" iconName="mdi:truck-fast-outline" />
<card-link label="PLAN DE SITE" link="/" iconName="mdi:warehouse" />
<card-link label="RÉCEPTIONS EN ATTENTE" link="/reception/waiting-reception" iconName="mdi:truck-remove-outline" />
<card-link label="EXPÉDITIONS EN ATTENTE" link="/" iconName="mdi:truck-cargo-container" />

View File

@@ -0,0 +1,88 @@
<template>
<div>
<div class="flex justify-between h-[52px] mb-[80px]">
<div class="flex flex-1 mr-16">
<UiStepper
:labels="SHIPMENT_STEP_LABELS"
:current-step="storeShipment?.currentStep ?? 0"
@select="handleStepSelect"
/>
</div>
<button
type="button"
class="flex flex-col justify-center uppercase text-xl bg-black text-white h-[50px] w-[272px] text-center"
@click="saveAndHold"
>Mettre en attente
</button>
</div>
<ShipmentForm v-if="!storeShipment || storeShipment.currentStep === 0"/>
<button
v-if="storeShipment?.currentStep === 1">
TEST ETAPE 2
</button>
</div>
</template>
<script setup lang="ts">
import {SHIPMENT_STEP_LABELS} from "~/constants/steps";
import {storeToRefs} from "pinia";
import {useShipmentStore} from "~/stores/shipment";
const shipmentStore = useShipmentStore()
const {current: storeShipment} = storeToRefs(shipmentStore)
const route = useRoute()
const router = useRouter()
const resolveShipmentId = (param: unknown) => {
const idStr = Array.isArray(param) ? param[0] : param
if (!idStr) {
return null
}
const id = Number(idStr)
return Number.isFinite(id) ? id : null
}
watch (
() => route.params.id,
async (param) => {
const id = resolveShipmentId(param)
if (id === null) {
shipmentStore.clearCurrent()
return
}
await shipmentStore.loadShipment(id)
},
{immediate: true}
)
const saveAndHold = async () => {
if (!shipmentStore.current) {
await router.push('/')
return
}
await shipmentStore.updateShipment(shipmentStore.current.id, {
currentStep: shipmentStore.current.currentStep,
licencePlate: shipmentStore.current.licencePlate,
shipmentDate: shipmentStore.current.shipmentDate
})
await router.push('/')
}
const handleStepSelect = async (step: number) => {
if (!shipmentStore.current) {
return
}
if (step === shipmentStore.current.currentStep) {
return
}
await shipmentStore.updateShipment(shipmentStore.current.id, {
currentStep: step
})
await shipmentStore.loadShipment(shipmentStore.current.id)
}
</script>

View File

@@ -0,0 +1,58 @@
import { useApi } from '~/composables/useApi'
import type { BovinShipmentData } from '~/services/dto/bovin-shipment-data'
export type BovinShipmentListResponse =
| BovinShipmentData[]
| { 'hydra:member'?: BovinShipmentData[] }
export type ShipmentBovinePayload = {
nbBovinSend: number
shipment: string
shipmentType: string
}
export async function getBovinShipmentList(
shipmentIri: string
): Promise<BovinShipmentData[]> {
const api = useApi()
const response = await api.get<BovinShipmentListResponse>(
'bovin_shipments',
{ shipment: shipmentIri },
{
toastErrorKey: 'errors.shipmentBovine.list'
}
)
if (Array.isArray(response)) {
return response
}
if (response && typeof response === 'object' && Array.isArray(response['hydra:member'])) {
return response['hydra:member']
}
return []
}
export async function createShipmentBovine(
payload: ShipmentBovinePayload
): Promise<BovinShipmentData> {
const api = useApi()
return api.post<BovinShipmentData>('bovin_shipments', payload, {
toastErrorKey: 'errors.shipmentBovine.create'
})
}
export async function deleteShipmentBovine(id: number): Promise<void> {
const api = useApi()
await api.delete<void>(`bovin_shipments/${id}`, {}, {
toastErrorKey: 'errors.shipmentBovine.delete'
})
}
export async function updateShipmentBovine(
id: number,
payload: Partial<ShipmentBovinePayload>
): Promise<BovinShipmentData> {
const api = useApi()
return api.patch<BovinShipmentData>(`bovin_shipments/${id}`, payload, {
toastErrorKey: 'errors.shipmentBovine.update'
})
}

View File

@@ -0,0 +1,23 @@
import { useApi } from '~/composables/useApi'
import type { CustomerData } from '~/services/dto/customer-data'
export type CustomerListResponse =
| CustomerData[]
| { 'hydra:member'?: CustomerData[] }
export async function getCustomerList(): Promise<CustomerData[]> {
const api = useApi()
const response = await api.get<CustomerListResponse>('customers', {}, {
toastErrorKey: 'errors.customer.list'
})
if (Array.isArray(response)) {
return response
}
if (response && typeof response === 'object' && Array.isArray(response['hydra:member'])) {
return response['hydra:member']
}
return []
}

View File

@@ -0,0 +1,8 @@
import type {ShipmentTypeData} from "~/services/dto/shipment-type-data";
export interface BovinShipmentData {
id: number
nbBovinSend: number | null
shipment?: string | null
shipmentType?: ShipmentTypeData | null
}

View File

@@ -0,0 +1,8 @@
import type { AddressData } from "~/services/dto/address-data"
export interface CustomerData {
id: number
label: string
code?: string | null
addresses?: AddressData[] | null
}

View File

@@ -0,0 +1,51 @@
import type {CarrierData} from '~/services/dto/carrier-data'
import type {TruckData} from '~/services/dto/truck-data'
import type {CustomerData} from '~/services/dto/customer-data'
export interface ShipmentTypeData {
id: number
label: string
code: string
}
export interface BovinShipmentData {
id?: number
shipmentType?: ShipmentTypeData | string | null
nbBovinSend: number | null
}
export type ShipmentData = {
id: number
identificationNumber?: string | null
licencePlate: string | null
shipmentDate: string
currentStep: number
isValid: boolean
carrier?: CarrierData | null
truck?: TruckData | null
customer?: CustomerData | null
bovinShipments?: BovinShipmentData[] | null
}
export type ShipmentFormData = {
userId: string,
shipmentDate: string,
customerId: string,
addressId: string,
truckId: string,
carrierId: string,
driverId: string,
vehicleId: string,
licencePlate: string,
}
export type ShipmentPayload = {
licencePlate?: string | null
shipmentDate?: string
currentStep?: number
isValid?: boolean
carrier?: string | null
truck?: string | null
customer?: string | null
bovinShipments?: string[] | null
}

View File

@@ -0,0 +1,5 @@
export interface ShipmentTypeData {
id: number
label: string
code: string
}

View File

@@ -0,0 +1,24 @@
import { useApi } from '~/composables/useApi'
import type {ShipmentTypeData} from "~/services/dto/shipment-type-data";
export type ShipmentTypeListResponse =
| ShipmentTypeData[]
| { 'hydra:member'?: ShipmentTypeData[] }
export async function getShipmentTypeList(): Promise<ShipmentTypeData[]> {
const api = useApi()
const response = await api.get<ShipmentTypeListResponse>('shipment_types', {}, {
toastErrorKey: 'errors.shipment_type.list'
})
if (Array.isArray(response)) {
return response
}
if (response && typeof response === 'object' && Array.isArray(response['hydra:member'])) {
return response['hydra:member']
}
return []
}

View File

@@ -0,0 +1,40 @@
import {useApi} from '~/composables/useApi'
import type {ShipmentData, ShipmentPayload} from '~/services/dto/shipment-data'
import type {WeightData} from '~/services/dto/weight-data'
export async function getShipmentList(isValid: boolean|null = null) {
const api = useApi()
const query = isValid !== null ? { isValid: isValid} : {}
return api.get<ShipmentData[]>('shipments', query, {
toastErrorKey: 'errors.shipment.list'
})
}
export async function getShipment(id: number) {
const api = useApi()
return api.get<ShipmentData>(`shipments/${id}`, {}, {
toastErrorKey: 'errors.shipment.fetch'
})
}
export async function createShipment(payload: ShipmentPayload = {}) {
const api = useApi()
return api.post<ShipmentData>('shipments', payload, {
toastErrorKey: 'errors.shipment.create'
})
}
export async function updateShipment(id: number, payload: ShipmentPayload) {
const api = useApi()
return api.patch<ShipmentData>(`shipments/${id}`, payload, {
toastErrorKey: 'errors.shipment.update',
toastSuccessKey: 'success.shipment.update'
})
}
export async function getWeight(): Promise<WeightData> {
const api = useApi()
return api.get<WeightData>('shipments/weigh', {}, {
toastErrorKey: 'errors.shipment.weigh'
})
}

View File

@@ -0,0 +1,58 @@
import { defineStore } from 'pinia'
import type {ShipmentData, ShipmentPayload} from "~/services/dto/shipment-data";
import {createShipment, getShipment, updateShipment} from "~/services/shipment";
const isShipmentData = (value: unknown): value is ShipmentData => {
return Boolean(value && typeof value === 'object' && 'id' in value)
}
export const useShipmentStore = defineStore('shipment', {
state: () => ({
current: null as ShipmentData | null,
isLoading: false
}),
actions: {
setCurrent(shipment: ShipmentData | null) {
this.current = shipment
},
clearCurrent() {
this.current = null
},
async loadShipment(id: number) {
this.isLoading = true
const result = await getShipment(id).finally(() => {
this.isLoading = false
})
if (!isShipmentData(result)) {
this.current = null
return null
}
this.current = result
return result
},
async createShipment(payload: ShipmentPayload = {}) {
this.isLoading = true
const result = await createShipment(payload).finally(() => {
this.isLoading = false
})
if (!isShipmentData(result)) {
return null
}
this.current = result
return result
},
async updateShipment(id: number, payload: ShipmentPayload) {
this.isLoading = true
const result = await updateShipment(id, payload).finally(() => {
this.isLoading = false
})
if (!isShipmentData(result)) {
return null
}
this.current = result
return result
}
}
})