fix(bestellungen): automate delivery status transitions, enable received-qty input for creators, and add im_haus tracking to positionen
This commit is contained in:
@@ -8,6 +8,20 @@ import fs from 'fs';
|
||||
const param = (req: Request, key: string): string => req.params[key] as string;
|
||||
|
||||
class BestellungController {
|
||||
// ---------------------------------------------------------------------------
|
||||
// Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async listMembers(_req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const members = await bestellungService.getAllMembers();
|
||||
res.status(200).json({ success: true, data: members });
|
||||
} catch (error) {
|
||||
logger.error('BestellungController.listMembers error', { error });
|
||||
res.status(500).json({ success: false, message: 'Mitglieder konnten nicht geladen werden' });
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Catalog (shared ausruestung_artikel)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
-- Migration 096: Add im_haus to internal request positions and FK back from external order positions
|
||||
--
|
||||
-- im_haus (ausruestung_anfrage_positionen): auto-set when external bestellposition is received
|
||||
-- anfrage_position_id (bestellpositionen): explicit link so receipt can sync back
|
||||
|
||||
ALTER TABLE ausruestung_anfrage_positionen
|
||||
ADD COLUMN IF NOT EXISTS im_haus BOOLEAN DEFAULT false;
|
||||
|
||||
ALTER TABLE bestellpositionen
|
||||
ADD COLUMN IF NOT EXISTS anfrage_position_id INT
|
||||
REFERENCES ausruestung_anfrage_positionen(id) ON DELETE SET NULL;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Router } from 'express';
|
||||
import bestellungController from '../controllers/bestellung.controller';
|
||||
import { authenticate } from '../middleware/auth.middleware';
|
||||
import { requirePermission } from '../middleware/rbac.middleware';
|
||||
import { requirePermission, requireAnyPermission } from '../middleware/rbac.middleware';
|
||||
import { uploadBestellung } from '../middleware/upload';
|
||||
|
||||
const router = Router();
|
||||
@@ -77,6 +77,17 @@ router.get(
|
||||
bestellungController.listKatalogKategorien.bind(bestellungController)
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Members (all active users for member selector)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
router.get(
|
||||
'/members',
|
||||
authenticate,
|
||||
requirePermission('bestellungen:view'),
|
||||
bestellungController.listMembers.bind(bestellungController)
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Orders (Bestellungen)
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -159,8 +170,7 @@ router.delete(
|
||||
router.patch(
|
||||
'/items/:itemId/received',
|
||||
authenticate,
|
||||
requirePermission('bestellungen:manage_orders'),
|
||||
bestellungController.updateReceivedQuantity.bind(bestellungController)
|
||||
requireAnyPermission('bestellungen:manage_orders', 'bestellungen:create'), bestellungController.updateReceivedQuantity.bind(bestellungController)
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -325,6 +325,7 @@ async function getRequests(filters?: { status?: string; anfrager_id?: string })
|
||||
a.fuer_benutzer_name,
|
||||
(SELECT COUNT(*)::int FROM ausruestung_anfrage_positionen p WHERE p.anfrage_id = a.id) AS positionen_count,
|
||||
(SELECT COUNT(*)::int FROM ausruestung_anfrage_positionen p WHERE p.anfrage_id = a.id AND p.geliefert) AS geliefert_count,
|
||||
(SELECT COUNT(*)::int FROM ausruestung_anfrage_positionen p WHERE p.anfrage_id = a.id AND p.im_haus) AS im_haus_count,
|
||||
EXISTS(
|
||||
SELECT 1 FROM ausruestung_anfrage_bestellung ab
|
||||
JOIN bestellungen b ON b.id = ab.bestellung_id
|
||||
@@ -346,6 +347,7 @@ async function getMyRequests(userId: string) {
|
||||
`SELECT a.*,
|
||||
(SELECT COUNT(*)::int FROM ausruestung_anfrage_positionen p WHERE p.anfrage_id = a.id) AS positionen_count,
|
||||
(SELECT COUNT(*)::int FROM ausruestung_anfrage_positionen p WHERE p.anfrage_id = a.id AND p.geliefert) AS geliefert_count,
|
||||
(SELECT COUNT(*)::int FROM ausruestung_anfrage_positionen p WHERE p.anfrage_id = a.id AND p.im_haus) AS im_haus_count,
|
||||
EXISTS(
|
||||
SELECT 1 FROM ausruestung_anfrage_bestellung ab
|
||||
JOIN bestellungen b ON b.id = ab.bestellung_id
|
||||
@@ -859,9 +861,9 @@ async function createOrdersFromRequest(
|
||||
}
|
||||
|
||||
await client.query(
|
||||
`INSERT INTO bestellpositionen (bestellung_id, bezeichnung, menge, einheit, notizen, artikel_id, spezifikationen)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb)`,
|
||||
[bestellung.id, pos.bezeichnung, pos.menge, pos.einheit || 'Stk', pos.notizen || null, artikelId, JSON.stringify(spezifikationen)]
|
||||
`INSERT INTO bestellpositionen (bestellung_id, bezeichnung, menge, einheit, notizen, artikel_id, spezifikationen, anfrage_position_id)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb, $8)`,
|
||||
[bestellung.id, pos.bezeichnung, pos.menge, pos.einheit || 'Stk', pos.notizen || null, artikelId, JSON.stringify(spezifikationen), pos.position_id || null]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,25 @@ async function getKatalogKategorien() {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Members (all active users for member selector)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function getAllMembers() {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`SELECT id, COALESCE(given_name || ' ' || family_name, name) AS name
|
||||
FROM users
|
||||
WHERE is_active = true
|
||||
ORDER BY name`,
|
||||
);
|
||||
return result.rows;
|
||||
} catch (error) {
|
||||
logger.error('BestellungService.getAllMembers failed', { error });
|
||||
throw new Error('Mitglieder konnten nicht geladen werden');
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Vendors (Lieferanten)
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -574,6 +593,15 @@ async function updateReceivedQuantity(id: number, menge: number, userId: string)
|
||||
const item = result.rows[0];
|
||||
await logAction(item.bestellung_id, 'Liefermenge aktualisiert', `"${item.bezeichnung}": ${menge} von ${item.menge} erhalten`, userId);
|
||||
|
||||
// Sync im_haus on linked internal request position
|
||||
if (item.anfrage_position_id) {
|
||||
const imHaus = Number(item.erhalten_menge) >= Number(item.menge);
|
||||
await pool.query(
|
||||
`UPDATE ausruestung_anfrage_positionen SET im_haus = $1 WHERE id = $2`,
|
||||
[imHaus, item.anfrage_position_id]
|
||||
);
|
||||
}
|
||||
|
||||
// Check if all items for this order are fully received
|
||||
const allItems = await pool.query(
|
||||
`SELECT menge, erhalten_menge FROM bestellpositionen WHERE bestellung_id = $1`,
|
||||
@@ -760,6 +788,8 @@ async function getHistory(bestellungId: number) {
|
||||
}
|
||||
|
||||
export default {
|
||||
// Members
|
||||
getAllMembers,
|
||||
// Catalog
|
||||
getKatalogItems,
|
||||
getKatalogItem,
|
||||
|
||||
Reference in New Issue
Block a user