feat: personal equipment tracking, order assignment, purge fix, widget consolidation
- Migration 084: new persoenliche_ausruestung table with catalog link, user assignment, soft delete; adds zuweisung_typ/ausruestung_id/persoenlich_id columns to ausruestung_anfrage_positionen; seeds feature group + 5 permissions - Fix user data purge: table was shop_anfragen, renamed to ausruestung_anfragen in mig 046 — caused full transaction rollback. Also keep mitglieder_profile row but NULL FDISK-synced fields (dienstgrad, geburtsdatum, etc.) instead of deleting the profile - Personal equipment CRUD: backend service/controller/routes at /api/persoenliche-ausruestung; frontend page with DataTable, user filter, catalog Autocomplete, FAB create dialog; widget in Status group; sidebar entry (Checkroom icon); card in MitgliedDetail Tab 0 - Ausruestungsanfrage item assignment: when a request reaches erledigt, auto-opens ItemAssignmentDialog listing all delivered positions; each item can be assigned as general equipment (vehicle/storage), personal item (user, prefilled with requester), or not tracked; POST /requests/:id/assign backend - StatCard refactored to use WidgetCard as outer shell for consistent header styling across all dashboard widget templates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -889,6 +889,145 @@ async function getWidgetOverview() {
|
||||
return result.rows[0];
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Assignment of delivered items
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface AssignmentInput {
|
||||
positionId: number;
|
||||
typ: 'ausruestung' | 'persoenlich' | 'keine';
|
||||
fahrzeugId?: string;
|
||||
standort?: string;
|
||||
userId?: string;
|
||||
benutzerName?: string;
|
||||
groesse?: string;
|
||||
kategorie?: string;
|
||||
}
|
||||
|
||||
async function assignDeliveredItems(
|
||||
anfrageId: number,
|
||||
requestingUserId: string,
|
||||
assignments: AssignmentInput[],
|
||||
) {
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
|
||||
// Load anfrage to get anfrager_id (default user for personal assignments)
|
||||
const anfrageResult = await client.query(
|
||||
'SELECT anfrager_id FROM ausruestung_anfragen WHERE id = $1',
|
||||
[anfrageId],
|
||||
);
|
||||
if (anfrageResult.rows.length === 0) {
|
||||
throw new Error('Anfrage nicht gefunden');
|
||||
}
|
||||
const anfragerId = anfrageResult.rows[0].anfrager_id;
|
||||
|
||||
let assigned = 0;
|
||||
|
||||
for (const a of assignments) {
|
||||
// Load position details
|
||||
const posResult = await client.query(
|
||||
'SELECT bezeichnung, artikel_id FROM ausruestung_anfrage_positionen WHERE id = $1 AND anfrage_id = $2',
|
||||
[a.positionId, anfrageId],
|
||||
);
|
||||
if (posResult.rows.length === 0) continue;
|
||||
const pos = posResult.rows[0];
|
||||
|
||||
if (a.typ === 'ausruestung') {
|
||||
// Look up kategorie_id from artikel if available
|
||||
let kategorieId: string | null = null;
|
||||
if (pos.artikel_id) {
|
||||
const artikelResult = await client.query(
|
||||
'SELECT kategorie_id FROM ausruestung_artikel WHERE id = $1',
|
||||
[pos.artikel_id],
|
||||
);
|
||||
if (artikelResult.rows[0]?.kategorie_id) {
|
||||
// artikel has kategorie_id (int FK to ausruestung_kategorien_katalog), but
|
||||
// ausruestung.kategorie_id is UUID FK to ausruestung_kategorien — look up by name
|
||||
const katNameResult = await client.query(
|
||||
'SELECT name FROM ausruestung_kategorien_katalog WHERE id = $1',
|
||||
[artikelResult.rows[0].kategorie_id],
|
||||
);
|
||||
if (katNameResult.rows[0]?.name) {
|
||||
const katResult = await client.query(
|
||||
'SELECT id FROM ausruestung_kategorien WHERE name = $1 LIMIT 1',
|
||||
[katNameResult.rows[0].name],
|
||||
);
|
||||
kategorieId = katResult.rows[0]?.id ?? null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fallback: pick first category
|
||||
if (!kategorieId) {
|
||||
const fallback = await client.query('SELECT id FROM ausruestung_kategorien LIMIT 1');
|
||||
kategorieId = fallback.rows[0]?.id ?? null;
|
||||
}
|
||||
|
||||
const insertResult = await client.query(
|
||||
`INSERT INTO ausruestung (
|
||||
id, bezeichnung, kategorie_id, fahrzeug_id, standort, status, ist_wichtig
|
||||
) VALUES (gen_random_uuid(), $1, $2, $3, $4, 'einsatzbereit', false)
|
||||
RETURNING id`,
|
||||
[pos.bezeichnung, kategorieId, a.fahrzeugId ?? null, a.standort ?? 'Lager'],
|
||||
);
|
||||
const newId = insertResult.rows[0].id;
|
||||
|
||||
await client.query(
|
||||
`UPDATE ausruestung_anfrage_positionen
|
||||
SET zuweisung_typ = 'ausruestung', zuweisung_ausruestung_id = $1
|
||||
WHERE id = $2`,
|
||||
[newId, a.positionId],
|
||||
);
|
||||
} else if (a.typ === 'persoenlich') {
|
||||
const insertResult = await client.query(
|
||||
`INSERT INTO persoenliche_ausruestung (
|
||||
bezeichnung, kategorie, groesse, user_id, benutzer_name,
|
||||
anfrage_id, anfrage_position_id, artikel_id, erstellt_von
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
RETURNING id`,
|
||||
[
|
||||
pos.bezeichnung,
|
||||
a.kategorie ?? null,
|
||||
a.groesse ?? null,
|
||||
a.userId ?? anfragerId,
|
||||
a.benutzerName ?? null,
|
||||
anfrageId,
|
||||
a.positionId,
|
||||
pos.artikel_id ?? null,
|
||||
requestingUserId,
|
||||
],
|
||||
);
|
||||
const newId = insertResult.rows[0].id;
|
||||
|
||||
await client.query(
|
||||
`UPDATE ausruestung_anfrage_positionen
|
||||
SET zuweisung_typ = 'persoenlich', zuweisung_persoenlich_id = $1
|
||||
WHERE id = $2`,
|
||||
[newId, a.positionId],
|
||||
);
|
||||
} else {
|
||||
// typ === 'keine'
|
||||
await client.query(
|
||||
`UPDATE ausruestung_anfrage_positionen SET zuweisung_typ = 'keine' WHERE id = $1`,
|
||||
[a.positionId],
|
||||
);
|
||||
}
|
||||
|
||||
assigned++;
|
||||
}
|
||||
|
||||
await client.query('COMMIT');
|
||||
return { assigned };
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK');
|
||||
logger.error('ausruestungsanfrageService.assignDeliveredItems failed', { error, anfrageId });
|
||||
throw error;
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
getAllUsers,
|
||||
getKategorien,
|
||||
@@ -919,4 +1058,5 @@ export default {
|
||||
createOrdersFromRequest,
|
||||
getOverview,
|
||||
getWidgetOverview,
|
||||
assignDeliveredItems,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user