feat(persoenliche-ausruestung): add quantity field and article-grouped replacement flow in order dialog
This commit is contained in:
@@ -573,6 +573,7 @@ class AusruestungsanfrageController {
|
|||||||
groesse?: string;
|
groesse?: string;
|
||||||
kategorie?: string;
|
kategorie?: string;
|
||||||
eigenschaften?: Array<{ eigenschaft_id?: number; name: string; wert: string }>;
|
eigenschaften?: Array<{ eigenschaft_id?: number; name: string; wert: string }>;
|
||||||
|
replacedItemIds?: string[];
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ const CreateSchema = z.object({
|
|||||||
anschaffung_datum: isoDate.optional(),
|
anschaffung_datum: isoDate.optional(),
|
||||||
zustand: ZustandEnum.optional(),
|
zustand: ZustandEnum.optional(),
|
||||||
notizen: z.string().max(2000).optional(),
|
notizen: z.string().max(2000).optional(),
|
||||||
|
menge: z.number().int().min(1).default(1).optional(),
|
||||||
eigenschaften: z.array(EigenschaftInput).optional(),
|
eigenschaften: z.array(EigenschaftInput).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -49,6 +50,7 @@ const UpdateSchema = z.object({
|
|||||||
anschaffung_datum: isoDate.nullable().optional(),
|
anschaffung_datum: isoDate.nullable().optional(),
|
||||||
zustand: ZustandEnum.optional(),
|
zustand: ZustandEnum.optional(),
|
||||||
notizen: z.string().max(2000).nullable().optional(),
|
notizen: z.string().max(2000).nullable().optional(),
|
||||||
|
menge: z.number().int().min(1).nullable().optional(),
|
||||||
eigenschaften: z.array(EigenschaftInput).nullable().optional(),
|
eigenschaften: z.array(EigenschaftInput).nullable().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE persoenliche_ausruestung ADD COLUMN IF NOT EXISTS menge INT NOT NULL DEFAULT 1;
|
||||||
@@ -1007,6 +1007,7 @@ interface AssignmentInput {
|
|||||||
groesse?: string;
|
groesse?: string;
|
||||||
kategorie?: string;
|
kategorie?: string;
|
||||||
eigenschaften?: { eigenschaft_id?: number; name: string; wert: string }[];
|
eigenschaften?: { eigenschaft_id?: number; name: string; wert: string }[];
|
||||||
|
replacedItemIds?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function assignDeliveredItems(
|
async function assignDeliveredItems(
|
||||||
@@ -1033,7 +1034,7 @@ async function assignDeliveredItems(
|
|||||||
for (const a of assignments) {
|
for (const a of assignments) {
|
||||||
// Load position details
|
// Load position details
|
||||||
const posResult = await client.query(
|
const posResult = await client.query(
|
||||||
'SELECT bezeichnung, artikel_id FROM ausruestung_anfrage_positionen WHERE id = $1 AND anfrage_id = $2',
|
'SELECT bezeichnung, artikel_id, menge FROM ausruestung_anfrage_positionen WHERE id = $1 AND anfrage_id = $2',
|
||||||
[a.positionId, anfrageId],
|
[a.positionId, anfrageId],
|
||||||
);
|
);
|
||||||
if (posResult.rows.length === 0) continue;
|
if (posResult.rows.length === 0) continue;
|
||||||
@@ -1088,8 +1089,8 @@ async function assignDeliveredItems(
|
|||||||
const insertResult = await client.query(
|
const insertResult = await client.query(
|
||||||
`INSERT INTO persoenliche_ausruestung (
|
`INSERT INTO persoenliche_ausruestung (
|
||||||
bezeichnung, kategorie, groesse, user_id, benutzer_name,
|
bezeichnung, kategorie, groesse, user_id, benutzer_name,
|
||||||
anfrage_id, anfrage_position_id, artikel_id, erstellt_von
|
anfrage_id, anfrage_position_id, artikel_id, menge, erstellt_von
|
||||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||||
RETURNING id`,
|
RETURNING id`,
|
||||||
[
|
[
|
||||||
pos.bezeichnung,
|
pos.bezeichnung,
|
||||||
@@ -1100,6 +1101,7 @@ async function assignDeliveredItems(
|
|||||||
anfrageId,
|
anfrageId,
|
||||||
a.positionId,
|
a.positionId,
|
||||||
pos.artikel_id ?? null,
|
pos.artikel_id ?? null,
|
||||||
|
pos.menge ?? 1,
|
||||||
requestingUserId,
|
requestingUserId,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -1146,6 +1148,15 @@ async function assignDeliveredItems(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (a.replacedItemIds && a.replacedItemIds.length > 0) {
|
||||||
|
await client.query(
|
||||||
|
`UPDATE persoenliche_ausruestung
|
||||||
|
SET geloescht_am = NOW()
|
||||||
|
WHERE id = ANY($1::uuid[]) AND geloescht_am IS NULL`,
|
||||||
|
[a.replacedItemIds],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
assigned++;
|
assigned++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ interface CreatePersonalEquipmentData {
|
|||||||
anschaffung_datum?: string;
|
anschaffung_datum?: string;
|
||||||
zustand?: string;
|
zustand?: string;
|
||||||
notizen?: string;
|
notizen?: string;
|
||||||
|
menge?: number;
|
||||||
eigenschaften?: { eigenschaft_id?: number | null; name: string; wert: string }[];
|
eigenschaften?: { eigenschaft_id?: number | null; name: string; wert: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ interface UpdatePersonalEquipmentData {
|
|||||||
anschaffung_datum?: string | null;
|
anschaffung_datum?: string | null;
|
||||||
zustand?: string;
|
zustand?: string;
|
||||||
notizen?: string | null;
|
notizen?: string | null;
|
||||||
|
menge?: number | null;
|
||||||
eigenschaften?: { eigenschaft_id?: number | null; name: string; wert: string }[] | null;
|
eigenschaften?: { eigenschaft_id?: number | null; name: string; wert: string }[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,8 +156,8 @@ async function create(data: CreatePersonalEquipmentData, requestingUserId: strin
|
|||||||
`INSERT INTO persoenliche_ausruestung (
|
`INSERT INTO persoenliche_ausruestung (
|
||||||
bezeichnung, kategorie, artikel_id, user_id, benutzer_name,
|
bezeichnung, kategorie, artikel_id, user_id, benutzer_name,
|
||||||
groesse, seriennummer, inventarnummer, anschaffung_datum,
|
groesse, seriennummer, inventarnummer, anschaffung_datum,
|
||||||
zustand, notizen, erstellt_von
|
zustand, notizen, menge, erstellt_von
|
||||||
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12)
|
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13)
|
||||||
RETURNING *`,
|
RETURNING *`,
|
||||||
[
|
[
|
||||||
data.bezeichnung,
|
data.bezeichnung,
|
||||||
@@ -169,6 +171,7 @@ async function create(data: CreatePersonalEquipmentData, requestingUserId: strin
|
|||||||
data.anschaffung_datum ?? null,
|
data.anschaffung_datum ?? null,
|
||||||
data.zustand ?? 'gut',
|
data.zustand ?? 'gut',
|
||||||
data.notizen ?? null,
|
data.notizen ?? null,
|
||||||
|
data.menge ?? 1,
|
||||||
requestingUserId,
|
requestingUserId,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -214,6 +217,7 @@ async function update(id: string, data: UpdatePersonalEquipmentData) {
|
|||||||
if (data.anschaffung_datum !== undefined) addField('anschaffung_datum', data.anschaffung_datum);
|
if (data.anschaffung_datum !== undefined) addField('anschaffung_datum', data.anschaffung_datum);
|
||||||
if (data.zustand !== undefined) addField('zustand', data.zustand);
|
if (data.zustand !== undefined) addField('zustand', data.zustand);
|
||||||
if (data.notizen !== undefined) addField('notizen', data.notizen);
|
if (data.notizen !== undefined) addField('notizen', data.notizen);
|
||||||
|
if (data.menge !== undefined) addField('menge', data.menge ?? 1);
|
||||||
|
|
||||||
if (fields.length === 0 && data.eigenschaften === undefined) {
|
if (fields.length === 0 && data.eigenschaften === undefined) {
|
||||||
throw new Error('No fields to update');
|
throw new Error('No fields to update');
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { List, ListItem, ListItemText, Chip, Typography } from '@mui/material';
|
import { Box, List, ListItem, ListItemText, Chip, Typography } from '@mui/material';
|
||||||
import CheckroomIcon from '@mui/icons-material/Checkroom';
|
import CheckroomIcon from '@mui/icons-material/Checkroom';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
@@ -51,11 +51,17 @@ function PersoenlicheAusruestungWidget() {
|
|||||||
{displayItems.map((item) => (
|
{displayItems.map((item) => (
|
||||||
<ListItem key={item.id} disablePadding sx={{ py: 0.5 }}>
|
<ListItem key={item.id} disablePadding sx={{ py: 0.5 }}>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={item.bezeichnung}
|
primary={
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
||||||
|
<Typography variant="body2" noWrap>{item.bezeichnung}</Typography>
|
||||||
|
{item.menge > 1 && (
|
||||||
|
<Chip label={`${item.menge}x`} size="small" sx={{ ml: 0.5, height: 18, fontSize: '0.65rem' }} />
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
secondary={item.artikel_kategorie_parent_name
|
secondary={item.artikel_kategorie_parent_name
|
||||||
? `${item.artikel_kategorie_parent_name} > ${item.artikel_kategorie_name}`
|
? `${item.artikel_kategorie_parent_name} > ${item.artikel_kategorie_name}`
|
||||||
: item.artikel_kategorie_name ?? item.kategorie ?? undefined}
|
: item.artikel_kategorie_name ?? item.kategorie ?? undefined}
|
||||||
primaryTypographyProps={{ variant: 'body2', noWrap: true }}
|
|
||||||
secondaryTypographyProps={{ variant: 'caption' }}
|
secondaryTypographyProps={{ variant: 'caption' }}
|
||||||
/>
|
/>
|
||||||
<Chip
|
<Chip
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useCallback, useRef, useEffect } from 'react';
|
import { useState, useCallback, useRef, useEffect, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
Box, Typography, Paper, Button, TextField, IconButton,
|
Box, Typography, Paper, Button, TextField, IconButton,
|
||||||
Autocomplete, Divider, MenuItem, Checkbox, Chip, FormControlLabel, Switch,
|
Autocomplete, Divider, MenuItem, Checkbox, Chip, FormControlLabel, Switch,
|
||||||
@@ -11,7 +11,6 @@ import { useNotification } from '../contexts/NotificationContext';
|
|||||||
import { usePermissionContext } from '../contexts/PermissionContext';
|
import { usePermissionContext } from '../contexts/PermissionContext';
|
||||||
import { ausruestungsanfrageApi } from '../services/ausruestungsanfrage';
|
import { ausruestungsanfrageApi } from '../services/ausruestungsanfrage';
|
||||||
import { personalEquipmentApi } from '../services/personalEquipment';
|
import { personalEquipmentApi } from '../services/personalEquipment';
|
||||||
import type { ZustandOption } from '../types/personalEquipment.types';
|
|
||||||
import type {
|
import type {
|
||||||
AusruestungAnfrageFormItem,
|
AusruestungAnfrageFormItem,
|
||||||
AusruestungEigenschaft,
|
AusruestungEigenschaft,
|
||||||
@@ -80,7 +79,7 @@ export default function AusruestungsanfrageNeu() {
|
|||||||
const [fuerBenutzer, setFuerBenutzer] = useState<{ id: string; name: string } | string | null>(null);
|
const [fuerBenutzer, setFuerBenutzer] = useState<{ id: string; name: string } | string | null>(null);
|
||||||
const [catalogItems, setCatalogItems] = useState<AusruestungAnfrageFormItem[]>([{ bezeichnung: '', menge: 1 }]);
|
const [catalogItems, setCatalogItems] = useState<AusruestungAnfrageFormItem[]>([{ bezeichnung: '', menge: 1 }]);
|
||||||
const [freeItems, setFreeItems] = useState<{ bezeichnung: string; menge: number }[]>([]);
|
const [freeItems, setFreeItems] = useState<{ bezeichnung: string; menge: number }[]>([]);
|
||||||
const [assignedSelections, setAssignedSelections] = useState<Record<string, string>>({});
|
const [replacementSelections, setReplacementSelections] = useState<Record<number, number>>({});
|
||||||
const [catalogIstErsatz, setCatalogIstErsatz] = useState<Record<number, boolean>>({});
|
const [catalogIstErsatz, setCatalogIstErsatz] = useState<Record<number, boolean>>({});
|
||||||
const [freeIstErsatz, setFreeIstErsatz] = useState<Record<number, boolean>>({});
|
const [freeIstErsatz, setFreeIstErsatz] = useState<Record<number, boolean>>({});
|
||||||
|
|
||||||
@@ -96,15 +95,6 @@ export default function AusruestungsanfrageNeu() {
|
|||||||
queryFn: () => ausruestungsanfrageApi.getItems({ aktiv: true }),
|
queryFn: () => ausruestungsanfrageApi.getItems({ aktiv: true }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: zustandOptions = [] } = useQuery<ZustandOption[]>({
|
|
||||||
queryKey: ['persoenliche-ausruestung', 'zustand-options'],
|
|
||||||
queryFn: () => personalEquipmentApi.getZustandOptions(),
|
|
||||||
staleTime: 5 * 60 * 1000,
|
|
||||||
});
|
|
||||||
|
|
||||||
const getZustandLabel = (key: string) => zustandOptions.find(o => o.key === key)?.label ?? key;
|
|
||||||
const getZustandColor = (key: string) => zustandOptions.find(o => o.key === key)?.color ?? 'default';
|
|
||||||
|
|
||||||
const { data: orderUsers = [] } = useQuery({
|
const { data: orderUsers = [] } = useQuery({
|
||||||
queryKey: ['ausruestungsanfrage', 'orderUsers'],
|
queryKey: ['ausruestungsanfrage', 'orderUsers'],
|
||||||
queryFn: () => ausruestungsanfrageApi.getOrderUsers(),
|
queryFn: () => ausruestungsanfrageApi.getOrderUsers(),
|
||||||
@@ -121,19 +111,48 @@ export default function AusruestungsanfrageNeu() {
|
|||||||
staleTime: 2 * 60 * 1000,
|
staleTime: 2 * 60 * 1000,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clear assigned selections when switching user
|
// Clear replacement selections when switching user
|
||||||
const prevTargetUserRef = useRef(targetUserId);
|
const prevTargetUserRef = useRef(targetUserId);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (prevTargetUserRef.current !== targetUserId) {
|
if (prevTargetUserRef.current !== targetUserId) {
|
||||||
setAssignedSelections({});
|
setReplacementSelections({});
|
||||||
prevTargetUserRef.current = targetUserId;
|
prevTargetUserRef.current = targetUserId;
|
||||||
}
|
}
|
||||||
}, [targetUserId]);
|
}, [targetUserId]);
|
||||||
|
|
||||||
|
// Group personal items by artikel_id (skip items without artikel_id)
|
||||||
|
const personalItemGroups = useMemo(() => {
|
||||||
|
const groups = new Map<number, {
|
||||||
|
bezeichnung: string;
|
||||||
|
totalMenge: number;
|
||||||
|
eigenschaften: { name: string; wert: string }[];
|
||||||
|
}>();
|
||||||
|
for (const item of myPersonalItems) {
|
||||||
|
if (!item.artikel_id) continue;
|
||||||
|
const itemMenge = item.menge;
|
||||||
|
const existing = groups.get(item.artikel_id);
|
||||||
|
if (existing) {
|
||||||
|
existing.totalMenge += itemMenge;
|
||||||
|
for (const e of item.eigenschaften ?? []) {
|
||||||
|
if (!existing.eigenschaften.some(x => x.name === e.name && x.wert === e.wert)) {
|
||||||
|
existing.eigenschaften.push({ name: e.name, wert: e.wert });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
groups.set(item.artikel_id, {
|
||||||
|
bezeichnung: item.artikel_bezeichnung || item.bezeichnung,
|
||||||
|
totalMenge: itemMenge,
|
||||||
|
eigenschaften: (item.eigenschaften ?? []).map(e => ({ name: e.name, wert: e.wert })),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return groups;
|
||||||
|
}, [myPersonalItems]);
|
||||||
|
|
||||||
// ── Mutations ──
|
// ── Mutations ──
|
||||||
const createMut = useMutation({
|
const createMut = useMutation({
|
||||||
mutationFn: (args: { items: AusruestungAnfrageFormItem[]; notizen?: string; bezeichnung?: string; fuer_benutzer_id?: string; fuer_benutzer_name?: string; assignedItems?: { persoenlich_id: string; neuer_zustand: string }[] }) =>
|
mutationFn: (args: { items: AusruestungAnfrageFormItem[]; notizen?: string; bezeichnung?: string; fuer_benutzer_id?: string; fuer_benutzer_name?: string }) =>
|
||||||
ausruestungsanfrageApi.createRequest(args.items, args.notizen, args.bezeichnung, args.fuer_benutzer_id, args.fuer_benutzer_name, args.assignedItems),
|
ausruestungsanfrageApi.createRequest(args.items, args.notizen, args.bezeichnung, args.fuer_benutzer_id, args.fuer_benutzer_name),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['ausruestungsanfrage'] });
|
queryClient.invalidateQueries({ queryKey: ['ausruestungsanfrage'] });
|
||||||
showSuccess('Anfrage erstellt');
|
showSuccess('Anfrage erstellt');
|
||||||
@@ -171,7 +190,21 @@ export default function AusruestungsanfrageNeu() {
|
|||||||
.filter(i => i.bezeichnung.trim())
|
.filter(i => i.bezeichnung.trim())
|
||||||
.map((i, idx) => ({ bezeichnung: i.bezeichnung, menge: i.menge, ist_ersatz: freeIstErsatz[idx] || false }));
|
.map((i, idx) => ({ bezeichnung: i.bezeichnung, menge: i.menge, ist_ersatz: freeIstErsatz[idx] || false }));
|
||||||
|
|
||||||
const allItems = [...catalogValidItems, ...freeValidItems];
|
// Replacement items (from grouped personal items)
|
||||||
|
const replacementItems: AusruestungAnfrageFormItem[] = Object.entries(replacementSelections)
|
||||||
|
.filter(([, menge]) => menge > 0)
|
||||||
|
.map(([artikelIdStr, menge]) => {
|
||||||
|
const artikelId = Number(artikelIdStr);
|
||||||
|
const group = personalItemGroups.get(artikelId);
|
||||||
|
return {
|
||||||
|
artikel_id: artikelId,
|
||||||
|
bezeichnung: group?.bezeichnung ?? '',
|
||||||
|
menge,
|
||||||
|
ist_ersatz: true,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const allItems = [...catalogValidItems, ...freeValidItems, ...replacementItems];
|
||||||
if (allItems.length === 0) return;
|
if (allItems.length === 0) return;
|
||||||
|
|
||||||
// Check required eigenschaften for catalog items
|
// Check required eigenschaften for catalog items
|
||||||
@@ -194,13 +227,10 @@ export default function AusruestungsanfrageNeu() {
|
|||||||
bezeichnung: bezeichnung || undefined,
|
bezeichnung: bezeichnung || undefined,
|
||||||
fuer_benutzer_id: typeof fuerBenutzer === 'object' && fuerBenutzer ? fuerBenutzer.id : undefined,
|
fuer_benutzer_id: typeof fuerBenutzer === 'object' && fuerBenutzer ? fuerBenutzer.id : undefined,
|
||||||
fuer_benutzer_name: typeof fuerBenutzer === 'string' ? fuerBenutzer : undefined,
|
fuer_benutzer_name: typeof fuerBenutzer === 'string' ? fuerBenutzer : undefined,
|
||||||
assignedItems: Object.entries(assignedSelections)
|
|
||||||
.filter(([, v]) => !!v)
|
|
||||||
.map(([id, neuer_zustand]) => ({ persoenlich_id: id, neuer_zustand })),
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasValidItems = catalogItems.some(i => !!i.artikel_id) || freeItems.some(i => i.bezeichnung.trim()) || Object.keys(assignedSelections).length > 0;
|
const hasValidItems = catalogItems.some(i => !!i.artikel_id) || freeItems.some(i => i.bezeichnung.trim()) || Object.values(replacementSelections).some(m => m > 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
@@ -347,55 +377,58 @@ export default function AusruestungsanfrageNeu() {
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
<Typography variant="subtitle2">Zugewiesene Gegenstände</Typography>
|
<Typography variant="subtitle2">Zugewiesene Gegenstände (für Ersatz)</Typography>
|
||||||
{myPersonalItems.length === 0 ? (
|
{personalItemGroups.size === 0 ? (
|
||||||
<Typography variant="body2" color="text.secondary">Keine zugewiesenen Gegenstände vorhanden.</Typography>
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
{targetUserId || !canOrderForUser ? 'Keine zugewiesenen Gegenstände vorhanden.' : 'Wähle einen Benutzer, um zugewiesene Gegenstände zu sehen.'}
|
||||||
|
</Typography>
|
||||||
) : (
|
) : (
|
||||||
myPersonalItems.map((item) => (
|
Array.from(personalItemGroups.entries()).map(([artikelId, group]) => {
|
||||||
<Box key={item.id}>
|
const selectedMenge = replacementSelections[artikelId] ?? 0;
|
||||||
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center', py: 0.5 }}>
|
const checked = selectedMenge > 0;
|
||||||
<Checkbox
|
return (
|
||||||
size="small"
|
<Box key={artikelId} sx={{ display: 'flex', gap: 1, alignItems: 'center', flexWrap: 'wrap', py: 0.5 }}>
|
||||||
checked={!!assignedSelections[item.id]}
|
<Checkbox
|
||||||
onChange={(e) => {
|
|
||||||
if (e.target.checked) {
|
|
||||||
setAssignedSelections(prev => ({ ...prev, [item.id]: item.zustand }));
|
|
||||||
if (item.artikel_id) loadEigenschaften(item.artikel_id);
|
|
||||||
} else {
|
|
||||||
setAssignedSelections(prev => { const n = { ...prev }; delete n[item.id]; return n; });
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Typography variant="body2" sx={{ flex: 1 }}>{item.bezeichnung}</Typography>
|
|
||||||
{item.eigenschaften && item.eigenschaften.length > 0 && (
|
|
||||||
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap' }}>
|
|
||||||
{item.eigenschaften.map(e => (
|
|
||||||
<Chip key={e.id} label={`${e.name}: ${e.wert}`} size="small" variant="outlined" />
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
<Chip
|
|
||||||
label={getZustandLabel(item.zustand)}
|
|
||||||
color={getZustandColor(item.zustand) as any}
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
{!!assignedSelections[item.id] && (
|
|
||||||
<TextField
|
|
||||||
select
|
|
||||||
size="small"
|
size="small"
|
||||||
label="Neuer Status"
|
checked={checked}
|
||||||
sx={{ minWidth: 150 }}
|
onChange={(e) => {
|
||||||
value={assignedSelections[item.id]}
|
if (e.target.checked) {
|
||||||
onChange={(e) => setAssignedSelections(prev => ({ ...prev, [item.id]: e.target.value }))}
|
setReplacementSelections(prev => ({ ...prev, [artikelId]: 1 }));
|
||||||
>
|
} else {
|
||||||
{zustandOptions.map((opt) => (
|
setReplacementSelections(prev => {
|
||||||
<MenuItem key={opt.key} value={opt.key}>{opt.label}</MenuItem>
|
const n = { ...prev };
|
||||||
))}
|
delete n[artikelId];
|
||||||
</TextField>
|
return n;
|
||||||
)}
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Typography variant="body2" sx={{ flex: 1, minWidth: 160 }}>{group.bezeichnung}</Typography>
|
||||||
|
<Chip label={`${group.totalMenge} Stk. zugewiesen`} size="small" variant="outlined" />
|
||||||
|
{group.eigenschaften.length > 0 && (
|
||||||
|
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap' }}>
|
||||||
|
{group.eigenschaften.map((e, i) => (
|
||||||
|
<Chip key={`${e.name}-${e.wert}-${i}`} label={`${e.name}: ${e.wert}`} size="small" variant="outlined" />
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{checked && (
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
type="number"
|
||||||
|
label="Menge ersetzen"
|
||||||
|
value={selectedMenge}
|
||||||
|
onChange={(e) => {
|
||||||
|
const v = Math.max(1, Math.min(group.totalMenge, Number(e.target.value) || 1));
|
||||||
|
setReplacementSelections(prev => ({ ...prev, [artikelId]: v }));
|
||||||
|
}}
|
||||||
|
inputProps={{ min: 1, max: group.totalMenge }}
|
||||||
|
sx={{ width: 130 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
);
|
||||||
))
|
})
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useState, useMemo } from 'react';
|
|||||||
import {
|
import {
|
||||||
Box, Typography, Container, Button, Chip,
|
Box, Typography, Container, Button, Chip,
|
||||||
TextField, Autocomplete, ToggleButton, ToggleButtonGroup,
|
TextField, Autocomplete, ToggleButton, ToggleButtonGroup,
|
||||||
Stack, Divider, LinearProgress, MenuItem,
|
Stack, Divider, LinearProgress, MenuItem, Checkbox, FormControlLabel,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Assignment as AssignmentIcon } from '@mui/icons-material';
|
import { Assignment as AssignmentIcon } from '@mui/icons-material';
|
||||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
@@ -16,6 +16,7 @@ import { personalEquipmentApi } from '../services/personalEquipment';
|
|||||||
import { vehiclesApi } from '../services/vehicles';
|
import { vehiclesApi } from '../services/vehicles';
|
||||||
import { membersService } from '../services/members';
|
import { membersService } from '../services/members';
|
||||||
import type { AusruestungAnfragePosition, AusruestungEigenschaft } from '../types/ausruestungsanfrage.types';
|
import type { AusruestungAnfragePosition, AusruestungEigenschaft } from '../types/ausruestungsanfrage.types';
|
||||||
|
import type { ZustandOption } from '../types/personalEquipment.types';
|
||||||
|
|
||||||
type AssignmentTyp = 'ausruestung' | 'persoenlich' | 'keine';
|
type AssignmentTyp = 'ausruestung' | 'persoenlich' | 'keine';
|
||||||
|
|
||||||
@@ -130,13 +131,34 @@ export default function AusruestungsanfrageZuweisung() {
|
|||||||
staleTime: 2 * 60 * 1000,
|
staleTime: 2 * 60 * 1000,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: zustandOptions = [] } = useQuery<ZustandOption[]>({
|
||||||
|
queryKey: ['persoenliche-ausruestung', 'zustand-options'],
|
||||||
|
queryFn: () => personalEquipmentApi.getZustandOptions(),
|
||||||
|
staleTime: 5 * 60 * 1000,
|
||||||
|
});
|
||||||
|
|
||||||
|
const getZustandLabel = (key: string) => zustandOptions.find(o => o.key === key)?.label ?? key;
|
||||||
|
const getZustandColor = (key: string) => zustandOptions.find(o => o.key === key)?.color ?? 'default';
|
||||||
|
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
const [replacements, setReplacements] = useState<Record<number, string[]>>({});
|
||||||
|
|
||||||
const updateAssignment = (posId: number, patch: Partial<PositionAssignment>) => {
|
const updateAssignment = (posId: number, patch: Partial<PositionAssignment>) => {
|
||||||
setAssignments((prev) => ({
|
setAssignments((prev) => {
|
||||||
...prev,
|
const prevA = prev[posId];
|
||||||
[posId]: { ...prev[posId], ...patch },
|
// If userId changes, clear replacements for this position
|
||||||
}));
|
if (patch.userId !== undefined && prevA?.userId !== patch.userId) {
|
||||||
|
setReplacements((r) => {
|
||||||
|
const n = { ...r };
|
||||||
|
delete n[posId];
|
||||||
|
return n;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
[posId]: { ...prevA, ...patch },
|
||||||
|
};
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSkipAll = () => {
|
const handleSkipAll = () => {
|
||||||
@@ -190,6 +212,7 @@ export default function AusruestungsanfrageZuweisung() {
|
|||||||
wert,
|
wert,
|
||||||
}))
|
}))
|
||||||
: undefined,
|
: undefined,
|
||||||
|
replacedItemIds: pos?.ist_ersatz ? (replacements[Number(posId)] ?? []) : undefined,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
await ausruestungsanfrageApi.assignItems(anfrageId, payload);
|
await ausruestungsanfrageApi.assignItems(anfrageId, payload);
|
||||||
@@ -369,6 +392,65 @@ export default function AusruestungsanfrageZuweisung() {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
{pos.ist_ersatz && a.userId && pos.artikel_id && (() => {
|
||||||
|
const oldItems = allPersonalItems.filter(
|
||||||
|
i => i.artikel_id === pos.artikel_id && i.user_id === a.userId,
|
||||||
|
);
|
||||||
|
if (oldItems.length === 0) return null;
|
||||||
|
const selectedIds = replacements[pos.id] ?? [];
|
||||||
|
const selectedTotal = oldItems
|
||||||
|
.filter(i => selectedIds.includes(i.id))
|
||||||
|
.reduce((sum, i) => sum + i.menge, 0);
|
||||||
|
const limitReached = selectedTotal >= pos.menge;
|
||||||
|
return (
|
||||||
|
<Box sx={{ mt: 1 }}>
|
||||||
|
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mb: 0.5 }}>
|
||||||
|
Alte Gegenstände ersetzen ({selectedTotal} / {pos.menge})
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
|
||||||
|
{oldItems.map(item => {
|
||||||
|
const isChecked = selectedIds.includes(item.id);
|
||||||
|
const itemMenge = item.menge;
|
||||||
|
return (
|
||||||
|
<FormControlLabel
|
||||||
|
key={item.id}
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
size="small"
|
||||||
|
checked={isChecked}
|
||||||
|
disabled={!isChecked && limitReached}
|
||||||
|
onChange={(ev) => {
|
||||||
|
setReplacements(prev => {
|
||||||
|
const cur = prev[pos.id] ?? [];
|
||||||
|
const next = ev.target.checked
|
||||||
|
? [...cur, item.id]
|
||||||
|
: cur.filter(x => x !== item.id);
|
||||||
|
return { ...prev, [pos.id]: next };
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<Box sx={{ display: 'flex', gap: 0.5, alignItems: 'center', flexWrap: 'wrap' }}>
|
||||||
|
<Typography variant="body2">{item.bezeichnung}</Typography>
|
||||||
|
<Chip label={`${itemMenge} Stk.`} size="small" variant="outlined" />
|
||||||
|
{(item.eigenschaften ?? []).map(e => (
|
||||||
|
<Chip key={e.id} label={`${e.name}: ${e.wert}`} size="small" variant="outlined" />
|
||||||
|
))}
|
||||||
|
<Chip
|
||||||
|
label={getZustandLabel(item.zustand)}
|
||||||
|
color={getZustandColor(item.zustand) as any}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -882,7 +882,12 @@ function MitgliedDetail() {
|
|||||||
onClick={() => navigate(`/persoenliche-ausruestung/${item.id}`)}
|
onClick={() => navigate(`/persoenliche-ausruestung/${item.id}`)}
|
||||||
>
|
>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="body2" fontWeight={500}>{item.bezeichnung}</Typography>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
||||||
|
<Typography variant="body2" fontWeight={500}>{item.bezeichnung}</Typography>
|
||||||
|
{item.menge > 1 && (
|
||||||
|
<Chip label={`${item.menge}x`} size="small" sx={{ ml: 0.5, height: 18, fontSize: '0.65rem' }} />
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
{item.kategorie && (
|
{item.kategorie && (
|
||||||
<Typography variant="caption" color="text.secondary">{item.kategorie}</Typography>
|
<Typography variant="caption" color="text.secondary">{item.kategorie}</Typography>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ function PersoenlicheAusruestungPage() {
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Bezeichnung</th>
|
<th>Bezeichnung</th>
|
||||||
<th>Kategorie</th>
|
<th>Kategorie</th>
|
||||||
|
<th>Menge</th>
|
||||||
{canSeeAll && <th>Benutzer</th>}
|
{canSeeAll && <th>Benutzer</th>}
|
||||||
<th>Zustand</th>
|
<th>Zustand</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -188,7 +189,7 @@ function PersoenlicheAusruestungPage() {
|
|||||||
<tbody>
|
<tbody>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={canSeeAll ? 4 : 3}>
|
<td colSpan={canSeeAll ? 5 : 4}>
|
||||||
<Typography color="text.secondary" sx={{ py: 4, textAlign: 'center' }}>
|
<Typography color="text.secondary" sx={{ py: 4, textAlign: 'center' }}>
|
||||||
Lade Daten…
|
Lade Daten…
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -196,7 +197,7 @@ function PersoenlicheAusruestungPage() {
|
|||||||
</tr>
|
</tr>
|
||||||
) : filtered.length === 0 ? (
|
) : filtered.length === 0 ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={canSeeAll ? 4 : 3}>
|
<td colSpan={canSeeAll ? 5 : 4}>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', py: 6 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', py: 6 }}>
|
||||||
<CheckroomIcon sx={{ fontSize: 48, color: 'text.disabled', mb: 1 }} />
|
<CheckroomIcon sx={{ fontSize: 48, color: 'text.disabled', mb: 1 }} />
|
||||||
<Typography color="text.secondary">
|
<Typography color="text.secondary">
|
||||||
@@ -236,6 +237,9 @@ function PersoenlicheAusruestungPage() {
|
|||||||
? `${item.artikel_kategorie_parent_name} > ${item.artikel_kategorie_name}`
|
? `${item.artikel_kategorie_parent_name} > ${item.artikel_kategorie_name}`
|
||||||
: item.artikel_kategorie_name ?? item.kategorie ?? '—'}</Typography>
|
: item.artikel_kategorie_name ?? item.kategorie ?? '—'}</Typography>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
{item.menge > 1 && <Chip label={`${item.menge}x`} size="small" variant="outlined" />}
|
||||||
|
</td>
|
||||||
{canSeeAll && (
|
{canSeeAll && (
|
||||||
<td>
|
<td>
|
||||||
<Typography variant="body2">
|
<Typography variant="body2">
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ export default function PersoenlicheAusruestungDetail() {
|
|||||||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 1.5 }}>
|
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 1.5 }}>
|
||||||
{([
|
{([
|
||||||
['Benutzer', item.user_display_name || item.benutzer_name],
|
['Benutzer', item.user_display_name || item.benutzer_name],
|
||||||
|
['Menge', item.menge > 1 ? String(item.menge) : null],
|
||||||
['Seriennummer', item.seriennummer],
|
['Seriennummer', item.seriennummer],
|
||||||
['Inventarnummer', item.inventarnummer],
|
['Inventarnummer', item.inventarnummer],
|
||||||
['Erstellt am', new Date(item.erstellt_am).toLocaleDateString('de-AT')],
|
['Erstellt am', new Date(item.erstellt_am).toLocaleDateString('de-AT')],
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ export default function PersoenlicheAusruestungEdit() {
|
|||||||
const [seriennummer, setSeriennummer] = useState('');
|
const [seriennummer, setSeriennummer] = useState('');
|
||||||
const [inventarnummer, setInventarnummer] = useState('');
|
const [inventarnummer, setInventarnummer] = useState('');
|
||||||
const [zustand, setZustand] = useState('gut');
|
const [zustand, setZustand] = useState('gut');
|
||||||
|
const [menge, setMenge] = useState(1);
|
||||||
const [notizen, setNotizen] = useState('');
|
const [notizen, setNotizen] = useState('');
|
||||||
const [userId, setUserId] = useState<{ id: string; name: string } | null>(null);
|
const [userId, setUserId] = useState<{ id: string; name: string } | null>(null);
|
||||||
const [eigenschaften, setEigenschaften] = useState<EigenschaftRow[]>([]);
|
const [eigenschaften, setEigenschaften] = useState<EigenschaftRow[]>([]);
|
||||||
@@ -93,6 +94,7 @@ export default function PersoenlicheAusruestungEdit() {
|
|||||||
setSeriennummer(item.seriennummer ?? '');
|
setSeriennummer(item.seriennummer ?? '');
|
||||||
setInventarnummer(item.inventarnummer ?? '');
|
setInventarnummer(item.inventarnummer ?? '');
|
||||||
setZustand(item.zustand);
|
setZustand(item.zustand);
|
||||||
|
setMenge(item.menge ?? 1);
|
||||||
setNotizen(item.notizen ?? '');
|
setNotizen(item.notizen ?? '');
|
||||||
if (item.eigenschaften) {
|
if (item.eigenschaften) {
|
||||||
setEigenschaften(item.eigenschaften.map(e => ({
|
setEigenschaften(item.eigenschaften.map(e => ({
|
||||||
@@ -141,6 +143,7 @@ export default function PersoenlicheAusruestungEdit() {
|
|||||||
seriennummer: seriennummer || null,
|
seriennummer: seriennummer || null,
|
||||||
inventarnummer: inventarnummer || null,
|
inventarnummer: inventarnummer || null,
|
||||||
zustand,
|
zustand,
|
||||||
|
menge,
|
||||||
notizen: notizen || null,
|
notizen: notizen || null,
|
||||||
eigenschaften: item.artikel_id
|
eigenschaften: item.artikel_id
|
||||||
? Object.entries(catalogEigenschaftValues)
|
? Object.entries(catalogEigenschaftValues)
|
||||||
@@ -231,6 +234,16 @@ export default function PersoenlicheAusruestungEdit() {
|
|||||||
onChange={(e) => setInventarnummer(e.target.value)}
|
onChange={(e) => setInventarnummer(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
label="Menge"
|
||||||
|
type="number"
|
||||||
|
size="small"
|
||||||
|
value={menge}
|
||||||
|
onChange={(e) => setMenge(Math.max(1, parseInt(e.target.value, 10) || 1))}
|
||||||
|
inputProps={{ min: 1 }}
|
||||||
|
sx={{ width: 120 }}
|
||||||
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
label="Zustand"
|
label="Zustand"
|
||||||
select
|
select
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export default function PersoenlicheAusruestungNeu() {
|
|||||||
const [formUserId, setFormUserId] = useState<{ id: string; name: string } | null>(null);
|
const [formUserId, setFormUserId] = useState<{ id: string; name: string } | null>(null);
|
||||||
const [formBenutzerName, setFormBenutzerName] = useState('');
|
const [formBenutzerName, setFormBenutzerName] = useState('');
|
||||||
const [formZustand, setFormZustand] = useState('gut');
|
const [formZustand, setFormZustand] = useState('gut');
|
||||||
|
const [formMenge, setFormMenge] = useState(1);
|
||||||
const [formNotizen, setFormNotizen] = useState('');
|
const [formNotizen, setFormNotizen] = useState('');
|
||||||
const [eigenschaftValues, setEigenschaftValues] = useState<Record<number, string>>({});
|
const [eigenschaftValues, setEigenschaftValues] = useState<Record<number, string>>({});
|
||||||
|
|
||||||
@@ -93,6 +94,7 @@ export default function PersoenlicheAusruestungNeu() {
|
|||||||
user_id: formUserId?.id || undefined,
|
user_id: formUserId?.id || undefined,
|
||||||
benutzer_name: formBenutzerName || undefined,
|
benutzer_name: formBenutzerName || undefined,
|
||||||
zustand: formZustand,
|
zustand: formZustand,
|
||||||
|
menge: formMenge,
|
||||||
notizen: formNotizen || undefined,
|
notizen: formNotizen || undefined,
|
||||||
eigenschaften: Object.entries(eigenschaftValues)
|
eigenschaften: Object.entries(eigenschaftValues)
|
||||||
.filter(([, v]) => v.trim())
|
.filter(([, v]) => v.trim())
|
||||||
@@ -179,6 +181,16 @@ export default function PersoenlicheAusruestungNeu() {
|
|||||||
))}
|
))}
|
||||||
</TextField>
|
</TextField>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
label="Menge"
|
||||||
|
type="number"
|
||||||
|
size="small"
|
||||||
|
value={formMenge}
|
||||||
|
onChange={(e) => setFormMenge(Math.max(1, parseInt(e.target.value, 10) || 1))}
|
||||||
|
inputProps={{ min: 1 }}
|
||||||
|
sx={{ width: 120 }}
|
||||||
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
label="Notizen"
|
label="Notizen"
|
||||||
size="small"
|
size="small"
|
||||||
|
|||||||
@@ -99,20 +99,8 @@ export const ausruestungsanfrageApi = {
|
|||||||
bezeichnung?: string,
|
bezeichnung?: string,
|
||||||
fuer_benutzer_id?: string,
|
fuer_benutzer_id?: string,
|
||||||
fuer_benutzer_name?: string,
|
fuer_benutzer_name?: string,
|
||||||
assignedItems?: { persoenlich_id: string; neuer_zustand: string }[],
|
|
||||||
): Promise<AusruestungAnfrageDetailResponse> => {
|
): Promise<AusruestungAnfrageDetailResponse> => {
|
||||||
// Merge assigned personal items into the items array with ist_ersatz: true
|
const r = await api.post('/api/ausruestungsanfragen/requests', { items, notizen, bezeichnung, fuer_benutzer_id, fuer_benutzer_name });
|
||||||
const allItems = [
|
|
||||||
...items,
|
|
||||||
...(assignedItems ?? []).map(a => ({
|
|
||||||
bezeichnung: '', // backend resolves from persoenlich_id
|
|
||||||
menge: 1,
|
|
||||||
persoenlich_id: a.persoenlich_id,
|
|
||||||
neuer_zustand: a.neuer_zustand,
|
|
||||||
ist_ersatz: true,
|
|
||||||
})),
|
|
||||||
];
|
|
||||||
const r = await api.post('/api/ausruestungsanfragen/requests', { items: allItems, notizen, bezeichnung, fuer_benutzer_id, fuer_benutzer_name });
|
|
||||||
return r.data.data;
|
return r.data.data;
|
||||||
},
|
},
|
||||||
updateRequest: async (
|
updateRequest: async (
|
||||||
@@ -148,6 +136,7 @@ export const ausruestungsanfrageApi = {
|
|||||||
groesse?: string;
|
groesse?: string;
|
||||||
kategorie?: string;
|
kategorie?: string;
|
||||||
eigenschaften?: Array<{ eigenschaft_id?: number; name: string; wert: string }>;
|
eigenschaften?: Array<{ eigenschaft_id?: number; name: string; wert: string }>;
|
||||||
|
replacedItemIds?: string[];
|
||||||
}>,
|
}>,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
await api.post(`/api/ausruestungsanfragen/requests/${anfrageId}/assign`, { assignments });
|
await api.post(`/api/ausruestungsanfragen/requests/${anfrageId}/assign`, { assignments });
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export interface PersoenlicheAusruestung {
|
|||||||
inventarnummer?: string;
|
inventarnummer?: string;
|
||||||
anschaffung_datum?: string;
|
anschaffung_datum?: string;
|
||||||
zustand: string;
|
zustand: string;
|
||||||
|
menge: number;
|
||||||
notizen?: string;
|
notizen?: string;
|
||||||
anfrage_id?: number;
|
anfrage_id?: number;
|
||||||
anfrage_position_id?: number;
|
anfrage_position_id?: number;
|
||||||
@@ -41,6 +42,7 @@ export interface CreatePersoenlicheAusruestungPayload {
|
|||||||
inventarnummer?: string;
|
inventarnummer?: string;
|
||||||
anschaffung_datum?: string;
|
anschaffung_datum?: string;
|
||||||
zustand?: string;
|
zustand?: string;
|
||||||
|
menge?: number;
|
||||||
notizen?: string;
|
notizen?: string;
|
||||||
eigenschaften?: { eigenschaft_id?: number; name: string; wert: string }[];
|
eigenschaften?: { eigenschaft_id?: number; name: string; wert: string }[];
|
||||||
}
|
}
|
||||||
@@ -56,6 +58,7 @@ export interface UpdatePersoenlicheAusruestungPayload {
|
|||||||
inventarnummer?: string | null;
|
inventarnummer?: string | null;
|
||||||
anschaffung_datum?: string | null;
|
anschaffung_datum?: string | null;
|
||||||
zustand?: string;
|
zustand?: string;
|
||||||
|
menge?: number | null;
|
||||||
notizen?: string | null;
|
notizen?: string | null;
|
||||||
eigenschaften?: { eigenschaft_id?: number | null; name: string; wert: string }[] | null;
|
eigenschaften?: { eigenschaft_id?: number | null; name: string; wert: string }[] | null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user