import { BadgeCheck, ClipboardList, Scale, ShieldCheck, Users, Wallet, Warehouse, FileBarChart, } from 'lucide-react'; import type { LucideIcon } from 'lucide-react'; export type ProcurementEntry = { id: string; label: string; path: string; roles?: string[]; fullPage?: boolean; section?: string; }; export type ProcurementGroup = { id: string; label: string; icon: LucideIcon; entries: ProcurementEntry[]; }; const readers = ['admin', 'procurement', 'finance', 'department_head']; const finance = ['admin', 'finance']; const inventory = [...readers, 'warehouse']; export function procurementNavigation( role: string | string[], payables: boolean, publicPaths?: string[], ): ProcurementGroup[] { const groups: ProcurementGroup[] = [ { id: 'requests', label: 'Requisições', icon: ClipboardList, entries: [ { id: 'new_request', label: 'Nova requisição', path: '/requisicoes/criar', }, { id: 'my_requests', label: 'Minhas requisições', path: '/requisicoes?view=mine', }, { id: 'my_workflow_tasks', label: 'As minhas tarefas', path: '/requisicoes/tarefas', }, { id: 'my_drafts', label: 'Os meus rascunhos', path: '/requisicoes?view=mine&status=draft', }, { id: 'all_requests', label: 'Todas as autorizadas', path: '/requisicoes?view=all', }, { id: 'planning', label: 'Planeamento', path: '/planeamento' }, ], }, { id: 'approvals', label: 'Aprovações', icon: BadgeCheck, entries: [ { id: 'pending_approvals', label: 'Pendentes para mim', path: '/requisicoes?view=approvals', roles: readers, }, { id: 'workflow_notices', label: 'Avisos de aprovação', path: '/requisicoes/avisos', }, { id: 'decisions', label: 'Minhas decisões', path: '/requisicoes?view=decided', roles: readers, }, ], }, { id: 'sourcing', label: 'Consultas e adjudicações', icon: Scale, entries: [ { id: 'quotations', label: 'Cotações e comparação', path: '/cotacoes', roles: readers, }, { id: 'orders', label: 'Adjudicações e ordens', path: '/ordens', roles: readers, }, { id: 'purchase_review', label: 'Revisão e cancelamento', path: '/revisao-compras', }, { id: 'supply_agreements', label: 'Fornecimento habitual', path: '/compras/fornecimento?view=agreements', roles: [ 'admin', 'supply_read', 'supply_prepare', 'supply_review', ], }, { id: 'supply_calendar', label: 'Reposição e calendário', path: '/compras/fornecimento?view=calendar', roles: [ 'admin', 'supply_read', 'supply_prepare', 'supply_review', ], }, { id: 'supply_suggestions', label: 'Sugestões de reposição', path: '/compras/fornecimento?view=suggestions', roles: [ 'admin', 'supply_read', 'supply_suggest_prepare', 'supply_suggest_review', 'supply_suggest_convert', ], }, { id: 'supply_imports', label: 'Importações', path: '/compras/fornecimento?view=imports', roles: [ 'admin', 'import_read', 'import_prepare', 'import_customs', ], }, { id: 'supply_alternatives', label: 'Alternativas técnicas', path: '/compras/fornecimento?view=alternatives', roles: [ 'admin', 'supply_read', 'supply_technical_prepare', 'supply_technical_review', ], }, { id: 'supply_deliveries', label: 'Entregas confirmadas', path: '/compras/fornecimento?view=deliveries', roles: ['admin', 'supply_read', 'supply_delivery'], }, ], }, { id: 'suppliers', label: 'Fornecedores', icon: Users, entries: [ { id: 'suppliers', label: 'Cadastro e documentos', path: '/fornecedores', roles: readers, }, { id: 'supplier_evaluations', label: 'Avaliação de fornecedores', path: '/fornecedores/avaliacoes', roles: readers, }, ], }, { id: 'people', label: 'Pessoas e contratações', icon: Users, entries: [ { id: 'people_workflow_tasks', label: 'As minhas tarefas', path: '/pessoas/tarefas', roles: [ 'admin', 'people_service_prepare', 'people_service_review', 'people_staff_prepare', 'people_staff_review', ], }, { id: 'people_registry', label: 'Cadastro reutilizável de pessoas', path: '/pessoas/cadastro', roles: [ 'admin', 'people_service_prepare', 'people_staff_prepare', 'people_registry_read', 'people_registry_manage', ], }, { id: 'people_hiring', label: 'Processos e equipas', path: '/pessoas', roles: [ 'admin', 'people_service_prepare', 'people_service_review', 'people_staff_prepare', 'people_staff_review', ], }, { id: 'people_services', label: 'Serviços individuais', path: '/pessoas?kind=service', roles: [ 'admin', 'people_service_prepare', 'people_service_review', ], }, { id: 'people_staff', label: 'Recrutamento e selecção', path: '/pessoas?kind=staff', roles: [ 'admin', 'people_staff_prepare', 'people_staff_review', ], }, { id: 'people_reports', label: 'Relatórios de pessoas e contratações', path: '/pessoas/relatorios', roles: [ 'admin', 'people_service_prepare', 'people_service_review', 'people_staff_prepare', 'people_staff_review', ], }, ], }, { id: 'receiving', label: 'Recepções e armazém', icon: Warehouse, entries: [ { id: 'receipts', label: 'Recepções e divergências', path: '/recepcoes', roles: inventory, }, { id: 'supply_reservations', label: 'Reservas de necessidades', path: '/compras/fornecimento?view=reservations', roles: ['admin', 'supply_read', 'supply_reserve'], }, { id: 'supply_history', label: 'Histórico de consumo', path: '/compras/fornecimento?view=history', roles: [ 'admin', 'supply_read', 'supply_history_prepare', 'supply_history_review', ], }, { id: 'warehouse_availability', label: 'Disponibilidade para requisições', path: '/stock/disponibilidade', roles: [ 'admin', 'warehouse', 'procurement', 'finance', 'warehouse_availability_read', ], }, { id: 'warehouse_replenishment', label: 'Reposição automática', path: '/stock/reposicao', roles: [ 'admin', 'supply_read', 'supply_prepare', 'supply_review', ], }, { id: 'warehouse_pilot', label: 'Preparação do piloto', path: '/stock/piloto', roles: ['admin'], }, { id: 'warehouse_requests', label: 'Requisições ao armazém', path: '/stock/requisicoes', roles: [ 'admin', 'warehouse_request_read', 'warehouse_request_prepare', 'warehouse_request_review', 'warehouse_request_deliver', ], }, { id: 'inventory_lots', label: 'Lotes, validade e quarentena', path: '/stock/lotes', roles: [ 'admin', 'inventory_lot_read', 'inventory_lot_prepare', 'inventory_lot_review', 'inventory_lot_hold', 'inventory_lot_release', ], }, { id: 'stock', label: 'Artigos e stock', path: '/stock', roles: ['admin', 'procurement', 'finance', 'warehouse'], }, { id: 'inventory_dispositions', label: 'Devoluções e correcções', path: '/stock/ocorrencias', roles: [ 'admin', 'warehouse', 'inventory_disposition_read', 'inventory_disposition_prepare', 'inventory_disposition_review', 'inventory_reversal_read', 'inventory_reversal_prepare', 'inventory_reversal_review', ], }, { id: 'stock_operations', label: 'Saídas e inventários', path: '/stock/operacoes', roles: [ 'admin', 'procurement', 'finance', 'warehouse', 'inventory_adjust_review', ], }, { id: 'equipment', label: 'Equipamentos e peças', path: '/equipamentos', roles: inventory, }, ], }, { id: 'finance', label: 'Finanças', icon: Wallet, entries: [ { id: 'invoices', label: 'Facturas e conferência', path: '/facturas', roles: finance, }, ...(payables ? [ { id: 'payables', label: 'Obrigações a fornecedores', path: '/obrigacoes-fornecedores', roles: finance, fullPage: true, }, ] : []), { id: 'payments', label: 'Pagamentos externos', path: '/pagamentos', roles: finance, }, { id: 'budgets', label: 'Orçamentos e reforços', path: '/orcamentos', roles: finance, }, { id: 'investment', label: 'Regras de investimento', path: '/regras-investimento', roles: ['admin', 'procurement', 'finance'], }, ], }, { id: 'administration', label: 'Administração e controlo', icon: ShieldCheck, entries: [ { id: 'company', label: 'Empresa, equipa e centros', path: '/empresa', roles: ['admin'], }, { id: 'organization_parameters', label: 'Parametrização da aplicação', path: '/administracao/parametrizacao', roles: ['admin'], }, { id: 'admission_policy', label: 'Política de admissão', path: '/politica-admissao', roles: ['admin'], }, { id: 'recommendation_policy', label: 'Política de recomendação', path: '/politica-recomendacao', roles: ['admin'], }, { id: 'conflicts', label: 'Conflitos de interesses', path: '/conflitos', }, ], }, { id: 'reports', label: 'Relatórios', icon: FileBarChart, entries: [ { id: 'procurement_reports', label: 'Relatórios de compras', path: '/relatorios', roles: readers, }, { id: 'supplier_improvement_reports', label: 'Medidas de melhoria', path: '/relatorios/medidas-fornecedores', roles: readers, }, ], }, ]; const purchasing = groups.filter((group) => ['requests', 'approvals', 'sourcing', 'suppliers'].includes(group.id), ); const compact = [ { id: 'purchasing', label: 'Compras', icon: ClipboardList, entries: purchasing.flatMap((group) => group.entries.map((entry) => ({ ...entry, section: group.label, })), ), }, ...groups .filter( (group) => ![ 'requests', 'approvals', 'sourcing', 'suppliers', ].includes(group.id), ) .map((group) => ({ ...group, label: group.id === 'administration' ? 'Administração' : group.id === 'receiving' ? 'Armazém e activos' : group.label, })), ]; return compact .map((group) => ({ ...group, entries: group.entries.filter((entry) => publicPaths ? publicPaths.includes( new URL(entry.path, 'http://local').pathname, ) : (entry.id !== 'new_request' || !Array.isArray(role) || role.includes('requester')) && (!entry.roles || entry.roles.some((permission) => (Array.isArray(role) ? role : [role]).includes( permission, ), )), ), })) .filter((group) => group.entries.length); } export function activeProcurementEntry( groups: ProcurementGroup[], url: string, ): string | undefined { const current = new URL(url, 'http://local'); if (/^\/ordens\/\d+\/recepcoes/.test(current.pathname)) return 'receipts'; if (/^\/requisicoes\/\d+\/adjudicacao(?:\/|$)/.test(current.pathname)) return 'orders'; if ( /^\/requisicoes\/\d+\/(cotacoes|abertura|qualificacao|admissao|recomendacao|relatorio)(?:\/|$)/.test( current.pathname, ) ) return 'quotations'; const matches = groups .flatMap((group) => group.entries) .filter((entry) => { const target = new URL(entry.path, 'http://local'); if ( current.pathname !== target.pathname && !current.pathname.startsWith(target.pathname + '/') ) return false; return [...target.searchParams].every( ([key, value]) => (current.searchParams.get(key) ?? (key === 'view' ? 'all' : null)) === value, ); }); const specificity = (path: string) => { const target = new URL(path, 'http://local'); return target.pathname.length * 100 + target.searchParams.size; }; matches.sort((a, b) => specificity(b.path) - specificity(a.path)); return matches[0]?.id; }