[FEAT] A4 Fasi 4.4 + 4.5 (A4 COMPLETA 5/5): scadenziario centralizzato review_schedule (mig.044; ReviewScheduleController list/create/update/delete/complete/sync) + stakeholder estesi clienti/partner GV.SC-02 (mig.045 suppliers.stakeholder_type, SupplyChainController::stakeholderMap). Pagine review-schedule.html + stakeholders.html (Bootstrap Italia/AGID). Workflow build+review adversariale (3 major risolti: 045 da DELIMITER -> ALTER bare runner-safe). help GV.SC-04/05 allineati al titolo canonico ACN. Cache-buster ?v=20260620.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c7d36c910f
commit
c5b00ccd96
@@ -29,6 +29,7 @@ class SupplyChainController extends BaseController
|
||||
$supplierId = Database::insert('suppliers', [
|
||||
'organization_id' => $this->getCurrentOrgId(),
|
||||
'name' => trim($this->getParam('name')),
|
||||
'stakeholder_type' => $this->validateStakeholderType($this->getParam('stakeholder_type', 'supplier')),
|
||||
'vat_number' => $this->getParam('vat_number'),
|
||||
'contact_email' => $this->getParam('contact_email'),
|
||||
'contact_name' => $this->getParam('contact_name'),
|
||||
@@ -76,6 +77,11 @@ class SupplyChainController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
// stakeholder_type (GV.SC-02): gestito a parte per validare l'enum
|
||||
if ($this->hasParam('stakeholder_type')) {
|
||||
$updates['stakeholder_type'] = $this->validateStakeholderType($this->getParam('stakeholder_type'));
|
||||
}
|
||||
|
||||
if (!empty($updates)) {
|
||||
Database::update('suppliers', $updates, 'id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]);
|
||||
$this->logAudit('supplier_updated', 'supplier', $id, $updates);
|
||||
@@ -146,6 +152,58 @@ class SupplyChainController extends BaseController
|
||||
]);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
// STAKEHOLDER ESTESI (A4 Fase 4.5) — GV.SC-02: fornitori + clienti + partner
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* GET /api/supply-chain/stakeholder-map — mappa di sintesi degli stakeholder
|
||||
* dell'org raggruppati per tipo (supplier/customer/partner). RIUSA la tabella
|
||||
* suppliers (nessun duplicato): il dettaglio fornitori resta nel modulo Supply
|
||||
* Chain. Ancoraggio GV.SC-02 (ruoli/responsabilita verso fornitori, clienti E
|
||||
* partner) + GV.SC-04/05/07. Org-scoped, esclude i soft-deleted.
|
||||
*/
|
||||
public function stakeholderMap(): void
|
||||
{
|
||||
$this->requireOrgAccess();
|
||||
$orgId = $this->getCurrentOrgId();
|
||||
|
||||
$rows = Database::fetchAll(
|
||||
'SELECT id, name, stakeholder_type, service_type, criticality, status,
|
||||
contact_email, contact_name, risk_score
|
||||
FROM suppliers
|
||||
WHERE organization_id = ? AND deleted_at IS NULL
|
||||
ORDER BY stakeholder_type, criticality DESC, name',
|
||||
[$orgId]
|
||||
);
|
||||
|
||||
$groups = ['supplier' => [], 'customer' => [], 'partner' => []];
|
||||
foreach ($rows as $r) {
|
||||
$t = $r['stakeholder_type'] ?? 'supplier';
|
||||
if (!isset($groups[$t])) {
|
||||
$t = 'supplier';
|
||||
}
|
||||
$groups[$t][] = $r;
|
||||
}
|
||||
|
||||
$this->jsonSuccess([
|
||||
'groups' => $groups,
|
||||
'counts' => [
|
||||
'supplier' => count($groups['supplier']),
|
||||
'customer' => count($groups['customer']),
|
||||
'partner' => count($groups['partner']),
|
||||
],
|
||||
'total' => count($rows),
|
||||
]);
|
||||
}
|
||||
|
||||
/** Normalizza/valida il tipo stakeholder (default 'supplier' se non valido). */
|
||||
private function validateStakeholderType($t): string
|
||||
{
|
||||
$t = strtolower((string) $t);
|
||||
return in_array($t, ['supplier', 'customer', 'partner'], true) ? $t : 'supplier';
|
||||
}
|
||||
|
||||
private function calculateSupplierRiskScore(array $responses): int
|
||||
{
|
||||
if (empty($responses)) return 0;
|
||||
|
||||
Reference in New Issue
Block a user