Fix Mitarbeiter-Uebersicht: keine directus_users-Abhaengigkeit + api_clients-Leserecht

Mitarbeiter (Staff-Rolle) sah nichts + keine Suche: Order-Abfrage forderte
customer.company (directus_users) UND api_client.name (api_clients) an - beide
fuer Staff nicht lesbar -> 403 -> ganze Abfrage scheitert.

- customer.* aus der Uebersicht-Abfrage entfernt (Name kommt aus api_client);
  kundeName nutzt nur noch api_client.name
- Order-Abfrage 3-stufig robust (mit kuvertieren+client -> ohne kuvertieren -> ohne client),
  jobs/machines-Fetch mit Feld-Fallback -> Ansicht laedt auch ohne Bootstrap
- Bootstrap: Staff darf api_clients lesen (nur id+name, keine Secrets)

Fuer die API-Kundennamen Bootstrap laufen lassen; die Ansicht selbst geht schon nach Extension-Deploy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
s4luorth
2026-10-01 14:01:35 +02:00
parent 0f7f9c3bd9
commit 4b314d65ba
2 changed files with 28 additions and 10 deletions

View File

@@ -1252,6 +1252,9 @@ async function applyPermissions() {
for (const col of BIZ)
for (const act of ['create', 'read', 'update', 'delete'])
await ensurePermission(staffPol.id, col, act);
// Mitarbeiter-Übersicht zeigt API-Kundennamen bei Sammelaufträgen (Chargen) –
// nur id + name lesen, NICHT die Token/Secrets des Zugangs.
await ensurePermission(staffPol.id, 'api_clients', 'read', ['id', 'name']);
// Customer: nur Eigenes + Lesen der Kataloge
// Alt-Namen auf den finalen "Kunde" ziehen (idempotent).

View File

@@ -65,7 +65,9 @@ const Board = {
const sucheTyp = ref('brief');
const aktiveMaschinen = computed(() => machines.value.filter((m) => m.active !== false));
function kundeName(o) { return [o.customer?.company, [o.customer?.first_name, o.customer?.last_name].filter(Boolean).join(' ')].filter(Boolean).join(' · '); }
// Mitarbeiter-Ansicht liest keine directus_users-Felder (Rechte) → Name aus
// API-Kunde; manuelle Aufträge zeigen sonst Auftragsname/-nummer.
function kundeName(o) { return o.api_client?.name || ''; }
// „Seite 1" o. Ä. = Seiten eines mehrseitigen Briefs, kein Empfänger.
const istSeitenmarker = (z) => /^seite\s*\d+$/i.test(String(z || '').trim());
function freitextName(ft) {
@@ -86,16 +88,29 @@ const Board = {
if (!leise) { loading.value = true; err.value = ''; }
try {
const [o, j, m, t] = await Promise.all([
api.get('/items/orders', { params: {
limit: -1, sort: 'date_created',
filter: { production_status: { _in: AKTIV_STATUS } },
fields: 'id,order_number,order_name,production_status,needs_envelope,kuvertieren,brief_gedruckt,kuvert_gedruckt,frankiert,'
(async () => {
// Sichere Basisfelder (alle auf orders/formats/products – von der
// Mitarbeiter-Rolle lesbar). api_client (API-Kundenname) und kuvertieren
// sind optional: fehlende Rechte/Felder dürfen die Ansicht NICHT blockieren.
const basis = 'id,order_number,order_name,production_status,needs_envelope,brief_gedruckt,kuvert_gedruckt,frankiert,'
+ 'format.key,format.name,shipping_day,shipping_type,versanddatum,date_created,entries_count,source,'
+ 'envelope_labeling,envelope_format,api_client.id,api_client.name,product.name,'
+ 'customer.first_name,customer.last_name,customer.company',
} }),
api.get('/items/jobs', { params: { limit: -1, sort: '-priority,position,date_created', filter: { status: { _in: ['queued', 'printing'] } }, fields: 'id,type,status,format,format_label,position,machine.id,order.id,order.order_number' } }).catch(() => ({ data: { data: [] } })),
api.get('/items/machines', { params: { limit: -1, filter: { active: { _eq: true } }, fields: 'id,name,current_format,pending_format,status,active' } }).catch(() => ({ data: { data: [] } })),
+ 'envelope_labeling,envelope_format,product.name';
const mitClient = basis + ',api_client.id,api_client.name';
const p = (fields) => ({ limit: -1, sort: 'date_created', filter: { production_status: { _in: AKTIV_STATUS } }, fields });
try { return await api.get('/items/orders', { params: p(mitClient + ',kuvertieren') }); }
catch {
try { return await api.get('/items/orders', { params: p(mitClient) }); }
catch { return await api.get('/items/orders', { params: p(basis) }); }
}
})(),
(async () => {
try { return await api.get('/items/jobs', { params: { limit: -1, sort: '-priority,position,date_created', filter: { status: { _in: ['queued', 'printing'] } }, fields: 'id,type,status,format,format_label,position,machine.id,order.id,order.order_number' } }); }
catch { try { return await api.get('/items/jobs', { params: { limit: -1, filter: { status: { _in: ['queued', 'printing'] } }, fields: 'id,type,status,format,format_label,machine.id,order.id' } }); } catch { return { data: { data: [] } }; } }
})(),
(async () => {
try { return await api.get('/items/machines', { params: { limit: -1, filter: { active: { _eq: true } }, fields: 'id,name,current_format,pending_format,status,active' } }); }
catch { try { return await api.get('/items/machines', { params: { limit: -1, filter: { active: { _eq: true } }, fields: 'id,name,current_format,status,active' } }); } catch { return { data: { data: [] } }; } }
})(),
api.get('/items/format_templates', { params: { limit: -1, fields: 'id,key,name,type,default_format,search_default' } }).catch(() => ({ data: { data: [] } })),
]);
const vorherAnzahl = orders.value.length;