215 Commits 22b75e1c49 ... 85c92d4bda

Tác giả SHA1 Thông báo Ngày
  Gustavo Mantovani 85c92d4bda refactor: regras de atualizacao contrato 1 tháng trước cách đây
  Gustavo Mantovani 56d71e2cda feat: add track do historico dos contratos do estudante 1 tháng trước cách đây
  Gustavo Mantovani b420267815 refactor: altera obrigatoriedade dos campos de valores monetarios e remove input dos campos de semana no request de associados a package 1 tháng trước cách đây
  Gustavo Mantovani 417ffe7254 rfeat: adiciona bloqueio para assim como nao deixar email repetir entre responsaveis por mesmo aluno tbm nao deixa o cpf se repetir 1 tháng trước cách đây
  Gustavo Mantovani 5c371753df feat: add bloqueio de email repetido somente para responsaveis por mesmo aluno 1 tháng trước cách đây
  Gustavo Mantovani 19cc78abbe chore: formatacao 1 tháng trước cách đây
  Gustavo Mantovani f61282f755 chore: add comentarios 1 tháng trước cách đây
  Gustavo Mantovani cfb83e5450 refactor: fluxo para bloquear valores de rascunho de student 1 tháng trước cách đây
  Gustavo Mantovani 49a0c6c8e4 frefactor: add constraint unique para cpf em students 1 tháng trước cách đây
  Gustavo Mantovani 1bfb648b74 feat: protege para que responsaveis por estudantes nao sejam menores de idade 1 tháng trước cách đây
  Gustavo Mantovani 7b9d0dcc1a feat: add obrigatoriedade para cadastrar responsavel quando student eh menor de idade 1 tháng trước cách đây
  Gustavo Mantovani eb72b156db feat: obrigatoriedade de cadastro de responsavel caso estudante seja menor de idade 1 tháng trước cách đây
  Gustavo Mantovani 53fc066166 feat: add value object cnpj 1 tháng trước cách đây
  Gustavo Mantovani a235dd57b7 refactor: concede permissoes para os partners no seeder de development (teste) 1 tháng trước cách đây
  Gustavo Mantovani cb993574ae feat: add seeder para ambiente de development com units e partners ja cadastrados 1 tháng trước cách đây
  Gustavo Mantovani 2476391ab8 refactor: deixa address_number required no request de unit 1 tháng trước cách đây
  Gustavo Mantovani 4ca6706b21 refactor: deixa somente name e cpf obrigatorios no cadastro de unit_partner 1 tháng trước cách đây
  Gustavo Mantovani 349e0ff87c fix: middleware para transformar 'null' em null (problema quando tem multipart data) e ajuste em unit para deixar phone_number nullable e cell_number required no request 1 tháng trước cách đây
  Gustavo Mantovani 1879374303 feat: add value object 1 tháng trước cách đây
  Gustavo Mantovani 62214482d1 chore: formatacao 1 tháng trước cách đây
  Gustavo Mantovani c97734fc3c feat: add name para unit 1 tháng trước cách đây
  Gustavo Mantovani 93d7b5e0c5 reactor: torna fantasy name nullable 1 tháng trước cách đây
  ebagabee 130e983194 feat(financial): generate Asaas charges and lock paid status 1 tháng trước cách đây
  ebagabee 8a63edf554 fix(users): preserve timezone in last login timestamp 1 tháng trước cách đây
  ebagabee 47ee5253bd fix(contracts): show archived student data in listings 1 tháng trước cách đây
  ebagabee 108c4db688 fix(auth): allow franchisor scoped users to log in 1 tháng trước cách đây
  ebagabee 8cadf61585 feat: refactor FranchisorDashboardService to calculate unit receivables and remove unused constructor 1 tháng trước cách đây
  ebagabee da586dbeb7 feat: enhance UserSelectResource with additional user type fields 1 tháng trước cách đây
  ebagabee b70e514fb6 feat: add update and delete routes for treasury launches with appropriate permissions 1 tháng trước cách đây
  ebagabee 8c799ce2d0 feat: add notification for payment credited in FranchisorReceivableService and ProcessAsaasWebhookJob 1 tháng trước cách đây
  ebagabee 095958941d feat: Add manual receivable creation and management for franchisee accounts 1 tháng trước cách đây
  ebagabee a2a4cfe754 feat: add update and delete routes for treasury-launch with appropriate permissions 1 tháng trước cách đây
  ebagabee b9e2154ffd fix: simplify present_count calculation in attendance method 1 tháng trước cách đây
  ebagabee b086d58703 feat: enhance FranchisorDashboardService to include attendance and ticket details in overview 1 tháng trước cách đây
  ebagabee a8aa787a15 feat: add document_number and birth_date fields to StudentSelectResource 1 tháng trước cách đây
  ebagabee f0bdf3c070 feat: add scheduling fields to ClassPackage and ClassPackageUnit models, requests, resources, and migration 1 tháng trước cách đây
  ebagabee 1282bcb7f1 feat: enhance overview method to support date filtering and update service for revenue calculations 1 tháng trước cách đây
  ebagabee ce4d7e69d8 feat: remove unused selectList methods and add SelectController for selection endpoints 1 tháng trước cách đây
  ebagabee 7b4b9601f7 feat: add access_scope preparation in UserRequest for unit-based validation 1 tháng trước cách đây
  ebagabee a07506f608 feat: enhance getUserTypes method to support franchisor assignment and unit filtering 1 tháng trước cách đây
  ebagabee 20dcabaa87 Refactor and clean up routes and migrations 1 tháng trước cách đây
  ebagabee c2f85713cc feat: implement franchisee permissions management; add routes and update UserTypeController for permissions handling 1 tháng trước cách đây
  ebagabee 9aaff41ce7 feat: enhance UserController for franchisee user handling; add franchisee activities, integrations, support, and unit routes 1 tháng trước cách đây
  ebagabee 1f97081a20 feat: refactor ClassPackageUnit routes and service methods for active unit handling; add franchisee package routes 1 tháng trước cách đây
  ebagabee 642340c274 feat: remove obsolete financial and treasury routes; add new franchisee financial routes 1 tháng trước cách đây
  ebagabee f97271a728 feat: refactor ClassController and ClassService for active unit handling; add franchisee classes routes 1 tháng trước cách đây
  ebagabee c66226913c feat: add FranchiseeDashboardController and related routes; update permissions for franchisee students and contracts 1 tháng trước cách đây
  ebagabee 1e3aeeb955 feat: update permissions for class package unit and unit financial routes; add franchisee students routes 1 tháng trước cách đây
  ebagabee 1f977dfe49 feat: update routes for franchisee dashboard and remove unused holiday routes 1 tháng trước cách đây
  ebagabee 83efa081af feat: Refactor user type and permissions management 1 tháng trước cách đây
  ebagabee 82a15c8897 feat(permission-scope): rename 'config' scope to 'franchisor_registrations' and update related routes 1 tháng trước cách đây
  ebagabee 810a8af9d2 feat(permission-scope): rename 'kanban' scope to 'franchisor_activities' and update related routes 1 tháng trước cách đây
  ebagabee a7f3a64078 feat(permission-scope): rename 'support-ticket' scope to 'franchisor_support' and update related routes 1 tháng trước cách đây
  ebagabee 38b04fb091 feat(permission-scope): rename 'financial' scope to 'franchisor_financial' and update related routes 1 tháng trước cách đây
  ebagabee e38d04105f feat(permission-scope): rename 'tbr' scope to 'franchisor_tbr' and update related routes 1 tháng trước cách đây
  ebagabee 3bbd771fcd feat(permission-scope): rename 'product' scope to 'franchisor_products' and update related routes 1 tháng trước cách đây
  ebagabee 9bd4c48b6a feat(permission-scope): rename 'class-package' scope to 'franchisor_packages' and update related routes 1 tháng trước cách đây
  ebagabee 93210f39ea refactor(routes): remove unused franchisee and franchisee-unit routes 1 tháng trước cách đây
  ebagabee 25f429c51c feat(permission-scope): rename 'franchisee' scope to 'franchisor_franchisee' and update related routes 1 tháng trước cách đây
  ebagabee 5c040725d4 feat(access-scope): add access_scope field to user and user_type models and update related logic 1 tháng trước cách đây
  ebagabee cf65f4967a refactor(routes): agrupa o fluxo Dashboard (Franqueadora) no scope dashboard 1 tháng trước cách đây
  ebagabee 231a57ce58 feat(user-type): permite código customizado e edição de grupos 1 tháng trước cách đây
  ebagabee 9e68409e49 feat(user-type): gestão da matriz de permissões por perfil (etapa 1) 1 tháng trước cách đây
  ebagabee 75cf33ba73 feat(financial-dashboard): adiciona valor total do estoque no overview 1 tháng trước cách đây
  ebagabee 2e82dda7bf feat(student): enhance getAll method to filter students by contract dates 2 tháng trước cách đây
  ebagabee f4ce90a209 feat(financial): add manual accounts payable and receivable management 2 tháng trước cách đây
  ebagabee 194674e99c feat(financial): avulsos entram no dashboard da unidade e no faturamento do TBR 2 tháng trước cách đây
  ebagabee 2dc07264f8 feat(unit-receivable): recebíveis avulsos manuais da unidade 2 tháng trước cách đây
  ebagabee 8e9f21abdc feat(plan-account): hierarquia parent_id + visibilidade matriz/unidade 2 tháng trước cách đây
  ebagabee 23b93ffc1c feat(notifications): dispatch in-app, endpoints do sino e gatilhos 2 tháng trước cách đây
  ebagabee 6dcc583fe0 fix(permissions): franqueado pode criar/excluir usuários da unidade (config.user add/delete) 2 tháng trước cách đây
  ebagabee 8b3fc93af9 feat(treasury-launch): extrato e movimentação /me com guarda de posse da conta 2 tháng trước cách đây
  ebagabee 5703d50e05 feat(treasury-account): endpoints /me com escopo pela unidade e saldo por conta 2 tháng trước cách đây
  ebagabee 7f4bed823f feat(financial-plan-account): endpoints /me com escopo pela unidade ativa (CRUD) 2 tháng trước cách đây
  ebagabee 89dfb2dce4 feat(unit-financial-dashboard): agregação de recebíveis e pagáveis da unidade no mês 2 tháng trước cách đây
  ebagabee 3a4ce0288b feat(tbr): geração automática mensal do TBR do mês fechado via schedule 2 tháng trước cách đây
  ebagabee a833f354af feat(tbr): faturamento real por competência alimenta o cálculo (fixo x % faturamento) 2 tháng trước cách đây
  ebagabee fec73f2ff9 feat(tbr): permite user_id nulo em tbr_calculations (geração automática pelo sistema) 2 tháng trước cách đây
  ebagabee 47af112e21 feat(company-payment-account): endpoints para a matriz configurar a própria chave Asaas 2 tháng trước cách đây
  ebagabee 7809b7d511 feat(asaas): resolve a chave da matriz do banco com fallback para o .env 2 tháng trước cách đây
  ebagabee ac285f9970 feat(company-payment-account): tabela e model da conta Asaas da matriz (linha única) 2 tháng trước cách đây
  ebagabee 7b21f1ca21 feat(unit-payment-account): dedupe de webhook quando várias unidades usam a mesma chave 2 tháng trước cách đây
  ebagabee a28f5fbc4e feat(unit-payment-account): coluna de hash da chave para detectar conta compartilhada 2 tháng trước cách đây
  ebagabee f97bf8a29d feat(unit-receivable): endpoint de gerar cobrança sob demanda e campos Asaas no resource 2 tháng trước cách đây
  ebagabee 21de89e46d feat(asaas): cobrança da parcela do aluno na conta Asaas da própria unidade 2 tháng trước cách đây
  ebagabee 96cee323b9 feat(asaas): ramo STU_ no webhook para baixar a parcela do aluno quando paga 2 tháng trước cách đây
  ebagabee d35f41cde7 feat(unit-payment-account): valida chave e registra webhook ao salvar; remove ao limpar 2 tháng trước cách đây
  ebagabee c889955537 feat(asaas): service para validar chave e registrar/remover webhook da unidade 2 tháng trước cách đây
  ebagabee adf3825fdf feat(asaas): infra de webhook por unidade (coluna, config de URL e delete no client) 2 tháng trước cách đây
  ebagabee 6c172d50b5 feat(asaas): baixa automática do a pagar da unidade quando o TBR é pago via webhook 2 tháng trước cách đây
  ebagabee 6c8dbb3c1b feat(asaas): emite cobrança do TBR apenas quando a matriz tem chave configurada 2 tháng trước cách đây
  ebagabee 49719e8f52 feat(unit-payable): endpoints /me de listagem, criação, baixa e reabertura 2 tháng trước cách đây
  ebagabee 0b86aa11b9 feat(unit-payable): service com baixa manual, reabertura e lançamento avulso 2 tháng trước cách đây
  ebagabee 65bbce6896 feat(tbr): gera conta a pagar da unidade ao efetivar a cobrança do TBR 2 tháng trước cách đây
  ebagabee 96ea15f1a8 feat(unit-payable): model UnitAccountPayable com origem tbr/manual 2 tháng trước cách đây
  ebagabee 8117464285 feat(unit-payable): migration de contas a pagar da unidade 2 tháng trước cách đây
  ebagabee ddc12954e4 feat(unit-receivable): endpoints /me de listagem, baixa e reabertura 2 tháng trước cách đây
  ebagabee 66708d99b8 feat(unit-receivable): service de contas a receber da unidade com baixa manual 2 tháng trước cách đây
  ebagabee d07878b1f6 feat(unit-payment-account): endpoints /me para salvar e consultar a chave Asaas da unidade 2 tháng trước cách đây
  ebagabee 4418b64591 feat(unit-payment-account): model com api_key criptografada e relação em Unit 2 tháng trước cách đây
  ebagabee 336b1db5f6 feat(unit-payment-account): migration da conta de pagamento própria da unidade 2 tháng trước cách đây
  ebagabee 25859101d3 feat(class): endpoints de alunos e registro de presença 2 tháng trước cách đây
  ebagabee d59b3f85ee feat(class): lógica de presença e alunos por contrato ativo 2 tháng trước cách đây
  ebagabee e2700931e2 feat(class): resource com campos reais e validação do request 2 tháng trước cách đây
  ebagabee 1a8d40f1a8 feat(class): relacionamentos e casts em SchoolClass e ClassAttendance 2 tháng trước cách đây
  ebagabee ab7b5a2f56 feat(class): migration de campos de aula e justificativa de presença 2 tháng trước cách đây
  ebagabee 9bda05605f refactor(franchisor-dashboard): optimize enrollments query and sort birthdays 2 tháng trước cách đây
  ebagabee af6a4ac2e7 feat(franchisor-dashboard): add overview endpoint and service for franchisor dashboard metrics 2 tháng trước cách đây
  ebagabee 73062437e7 refactor(treasury): add financial plan account relationship and include in resource 2 tháng trước cách đây
  ebagabee a67152d595 refactor(asaas): trim webhook and customer service to the matriz flow 2 tháng trước cách đây
  ebagabee 30f86d20e8 refactor(asaas): stop dispatching student charges 2 tháng trước cách đây
  ebagabee 54e4ee236b refactor(asaas): drop unit_payment_accounts table 2 tháng trước cách đây
  ebagabee becc737884 refactor(asaas): remove white-label subaccount code 2 tháng trước cách đây
  ebagabee c5580a7f36 refactor(asaas): stop creating unit subaccounts on unit creation 2 tháng trước cách đây
  ebagabee 8c0dfcfd66 fix(tbr): skip royalties bracket lookup when ROI charge is off 2 tháng trước cách đây
  ebagabee 73f9a69f22 fix(tbr): allow null royalties bracket and free criteria value 2 tháng trước cách đây
  ebagabee b79d87fd36 feat(tbr): respect unit ROI/FNM charge flags in calculation 2 tháng trước cách đây
  ebagabee ed8ff2c52e feat(unit-financial): create financial record on unit creation 2 tháng trước cách đây
  ebagabee 20bac4a57c feat(unit-financial): default ROI/FNM charge flags to true 2 tháng trước cách đây
  ebagabee 409787f288 feat(asaas-account): enhance subaccount creation with error handling and logging 2 tháng trước cách đây
  ebagabee ab93f46a41 fix(chart-of-accounts): grant franchisor full perms on plan accounts 2 tháng trước cách đây
  ebagabee 589cf6aad4 feat(chart-of-accounts): seed default matriz chart of accounts 2 tháng trước cách đây
  ebagabee 1996ff1565 feat(chart-of-accounts): scope listing by unit and order by code 2 tháng trước cách đây
  ebagabee a0fb2f6175 feat(chart-of-accounts): expose plan account fields in resource 2 tháng trước cách đây
  ebagabee efa1479a84 feat(chart-of-accounts): add validation to plan account request 2 tháng trước cách đây
  ebagabee a5761e8e01 feat(chart-of-accounts): allow null unit_id for matriz plan accounts 2 tháng trước cách đây
  ebagabee 2279932a1b feat(treasury): compute account balance from launches 2 tháng trước cách đây
  ebagabee c30e843144 feat(treasury): map manual entrada/saida and scope listing 2 tháng trước cách đây
  ebagabee 3dba897b5e feat(treasury): expose launch fields and add casts 2 tháng trước cách đây
  ebagabee 5cf9681b9c feat(treasury): add validation to launch request 2 tháng trước cách đây
  ebagabee 696e994782 feat(treasury): relax launch columns for manual entries 2 tháng trước cách đây
  ebagabee 586c4434e6 feat(treasury): scope account listing by unit 2 tháng trước cách đây
  ebagabee 59f37586e9 feat(treasury): expose full account fields in resource 2 tháng trước cách đây
  ebagabee 927ac22c8b feat(treasury): add validation rules to account request 2 tháng trước cách đây
  ebagabee ce6c862ca1 feat(treasury): allow null unit_id for franchisor network accounts 2 tháng trước cách đây
  ebagabee e2e26489a2 feat: endpoint consolidado de contas a receber da matriz (TBR) 2 tháng trước cách đây
  ebagabee 521496a0cc feat: TBR usa o mês de competência escolhido na geração do título 2 tháng trước cách đây
  ebagabee 60dda4c185 fix: idempotência e retry do CreateAsaasSubaccountJob 2 tháng trước cách đây
  ebagabee 3e032ed7e7 feat: endpoint de visão geral financeira consolidada (#CB017) 2 tháng trước cách đây
  ebagabee 4d63e97587 feat: reautenticação por senha para acesso ao Financeiro (#CB017) 2 tháng trước cách đây
  ebagabee 6c4414ae9b feat: implement franchisee receivables endpoint and automate Asaas subaccount and payment synchronization jobs 3 tháng trước cách đây
  ebagabee 7fa0739063 feat: implement student installment synchronization with Asaas, including encryption for API keys and database schema updates. 3 tháng trước cách đây
  ebagabee 87c8fbe94e feat: implement asynchronous webhook handler for Asaas payment notifications 3 tháng trước cách đây
  ebagabee 76f840a88c fix: add missing line breaks to franchisee account receive detail creation logic 3 tháng trước cách đây
  ebagabee a0195e7f8b fix: validação de telefone na integração com Asaas 3 tháng trước cách đây
  ebagabee 7eec42feee feat: motor de cobrança TBR via Asaas (Phase 3) 3 tháng trước cách đây
  ebagabee b17d87b70f feat: integração subcontas white-label Asaas (Phase 2) 3 tháng trước cách đây
  ebagabee 6fe5f8a8cd feat: fundação da integração com Asaas (Phase 1) 3 tháng trước cách đây
  ebagabee bad55403b1 feat: adiciona tab de midias 3 tháng trước cách đây
  Gabriel Alves 5a53387ebd Merge branch 'feature/GINC-GAB-fluxo-kanban-franqueadora-franquia' of Softpar/sfp_api_laravel_ginastica_cerebro into development 3 tháng trước cách đây
  ebagabee 4b019288c7 fix(kanban): remove orWhere(scope, all) from visibleToUnit scope 3 tháng trước cách đây
  ebagabee 81cb2e7585 feat(kanban): add order field and reorder endpoint for drag-and-drop 3 tháng trước cách đây
  ebagabee 826eef8ca6 feat(kanban): adapt backend for kanban task board 3 tháng trước cách đây
  ebagabee 8966bb88a4 feat: resolve active unit from request in controllers and services 3 tháng trước cách đây
  ebagabee ad254ea2f1 feat: support multi-unit association for users 3 tháng trước cách đây
  ebagabee 6bb046d58d feat: add ResolveActiveUnit middleware and ResolvesActiveUnit trait 3 tháng trước cách đây
  ebagabee e527749efd feat: adiciona dashboard correto 3 tháng trước cách đây
  ebagabee 26c40948e5 feat: adiciona nova logica em feriado criado por franchisor 3 tháng trước cách đây
  ebagabee e9865e71bc feat: adiciona congelamento de contrato 3 tháng trước cách đây
  ebagabee 1b393aea71 feat: adiciona novos campos para cadastro/edit de unidade 3 tháng trước cách đây
  ebagabee fac716ade5 feat: adiciona funcionalidade de criacao de contas a receber do aluno 3 tháng trước cách đây
  ebagabee 8c2872d572 chore(tbr): remove franchisee_tbrs, tabelas de brackets e unit_inhabitant_classifications 3 tháng trước cách đây
  ebagabee 7d3ba4587c feat(tbr-calculation): refatora calculo usando contratos como fonte + fluxo em lote 3 tháng trước cách đây
  ebagabee 5cc68040d8 feat(receivables): adiciona tabelas de Contas a Receber dos franqueados 3 tháng trước cách đây
  ebagabee 62261ee16a feat(franchisee-contracts): troca inhabitant_classification_id por municipality_size_id 3 tháng trước cách đây
  ebagabee 58e17a37e8 refactor(inhabitant-classifications): consolida base de faixas por porte 3 tháng trước cách đây
  ebagabee fe0af22a8e feat(tbr): adiciona seeder com valores anuais de TBR (2024/2025/2026) 3 tháng trước cách đây
  ebagabee 235cf499cf feat(municipality-sizes): adiciona tabela de portes de municipio (PP/MP/GP/MGP) 3 tháng trước cách đây
  ebagabee aa243e2fb3 feat(packages): corrige, buscando apenas visible true 3 tháng trước cách đây
  ebagabee c88176876f feat(filter): adiciona filtros de unidades e grupos em dash 3 tháng trước cách đây
  ebagabee 77aae87f0e feat(class_package): adiciona grupos 3 tháng trước cách đây
  ebagabee 7e7d6a7d41 feat: adiciona modulo de grupos 3 tháng trước cách đây
  Gabriel Alves 60fa1f4f5c Merge branch 'feature/GINC-GAB-suporte' of Softpar/sfp_api_laravel_ginastica_cerebro into development 3 tháng trước cách đây
  ebagabee 8484c56ffb feat(support): adiciona validacao de suporte 3 tháng trước cách đây
  ebagabee cce3c8a530 feat(support): adiciona ticket para franchisee 3 tháng trước cách đây
  ebagabee f60c64a0f6 feat: adiciona botao de editar e excluir 3 tháng trước cách đây
  ebagabee 668dbc530d feat: adiciona comentarios em ticket 3 tháng trước cách đây
  ebagabee 408ea0905d feat(support): altera migration e adiciona escopo e setor 3 tháng trước cách đây
  ebagabee fb16d87bac feat: adiciona permissoes geral para holidays 3 tháng trước cách đây
  ebagabee 39827a8e37 chore: adiciona limite maior em imagem 4 tháng trước cách đây
  ebagabee b66562ee4a chore: ajusta tamanho maximo de imagem 4 tháng trước cách đây
  Gabriel Alves 8f2fe80561 Merge branch 'feature/GINC-GAB-CB007-fluxo-produtos' of Softpar/sfp_api_laravel_ginastica_cerebro into development 4 tháng trước cách đây
  ebagabee ee119fffbe feat(products): adiciona ordem alfabetica 4 tháng trước cách đây
  ebagabee 56df5c13f6 feat(products): adiciona importacao de produtos 4 tháng trước cách đây
  ebagabee c6f62cbf1e feat(products): adiciona estoque 4 tháng trước cách đây
  ebagabee 437fe2dc96 feat(products): adiciona quantidade 4 tháng trước cách đây
  ebagabee ee54318d3a feat(products): remove campos obrigatorios, adiciona request e resource produto 4 tháng trước cách đây
  ebagabee aff07c5dbe chore: adiciona middleware em rotas que nao tinham 4 tháng trước cách đây
  ebagabee 9656bd2ba6 feat: adiciona metodos para buscar contratos congelados, ativos e cancelados 4 tháng trước cách đây
  ebagabee 4d890c39dd feat(students): adiciona novo metodo para capturar alunos ativos 4 tháng trước cách đây
  Gabriel Alves 7ba25b81a2 Merge branch 'feature/GINC-GAB-CB003-descongelar-contrato' of Softpar/sfp_api_laravel_ginastica_cerebro into development 4 tháng trước cách đây
  ebagabee aaf5b76289 feat: adiciona midia de students 4 tháng trước cách đây
  ebagabee 09fe77b912 chore: renomeia unfreeze para reactivate 4 tháng trước cách đây
  ebagabee e861c7a795 refactor(student_contracts): adiciona metodos para congelar e cancelar contrato 4 tháng trước cách đây
  ebagabee 9d4d594e8b feat(student_contracts): adiciona logica de descongelar contrato. 4 tháng trước cách đây
  Gabriel Alves e66d2fba05 Merge branch 'feature/GINC-GAB-CB002-feriados-base' of Softpar/sfp_api_laravel_ginastica_cerebro into development 4 tháng trước cách đây
  ebagabee ef4df2b281 feat: adiciona base holiday no sistema 4 tháng trước cách đây
  ebagabee 46409639c2 chore: altera restricao de 2mb para 10mb em avatar para unidade 4 tháng trước cách đây
  ebagabee 38e9f81db9 feat: adiciona listagem de responsaveis 4 tháng trước cách đây
  ebagabee 41fb99117f feat: adiciona tipo de usuarios 4 tháng trước cách đây
  ebagabee 5c78999de8 feat: adiciona qual o pacote dentro de contratos 4 tháng trước cách đây
  ebagabee 5cdd993bc7 feat: adiciona dia recorrente 4 tháng trước cách đây
  ebagabee 192bff34d9 fix: resolve origem 4 tháng trước cách đây
  ebagabee d39397cfe1 feat: adiciona faixa de habitantes em contrato 4 tháng trước cách đây
  ebagabee d22a84b7fc chore: remove middleware por ora em inhabitant class 4 tháng trước cách đây
  Gabriel Alves 394ccfb107 Merge branch 'feature/GC-GAB-cadastro-colaboradores' of Softpar/sfp_api_laravel_ginastica_cerebro into development 4 tháng trước cách đây
  ebagabee 29d7fe1699 feat: adiciona crud de tipo de usuarios e adiciona usuario para franquia 4 tháng trước cách đây
  ebagabee fa1ee7b280 fix: correcao de senha 4 tháng trước cách đây
  ebagabee ea3e116094 feat: adiciona criacao e edicao de usuario 4 tháng trước cách đây
  ebagabee fdb997b7da feat: adiciona layout para adicionar usuarios a unidade 4 tháng trước cách đây
  ebagabee 2c65790730 feat: ajusta ordem 4 tháng trước cách đây
  ebagabee fd9d87744a feat: ordem de contratos 4 tháng trước cách đây
  ebagabee 9a8143294d feat: busca por contrato 4 tháng trước cách đây
  ebagabee 777ce790bc Merge branch 'development' of gogs.softpar.inf.br:Softpar/sfp_api_laravel_ginastica_cerebro into development 4 tháng trước cách đây
  ebagabee 99ed16bbf0 feat: adiciona funcionalidade de congelar e cancelar contrato 4 tháng trước cách đây
100 tập tin đã thay đổi với 2938 bổ sung1845 xóa
  1. 4 0
      .env.example
  2. 1 0
      .gitignore
  3. 32 32
      .phpstorm.meta.php
  4. 90 108
      _ide_helper.php
  5. 24 0
      app/Casts/CnpjCast.php
  6. 24 0
      app/Casts/CpfCast.php
  7. 51 0
      app/Console/Commands/AsaasSmokeTestCommand.php
  8. 41 0
      app/Console/Commands/ClearStudentRegistrationDrafts.php
  9. 101 0
      app/Console/Commands/GenerateMonthlyTbrCommand.php
  10. 36 0
      app/Enums/NotificationType.php
  11. 12 0
      app/Enums/ReceivableStatus.php
  12. 28 0
      app/Exceptions/AsaasException.php
  13. 16 0
      app/Http/Controllers/AuthController.php
  14. 45 0
      app/Http/Controllers/BaseHolidayController.php
  15. 52 5
      app/Http/Controllers/ClassController.php
  16. 4 2
      app/Http/Controllers/ClassPackageController.php
  17. 0 45
      app/Http/Controllers/ClassPackageFranchiseeController.php
  18. 22 9
      app/Http/Controllers/ClassPackageUnitController.php
  19. 72 0
      app/Http/Controllers/CompanyAccountPayableController.php
  20. 32 0
      app/Http/Controllers/CompanyPaymentAccountController.php
  21. 28 0
      app/Http/Controllers/Concerns/ResolvesActiveUnit.php
  22. 0 45
      app/Http/Controllers/FinancialAccountPayableController.php
  23. 0 45
      app/Http/Controllers/FinancialAccountReceiveController.php
  24. 18 0
      app/Http/Controllers/FinancialDashboardController.php
  25. 0 45
      app/Http/Controllers/FinancialInvoiceController.php
  26. 62 1
      app/Http/Controllers/FinancialPlanAccountController.php
  27. 0 45
      app/Http/Controllers/FnmBaseBracketController.php
  28. 92 0
      app/Http/Controllers/FranchiseeAccountReceiveController.php
  29. 4 4
      app/Http/Controllers/FranchiseeContractController.php
  30. 42 0
      app/Http/Controllers/FranchiseeDashboardController.php
  31. 0 46
      app/Http/Controllers/FranchiseeFnmBracketController.php
  32. 0 45
      app/Http/Controllers/FranchiseeInventoryController.php
  33. 0 46
      app/Http/Controllers/FranchiseeMaintenanceBracketController.php
  34. 0 46
      app/Http/Controllers/FranchiseeRoyaltiesBracketController.php
  35. 0 46
      app/Http/Controllers/FranchiseeTbrController.php
  36. 32 0
      app/Http/Controllers/FranchisorDashboardController.php
  37. 0 45
      app/Http/Controllers/FranchisorInventoryController.php
  38. 50 0
      app/Http/Controllers/GroupController.php
  39. 14 0
      app/Http/Controllers/HolidayController.php
  40. 0 5
      app/Http/Controllers/InhabitantClassificationController.php
  41. 0 45
      app/Http/Controllers/IntegrationVariableController.php
  42. 0 45
      app/Http/Controllers/IntegrationsController.php
  43. 101 3
      app/Http/Controllers/KanbanController.php
  44. 33 0
      app/Http/Controllers/KanbanMediaController.php
  45. 0 45
      app/Http/Controllers/KanbanMovementController.php
  46. 80 0
      app/Http/Controllers/KanbanReplyController.php
  47. 0 45
      app/Http/Controllers/KanbanStatusController.php
  48. 0 45
      app/Http/Controllers/MaintenanceBaseBracketController.php
  49. 0 46
      app/Http/Controllers/MediaController.php
  50. 0 45
      app/Http/Controllers/ModalityController.php
  51. 29 0
      app/Http/Controllers/NotificationController.php
  52. 0 45
      app/Http/Controllers/NotificationRecipientController.php
  53. 0 45
      app/Http/Controllers/PaymentMethodController.php
  54. 0 55
      app/Http/Controllers/PermissionController.php
  55. 59 6
      app/Http/Controllers/ProductController.php
  56. 0 45
      app/Http/Controllers/ProductKitController.php
  57. 0 45
      app/Http/Controllers/ProductMovementController.php
  58. 0 45
      app/Http/Controllers/ProductOrderController.php
  59. 0 45
      app/Http/Controllers/ProductOrderItemController.php
  60. 0 45
      app/Http/Controllers/RoyaltiesBaseBracketController.php
  61. 171 0
      app/Http/Controllers/SelectController.php
  62. 112 10
      app/Http/Controllers/StudentContractController.php
  63. 37 3
      app/Http/Controllers/StudentController.php
  64. 40 0
      app/Http/Controllers/StudentMediaController.php
  65. 2 2
      app/Http/Controllers/StudentResponsibleController.php
  66. 0 45
      app/Http/Controllers/SupplierController.php
  67. 0 45
      app/Http/Controllers/SupportMovementController.php
  68. 80 0
      app/Http/Controllers/SupportReplyController.php
  69. 0 45
      app/Http/Controllers/SupportStatusController.php
  70. 130 4
      app/Http/Controllers/SupportTicketController.php
  71. 49 2
      app/Http/Controllers/TbrCalculationController.php
  72. 63 2
      app/Http/Controllers/TreasuryAccountController.php
  73. 0 45
      app/Http/Controllers/TreasuryImportLineController.php
  74. 0 45
      app/Http/Controllers/TreasuryImportsController.php
  75. 90 3
      app/Http/Controllers/TreasuryLaunchController.php
  76. 95 0
      app/Http/Controllers/UnitAccountPayableController.php
  77. 9 19
      app/Http/Controllers/UnitController.php
  78. 20 4
      app/Http/Controllers/UnitFinancialController.php
  79. 27 0
      app/Http/Controllers/UnitFinancialDashboardController.php
  80. 4 4
      app/Http/Controllers/UnitHistoryController.php
  81. 0 53
      app/Http/Controllers/UnitInhabitantClassificationController.php
  82. 4 4
      app/Http/Controllers/UnitMediaController.php
  83. 4 4
      app/Http/Controllers/UnitPartnerController.php
  84. 49 0
      app/Http/Controllers/UnitPaymentAccountController.php
  85. 173 0
      app/Http/Controllers/UnitReceivableController.php
  86. 0 45
      app/Http/Controllers/UnitUserController.php
  87. 51 14
      app/Http/Controllers/UserController.php
  88. 189 0
      app/Http/Controllers/UserTypeController.php
  89. 4 1
      app/Http/Controllers/UserTypePermissionController.php
  90. 44 0
      app/Http/Controllers/Webhooks/AsaasWebhookController.php
  91. 20 0
      app/Http/Middleware/ConvertStringNullToNull.php
  92. 34 0
      app/Http/Middleware/ResolveActiveUnit.php
  93. 27 0
      app/Http/Requests/BaseHolidayRequest.php
  94. 0 34
      app/Http/Requests/ClassPackageFranchiseeRequest.php
  95. 19 6
      app/Http/Requests/ClassPackageRequest.php
  96. 8 2
      app/Http/Requests/ClassPackageUnitRequest.php
  97. 22 10
      app/Http/Requests/ClassRequest.php
  98. 15 0
      app/Http/Requests/CompanyPaymentAccountRequest.php
  99. 15 0
      app/Http/Requests/ConfirmPasswordRequest.php
  100. 0 34
      app/Http/Requests/FinancialAccountPayableRequest.php

+ 4 - 0
.env.example

@@ -13,6 +13,7 @@ APP_MAINTENANCE_DRIVER=file
 APP_MAINTENANCE_STORE=database
 APP_MAINTENANCE_STORE=database
 
 
 BCRYPT_ROUNDS=12
 BCRYPT_ROUNDS=12
+STUDENT_REGISTRATION_DRAFT_TTL_MINUTES=15
 
 
 LOG_CHANNEL=stack
 LOG_CHANNEL=stack
 LOG_STACK=single
 LOG_STACK=single
@@ -64,3 +65,6 @@ AWS_BUCKET=
 AWS_URL=https://
 AWS_URL=https://
 AWS_USE_PATH_STYLE_ENDPOINT=false
 AWS_USE_PATH_STYLE_ENDPOINT=false
 
 
+ASAAS_BASE_URL=https://sandbox.asaas.com/api/v3
+ASAAS_API_KEY=
+ASAAS_WEBHOOK_TOKEN=

+ 1 - 0
.gitignore

@@ -18,3 +18,4 @@ yarn-error.log
 /.fleet
 /.fleet
 /.idea
 /.idea
 /.vscode
 /.vscode
+dev_scripts/

+ 32 - 32
.phpstorm.meta.php

@@ -46,8 +46,8 @@
             'events' => \Illuminate\Events\Dispatcher::class,
             'events' => \Illuminate\Events\Dispatcher::class,
             'files' => \Illuminate\Filesystem\Filesystem::class,
             'files' => \Illuminate\Filesystem\Filesystem::class,
             'filesystem' => \Illuminate\Filesystem\FilesystemManager::class,
             'filesystem' => \Illuminate\Filesystem\FilesystemManager::class,
-            'filesystem.cloud' => \Illuminate\Filesystem\LocalFilesystemAdapter::class,
-            'filesystem.disk' => \Illuminate\Filesystem\LocalFilesystemAdapter::class,
+            'filesystem.cloud' => \Illuminate\Filesystem\AwsS3V3Adapter::class,
+            'filesystem.disk' => \Illuminate\Filesystem\AwsS3V3Adapter::class,
             'hash' => \Illuminate\Hashing\HashManager::class,
             'hash' => \Illuminate\Hashing\HashManager::class,
             'hash.driver' => \Illuminate\Hashing\BcryptHasher::class,
             'hash.driver' => \Illuminate\Hashing\BcryptHasher::class,
             'log' => \Illuminate\Log\LogManager::class,
             'log' => \Illuminate\Log\LogManager::class,
@@ -112,8 +112,8 @@
             'events' => \Illuminate\Events\Dispatcher::class,
             'events' => \Illuminate\Events\Dispatcher::class,
             'files' => \Illuminate\Filesystem\Filesystem::class,
             'files' => \Illuminate\Filesystem\Filesystem::class,
             'filesystem' => \Illuminate\Filesystem\FilesystemManager::class,
             'filesystem' => \Illuminate\Filesystem\FilesystemManager::class,
-            'filesystem.cloud' => \Illuminate\Filesystem\LocalFilesystemAdapter::class,
-            'filesystem.disk' => \Illuminate\Filesystem\LocalFilesystemAdapter::class,
+            'filesystem.cloud' => \Illuminate\Filesystem\AwsS3V3Adapter::class,
+            'filesystem.disk' => \Illuminate\Filesystem\AwsS3V3Adapter::class,
             'hash' => \Illuminate\Hashing\HashManager::class,
             'hash' => \Illuminate\Hashing\HashManager::class,
             'hash.driver' => \Illuminate\Hashing\BcryptHasher::class,
             'hash.driver' => \Illuminate\Hashing\BcryptHasher::class,
             'log' => \Illuminate\Log\LogManager::class,
             'log' => \Illuminate\Log\LogManager::class,
@@ -178,8 +178,8 @@
             'events' => \Illuminate\Events\Dispatcher::class,
             'events' => \Illuminate\Events\Dispatcher::class,
             'files' => \Illuminate\Filesystem\Filesystem::class,
             'files' => \Illuminate\Filesystem\Filesystem::class,
             'filesystem' => \Illuminate\Filesystem\FilesystemManager::class,
             'filesystem' => \Illuminate\Filesystem\FilesystemManager::class,
-            'filesystem.cloud' => \Illuminate\Filesystem\LocalFilesystemAdapter::class,
-            'filesystem.disk' => \Illuminate\Filesystem\LocalFilesystemAdapter::class,
+            'filesystem.cloud' => \Illuminate\Filesystem\AwsS3V3Adapter::class,
+            'filesystem.disk' => \Illuminate\Filesystem\AwsS3V3Adapter::class,
             'hash' => \Illuminate\Hashing\HashManager::class,
             'hash' => \Illuminate\Hashing\HashManager::class,
             'hash.driver' => \Illuminate\Hashing\BcryptHasher::class,
             'hash.driver' => \Illuminate\Hashing\BcryptHasher::class,
             'log' => \Illuminate\Log\LogManager::class,
             'log' => \Illuminate\Log\LogManager::class,
@@ -244,8 +244,8 @@
             'events' => \Illuminate\Events\Dispatcher::class,
             'events' => \Illuminate\Events\Dispatcher::class,
             'files' => \Illuminate\Filesystem\Filesystem::class,
             'files' => \Illuminate\Filesystem\Filesystem::class,
             'filesystem' => \Illuminate\Filesystem\FilesystemManager::class,
             'filesystem' => \Illuminate\Filesystem\FilesystemManager::class,
-            'filesystem.cloud' => \Illuminate\Filesystem\LocalFilesystemAdapter::class,
-            'filesystem.disk' => \Illuminate\Filesystem\LocalFilesystemAdapter::class,
+            'filesystem.cloud' => \Illuminate\Filesystem\AwsS3V3Adapter::class,
+            'filesystem.disk' => \Illuminate\Filesystem\AwsS3V3Adapter::class,
             'hash' => \Illuminate\Hashing\HashManager::class,
             'hash' => \Illuminate\Hashing\HashManager::class,
             'hash.driver' => \Illuminate\Hashing\BcryptHasher::class,
             'hash.driver' => \Illuminate\Hashing\BcryptHasher::class,
             'log' => \Illuminate\Log\LogManager::class,
             'log' => \Illuminate\Log\LogManager::class,
@@ -310,8 +310,8 @@
             'events' => \Illuminate\Events\Dispatcher::class,
             'events' => \Illuminate\Events\Dispatcher::class,
             'files' => \Illuminate\Filesystem\Filesystem::class,
             'files' => \Illuminate\Filesystem\Filesystem::class,
             'filesystem' => \Illuminate\Filesystem\FilesystemManager::class,
             'filesystem' => \Illuminate\Filesystem\FilesystemManager::class,
-            'filesystem.cloud' => \Illuminate\Filesystem\LocalFilesystemAdapter::class,
-            'filesystem.disk' => \Illuminate\Filesystem\LocalFilesystemAdapter::class,
+            'filesystem.cloud' => \Illuminate\Filesystem\AwsS3V3Adapter::class,
+            'filesystem.disk' => \Illuminate\Filesystem\AwsS3V3Adapter::class,
             'hash' => \Illuminate\Hashing\HashManager::class,
             'hash' => \Illuminate\Hashing\HashManager::class,
             'hash.driver' => \Illuminate\Hashing\BcryptHasher::class,
             'hash.driver' => \Illuminate\Hashing\BcryptHasher::class,
             'log' => \Illuminate\Log\LogManager::class,
             'log' => \Illuminate\Log\LogManager::class,
@@ -376,8 +376,8 @@
             'events' => \Illuminate\Events\Dispatcher::class,
             'events' => \Illuminate\Events\Dispatcher::class,
             'files' => \Illuminate\Filesystem\Filesystem::class,
             'files' => \Illuminate\Filesystem\Filesystem::class,
             'filesystem' => \Illuminate\Filesystem\FilesystemManager::class,
             'filesystem' => \Illuminate\Filesystem\FilesystemManager::class,
-            'filesystem.cloud' => \Illuminate\Filesystem\LocalFilesystemAdapter::class,
-            'filesystem.disk' => \Illuminate\Filesystem\LocalFilesystemAdapter::class,
+            'filesystem.cloud' => \Illuminate\Filesystem\AwsS3V3Adapter::class,
+            'filesystem.disk' => \Illuminate\Filesystem\AwsS3V3Adapter::class,
             'hash' => \Illuminate\Hashing\HashManager::class,
             'hash' => \Illuminate\Hashing\HashManager::class,
             'hash.driver' => \Illuminate\Hashing\BcryptHasher::class,
             'hash.driver' => \Illuminate\Hashing\BcryptHasher::class,
             'log' => \Illuminate\Log\LogManager::class,
             'log' => \Illuminate\Log\LogManager::class,
@@ -442,8 +442,8 @@
             'events' => \Illuminate\Events\Dispatcher::class,
             'events' => \Illuminate\Events\Dispatcher::class,
             'files' => \Illuminate\Filesystem\Filesystem::class,
             'files' => \Illuminate\Filesystem\Filesystem::class,
             'filesystem' => \Illuminate\Filesystem\FilesystemManager::class,
             'filesystem' => \Illuminate\Filesystem\FilesystemManager::class,
-            'filesystem.cloud' => \Illuminate\Filesystem\LocalFilesystemAdapter::class,
-            'filesystem.disk' => \Illuminate\Filesystem\LocalFilesystemAdapter::class,
+            'filesystem.cloud' => \Illuminate\Filesystem\AwsS3V3Adapter::class,
+            'filesystem.disk' => \Illuminate\Filesystem\AwsS3V3Adapter::class,
             'hash' => \Illuminate\Hashing\HashManager::class,
             'hash' => \Illuminate\Hashing\HashManager::class,
             'hash.driver' => \Illuminate\Hashing\BcryptHasher::class,
             'hash.driver' => \Illuminate\Hashing\BcryptHasher::class,
             'log' => \Illuminate\Log\LogManager::class,
             'log' => \Illuminate\Log\LogManager::class,
@@ -508,8 +508,8 @@
             'events' => \Illuminate\Events\Dispatcher::class,
             'events' => \Illuminate\Events\Dispatcher::class,
             'files' => \Illuminate\Filesystem\Filesystem::class,
             'files' => \Illuminate\Filesystem\Filesystem::class,
             'filesystem' => \Illuminate\Filesystem\FilesystemManager::class,
             'filesystem' => \Illuminate\Filesystem\FilesystemManager::class,
-            'filesystem.cloud' => \Illuminate\Filesystem\LocalFilesystemAdapter::class,
-            'filesystem.disk' => \Illuminate\Filesystem\LocalFilesystemAdapter::class,
+            'filesystem.cloud' => \Illuminate\Filesystem\AwsS3V3Adapter::class,
+            'filesystem.disk' => \Illuminate\Filesystem\AwsS3V3Adapter::class,
             'hash' => \Illuminate\Hashing\HashManager::class,
             'hash' => \Illuminate\Hashing\HashManager::class,
             'hash.driver' => \Illuminate\Hashing\BcryptHasher::class,
             'hash.driver' => \Illuminate\Hashing\BcryptHasher::class,
             'log' => \Illuminate\Log\LogManager::class,
             'log' => \Illuminate\Log\LogManager::class,
@@ -574,8 +574,8 @@
             'events' => \Illuminate\Events\Dispatcher::class,
             'events' => \Illuminate\Events\Dispatcher::class,
             'files' => \Illuminate\Filesystem\Filesystem::class,
             'files' => \Illuminate\Filesystem\Filesystem::class,
             'filesystem' => \Illuminate\Filesystem\FilesystemManager::class,
             'filesystem' => \Illuminate\Filesystem\FilesystemManager::class,
-            'filesystem.cloud' => \Illuminate\Filesystem\LocalFilesystemAdapter::class,
-            'filesystem.disk' => \Illuminate\Filesystem\LocalFilesystemAdapter::class,
+            'filesystem.cloud' => \Illuminate\Filesystem\AwsS3V3Adapter::class,
+            'filesystem.disk' => \Illuminate\Filesystem\AwsS3V3Adapter::class,
             'hash' => \Illuminate\Hashing\HashManager::class,
             'hash' => \Illuminate\Hashing\HashManager::class,
             'hash.driver' => \Illuminate\Hashing\BcryptHasher::class,
             'hash.driver' => \Illuminate\Hashing\BcryptHasher::class,
             'log' => \Illuminate\Log\LogManager::class,
             'log' => \Illuminate\Log\LogManager::class,
@@ -640,8 +640,8 @@
             'events' => \Illuminate\Events\Dispatcher::class,
             'events' => \Illuminate\Events\Dispatcher::class,
             'files' => \Illuminate\Filesystem\Filesystem::class,
             'files' => \Illuminate\Filesystem\Filesystem::class,
             'filesystem' => \Illuminate\Filesystem\FilesystemManager::class,
             'filesystem' => \Illuminate\Filesystem\FilesystemManager::class,
-            'filesystem.cloud' => \Illuminate\Filesystem\LocalFilesystemAdapter::class,
-            'filesystem.disk' => \Illuminate\Filesystem\LocalFilesystemAdapter::class,
+            'filesystem.cloud' => \Illuminate\Filesystem\AwsS3V3Adapter::class,
+            'filesystem.disk' => \Illuminate\Filesystem\AwsS3V3Adapter::class,
             'hash' => \Illuminate\Hashing\HashManager::class,
             'hash' => \Illuminate\Hashing\HashManager::class,
             'hash.driver' => \Illuminate\Hashing\BcryptHasher::class,
             'hash.driver' => \Illuminate\Hashing\BcryptHasher::class,
             'log' => \Illuminate\Log\LogManager::class,
             'log' => \Illuminate\Log\LogManager::class,
@@ -706,8 +706,8 @@
             'events' => \Illuminate\Events\Dispatcher::class,
             'events' => \Illuminate\Events\Dispatcher::class,
             'files' => \Illuminate\Filesystem\Filesystem::class,
             'files' => \Illuminate\Filesystem\Filesystem::class,
             'filesystem' => \Illuminate\Filesystem\FilesystemManager::class,
             'filesystem' => \Illuminate\Filesystem\FilesystemManager::class,
-            'filesystem.cloud' => \Illuminate\Filesystem\LocalFilesystemAdapter::class,
-            'filesystem.disk' => \Illuminate\Filesystem\LocalFilesystemAdapter::class,
+            'filesystem.cloud' => \Illuminate\Filesystem\AwsS3V3Adapter::class,
+            'filesystem.disk' => \Illuminate\Filesystem\AwsS3V3Adapter::class,
             'hash' => \Illuminate\Hashing\HashManager::class,
             'hash' => \Illuminate\Hashing\HashManager::class,
             'hash.driver' => \Illuminate\Hashing\BcryptHasher::class,
             'hash.driver' => \Illuminate\Hashing\BcryptHasher::class,
             'log' => \Illuminate\Log\LogManager::class,
             'log' => \Illuminate\Log\LogManager::class,
@@ -990,7 +990,7 @@
             'filesystems.disks.s3.endpoint' => 'NULL',
             'filesystems.disks.s3.endpoint' => 'NULL',
             'filesystems.disks.s3.use_path_style_endpoint' => 'boolean',
             'filesystems.disks.s3.use_path_style_endpoint' => 'boolean',
             'filesystems.disks.s3.throw' => 'boolean',
             'filesystems.disks.s3.throw' => 'boolean',
-            'filesystems.links./home/ghalves/workspace/ginastica_cerebro/api/public/storage' => 'string',
+            'filesystems.links./home/alves/workspace/ginastica_cerebro/sfp_api_laravel_ginastica_cerebro/public/storage' => 'string',
             'logging.default' => 'string',
             'logging.default' => 'string',
             'logging.deprecations.channel' => 'NULL',
             'logging.deprecations.channel' => 'NULL',
             'logging.deprecations.trace' => 'boolean',
             'logging.deprecations.trace' => 'boolean',
@@ -1129,9 +1129,9 @@
             'session.http_only' => 'boolean',
             'session.http_only' => 'boolean',
             'session.same_site' => 'string',
             'session.same_site' => 'string',
             'session.partitioned' => 'boolean',
             'session.partitioned' => 'boolean',
+            'concurrency.default' => 'string',
             'view.paths' => 'array',
             'view.paths' => 'array',
             'view.compiled' => 'string',
             'view.compiled' => 'string',
-            'concurrency.default' => 'string',
             'hashing.driver' => 'string',
             'hashing.driver' => 'string',
             'hashing.bcrypt.rounds' => 'string',
             'hashing.bcrypt.rounds' => 'string',
             'hashing.bcrypt.verify' => 'boolean',
             'hashing.bcrypt.verify' => 'boolean',
@@ -1408,7 +1408,7 @@
             'filesystems.disks.s3.endpoint' => 'NULL',
             'filesystems.disks.s3.endpoint' => 'NULL',
             'filesystems.disks.s3.use_path_style_endpoint' => 'boolean',
             'filesystems.disks.s3.use_path_style_endpoint' => 'boolean',
             'filesystems.disks.s3.throw' => 'boolean',
             'filesystems.disks.s3.throw' => 'boolean',
-            'filesystems.links./home/ghalves/workspace/ginastica_cerebro/api/public/storage' => 'string',
+            'filesystems.links./home/alves/workspace/ginastica_cerebro/sfp_api_laravel_ginastica_cerebro/public/storage' => 'string',
             'logging.default' => 'string',
             'logging.default' => 'string',
             'logging.deprecations.channel' => 'NULL',
             'logging.deprecations.channel' => 'NULL',
             'logging.deprecations.trace' => 'boolean',
             'logging.deprecations.trace' => 'boolean',
@@ -1547,9 +1547,9 @@
             'session.http_only' => 'boolean',
             'session.http_only' => 'boolean',
             'session.same_site' => 'string',
             'session.same_site' => 'string',
             'session.partitioned' => 'boolean',
             'session.partitioned' => 'boolean',
+            'concurrency.default' => 'string',
             'view.paths' => 'array',
             'view.paths' => 'array',
             'view.compiled' => 'string',
             'view.compiled' => 'string',
-            'concurrency.default' => 'string',
             'hashing.driver' => 'string',
             'hashing.driver' => 'string',
             'hashing.bcrypt.rounds' => 'string',
             'hashing.bcrypt.rounds' => 'string',
             'hashing.bcrypt.verify' => 'boolean',
             'hashing.bcrypt.verify' => 'boolean',
@@ -1826,7 +1826,7 @@
             'filesystems.disks.s3.endpoint' => 'NULL',
             'filesystems.disks.s3.endpoint' => 'NULL',
             'filesystems.disks.s3.use_path_style_endpoint' => 'boolean',
             'filesystems.disks.s3.use_path_style_endpoint' => 'boolean',
             'filesystems.disks.s3.throw' => 'boolean',
             'filesystems.disks.s3.throw' => 'boolean',
-            'filesystems.links./home/ghalves/workspace/ginastica_cerebro/api/public/storage' => 'string',
+            'filesystems.links./home/alves/workspace/ginastica_cerebro/sfp_api_laravel_ginastica_cerebro/public/storage' => 'string',
             'logging.default' => 'string',
             'logging.default' => 'string',
             'logging.deprecations.channel' => 'NULL',
             'logging.deprecations.channel' => 'NULL',
             'logging.deprecations.trace' => 'boolean',
             'logging.deprecations.trace' => 'boolean',
@@ -1965,9 +1965,9 @@
             'session.http_only' => 'boolean',
             'session.http_only' => 'boolean',
             'session.same_site' => 'string',
             'session.same_site' => 'string',
             'session.partitioned' => 'boolean',
             'session.partitioned' => 'boolean',
+            'concurrency.default' => 'string',
             'view.paths' => 'array',
             'view.paths' => 'array',
             'view.compiled' => 'string',
             'view.compiled' => 'string',
-            'concurrency.default' => 'string',
             'hashing.driver' => 'string',
             'hashing.driver' => 'string',
             'hashing.bcrypt.rounds' => 'string',
             'hashing.bcrypt.rounds' => 'string',
             'hashing.bcrypt.verify' => 'boolean',
             'hashing.bcrypt.verify' => 'boolean',
@@ -2096,7 +2096,7 @@
 'filesystems.disks.local.driver','filesystems.disks.local.root','filesystems.disks.local.throw','filesystems.disks.public.driver','filesystems.disks.public.root',
 'filesystems.disks.local.driver','filesystems.disks.local.root','filesystems.disks.local.throw','filesystems.disks.public.driver','filesystems.disks.public.root',
 'filesystems.disks.public.url','filesystems.disks.public.visibility','filesystems.disks.public.throw','filesystems.disks.s3.driver','filesystems.disks.s3.key',
 'filesystems.disks.public.url','filesystems.disks.public.visibility','filesystems.disks.public.throw','filesystems.disks.s3.driver','filesystems.disks.s3.key',
 'filesystems.disks.s3.secret','filesystems.disks.s3.region','filesystems.disks.s3.bucket','filesystems.disks.s3.url','filesystems.disks.s3.endpoint',
 'filesystems.disks.s3.secret','filesystems.disks.s3.region','filesystems.disks.s3.bucket','filesystems.disks.s3.url','filesystems.disks.s3.endpoint',
-'filesystems.disks.s3.use_path_style_endpoint','filesystems.disks.s3.throw','filesystems.links./home/ghalves/workspace/ginastica_cerebro/api/public/storage','logging.default','logging.deprecations.channel',
+'filesystems.disks.s3.use_path_style_endpoint','filesystems.disks.s3.throw','filesystems.links./home/alves/workspace/ginastica_cerebro/sfp_api_laravel_ginastica_cerebro/public/storage','logging.default','logging.deprecations.channel',
 'logging.deprecations.trace','logging.channels.stack.driver','logging.channels.stack.channels','logging.channels.stack.ignore_exceptions','logging.channels.single.driver',
 'logging.deprecations.trace','logging.channels.stack.driver','logging.channels.stack.channels','logging.channels.stack.ignore_exceptions','logging.channels.single.driver',
 'logging.channels.single.path','logging.channels.single.level','logging.channels.single.replace_placeholders','logging.channels.daily.driver','logging.channels.daily.path',
 'logging.channels.single.path','logging.channels.single.level','logging.channels.single.replace_placeholders','logging.channels.daily.driver','logging.channels.daily.path',
 'logging.channels.daily.level','logging.channels.daily.days','logging.channels.daily.replace_placeholders','logging.channels.slack.driver','logging.channels.slack.url',
 'logging.channels.daily.level','logging.channels.daily.days','logging.channels.daily.replace_placeholders','logging.channels.slack.driver','logging.channels.slack.url',
@@ -2124,7 +2124,7 @@
 'session.driver','session.lifetime','session.expire_on_close','session.encrypt','session.files',
 'session.driver','session.lifetime','session.expire_on_close','session.encrypt','session.files',
 'session.connection','session.table','session.store','session.lottery','session.cookie',
 'session.connection','session.table','session.store','session.lottery','session.cookie',
 'session.path','session.domain','session.secure','session.http_only','session.same_site',
 'session.path','session.domain','session.secure','session.http_only','session.same_site',
-'session.partitioned','view.paths','view.compiled','concurrency.default','hashing.driver',
+'session.partitioned','concurrency.default','view.paths','view.compiled','hashing.driver',
 'hashing.bcrypt.rounds','hashing.bcrypt.verify','hashing.bcrypt.limit','hashing.argon.memory','hashing.argon.threads',
 'hashing.bcrypt.rounds','hashing.bcrypt.verify','hashing.bcrypt.limit','hashing.argon.memory','hashing.argon.threads',
 'hashing.argon.time','hashing.argon.verify','hashing.rehash_on_login','ide-helper.filename','ide-helper.models_filename',
 'hashing.argon.time','hashing.argon.verify','hashing.rehash_on_login','ide-helper.filename','ide-helper.models_filename',
 'ide-helper.meta_filename','ide-helper.include_fluent','ide-helper.include_factory_builders','ide-helper.write_model_magic_where','ide-helper.write_model_external_builder_methods',
 'ide-helper.meta_filename','ide-helper.include_fluent','ide-helper.include_factory_builders','ide-helper.write_model_magic_where','ide-helper.write_model_external_builder_methods',
@@ -2190,8 +2190,8 @@
 'messages.buyer_not_allowed','validation.cannot_delete_related','validation.event_full','validation.payment_cant_be_refunded','validation.refund_value_greater_than_payment_value',);
 'messages.buyer_not_allowed','validation.cannot_delete_related','validation.event_full','validation.payment_cant_be_refunded','validation.refund_value_greater_than_payment_value',);
         registerArgumentsSet('env', 
         registerArgumentsSet('env', 
 'APP_NAME','APP_ENV','APP_KEY','APP_DEBUG','APP_TIMEZONE',
 'APP_NAME','APP_ENV','APP_KEY','APP_DEBUG','APP_TIMEZONE',
-'APP_URL','FRANCHISOR_URL','FRANCHISEE_URL','APP_LOCALE','APP_FALLBACK_LOCALE',
-'APP_FAKER_LOCALE','APP_MAINTENANCE_DRIVER','APP_MAINTENANCE_STORE','BCRYPT_ROUNDS','LOG_CHANNEL',
+'APP_URL','APP_LOCALE','APP_FALLBACK_LOCALE','APP_FAKER_LOCALE','APP_MAINTENANCE_DRIVER',
+'APP_MAINTENANCE_STORE','FRANCHISOR_URL','FRANCHISEE_URL','BCRYPT_ROUNDS','LOG_CHANNEL',
 'LOG_STACK','LOG_DEPRECATIONS_CHANNEL','LOG_LEVEL','DB_CONNECTION','DB_HOST',
 'LOG_STACK','LOG_DEPRECATIONS_CHANNEL','LOG_LEVEL','DB_CONNECTION','DB_HOST',
 'DB_PORT','DB_DATABASE','DB_USERNAME','DB_PASSWORD','SESSION_DRIVER',
 'DB_PORT','DB_DATABASE','DB_USERNAME','DB_PASSWORD','SESSION_DRIVER',
 'SESSION_LIFETIME','SESSION_ENCRYPT','SESSION_PATH','SESSION_DOMAIN','BROADCAST_CONNECTION',
 'SESSION_LIFETIME','SESSION_ENCRYPT','SESSION_PATH','SESSION_DOMAIN','BROADCAST_CONNECTION',

+ 90 - 108
_ide_helper.php

@@ -19541,7 +19541,7 @@ class Storage {
          * Get a filesystem instance.
          * Get a filesystem instance.
          *
          *
          * @param string|null $name
          * @param string|null $name
-         * @return \Illuminate\Filesystem\LocalFilesystemAdapter
+         * @return \Illuminate\Filesystem\AwsS3V3Adapter
          * @static
          * @static
          */
          */
         public static function drive($name = null)
         public static function drive($name = null)
@@ -19554,7 +19554,7 @@ public static function drive($name = null)
          * Get a filesystem instance.
          * Get a filesystem instance.
          *
          *
          * @param string|null $name
          * @param string|null $name
-         * @return \Illuminate\Filesystem\LocalFilesystemAdapter
+         * @return \Illuminate\Filesystem\AwsS3V3Adapter
          * @static
          * @static
          */
          */
         public static function disk($name = null)
         public static function disk($name = null)
@@ -19579,7 +19579,7 @@ public static function cloud()
          * Build an on-demand disk.
          * Build an on-demand disk.
          *
          *
          * @param string|array $config
          * @param string|array $config
-         * @return \Illuminate\Filesystem\LocalFilesystemAdapter
+         * @return \Illuminate\Filesystem\AwsS3V3Adapter
          * @static
          * @static
          */
          */
         public static function build($config)
         public static function build($config)
@@ -19593,7 +19593,7 @@ public static function build($config)
          *
          *
          * @param array $config
          * @param array $config
          * @param string $name
          * @param string $name
-         * @return \Illuminate\Filesystem\LocalFilesystemAdapter
+         * @return \Illuminate\Filesystem\AwsS3V3Adapter
          * @static
          * @static
          */
          */
         public static function createLocalDriver($config, $name = 'local')
         public static function createLocalDriver($config, $name = 'local')
@@ -19606,7 +19606,7 @@ public static function createLocalDriver($config, $name = 'local')
          * Create an instance of the ftp driver.
          * Create an instance of the ftp driver.
          *
          *
          * @param array $config
          * @param array $config
-         * @return \Illuminate\Filesystem\LocalFilesystemAdapter
+         * @return \Illuminate\Filesystem\AwsS3V3Adapter
          * @static
          * @static
          */
          */
         public static function createFtpDriver($config)
         public static function createFtpDriver($config)
@@ -19619,7 +19619,7 @@ public static function createFtpDriver($config)
          * Create an instance of the sftp driver.
          * Create an instance of the sftp driver.
          *
          *
          * @param array $config
          * @param array $config
-         * @return \Illuminate\Filesystem\LocalFilesystemAdapter
+         * @return \Illuminate\Filesystem\AwsS3V3Adapter
          * @static
          * @static
          */
          */
         public static function createSftpDriver($config)
         public static function createSftpDriver($config)
@@ -19645,7 +19645,7 @@ public static function createS3Driver($config)
          * Create a scoped driver.
          * Create a scoped driver.
          *
          *
          * @param array $config
          * @param array $config
-         * @return \Illuminate\Filesystem\LocalFilesystemAdapter
+         * @return \Illuminate\Filesystem\AwsS3V3Adapter
          * @static
          * @static
          */
          */
         public static function createScopedDriver($config)
         public static function createScopedDriver($config)
@@ -19745,6 +19745,20 @@ public static function setApplication($app)
             return $instance->setApplication($app);
             return $instance->setApplication($app);
         }
         }
 
 
+        /**
+         * Get the URL for the file at the given path.
+         *
+         * @param string $path
+         * @return string
+         * @throws \RuntimeException
+         * @static
+         */
+        public static function url($path)
+        {
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
+            return $instance->url($path);
+        }
+
         /**
         /**
          * Determine if temporary URLs can be generated.
          * Determine if temporary URLs can be generated.
          *
          *
@@ -19753,7 +19767,7 @@ public static function setApplication($app)
          */
          */
         public static function providesTemporaryUrls()
         public static function providesTemporaryUrls()
         {
         {
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->providesTemporaryUrls();
             return $instance->providesTemporaryUrls();
         }
         }
 
 
@@ -19768,35 +19782,35 @@ public static function providesTemporaryUrls()
          */
          */
         public static function temporaryUrl($path, $expiration, $options = [])
         public static function temporaryUrl($path, $expiration, $options = [])
         {
         {
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->temporaryUrl($path, $expiration, $options);
             return $instance->temporaryUrl($path, $expiration, $options);
         }
         }
 
 
         /**
         /**
-         * Specify the name of the disk the adapter is managing.
+         * Get a temporary upload URL for the file at the given path.
          *
          *
-         * @param string $disk
-         * @return \Illuminate\Filesystem\LocalFilesystemAdapter
+         * @param string $path
+         * @param \DateTimeInterface $expiration
+         * @param array $options
+         * @return array
          * @static
          * @static
          */
          */
-        public static function diskName($disk)
+        public static function temporaryUploadUrl($path, $expiration, $options = [])
         {
         {
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
-            return $instance->diskName($disk);
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
+            return $instance->temporaryUploadUrl($path, $expiration, $options);
         }
         }
 
 
         /**
         /**
-         * Indicate that signed URLs should serve the corresponding files.
+         * Get the underlying S3 client.
          *
          *
-         * @param bool $serve
-         * @param \Closure|null $urlGeneratorResolver
-         * @return \Illuminate\Filesystem\LocalFilesystemAdapter
+         * @return \Aws\S3\S3Client
          * @static
          * @static
          */
          */
-        public static function shouldServeSignedUrls($serve = true, $urlGeneratorResolver = null)
+        public static function getClient()
         {
         {
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
-            return $instance->shouldServeSignedUrls($serve, $urlGeneratorResolver);
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
+            return $instance->getClient();
         }
         }
 
 
         /**
         /**
@@ -19804,13 +19818,13 @@ public static function shouldServeSignedUrls($serve = true, $urlGeneratorResolve
          *
          *
          * @param string|array $path
          * @param string|array $path
          * @param string|null $content
          * @param string|null $content
-         * @return \Illuminate\Filesystem\LocalFilesystemAdapter
+         * @return \Illuminate\Filesystem\AwsS3V3Adapter
          * @static
          * @static
          */
          */
         public static function assertExists($path, $content = null)
         public static function assertExists($path, $content = null)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->assertExists($path, $content);
             return $instance->assertExists($path, $content);
         }
         }
 
 
@@ -19820,13 +19834,13 @@ public static function assertExists($path, $content = null)
          * @param string $path
          * @param string $path
          * @param int $count
          * @param int $count
          * @param bool $recursive
          * @param bool $recursive
-         * @return \Illuminate\Filesystem\LocalFilesystemAdapter
+         * @return \Illuminate\Filesystem\AwsS3V3Adapter
          * @static
          * @static
          */
          */
         public static function assertCount($path, $count, $recursive = false)
         public static function assertCount($path, $count, $recursive = false)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->assertCount($path, $count, $recursive);
             return $instance->assertCount($path, $count, $recursive);
         }
         }
 
 
@@ -19834,13 +19848,13 @@ public static function assertCount($path, $count, $recursive = false)
          * Assert that the given file or directory does not exist.
          * Assert that the given file or directory does not exist.
          *
          *
          * @param string|array $path
          * @param string|array $path
-         * @return \Illuminate\Filesystem\LocalFilesystemAdapter
+         * @return \Illuminate\Filesystem\AwsS3V3Adapter
          * @static
          * @static
          */
          */
         public static function assertMissing($path)
         public static function assertMissing($path)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->assertMissing($path);
             return $instance->assertMissing($path);
         }
         }
 
 
@@ -19848,13 +19862,13 @@ public static function assertMissing($path)
          * Assert that the given directory is empty.
          * Assert that the given directory is empty.
          *
          *
          * @param string $path
          * @param string $path
-         * @return \Illuminate\Filesystem\LocalFilesystemAdapter
+         * @return \Illuminate\Filesystem\AwsS3V3Adapter
          * @static
          * @static
          */
          */
         public static function assertDirectoryEmpty($path)
         public static function assertDirectoryEmpty($path)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->assertDirectoryEmpty($path);
             return $instance->assertDirectoryEmpty($path);
         }
         }
 
 
@@ -19868,7 +19882,7 @@ public static function assertDirectoryEmpty($path)
         public static function exists($path)
         public static function exists($path)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->exists($path);
             return $instance->exists($path);
         }
         }
 
 
@@ -19882,7 +19896,7 @@ public static function exists($path)
         public static function missing($path)
         public static function missing($path)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->missing($path);
             return $instance->missing($path);
         }
         }
 
 
@@ -19896,7 +19910,7 @@ public static function missing($path)
         public static function fileExists($path)
         public static function fileExists($path)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->fileExists($path);
             return $instance->fileExists($path);
         }
         }
 
 
@@ -19910,7 +19924,7 @@ public static function fileExists($path)
         public static function fileMissing($path)
         public static function fileMissing($path)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->fileMissing($path);
             return $instance->fileMissing($path);
         }
         }
 
 
@@ -19924,7 +19938,7 @@ public static function fileMissing($path)
         public static function directoryExists($path)
         public static function directoryExists($path)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->directoryExists($path);
             return $instance->directoryExists($path);
         }
         }
 
 
@@ -19938,7 +19952,7 @@ public static function directoryExists($path)
         public static function directoryMissing($path)
         public static function directoryMissing($path)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->directoryMissing($path);
             return $instance->directoryMissing($path);
         }
         }
 
 
@@ -19952,7 +19966,7 @@ public static function directoryMissing($path)
         public static function path($path)
         public static function path($path)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->path($path);
             return $instance->path($path);
         }
         }
 
 
@@ -19966,7 +19980,7 @@ public static function path($path)
         public static function get($path)
         public static function get($path)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->get($path);
             return $instance->get($path);
         }
         }
 
 
@@ -19981,7 +19995,7 @@ public static function get($path)
         public static function json($path, $flags = 0)
         public static function json($path, $flags = 0)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->json($path, $flags);
             return $instance->json($path, $flags);
         }
         }
 
 
@@ -19998,7 +20012,7 @@ public static function json($path, $flags = 0)
         public static function response($path, $name = null, $headers = [], $disposition = 'inline')
         public static function response($path, $name = null, $headers = [], $disposition = 'inline')
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->response($path, $name, $headers, $disposition);
             return $instance->response($path, $name, $headers, $disposition);
         }
         }
 
 
@@ -20015,7 +20029,7 @@ public static function response($path, $name = null, $headers = [], $disposition
         public static function serve($request, $path, $name = null, $headers = [])
         public static function serve($request, $path, $name = null, $headers = [])
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->serve($request, $path, $name, $headers);
             return $instance->serve($request, $path, $name, $headers);
         }
         }
 
 
@@ -20031,7 +20045,7 @@ public static function serve($request, $path, $name = null, $headers = [])
         public static function download($path, $name = null, $headers = [])
         public static function download($path, $name = null, $headers = [])
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->download($path, $name, $headers);
             return $instance->download($path, $name, $headers);
         }
         }
 
 
@@ -20047,7 +20061,7 @@ public static function download($path, $name = null, $headers = [])
         public static function put($path, $contents, $options = [])
         public static function put($path, $contents, $options = [])
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->put($path, $contents, $options);
             return $instance->put($path, $contents, $options);
         }
         }
 
 
@@ -20063,7 +20077,7 @@ public static function put($path, $contents, $options = [])
         public static function putFile($path, $file = null, $options = [])
         public static function putFile($path, $file = null, $options = [])
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->putFile($path, $file, $options);
             return $instance->putFile($path, $file, $options);
         }
         }
 
 
@@ -20080,7 +20094,7 @@ public static function putFile($path, $file = null, $options = [])
         public static function putFileAs($path, $file, $name = null, $options = [])
         public static function putFileAs($path, $file, $name = null, $options = [])
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->putFileAs($path, $file, $name, $options);
             return $instance->putFileAs($path, $file, $name, $options);
         }
         }
 
 
@@ -20094,7 +20108,7 @@ public static function putFileAs($path, $file, $name = null, $options = [])
         public static function getVisibility($path)
         public static function getVisibility($path)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->getVisibility($path);
             return $instance->getVisibility($path);
         }
         }
 
 
@@ -20109,7 +20123,7 @@ public static function getVisibility($path)
         public static function setVisibility($path, $visibility)
         public static function setVisibility($path, $visibility)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->setVisibility($path, $visibility);
             return $instance->setVisibility($path, $visibility);
         }
         }
 
 
@@ -20126,7 +20140,7 @@ public static function prepend($path, $data, $separator = '
 ')
 ')
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->prepend($path, $data, $separator);
             return $instance->prepend($path, $data, $separator);
         }
         }
 
 
@@ -20143,7 +20157,7 @@ public static function append($path, $data, $separator = '
 ')
 ')
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->append($path, $data, $separator);
             return $instance->append($path, $data, $separator);
         }
         }
 
 
@@ -20157,7 +20171,7 @@ public static function append($path, $data, $separator = '
         public static function delete($paths)
         public static function delete($paths)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->delete($paths);
             return $instance->delete($paths);
         }
         }
 
 
@@ -20172,7 +20186,7 @@ public static function delete($paths)
         public static function copy($from, $to)
         public static function copy($from, $to)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->copy($from, $to);
             return $instance->copy($from, $to);
         }
         }
 
 
@@ -20187,7 +20201,7 @@ public static function copy($from, $to)
         public static function move($from, $to)
         public static function move($from, $to)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->move($from, $to);
             return $instance->move($from, $to);
         }
         }
 
 
@@ -20201,7 +20215,7 @@ public static function move($from, $to)
         public static function size($path)
         public static function size($path)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->size($path);
             return $instance->size($path);
         }
         }
 
 
@@ -20215,7 +20229,7 @@ public static function size($path)
         public static function checksum($path, $options = [])
         public static function checksum($path, $options = [])
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->checksum($path, $options);
             return $instance->checksum($path, $options);
         }
         }
 
 
@@ -20229,7 +20243,7 @@ public static function checksum($path, $options = [])
         public static function mimeType($path)
         public static function mimeType($path)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->mimeType($path);
             return $instance->mimeType($path);
         }
         }
 
 
@@ -20243,7 +20257,7 @@ public static function mimeType($path)
         public static function lastModified($path)
         public static function lastModified($path)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->lastModified($path);
             return $instance->lastModified($path);
         }
         }
 
 
@@ -20257,7 +20271,7 @@ public static function lastModified($path)
         public static function readStream($path)
         public static function readStream($path)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->readStream($path);
             return $instance->readStream($path);
         }
         }
 
 
@@ -20273,42 +20287,10 @@ public static function readStream($path)
         public static function writeStream($path, $resource, $options = [])
         public static function writeStream($path, $resource, $options = [])
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->writeStream($path, $resource, $options);
             return $instance->writeStream($path, $resource, $options);
         }
         }
 
 
-        /**
-         * Get the URL for the file at the given path.
-         *
-         * @param string $path
-         * @return string
-         * @throws \RuntimeException
-         * @static
-         */
-        public static function url($path)
-        {
-            //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
-            return $instance->url($path);
-        }
-
-        /**
-         * Get a temporary upload URL for the file at the given path.
-         *
-         * @param string $path
-         * @param \DateTimeInterface $expiration
-         * @param array $options
-         * @return array
-         * @throws \RuntimeException
-         * @static
-         */
-        public static function temporaryUploadUrl($path, $expiration, $options = [])
-        {
-            //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
-            return $instance->temporaryUploadUrl($path, $expiration, $options);
-        }
-
         /**
         /**
          * Get an array of all files in a directory.
          * Get an array of all files in a directory.
          *
          *
@@ -20320,7 +20302,7 @@ public static function temporaryUploadUrl($path, $expiration, $options = [])
         public static function files($directory = null, $recursive = false)
         public static function files($directory = null, $recursive = false)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->files($directory, $recursive);
             return $instance->files($directory, $recursive);
         }
         }
 
 
@@ -20334,7 +20316,7 @@ public static function files($directory = null, $recursive = false)
         public static function allFiles($directory = null)
         public static function allFiles($directory = null)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->allFiles($directory);
             return $instance->allFiles($directory);
         }
         }
 
 
@@ -20349,7 +20331,7 @@ public static function allFiles($directory = null)
         public static function directories($directory = null, $recursive = false)
         public static function directories($directory = null, $recursive = false)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->directories($directory, $recursive);
             return $instance->directories($directory, $recursive);
         }
         }
 
 
@@ -20363,7 +20345,7 @@ public static function directories($directory = null, $recursive = false)
         public static function allDirectories($directory = null)
         public static function allDirectories($directory = null)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->allDirectories($directory);
             return $instance->allDirectories($directory);
         }
         }
 
 
@@ -20377,7 +20359,7 @@ public static function allDirectories($directory = null)
         public static function makeDirectory($path)
         public static function makeDirectory($path)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->makeDirectory($path);
             return $instance->makeDirectory($path);
         }
         }
 
 
@@ -20391,7 +20373,7 @@ public static function makeDirectory($path)
         public static function deleteDirectory($directory)
         public static function deleteDirectory($directory)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->deleteDirectory($directory);
             return $instance->deleteDirectory($directory);
         }
         }
 
 
@@ -20404,7 +20386,7 @@ public static function deleteDirectory($directory)
         public static function getDriver()
         public static function getDriver()
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->getDriver();
             return $instance->getDriver();
         }
         }
 
 
@@ -20417,7 +20399,7 @@ public static function getDriver()
         public static function getAdapter()
         public static function getAdapter()
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->getAdapter();
             return $instance->getAdapter();
         }
         }
 
 
@@ -20430,7 +20412,7 @@ public static function getAdapter()
         public static function getConfig()
         public static function getConfig()
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->getConfig();
             return $instance->getConfig();
         }
         }
 
 
@@ -20444,7 +20426,7 @@ public static function getConfig()
         public static function serveUsing($callback)
         public static function serveUsing($callback)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             $instance->serveUsing($callback);
             $instance->serveUsing($callback);
         }
         }
 
 
@@ -20458,7 +20440,7 @@ public static function serveUsing($callback)
         public static function buildTemporaryUrlsUsing($callback)
         public static function buildTemporaryUrlsUsing($callback)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             $instance->buildTemporaryUrlsUsing($callback);
             $instance->buildTemporaryUrlsUsing($callback);
         }
         }
 
 
@@ -20475,7 +20457,7 @@ public static function buildTemporaryUrlsUsing($callback)
          */
          */
         public static function when($value = null, $callback = null, $default = null)
         public static function when($value = null, $callback = null, $default = null)
         {
         {
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->when($value, $callback, $default);
             return $instance->when($value, $callback, $default);
         }
         }
 
 
@@ -20492,7 +20474,7 @@ public static function when($value = null, $callback = null, $default = null)
          */
          */
         public static function unless($value = null, $callback = null, $default = null)
         public static function unless($value = null, $callback = null, $default = null)
         {
         {
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->unless($value, $callback, $default);
             return $instance->unless($value, $callback, $default);
         }
         }
 
 
@@ -20508,7 +20490,7 @@ public static function unless($value = null, $callback = null, $default = null)
         public static function macro($name, $macro)
         public static function macro($name, $macro)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            \Illuminate\Filesystem\LocalFilesystemAdapter::macro($name, $macro);
+            \Illuminate\Filesystem\AwsS3V3Adapter::macro($name, $macro);
         }
         }
 
 
         /**
         /**
@@ -20523,7 +20505,7 @@ public static function macro($name, $macro)
         public static function mixin($mixin, $replace = true)
         public static function mixin($mixin, $replace = true)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            \Illuminate\Filesystem\LocalFilesystemAdapter::mixin($mixin, $replace);
+            \Illuminate\Filesystem\AwsS3V3Adapter::mixin($mixin, $replace);
         }
         }
 
 
         /**
         /**
@@ -20536,7 +20518,7 @@ public static function mixin($mixin, $replace = true)
         public static function hasMacro($name)
         public static function hasMacro($name)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            return \Illuminate\Filesystem\LocalFilesystemAdapter::hasMacro($name);
+            return \Illuminate\Filesystem\AwsS3V3Adapter::hasMacro($name);
         }
         }
 
 
         /**
         /**
@@ -20548,7 +20530,7 @@ public static function hasMacro($name)
         public static function flushMacros()
         public static function flushMacros()
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            \Illuminate\Filesystem\LocalFilesystemAdapter::flushMacros();
+            \Illuminate\Filesystem\AwsS3V3Adapter::flushMacros();
         }
         }
 
 
         /**
         /**
@@ -20563,7 +20545,7 @@ public static function flushMacros()
         public static function macroCall($method, $parameters)
         public static function macroCall($method, $parameters)
         {
         {
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
             //Method inherited from \Illuminate\Filesystem\FilesystemAdapter 
-            /** @var \Illuminate\Filesystem\LocalFilesystemAdapter $instance */
+            /** @var \Illuminate\Filesystem\AwsS3V3Adapter $instance */
             return $instance->macroCall($method, $parameters);
             return $instance->macroCall($method, $parameters);
         }
         }
 
 

+ 24 - 0
app/Casts/CnpjCast.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace App\Casts;
+
+use App\ValueObjects\Cnpj;
+use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
+use Illuminate\Database\Eloquent\Model;
+
+class CnpjCast implements CastsAttributes
+{
+    public function get(Model $model, string $key, mixed $value, array $attributes): ?Cnpj
+    {
+        return $value === null ? null : new Cnpj((string) $value);
+    }
+
+    public function set(Model $model, string $key, mixed $value, array $attributes): ?string
+    {
+        if ($value === null) {
+            return null;
+        }
+
+        return ($value instanceof Cnpj ? $value : new Cnpj((string) $value))->value();
+    }
+}

+ 24 - 0
app/Casts/CpfCast.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace App\Casts;
+
+use App\ValueObjects\Cpf;
+use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
+use Illuminate\Database\Eloquent\Model;
+
+class CpfCast implements CastsAttributes
+{
+    public function get(Model $model, string $key, mixed $value, array $attributes): ?Cpf
+    {
+        return $value === null ? null : new Cpf((string) $value);
+    }
+
+    public function set(Model $model, string $key, mixed $value, array $attributes): ?string
+    {
+        if ($value === null) {
+            return null;
+        }
+
+        return ($value instanceof Cpf ? $value : new Cpf((string) $value))->value();
+    }
+}

+ 51 - 0
app/Console/Commands/AsaasSmokeTestCommand.php

@@ -0,0 +1,51 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Services\Integrations\Asaas\AsaasClient;
+use Illuminate\Console\Command;
+use Illuminate\Support\Str;
+
+class AsaasSmokeTestCommand extends Command
+{
+    protected $signature = 'asaas:smoke-test';
+    protected $description = 'Executa um teste de fumaça criando um cliente e uma cobrança no Asaas Sandbox';
+
+    public function handle()
+    {
+        $this->info('Iniciando teste de fumaça com o Asaas...');
+
+        $client = new AsaasClient();
+
+        try {
+            $this->info('1. Criando Customer de teste...');
+            $customer = $client->post('/customers', [
+                'name' => 'Teste Fumaça ' . Str::random(5),
+                'cpfCnpj' => '12345678909', // CPF matematicamente válido para testes
+                'email' => 'teste@ginasticacerebro.com.br',
+            ]);
+
+            $customerId = $customer['id'];
+            $this->info("Customer criado com sucesso: $customerId");
+
+            $this->info('2. Criando Cobrança (Payment) PIX...');
+            $payment = $client->post('/payments', [
+                'customer' => $customerId,
+                'billingType' => 'PIX',
+                'value' => 10.50,
+                'dueDate' => now()->addDays(5)->format('Y-m-d'),
+                'description' => 'Cobrança Teste de Fumaça',
+            ]);
+
+            $this->info('Cobrança criada com sucesso: ' . $payment['id']);
+            $this->info('Link de pagamento: ' . $payment['invoiceUrl']);
+
+            $this->info('Teste finalizado com sucesso! 🎉');
+        } catch (\Exception $e) {
+            $this->error('Falha no teste: ' . $e->getMessage());
+            return Command::FAILURE;
+        }
+
+        return Command::SUCCESS;
+    }
+}

+ 41 - 0
app/Console/Commands/ClearStudentRegistrationDrafts.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Services\StudentRegistrationDraftService;
+use Illuminate\Console\Command;
+
+class ClearStudentRegistrationDrafts extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'students:clear-registration-drafts';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Remove os rascunhos de estudantes e suas reservas de CPF e e-mail';
+
+    /**
+     * Execute the console command.
+     */
+    public function handle(StudentRegistrationDraftService $service): int
+    {
+        $clearedDrafts = $service->clearAll();
+
+        $this->components->info(
+            trans_choice(
+                '{0} Nenhum rascunho de estudante foi encontrado.|{1} Um rascunho de estudante foi removido.|[2,*] :count rascunhos de estudantes foram removidos.',
+                $clearedDrafts,
+                ['count' => $clearedDrafts],
+            ),
+        );
+
+        return self::SUCCESS;
+    }
+}

+ 101 - 0
app/Console/Commands/GenerateMonthlyTbrCommand.php

@@ -0,0 +1,101 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Enums\NotificationType;
+use App\Services\NotificationService;
+use App\Services\TbrCalculationService;
+use Carbon\Carbon;
+use Illuminate\Console\Command;
+
+/**
+ * Geração automática do TBR do mês fechado (roda no schedule mensal).
+ *
+ * Sem opções, gera o TBR do MÊS ANTERIOR (já fechado) — ex.: rodando em 01/ago,
+ * gera as contas referentes a julho. Idempotente: pula o que já foi gerado.
+ */
+class GenerateMonthlyTbrCommand extends Command
+{
+    protected $signature = 'tbr:generate-monthly {--year=} {--month=}';
+
+    protected $description = 'Gera automaticamente as cobranças do TBR do mês fechado';
+
+    private const MONTHS = [
+        1 => 'janeiro', 2 => 'fevereiro', 3 => 'março', 4 => 'abril',
+        5 => 'maio', 6 => 'junho', 7 => 'julho', 8 => 'agosto',
+        9 => 'setembro', 10 => 'outubro', 11 => 'novembro', 12 => 'dezembro',
+    ];
+
+    public function handle(TbrCalculationService $service, NotificationService $notifications): int
+    {
+        $reference = Carbon::now()->subMonthNoOverflow();
+
+        $year  = (int) ($this->option('year')  ?: $reference->year);
+        $month = (int) ($this->option('month') ?: $reference->month);
+
+        $this->info("Gerando TBR automático para {$month}/{$year}...");
+
+        $result = $service->generateBatch($year, $month, null);
+
+        $this->info(sprintf(
+            '%d gerado(s), %d pulado(s), %d erro(s).',
+            $result['generated_count'],
+            $result['skipped_count'],
+            $result['error_count'],
+        ));
+
+        if ($result['generated_count'] > 0) {
+            $this->notifyMonthClosed($notifications, $year, $month, $result['generated']);
+        }
+
+        return self::SUCCESS;
+    }
+
+    /**
+     * Notifica a matriz (contas a receber) e cada unidade (conta a pagar).
+     */
+    private function notifyMonthClosed(
+        NotificationService $notifications,
+        int $year,
+        int $month,
+        array $generated,
+    ): void {
+        $monthLabel = (self::MONTHS[$month] ?? $month) . "/{$year}";
+        $count      = count($generated);
+
+        // Matriz: visão consolidada no Contas a Receber.
+        $notifications->dispatch([
+            'origin_table'       => 'tbr_calculations',
+            'origin_id'          => $year * 100 + $month,
+            'type'               => NotificationType::TBR_MONTH_CLOSED->value,
+            'title'              => 'Fechamento do mês',
+            'message'            => "As cobranças de {$monthLabel} foram geradas no Contas a Receber "
+                . "({$count} unidade(s)).",
+            'url'                => '/financial/accounts-receivable',
+            'action_text'        => 'Ver Contas a Receber',
+            'priority'           => 'normal',
+            'recipient_user_ids' => $notifications->matrizUserIds(),
+        ]);
+
+        // Cada unidade: sua conta a pagar (TBR) do mês.
+        foreach ($generated as $item) {
+            $unitId = $item['unit_id'] ?? null;
+            if (!$unitId) {
+                continue;
+            }
+
+            $notifications->dispatch([
+                'origin_table'       => 'unit_account_payables',
+                'origin_id'          => $item['receivable_id'] ?? ($year * 100 + $month),
+                'unit_id'            => $unitId,
+                'type'               => NotificationType::TBR_MONTH_CLOSED->value,
+                'title'              => 'Conta a pagar gerada',
+                'message'            => "Sua cobrança (TBR) referente a {$monthLabel} foi gerada em Contas a Pagar.",
+                'url'                => '/financial/accounts-payable',
+                'action_text'        => 'Ver Contas a Pagar',
+                'priority'           => 'normal',
+                'recipient_user_ids' => $notifications->unitUserIds($unitId),
+            ]);
+        }
+    }
+}

+ 36 - 0
app/Enums/NotificationType.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace App\Enums;
+
+use App\Traits\EnumHelper;
+
+/**
+ * Tipos de notificação. O frontend usa o valor para escolher ícone e rota.
+ */
+enum NotificationType: string
+{
+    use EnumHelper;
+
+    case SUPPORT_TICKET_CREATED = 'support_ticket_created';
+    case SUPPORT_TICKET_REPLY = 'support_ticket_reply';
+    case KANBAN_CREATED = 'kanban_created';
+    case KANBAN_REPLY = 'kanban_reply';
+    case KANBAN_STATUS_CHANGED = 'kanban_status_changed';
+    case TBR_MONTH_CLOSED = 'tbr_month_closed';
+    case ACCOUNT_PAYABLE_CREATED = 'account_payable_created';
+    case PAYMENT_CREDITED = 'payment_credited';
+
+    public function label(): string
+    {
+        return match ($this) {
+            self::SUPPORT_TICKET_CREATED => 'Novo chamado de suporte',
+            self::SUPPORT_TICKET_REPLY => 'Nova resposta no chamado',
+            self::KANBAN_CREATED => 'Nova atividade',
+            self::KANBAN_REPLY => 'Novo comentário na atividade',
+            self::KANBAN_STATUS_CHANGED => 'Status da atividade atualizado',
+            self::TBR_MONTH_CLOSED => 'Fechamento do mês',
+            self::ACCOUNT_PAYABLE_CREATED => 'Nova conta a pagar',
+            self::PAYMENT_CREDITED => 'Pagamento creditado',
+        };
+    }
+}

+ 12 - 0
app/Enums/ReceivableStatus.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace App\Enums;
+
+enum ReceivableStatus: string
+{
+    case PENDING = 'pending';
+    case AWAITING_PAYMENT = 'awaiting_payment';
+    case PAID = 'paid';
+    case OVERDUE = 'overdue';
+    case CANCELLED = 'cancelled';
+}

+ 28 - 0
app/Exceptions/AsaasException.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace App\Exceptions;
+
+use Exception;
+use Illuminate\Http\Client\Response;
+
+class AsaasException extends Exception
+{
+    public array $errors;
+    public $rawBody;
+
+    public function __construct(Response $response)
+    {
+        $status = $response->status();
+        $this->rawBody = $response->json() ?? $response->body();
+        $body = $response->json();
+
+        $this->errors = $body['errors'] ?? [];
+        $message = "Asaas API Error ($status): " . collect($this->errors)->pluck('description')->join(', ');
+        
+        if (empty($this->errors) && is_string($this->rawBody)) {
+            $message .= " | Response: " . $this->rawBody;
+        }
+
+        parent::__construct($message, $status);
+    }
+}

+ 16 - 0
app/Http/Controllers/AuthController.php

@@ -3,6 +3,7 @@
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
 use App\Http\Requests\AuthRequest;
 use App\Http\Requests\AuthRequest;
+use App\Http\Requests\ConfirmPasswordRequest;
 use App\Http\Requests\ForgotPasswordRequest;
 use App\Http\Requests\ForgotPasswordRequest;
 use App\Http\Requests\RefreshTokenRequest;
 use App\Http\Requests\RefreshTokenRequest;
 use App\Http\Requests\ResetPasswordRequest;
 use App\Http\Requests\ResetPasswordRequest;
@@ -99,6 +100,21 @@ public function resetPassword(ResetPasswordRequest $request): JsonResponse
         return $this->successResponse(message: __('auth.password_reset_success'));
         return $this->successResponse(message: __('auth.password_reset_success'));
     }
     }
 
 
+    public function confirmPassword(ConfirmPasswordRequest $request): JsonResponse
+    {
+        $validated = $request->validated();
+
+        $confirmed = $this->authService->confirmPassword(
+            password: $validated["password"],
+        );
+
+        if (!$confirmed) {
+            return $this->errorResponse(message: __("auth.password"), code: 422);
+        }
+
+        return $this->successResponse(message: __("auth.password_confirmed"));
+    }
+
     public function logout(Request $request): JsonResponse
     public function logout(Request $request): JsonResponse
     {
     {
         $this->authService->logout();
         $this->authService->logout();

+ 45 - 0
app/Http/Controllers/BaseHolidayController.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Http\Requests\BaseHolidayRequest;
+use App\Http\Resources\BaseHolidayResource;
+use App\Services\BaseHolidayService;
+use Illuminate\Http\JsonResponse;
+
+class BaseHolidayController extends Controller
+{
+    public function __construct(
+        protected BaseHolidayService $service,
+    ) {}
+
+    public function index(): JsonResponse
+    {
+        $items = $this->service->getAll();
+        return $this->successResponse(payload: BaseHolidayResource::collection($items));
+    }
+
+    public function store(BaseHolidayRequest $request): JsonResponse
+    {
+        $item = $this->service->create($request->validated());
+        return $this->successResponse(payload: new BaseHolidayResource($item), message: __('messages.created'), code: 201);
+    }
+
+    public function show(int $id): JsonResponse
+    {
+        $item = $this->service->findById($id);
+        return $this->successResponse(payload: new BaseHolidayResource($item));
+    }
+
+    public function update(BaseHolidayRequest $request, int $id): JsonResponse
+    {
+        $item = $this->service->update($id, $request->validated());
+        return $this->successResponse(payload: new BaseHolidayResource($item), message: __('messages.updated'));
+    }
+
+    public function destroy(int $id): JsonResponse
+    {
+        $this->service->delete($id);
+        return $this->successResponse(message: __('messages.deleted'), code: 204);
+    }
+}

+ 52 - 5
app/Http/Controllers/ClassController.php

@@ -2,44 +2,91 @@
 
 
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
+use App\Http\Controllers\Concerns\ResolvesActiveUnit;
 use App\Services\ClassService;
 use App\Services\ClassService;
 use App\Http\Requests\ClassRequest;
 use App\Http\Requests\ClassRequest;
 use App\Http\Resources\ClassResource;
 use App\Http\Resources\ClassResource;
+use Illuminate\Http\Request;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\JsonResponse;
 
 
 class ClassController extends Controller
 class ClassController extends Controller
 {
 {
+    use ResolvesActiveUnit;
+
     public function __construct(
     public function __construct(
         protected ClassService $service,
         protected ClassService $service,
     ) {}
     ) {}
 
 
     public function index(): JsonResponse
     public function index(): JsonResponse
     {
     {
-        $items = $this->service->getAll();
+        $unitId = $this->activeUnitId();
+        if (!$unitId) {
+            return $this->successResponse(payload: []);
+        }
+
+        $items = $this->service->getAllByUnit($unitId);
         return $this->successResponse(payload: ClassResource::collection($items));
         return $this->successResponse(payload: ClassResource::collection($items));
     }
     }
 
 
     public function store(ClassRequest $request): JsonResponse
     public function store(ClassRequest $request): JsonResponse
     {
     {
-        $item = $this->service->create($request->validated());
+        $unitId = $this->activeUnitId();
+        abort_unless($unitId, 403, 'Nenhuma unidade ativa foi selecionada.');
+
+        $data = array_merge($request->validated(), ['unit_id' => $unitId]);
+        $item = $this->service->create($data);
         return $this->successResponse(payload: new ClassResource($item), message: __('messages.created'), code: 201);
         return $this->successResponse(payload: new ClassResource($item), message: __('messages.created'), code: 201);
     }
     }
 
 
     public function show(int $id): JsonResponse
     public function show(int $id): JsonResponse
     {
     {
-        $item = $this->service->findById($id);
+        $item = $this->service->findByIdForUnit($id, $this->requiredActiveUnitId());
         return $this->successResponse(payload: new ClassResource($item));
         return $this->successResponse(payload: new ClassResource($item));
     }
     }
 
 
     public function update(ClassRequest $request, int $id): JsonResponse
     public function update(ClassRequest $request, int $id): JsonResponse
     {
     {
-        $item = $this->service->update($id, $request->validated());
+        $item = $this->service->update($id, $this->requiredActiveUnitId(), $request->validated());
         return $this->successResponse(payload: new ClassResource($item), message: __('messages.updated'));
         return $this->successResponse(payload: new ClassResource($item), message: __('messages.updated'));
     }
     }
 
 
     public function destroy(int $id): JsonResponse
     public function destroy(int $id): JsonResponse
     {
     {
-        $this->service->delete($id);
+        $this->service->delete($id, $this->requiredActiveUnitId());
         return $this->successResponse(message: __('messages.deleted'), code: 204);
         return $this->successResponse(message: __('messages.deleted'), code: 204);
     }
     }
+
+    /**
+     * Alunos com contrato ativo no pacote da aula + presença registrada.
+     */
+    public function students(int $id): JsonResponse
+    {
+        $students = $this->service->getStudentsForClass($id, $this->requiredActiveUnitId());
+        return $this->successResponse(payload: $students);
+    }
+
+    /**
+     * Salva a lista de presença da aula (upsert em lote).
+     */
+    public function saveAttendance(Request $request, int $id): JsonResponse
+    {
+        $validated = $request->validate([
+            'attendances'              => 'required|array',
+            'attendances.*.student_id' => 'required|integer|exists:students,id',
+            'attendances.*.in_class'   => 'required|boolean',
+            'attendances.*.justified'  => 'sometimes|boolean',
+            'attendances.*.notes'      => 'nullable|string',
+        ]);
+
+        $this->service->saveAttendance($id, $this->requiredActiveUnitId(), $validated['attendances']);
+        return $this->successResponse(message: __('messages.updated'));
+    }
+
+    private function requiredActiveUnitId(): int
+    {
+        $unitId = $this->activeUnitId();
+        abort_unless($unitId, 403, 'Nenhuma unidade ativa foi selecionada.');
+
+        return $unitId;
+    }
 }
 }

+ 4 - 2
app/Http/Controllers/ClassPackageController.php

@@ -2,14 +2,16 @@
 
 
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
+use App\Http\Controllers\Concerns\ResolvesActiveUnit;
 use App\Services\ClassPackageService;
 use App\Services\ClassPackageService;
 use App\Http\Requests\ClassPackageRequest;
 use App\Http\Requests\ClassPackageRequest;
 use App\Http\Resources\ClassPackageResource;
 use App\Http\Resources\ClassPackageResource;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\JsonResponse;
-use Illuminate\Support\Facades\Auth;
 
 
 class ClassPackageController extends Controller
 class ClassPackageController extends Controller
 {
 {
+    use ResolvesActiveUnit;
+
     public function __construct(
     public function __construct(
         protected ClassPackageService $service,
         protected ClassPackageService $service,
     ) {}
     ) {}
@@ -22,7 +24,7 @@ public function index(): JsonResponse
 
 
     public function byUnit(): JsonResponse
     public function byUnit(): JsonResponse
     {
     {
-        $unitId = Auth::user()->load('units')->units->first()?->id;
+        $unitId = $this->activeUnitId();
         if (!$unitId) {
         if (!$unitId) {
             return $this->successResponse(payload: []);
             return $this->successResponse(payload: []);
         }
         }

+ 0 - 45
app/Http/Controllers/ClassPackageFranchiseeController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\ClassPackageFranchiseeService;
-use App\Http\Requests\ClassPackageFranchiseeRequest;
-use App\Http\Resources\ClassPackageFranchiseeResource;
-use Illuminate\Http\JsonResponse;
-
-class ClassPackageFranchiseeController extends Controller
-{
-    public function __construct(
-        protected ClassPackageFranchiseeService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: ClassPackageFranchiseeResource::collection($items));
-    }
-
-    public function store(ClassPackageFranchiseeRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new ClassPackageFranchiseeResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new ClassPackageFranchiseeResource($item));
-    }
-
-    public function update(ClassPackageFranchiseeRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new ClassPackageFranchiseeResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 22 - 9
app/Http/Controllers/ClassPackageUnitController.php

@@ -2,31 +2,34 @@
 
 
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
+use App\Http\Controllers\Concerns\ResolvesActiveUnit;
 use App\Services\ClassPackageUnitService;
 use App\Services\ClassPackageUnitService;
 use App\Http\Requests\ClassPackageUnitRequest;
 use App\Http\Requests\ClassPackageUnitRequest;
 use App\Http\Resources\ClassPackageUnitResource;
 use App\Http\Resources\ClassPackageUnitResource;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\JsonResponse;
-use Illuminate\Support\Facades\Auth;
 
 
 class ClassPackageUnitController extends Controller
 class ClassPackageUnitController extends Controller
 {
 {
+    use ResolvesActiveUnit;
+
     public function __construct(
     public function __construct(
         protected ClassPackageUnitService $service,
         protected ClassPackageUnitService $service,
     ) {}
     ) {}
 
 
     public function index(): JsonResponse
     public function index(): JsonResponse
     {
     {
-        $unitId = Auth::user()->load('units')->units->first()?->id;
+        $unitId = $this->activeUnitId();
         if (!$unitId) {
         if (!$unitId) {
             return $this->successResponse(payload: []);
             return $this->successResponse(payload: []);
         }
         }
-        $items = $this->service->getAllByUnit($unitId);
+        // Only return packages the franchisor made visible for this unit
+        $items = $this->service->getByUnit($unitId);
         return $this->successResponse(payload: ClassPackageUnitResource::collection($items));
         return $this->successResponse(payload: ClassPackageUnitResource::collection($items));
     }
     }
 
 
     public function byUnit(): JsonResponse
     public function byUnit(): JsonResponse
     {
     {
-        $unitId = Auth::user()->load('units')->units->first()?->id;
+        $unitId = $this->activeUnitId();
         if (!$unitId) {
         if (!$unitId) {
             return $this->successResponse(payload: []);
             return $this->successResponse(payload: []);
         }
         }
@@ -36,7 +39,9 @@ public function byUnit(): JsonResponse
 
 
     public function store(ClassPackageUnitRequest $request): JsonResponse
     public function store(ClassPackageUnitRequest $request): JsonResponse
     {
     {
-        $unitId = Auth::user()->load('units')->units->first()?->id;
+        $unitId = $this->activeUnitId();
+        abort_unless($unitId, 403, 'Nenhuma unidade ativa foi selecionada.');
+
         $data   = array_merge($request->validated(), ['unit_id' => $unitId]);
         $data   = array_merge($request->validated(), ['unit_id' => $unitId]);
         $item   = $this->service->create($data);
         $item   = $this->service->create($data);
         return $this->successResponse(payload: new ClassPackageUnitResource($item), message: __('messages.created'), code: 201);
         return $this->successResponse(payload: new ClassPackageUnitResource($item), message: __('messages.created'), code: 201);
@@ -44,25 +49,33 @@ public function store(ClassPackageUnitRequest $request): JsonResponse
 
 
     public function show(int $id): JsonResponse
     public function show(int $id): JsonResponse
     {
     {
-        $item = $this->service->findById($id);
+        $item = $this->service->findByIdForUnit($id, $this->requiredActiveUnitId());
         return $this->successResponse(payload: new ClassPackageUnitResource($item));
         return $this->successResponse(payload: new ClassPackageUnitResource($item));
     }
     }
 
 
     public function update(ClassPackageUnitRequest $request, int $id): JsonResponse
     public function update(ClassPackageUnitRequest $request, int $id): JsonResponse
     {
     {
-        $item = $this->service->update($id, $request->validated());
+        $item = $this->service->update($id, $this->requiredActiveUnitId(), $request->validated());
         return $this->successResponse(payload: new ClassPackageUnitResource($item), message: __('messages.updated'));
         return $this->successResponse(payload: new ClassPackageUnitResource($item), message: __('messages.updated'));
     }
     }
 
 
     public function toggleVisibility(int $id): JsonResponse
     public function toggleVisibility(int $id): JsonResponse
     {
     {
-        $item = $this->service->toggleVisibility($id);
+        $item = $this->service->toggleVisibility($id, $this->requiredActiveUnitId());
         return $this->successResponse(payload: new ClassPackageUnitResource($item), message: __('messages.updated'));
         return $this->successResponse(payload: new ClassPackageUnitResource($item), message: __('messages.updated'));
     }
     }
 
 
     public function destroy(int $id): JsonResponse
     public function destroy(int $id): JsonResponse
     {
     {
-        $this->service->delete($id);
+        $this->service->delete($id, $this->requiredActiveUnitId());
         return $this->successResponse(message: __('messages.deleted'), code: 204);
         return $this->successResponse(message: __('messages.deleted'), code: 204);
     }
     }
+
+    private function requiredActiveUnitId(): int
+    {
+        $unitId = $this->activeUnitId();
+        abort_unless($unitId, 403, 'Nenhuma unidade ativa foi selecionada.');
+
+        return $unitId;
+    }
 }
 }

+ 72 - 0
app/Http/Controllers/CompanyAccountPayableController.php

@@ -0,0 +1,72 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Http\Requests\SettlePayableRequest;
+use App\Http\Requests\StoreCompanyPayableRequest;
+use App\Http\Resources\CompanyAccountPayableResource;
+use App\Services\CompanyAccountPayableService;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+
+/**
+ * Contas a Pagar da matriz (franqueadora).
+ */
+class CompanyAccountPayableController extends Controller
+{
+    public function __construct(
+        protected CompanyAccountPayableService $service,
+    ) {}
+
+    public function index(Request $request): JsonResponse
+    {
+        $filters = array_filter($request->only(['status']), fn ($v) => $v !== null);
+
+        return $this->successResponse(
+            payload: CompanyAccountPayableResource::collection($this->service->list($filters)),
+        );
+    }
+
+    public function store(StoreCompanyPayableRequest $request): JsonResponse
+    {
+        $item = $this->service->createManual($request->validated());
+
+        return $this->successResponse(
+            payload: new CompanyAccountPayableResource($item),
+            message: __('messages.created'),
+            code: 201,
+        );
+    }
+
+    public function settle(SettlePayableRequest $request, int $id): JsonResponse
+    {
+        $payable = $this->service->findById($id);
+
+        if (!$payable) {
+            return $this->errorResponse(message: 'Conta a pagar não encontrada', code: 404);
+        }
+
+        $item = $this->service->settle($payable, $request->validated());
+
+        return $this->successResponse(
+            payload: new CompanyAccountPayableResource($item),
+            message: 'Baixa realizada com sucesso',
+        );
+    }
+
+    public function reopen(int $id): JsonResponse
+    {
+        $payable = $this->service->findById($id);
+
+        if (!$payable) {
+            return $this->errorResponse(message: 'Conta a pagar não encontrada', code: 404);
+        }
+
+        $item = $this->service->reopen($payable);
+
+        return $this->successResponse(
+            payload: new CompanyAccountPayableResource($item),
+            message: 'Conta reaberta',
+        );
+    }
+}

+ 32 - 0
app/Http/Controllers/CompanyPaymentAccountController.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Http\Requests\CompanyPaymentAccountRequest;
+use App\Http\Resources\CompanyPaymentAccountResource;
+use App\Services\CompanyPaymentAccountService;
+use Illuminate\Http\JsonResponse;
+
+class CompanyPaymentAccountController extends Controller
+{
+    public function __construct(
+        protected CompanyPaymentAccountService $service,
+    ) {}
+
+    public function show(): JsonResponse
+    {
+        return $this->successResponse(
+            payload: new CompanyPaymentAccountResource($this->service->current()),
+        );
+    }
+
+    public function upsert(CompanyPaymentAccountRequest $request): JsonResponse
+    {
+        $item = $this->service->upsertAsaasApiKey($request->validated('asaas_api_key'));
+
+        return $this->successResponse(
+            payload: new CompanyPaymentAccountResource($item),
+            message: __('messages.updated'),
+        );
+    }
+}

+ 28 - 0
app/Http/Controllers/Concerns/ResolvesActiveUnit.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace App\Http\Controllers\Concerns;
+
+use App\Models\User;
+use Illuminate\Support\Facades\Auth;
+
+/**
+ * Fornece um helper para obter o ID da unidade ativa da request.
+ *
+ * O middleware ResolveActiveUnit valida o header X-Unit-Id e injeta
+ * 'active_unit_id' na request quando o usuário possui múltiplas unidades
+ * e selecionou uma específica no frontend.
+ * Se nenhuma unidade estiver selecionada, retorna a primeira unidade do usuário.
+ */
+trait ResolvesActiveUnit
+{
+    protected function activeUnitId(?User $user = null): ?int
+    {
+        $fromRequest = request()->input('active_unit_id');
+        if ($fromRequest) {
+            return (int) $fromRequest;
+        }
+
+        $user = $user ?? Auth::user()?->loadMissing('units');
+        return $user?->units->first()?->id;
+    }
+}

+ 0 - 45
app/Http/Controllers/FinancialAccountPayableController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\FinancialAccountPayableService;
-use App\Http\Requests\FinancialAccountPayableRequest;
-use App\Http\Resources\FinancialAccountPayableResource;
-use Illuminate\Http\JsonResponse;
-
-class FinancialAccountPayableController extends Controller
-{
-    public function __construct(
-        protected FinancialAccountPayableService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: FinancialAccountPayableResource::collection($items));
-    }
-
-    public function store(FinancialAccountPayableRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new FinancialAccountPayableResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new FinancialAccountPayableResource($item));
-    }
-
-    public function update(FinancialAccountPayableRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new FinancialAccountPayableResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 0 - 45
app/Http/Controllers/FinancialAccountReceiveController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\FinancialAccountReceiveService;
-use App\Http\Requests\FinancialAccountReceiveRequest;
-use App\Http\Resources\FinancialAccountReceiveResource;
-use Illuminate\Http\JsonResponse;
-
-class FinancialAccountReceiveController extends Controller
-{
-    public function __construct(
-        protected FinancialAccountReceiveService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: FinancialAccountReceiveResource::collection($items));
-    }
-
-    public function store(FinancialAccountReceiveRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new FinancialAccountReceiveResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new FinancialAccountReceiveResource($item));
-    }
-
-    public function update(FinancialAccountReceiveRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new FinancialAccountReceiveResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 18 - 0
app/Http/Controllers/FinancialDashboardController.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Services\FinancialDashboardService;
+use Illuminate\Http\JsonResponse;
+
+class FinancialDashboardController extends Controller
+{
+    public function __construct(
+        protected FinancialDashboardService $service,
+    ) {}
+
+    public function overview(): JsonResponse
+    {
+        return $this->successResponse(payload: $this->service->overview());
+    }
+}

+ 0 - 45
app/Http/Controllers/FinancialInvoiceController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\FinancialInvoiceService;
-use App\Http\Requests\FinancialInvoiceRequest;
-use App\Http\Resources\FinancialInvoiceResource;
-use Illuminate\Http\JsonResponse;
-
-class FinancialInvoiceController extends Controller
-{
-    public function __construct(
-        protected FinancialInvoiceService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: FinancialInvoiceResource::collection($items));
-    }
-
-    public function store(FinancialInvoiceRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new FinancialInvoiceResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new FinancialInvoiceResource($item));
-    }
-
-    public function update(FinancialInvoiceRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new FinancialInvoiceResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 62 - 1
app/Http/Controllers/FinancialPlanAccountController.php

@@ -2,6 +2,7 @@
 
 
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
+use App\Http\Controllers\Concerns\ResolvesActiveUnit;
 use App\Services\FinancialPlanAccountService;
 use App\Services\FinancialPlanAccountService;
 use App\Http\Requests\FinancialPlanAccountRequest;
 use App\Http\Requests\FinancialPlanAccountRequest;
 use App\Http\Resources\FinancialPlanAccountResource;
 use App\Http\Resources\FinancialPlanAccountResource;
@@ -9,13 +10,73 @@
 
 
 class FinancialPlanAccountController extends Controller
 class FinancialPlanAccountController extends Controller
 {
 {
+    use ResolvesActiveUnit;
+
     public function __construct(
     public function __construct(
         protected FinancialPlanAccountService $service,
         protected FinancialPlanAccountService $service,
     ) {}
     ) {}
 
 
+    // --- Endpoints da unidade ativa (Franchisee) ---
+
+    public function indexMe(): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+        if (!$unitId) {
+            return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
+        }
+
+        return $this->successResponse(
+            payload: FinancialPlanAccountResource::collection($this->service->getAll($unitId)),
+        );
+    }
+
+    public function storeMe(FinancialPlanAccountRequest $request): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+        if (!$unitId) {
+            return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
+        }
+
+        $data = $request->validated();
+        $data['unit_id'] = $unitId;
+
+        $item = $this->service->create($data);
+
+        return $this->successResponse(payload: new FinancialPlanAccountResource($item), message: __('messages.created'), code: 201);
+    }
+
+    public function updateMe(FinancialPlanAccountRequest $request, int $id): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+        if (!$unitId || !$this->service->findByIdForUnit($id, $unitId)) {
+            return $this->errorResponse(message: 'Conta não encontrada', code: 404);
+        }
+
+        $data = $request->validated();
+        unset($data['unit_id']);
+
+        $item = $this->service->update($id, $data);
+
+        return $this->successResponse(payload: new FinancialPlanAccountResource($item), message: __('messages.updated'));
+    }
+
+    public function destroyMe(int $id): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+        if (!$unitId || !$this->service->findByIdForUnit($id, $unitId)) {
+            return $this->errorResponse(message: 'Conta não encontrada', code: 404);
+        }
+
+        $this->service->delete($id);
+
+        return $this->successResponse(message: __('messages.deleted'), code: 204);
+    }
+
     public function index(): JsonResponse
     public function index(): JsonResponse
     {
     {
-        $items = $this->service->getAll();
+        // Matriz enxerga apenas o próprio plano de contas (unit_id null);
+        // nunca os planos criados pelas unidades.
+        $items = $this->service->getAll(null);
         return $this->successResponse(payload: FinancialPlanAccountResource::collection($items));
         return $this->successResponse(payload: FinancialPlanAccountResource::collection($items));
     }
     }
 
 

+ 0 - 45
app/Http/Controllers/FnmBaseBracketController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\FnmBaseBracketService;
-use App\Http\Requests\FnmBaseBracketRequest;
-use App\Http\Resources\FnmBaseBracketResource;
-use Illuminate\Http\JsonResponse;
-
-class FnmBaseBracketController extends Controller
-{
-    public function __construct(
-        protected FnmBaseBracketService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: FnmBaseBracketResource::collection($items));
-    }
-
-    public function store(FnmBaseBracketRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new FnmBaseBracketResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new FnmBaseBracketResource($item));
-    }
-
-    public function update(FnmBaseBracketRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new FnmBaseBracketResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 92 - 0
app/Http/Controllers/FranchiseeAccountReceiveController.php

@@ -0,0 +1,92 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Http\Requests\SettlePayableRequest;
+use App\Http\Requests\StoreFranchisorReceivableRequest;
+use App\Http\Resources\FranchiseeAccountReceiveResource;
+use App\Models\FranchiseeAccountReceive;
+use App\Services\FranchisorReceivableService;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+
+class FranchiseeAccountReceiveController extends Controller
+{
+    public function __construct(
+        protected FranchisorReceivableService $service,
+    ) {}
+
+    /**
+     * Lista, na visão consolidada do Franqueador, os títulos a receber das
+     * franquias (TBR gerada). É o mesmo registro que, do lado da franquia,
+     * representa o "contas a pagar".
+     */
+    public function index(Request $request): JsonResponse
+    {
+        $query = FranchiseeAccountReceive::with(['unit', 'details', 'planAccount'])
+            ->orderByDesc('due_date')
+            ->orderByDesc('id');
+
+        if ($request->filled('status')) {
+            $query->where('status', $request->query('status'));
+        }
+
+        if ($request->filled('unit_id')) {
+            $query->where('unit_id', $request->query('unit_id'));
+        }
+
+        return $this->successResponse(
+            payload: FranchiseeAccountReceiveResource::collection($query->get()),
+        );
+    }
+
+    public function store(StoreFranchisorReceivableRequest $request): JsonResponse
+    {
+        $item = $this->service->createManual($request->validated());
+
+        return $this->successResponse(
+            payload: new FranchiseeAccountReceiveResource($item),
+            message: __('messages.created'),
+            code: 201,
+        );
+    }
+
+    public function settle(SettlePayableRequest $request, int $id): JsonResponse
+    {
+        $item = FranchiseeAccountReceive::find($id);
+        if (! $item) {
+            return $this->errorResponse(message: 'Conta a receber não encontrada', code: 404);
+        }
+
+        return $this->successResponse(
+            payload: new FranchiseeAccountReceiveResource($this->service->settle($item, $request->validated())),
+            message: 'Baixa realizada com sucesso',
+        );
+    }
+
+    public function charge(int $id): JsonResponse
+    {
+        $item = FranchiseeAccountReceive::find($id);
+        if (! $item) {
+            return $this->errorResponse(message: 'Conta a receber não encontrada', code: 404);
+        }
+
+        return $this->successResponse(
+            payload: new FranchiseeAccountReceiveResource($this->service->generateCharge($item)),
+            message: 'Cobrança gerada no Asaas',
+        );
+    }
+
+    public function reopen(int $id): JsonResponse
+    {
+        $item = FranchiseeAccountReceive::find($id);
+        if (! $item) {
+            return $this->errorResponse(message: 'Conta a receber não encontrada', code: 404);
+        }
+
+        return $this->successResponse(
+            payload: new FranchiseeAccountReceiveResource($this->service->reopen($item)),
+            message: 'Conta reaberta',
+        );
+    }
+}

+ 4 - 4
app/Http/Controllers/FranchiseeContractController.php

@@ -2,16 +2,18 @@
 
 
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
+use App\Http\Controllers\Concerns\ResolvesActiveUnit;
 use App\Services\FranchiseeContractService;
 use App\Services\FranchiseeContractService;
 use App\Http\Requests\FranchiseeContractRequest;
 use App\Http\Requests\FranchiseeContractRequest;
 use App\Http\Resources\FranchiseeContractResource;
 use App\Http\Resources\FranchiseeContractResource;
 use App\Http\Resources\FranchiseeContractTaxHistoryResource;
 use App\Http\Resources\FranchiseeContractTaxHistoryResource;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Auth;
 
 
 class FranchiseeContractController extends Controller
 class FranchiseeContractController extends Controller
 {
 {
+    use ResolvesActiveUnit;
+
     public function __construct(
     public function __construct(
         protected FranchiseeContractService $service,
         protected FranchiseeContractService $service,
     ) {}
     ) {}
@@ -95,8 +97,6 @@ public function taxHistoryMe(int $id): JsonResponse
 
 
     private function currentUnitId(): ?int
     private function currentUnitId(): ?int
     {
     {
-        $user = Auth::user()?->load('units');
-
-        return $user?->units->first()?->id;
+        return $this->activeUnitId();
     }
     }
 }
 }

+ 42 - 0
app/Http/Controllers/FranchiseeDashboardController.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Http\Controllers\Concerns\ResolvesActiveUnit;
+use App\Models\Student;
+use App\Models\StudentContract;
+use Illuminate\Http\JsonResponse;
+
+class FranchiseeDashboardController extends Controller
+{
+    use ResolvesActiveUnit;
+
+    public function summary(): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+        abort_if(! $unitId, 404, 'Unidade não encontrada.');
+
+        $students = Student::where('unit_id', $unitId)->get(['id', 'name', 'birth_date', 'created_at']);
+        $contracts = StudentContract::latestVersion()->where('unit_id', $unitId)->get(['id', 'status']);
+
+        return $this->successResponse(payload: [
+            'students_count' => $students->count(),
+            'contracts' => [
+                'active' => $contracts->where('status', 'active')->count(),
+                'frozen' => $contracts->where('status', 'frozen')->count(),
+                'cancelled' => $contracts->where('status', 'cancelled')->count(),
+            ],
+            'birthdays' => $students
+                ->filter(fn (Student $student) => $student->birth_date?->month === now()->month)
+                ->map(fn (Student $student) => [
+                    'day' => $student->birth_date->day,
+                    'name' => $student->name,
+                ])
+                ->sortBy('day')
+                ->values(),
+            'enrollments' => $students->map(fn (Student $student) => [
+                'created_at' => $student->created_at?->toISOString(),
+            ])->values(),
+        ]);
+    }
+}

+ 0 - 46
app/Http/Controllers/FranchiseeFnmBracketController.php

@@ -1,46 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\FranchiseeFnmBracketService;
-use App\Http\Requests\FranchiseeFnmBracketRequest;
-use App\Http\Resources\FranchiseeFnmBracketResource;
-use Illuminate\Http\JsonResponse;
-use Illuminate\Http\Request;
-
-class FranchiseeFnmBracketController extends Controller
-{
-    public function __construct(
-        protected FranchiseeFnmBracketService $service,
-    ) {}
-
-    public function index(Request $request): JsonResponse
-    {
-        $items = $this->service->getByFranchiseeId($request->integer('franchisee_id'));
-        return $this->successResponse(payload: FranchiseeFnmBracketResource::collection($items));
-    }
-
-    public function store(FranchiseeFnmBracketRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new FranchiseeFnmBracketResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new FranchiseeFnmBracketResource($item));
-    }
-
-    public function update(FranchiseeFnmBracketRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new FranchiseeFnmBracketResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 0 - 45
app/Http/Controllers/FranchiseeInventoryController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\FranchiseeInventoryService;
-use App\Http\Requests\FranchiseeInventoryRequest;
-use App\Http\Resources\FranchiseeInventoryResource;
-use Illuminate\Http\JsonResponse;
-
-class FranchiseeInventoryController extends Controller
-{
-    public function __construct(
-        protected FranchiseeInventoryService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: FranchiseeInventoryResource::collection($items));
-    }
-
-    public function store(FranchiseeInventoryRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new FranchiseeInventoryResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new FranchiseeInventoryResource($item));
-    }
-
-    public function update(FranchiseeInventoryRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new FranchiseeInventoryResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 0 - 46
app/Http/Controllers/FranchiseeMaintenanceBracketController.php

@@ -1,46 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\FranchiseeMaintenanceBracketService;
-use App\Http\Requests\FranchiseeMaintenanceBracketRequest;
-use App\Http\Resources\FranchiseeMaintenanceBracketResource;
-use Illuminate\Http\JsonResponse;
-use Illuminate\Http\Request;
-
-class FranchiseeMaintenanceBracketController extends Controller
-{
-    public function __construct(
-        protected FranchiseeMaintenanceBracketService $service,
-    ) {}
-
-    public function index(Request $request): JsonResponse
-    {
-        $items = $this->service->getByFranchiseeId($request->integer('franchisee_id'));
-        return $this->successResponse(payload: FranchiseeMaintenanceBracketResource::collection($items));
-    }
-
-    public function store(FranchiseeMaintenanceBracketRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new FranchiseeMaintenanceBracketResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new FranchiseeMaintenanceBracketResource($item));
-    }
-
-    public function update(FranchiseeMaintenanceBracketRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new FranchiseeMaintenanceBracketResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 0 - 46
app/Http/Controllers/FranchiseeRoyaltiesBracketController.php

@@ -1,46 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\FranchiseeRoyaltiesBracketService;
-use App\Http\Requests\FranchiseeRoyaltiesBracketRequest;
-use App\Http\Resources\FranchiseeRoyaltiesBracketResource;
-use Illuminate\Http\JsonResponse;
-use Illuminate\Http\Request;
-
-class FranchiseeRoyaltiesBracketController extends Controller
-{
-    public function __construct(
-        protected FranchiseeRoyaltiesBracketService $service,
-    ) {}
-
-    public function index(Request $request): JsonResponse
-    {
-        $items = $this->service->getByFranchiseeId($request->integer('franchisee_id'));
-        return $this->successResponse(payload: FranchiseeRoyaltiesBracketResource::collection($items));
-    }
-
-    public function store(FranchiseeRoyaltiesBracketRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new FranchiseeRoyaltiesBracketResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new FranchiseeRoyaltiesBracketResource($item));
-    }
-
-    public function update(FranchiseeRoyaltiesBracketRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new FranchiseeRoyaltiesBracketResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 0 - 46
app/Http/Controllers/FranchiseeTbrController.php

@@ -1,46 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\FranchiseeTbrService;
-use App\Http\Requests\FranchiseeTbrRequest;
-use App\Http\Resources\FranchiseeTbrResource;
-use Illuminate\Http\JsonResponse;
-use Illuminate\Http\Request;
-
-class FranchiseeTbrController extends Controller
-{
-    public function __construct(
-        protected FranchiseeTbrService $service,
-    ) {}
-
-    public function index(Request $request): JsonResponse
-    {
-        $items = $this->service->paginate($request->integer('per_page', 15));
-        return $this->successResponse(payload: FranchiseeTbrResource::collection($items));
-    }
-
-    public function store(FranchiseeTbrRequest $request): JsonResponse
-    {
-        $item = $this->service->createWithBrackets($request->validated());
-        return $this->successResponse(payload: new FranchiseeTbrResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new FranchiseeTbrResource($item));
-    }
-
-    public function update(FranchiseeTbrRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new FranchiseeTbrResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 32 - 0
app/Http/Controllers/FranchisorDashboardController.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Services\FranchisorDashboardService;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Support\Carbon;
+
+class FranchisorDashboardController extends Controller
+{
+    public function __construct(
+        protected FranchisorDashboardService $service,
+    ) {}
+
+    public function overview(): JsonResponse
+    {
+        $data = request()->validate([
+            'unit_ids'   => ['sometimes', 'array'],
+            'unit_ids.*' => ['integer', 'exists:units,id'],
+            'start_date' => ['nullable', 'date'],
+            'end_date'   => ['nullable', 'date', 'after_or_equal:start_date'],
+        ]);
+
+        $unitIds   = array_values(array_filter((array) ($data['unit_ids'] ?? [])));
+        $startDate = isset($data['start_date']) ? Carbon::parse($data['start_date'])->startOfDay() : null;
+        $endDate   = isset($data['end_date']) ? Carbon::parse($data['end_date'])->endOfDay() : null;
+
+        return $this->successResponse(
+            payload: $this->service->overview($unitIds, $startDate, $endDate),
+        );
+    }
+}

+ 0 - 45
app/Http/Controllers/FranchisorInventoryController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\FranchisorInventoryService;
-use App\Http\Requests\FranchisorInventoryRequest;
-use App\Http\Resources\FranchisorInventoryResource;
-use Illuminate\Http\JsonResponse;
-
-class FranchisorInventoryController extends Controller
-{
-    public function __construct(
-        protected FranchisorInventoryService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: FranchisorInventoryResource::collection($items));
-    }
-
-    public function store(FranchisorInventoryRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new FranchisorInventoryResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new FranchisorInventoryResource($item));
-    }
-
-    public function update(FranchisorInventoryRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new FranchisorInventoryResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 50 - 0
app/Http/Controllers/GroupController.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Services\GroupService;
+use App\Http\Requests\GroupRequest;
+use App\Http\Resources\GroupResource;
+use Illuminate\Http\JsonResponse;
+
+class GroupController extends Controller
+{
+    public function __construct(protected GroupService $service) {}
+
+    public function index(): JsonResponse
+    {
+        $items = $this->service->getAll();
+        return $this->successResponse(payload: GroupResource::collection($items));
+    }
+
+    public function store(GroupRequest $request): JsonResponse
+    {
+        $item = $this->service->create($request->validated());
+        return $this->successResponse(
+            payload: new GroupResource($item),
+            message: __('messages.created'),
+            code: 201,
+        );
+    }
+
+    public function show(int $id): JsonResponse
+    {
+        $item = $this->service->findById($id);
+        return $this->successResponse(payload: new GroupResource($item));
+    }
+
+    public function update(GroupRequest $request, int $id): JsonResponse
+    {
+        $item = $this->service->update($id, $request->validated());
+        return $this->successResponse(
+            payload: new GroupResource($item),
+            message: __('messages.updated'),
+        );
+    }
+
+    public function destroy(int $id): JsonResponse
+    {
+        $this->service->delete($id);
+        return $this->successResponse(message: __('messages.deleted'), code: 204);
+    }
+}

+ 14 - 0
app/Http/Controllers/HolidayController.php

@@ -34,12 +34,26 @@ public function show(int $id): JsonResponse
 
 
     public function update(HolidayRequest $request, int $id): JsonResponse
     public function update(HolidayRequest $request, int $id): JsonResponse
     {
     {
+        $item = $this->service->findById($id);
+        if (!$item) {
+            return $this->errorResponse(message: 'Feriado não encontrado.', code: 404);
+        }
+        if ($item->base_holiday_id !== null) {
+            return $this->errorResponse(message: 'Este feriado foi criado pela franqueadora e não pode ser editado.', code: 403);
+        }
         $item = $this->service->update($id, $request->validated());
         $item = $this->service->update($id, $request->validated());
         return $this->successResponse(payload: new HolidayResource($item), message: __('messages.updated'));
         return $this->successResponse(payload: new HolidayResource($item), message: __('messages.updated'));
     }
     }
 
 
     public function destroy(int $id): JsonResponse
     public function destroy(int $id): JsonResponse
     {
     {
+        $item = $this->service->findById($id);
+        if (!$item) {
+            return $this->errorResponse(message: 'Feriado não encontrado.', code: 404);
+        }
+        if ($item->base_holiday_id !== null) {
+            return $this->errorResponse(message: 'Este feriado foi criado pela franqueadora e não pode ser excluído.', code: 403);
+        }
         $this->service->delete($id);
         $this->service->delete($id);
         return $this->successResponse(message: __('messages.deleted'), code: 204);
         return $this->successResponse(message: __('messages.deleted'), code: 204);
     }
     }

+ 0 - 5
app/Http/Controllers/InhabitantClassificationController.php

@@ -43,9 +43,4 @@ public function destroy(int $id): JsonResponse
         return $this->successResponse(message: __('messages.deleted'), code: 204);
         return $this->successResponse(message: __('messages.deleted'), code: 204);
     }
     }
 
 
-    public function selectList(): JsonResponse
-    {
-        $items = $this->service->getSelectList();
-        return $this->successResponse(payload: $items);
-    }
 }
 }

+ 0 - 45
app/Http/Controllers/IntegrationVariableController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\IntegrationVariableService;
-use App\Http\Requests\IntegrationVariableRequest;
-use App\Http\Resources\IntegrationVariableResource;
-use Illuminate\Http\JsonResponse;
-
-class IntegrationVariableController extends Controller
-{
-    public function __construct(
-        protected IntegrationVariableService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: IntegrationVariableResource::collection($items));
-    }
-
-    public function store(IntegrationVariableRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new IntegrationVariableResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new IntegrationVariableResource($item));
-    }
-
-    public function update(IntegrationVariableRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new IntegrationVariableResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 0 - 45
app/Http/Controllers/IntegrationsController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\IntegrationsService;
-use App\Http\Requests\IntegrationsRequest;
-use App\Http\Resources\IntegrationsResource;
-use Illuminate\Http\JsonResponse;
-
-class IntegrationsController extends Controller
-{
-    public function __construct(
-        protected IntegrationsService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: IntegrationsResource::collection($items));
-    }
-
-    public function store(IntegrationsRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new IntegrationsResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new IntegrationsResource($item));
-    }
-
-    public function update(IntegrationsRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new IntegrationsResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 101 - 3
app/Http/Controllers/KanbanController.php

@@ -2,27 +2,114 @@
 
 
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
+use App\Http\Controllers\Concerns\ResolvesActiveUnit;
+use App\Enums\NotificationType;
 use App\Services\KanbanService;
 use App\Services\KanbanService;
+use App\Services\NotificationService;
 use App\Http\Requests\KanbanRequest;
 use App\Http\Requests\KanbanRequest;
+use App\Http\Requests\KanbanReorderRequest;
 use App\Http\Resources\KanbanResource;
 use App\Http\Resources\KanbanResource;
+use App\Models\Unit;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\JsonResponse;
 
 
 class KanbanController extends Controller
 class KanbanController extends Controller
 {
 {
+    use ResolvesActiveUnit;
+
     public function __construct(
     public function __construct(
         protected KanbanService $service,
         protected KanbanService $service,
+        protected NotificationService $notifications,
     ) {}
     ) {}
 
 
     public function index(): JsonResponse
     public function index(): JsonResponse
     {
     {
-        $items = $this->service->getAll();
+        $user = auth()->user();
+
+        if ($this->isMatriz($user)) {
+            $items = $this->service->getAll();
+        } else {
+            $items = $this->service->getAllForUnit($this->activeUnitId($user));
+        }
+
         return $this->successResponse(payload: KanbanResource::collection($items));
         return $this->successResponse(payload: KanbanResource::collection($items));
     }
     }
 
 
     public function store(KanbanRequest $request): JsonResponse
     public function store(KanbanRequest $request): JsonResponse
     {
     {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new KanbanResource($item), message: __('messages.created'), code: 201);
+        $data = $request->validated();
+        $user = auth()->user();
+        $isMatriz = $this->isMatriz($user);
+
+        $data['origin'] = $isMatriz ? 'matriz' : 'unit';
+        $data['created_by_user_id'] = $user->id;
+        $data['unit_id'] = $isMatriz ? null : $this->activeUnitId($user);
+
+        // Broadcast to all units
+        if ($isMatriz && ($data['scope'] ?? null) === 'all') {
+            $unitIds = Unit::query()->pluck('id');
+            $created = [];
+            foreach ($unitIds as $unitId) {
+                $card = $this->service->create(array_merge($data, [
+                    'target_unit_id' => $unitId,
+                ]));
+                $this->notifyKanbanCreated($card);
+                $created[] = $card;
+            }
+            return $this->successResponse(
+                payload: KanbanResource::collection($created),
+                message: __('messages.created'),
+                code: 201
+            );
+        }
+
+        if ($isMatriz) {
+            if (($data['scope'] ?? null) === 'internal') {
+                $data['target_unit_id'] = null;
+            }
+            // scope='specific' → target_unit_id already set from request
+        } else {
+            // Franchisee: internal = own unit, specific = Matriz (null target)
+            $data['target_unit_id'] = (($data['scope'] ?? null) === 'internal')
+                ? $this->activeUnitId($user)
+                : null;
+        }
+
+        $item = $this->service->create($data);
+        $this->notifyKanbanCreated($item);
+        return $this->successResponse(
+            payload: new KanbanResource($item),
+            message: __('messages.created'),
+            code: 201
+        );
+    }
+
+    /**
+     * Notifica o outro lado quando uma atividade é criada:
+     * franqueada → matriz; matriz → unidade alvo.
+     */
+    private function notifyKanbanCreated(\App\Models\Kanban $card): void
+    {
+        if ($card->origin === 'unit') {
+            $unitId     = $card->unit_id;
+            $recipients = $this->notifications->matrizUserIds();
+        } else {
+            $unitId     = $card->target_unit_id;
+            $recipients = $this->notifications->unitUserIds($unitId);
+        }
+
+        $this->notifications->dispatch([
+            'origin_table'       => 'kanbans',
+            'origin_id'          => $card->id,
+            'unit_id'            => $unitId,
+            'type'               => NotificationType::KANBAN_CREATED->value,
+            'title'              => 'Nova atividade',
+            'message'            => $card->title,
+            'url'                => '/kanban',
+            'action_text'        => 'Ver atividade',
+            'priority'           => $card->priority === 'alta' ? 'high' : 'normal',
+            'created_by_user_id' => $card->created_by_user_id,
+            'recipient_user_ids' => $recipients,
+        ]);
     }
     }
 
 
     public function show(int $id): JsonResponse
     public function show(int $id): JsonResponse
@@ -42,4 +129,15 @@ public function destroy(int $id): JsonResponse
         $this->service->delete($id);
         $this->service->delete($id);
         return $this->successResponse(message: __('messages.deleted'), code: 204);
         return $this->successResponse(message: __('messages.deleted'), code: 204);
     }
     }
+
+    public function reorder(KanbanReorderRequest $request): JsonResponse
+    {
+        $this->service->reorder($request->validated()['items']);
+        return $this->successResponse(message: __('messages.updated'));
+    }
+
+    private function isMatriz(\App\Models\User $user): bool
+    {
+        return $user->user_type === 'ADMIN';
+    }
 }
 }

+ 33 - 0
app/Http/Controllers/KanbanMediaController.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Services\KanbanMediaService;
+use App\Http\Requests\KanbanMediaRequest;
+use App\Http\Resources\KanbanMediaResource;
+use Illuminate\Http\JsonResponse;
+
+class KanbanMediaController extends Controller
+{
+    public function __construct(
+        protected KanbanMediaService $service,
+    ) {}
+
+    public function index(int $kanbanId): JsonResponse
+    {
+        $medias = $this->service->getByKanban($kanbanId);
+        return $this->successResponse(payload: KanbanMediaResource::collection($medias));
+    }
+
+    public function store(KanbanMediaRequest $request, int $kanbanId): JsonResponse
+    {
+        $media = $this->service->create($kanbanId, auth()->id(), $request->file('file'));
+        return $this->successResponse(payload: new KanbanMediaResource($media), message: __('messages.created'), code: 201);
+    }
+
+    public function destroy(int $kanbanId, int $id): JsonResponse
+    {
+        $this->service->delete($kanbanId, $id);
+        return $this->successResponse(message: __('messages.deleted'), code: 204);
+    }
+}

+ 0 - 45
app/Http/Controllers/KanbanMovementController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\KanbanMovementService;
-use App\Http\Requests\KanbanMovementRequest;
-use App\Http\Resources\KanbanMovementResource;
-use Illuminate\Http\JsonResponse;
-
-class KanbanMovementController extends Controller
-{
-    public function __construct(
-        protected KanbanMovementService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: KanbanMovementResource::collection($items));
-    }
-
-    public function store(KanbanMovementRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new KanbanMovementResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new KanbanMovementResource($item));
-    }
-
-    public function update(KanbanMovementRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new KanbanMovementResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 80 - 0
app/Http/Controllers/KanbanReplyController.php

@@ -0,0 +1,80 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Enums\NotificationType;
+use App\Models\Kanban;
+use App\Services\KanbanReplyService;
+use App\Services\NotificationService;
+use App\Http\Requests\KanbanReplyRequest;
+use App\Http\Resources\KanbanReplyResource;
+use Illuminate\Http\JsonResponse;
+
+class KanbanReplyController extends Controller
+{
+    public function __construct(
+        protected KanbanReplyService $service,
+        protected NotificationService $notifications,
+    ) {}
+
+    public function index(int $kanbanId): JsonResponse
+    {
+        $replies = $this->service->getByKanban($kanbanId);
+        return $this->successResponse(payload: KanbanReplyResource::collection($replies));
+    }
+
+    public function store(KanbanReplyRequest $request, int $kanbanId): JsonResponse
+    {
+        $reply = $this->service->create($kanbanId, auth()->id(), $request->validated()['reply']);
+        $this->notifyReplyCreated($kanbanId);
+        return $this->successResponse(payload: new KanbanReplyResource($reply), message: __('messages.created'), code: 201);
+    }
+
+    /**
+     * Notifica o outro lado da atividade quando há um novo comentário.
+     * Autor da matriz → unidade da atividade; autor da unidade → matriz.
+     */
+    private function notifyReplyCreated(int $kanbanId): void
+    {
+        $card = Kanban::find($kanbanId);
+        if (!$card) {
+            return;
+        }
+
+        $author         = auth()->user();
+        $authorIsMatriz = $author->user_type === 'ADMIN';
+        $unitId         = $card->unit_id ?? $card->target_unit_id;
+
+        $recipients = $authorIsMatriz
+            ? $this->notifications->unitUserIds($unitId)
+            : $this->notifications->matrizUserIds();
+
+        // Não notifica o próprio autor do comentário.
+        $recipients = array_values(array_diff($recipients, [$author->id]));
+
+        $this->notifications->dispatch([
+            'origin_table'       => 'kanban_replies',
+            'origin_id'          => $card->id,
+            'unit_id'            => $unitId,
+            'type'               => NotificationType::KANBAN_REPLY->value,
+            'title'              => 'Novo comentário na atividade',
+            'message'            => $card->title,
+            'url'                => '/kanban',
+            'action_text'        => 'Ver atividade',
+            'created_by_user_id' => $author->id,
+            'recipient_user_ids' => $recipients,
+        ]);
+    }
+
+    public function update(KanbanReplyRequest $request, int $kanbanId, int $id): JsonResponse
+    {
+        $reply = $this->service->update($kanbanId, $id, $request->validated()['reply']);
+        return $this->successResponse(payload: new KanbanReplyResource($reply), message: __('messages.updated'));
+    }
+
+    public function destroy(int $kanbanId, int $id): JsonResponse
+    {
+        $this->service->delete($kanbanId, $id);
+        return $this->successResponse(message: __('messages.deleted'), code: 204);
+    }
+}

+ 0 - 45
app/Http/Controllers/KanbanStatusController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\KanbanStatusService;
-use App\Http\Requests\KanbanStatusRequest;
-use App\Http\Resources\KanbanStatusResource;
-use Illuminate\Http\JsonResponse;
-
-class KanbanStatusController extends Controller
-{
-    public function __construct(
-        protected KanbanStatusService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: KanbanStatusResource::collection($items));
-    }
-
-    public function store(KanbanStatusRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new KanbanStatusResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new KanbanStatusResource($item));
-    }
-
-    public function update(KanbanStatusRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new KanbanStatusResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 0 - 45
app/Http/Controllers/MaintenanceBaseBracketController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\MaintenanceBaseBracketService;
-use App\Http\Requests\MaintenanceBaseBracketRequest;
-use App\Http\Resources\MaintenanceBaseBracketResource;
-use Illuminate\Http\JsonResponse;
-
-class MaintenanceBaseBracketController extends Controller
-{
-    public function __construct(
-        protected MaintenanceBaseBracketService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: MaintenanceBaseBracketResource::collection($items));
-    }
-
-    public function store(MaintenanceBaseBracketRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new MaintenanceBaseBracketResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new MaintenanceBaseBracketResource($item));
-    }
-
-    public function update(MaintenanceBaseBracketRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new MaintenanceBaseBracketResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 0 - 46
app/Http/Controllers/MediaController.php

@@ -1,46 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\MediaService;
-use App\Http\Requests\MediaRequest;
-use App\Http\Resources\MediaResource;
-use Illuminate\Http\JsonResponse;
-use Illuminate\Http\Request;
-
-class MediaController extends Controller
-{
-    public function __construct(
-        protected MediaService $service,
-    ) {}
-
-    public function index(Request $request): JsonResponse
-    {
-        $items = $this->service->getAll($request->only(['origem', 'origem_id']));
-        return $this->successResponse(payload: MediaResource::collection($items));
-    }
-
-    public function store(MediaRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new MediaResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new MediaResource($item));
-    }
-
-    public function update(MediaRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new MediaResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 0 - 45
app/Http/Controllers/ModalityController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\ModalityService;
-use App\Http\Requests\ModalityRequest;
-use App\Http\Resources\ModalityResource;
-use Illuminate\Http\JsonResponse;
-
-class ModalityController extends Controller
-{
-    public function __construct(
-        protected ModalityService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: ModalityResource::collection($items));
-    }
-
-    public function store(ModalityRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new ModalityResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new ModalityResource($item));
-    }
-
-    public function update(ModalityRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new ModalityResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 29 - 0
app/Http/Controllers/NotificationController.php

@@ -19,6 +19,35 @@ public function index(): JsonResponse
         return $this->successResponse(payload: NotificationResource::collection($items));
         return $this->successResponse(payload: NotificationResource::collection($items));
     }
     }
 
 
+    /** Notificações do usuário logado (sino). */
+    public function me(): JsonResponse
+    {
+        $items = $this->service->forUser(auth()->id());
+        return $this->successResponse(payload: NotificationResource::collection($items));
+    }
+
+    /** Contador de não lidas do usuário logado. */
+    public function unreadCount(): JsonResponse
+    {
+        return $this->successResponse(
+            payload: ['count' => $this->service->unreadCount(auth()->id())]
+        );
+    }
+
+    /** Marca uma notificação como lida para o usuário logado. */
+    public function markRead(int $id): JsonResponse
+    {
+        $this->service->markRead($id, auth()->id());
+        return $this->successResponse(message: __('messages.updated'));
+    }
+
+    /** Marca todas as notificações do usuário logado como lidas. */
+    public function markAllRead(): JsonResponse
+    {
+        $this->service->markAllRead(auth()->id());
+        return $this->successResponse(message: __('messages.updated'));
+    }
+
     public function store(NotificationRequest $request): JsonResponse
     public function store(NotificationRequest $request): JsonResponse
     {
     {
         $item = $this->service->create($request->validated());
         $item = $this->service->create($request->validated());

+ 0 - 45
app/Http/Controllers/NotificationRecipientController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\NotificationRecipientService;
-use App\Http\Requests\NotificationRecipientRequest;
-use App\Http\Resources\NotificationRecipientResource;
-use Illuminate\Http\JsonResponse;
-
-class NotificationRecipientController extends Controller
-{
-    public function __construct(
-        protected NotificationRecipientService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: NotificationRecipientResource::collection($items));
-    }
-
-    public function store(NotificationRecipientRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new NotificationRecipientResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new NotificationRecipientResource($item));
-    }
-
-    public function update(NotificationRecipientRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new NotificationRecipientResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 0 - 45
app/Http/Controllers/PaymentMethodController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\PaymentMethodService;
-use App\Http\Requests\PaymentMethodRequest;
-use App\Http\Resources\PaymentMethodResource;
-use Illuminate\Http\JsonResponse;
-
-class PaymentMethodController extends Controller
-{
-    public function __construct(
-        protected PaymentMethodService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: PaymentMethodResource::collection($items));
-    }
-
-    public function store(PaymentMethodRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new PaymentMethodResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new PaymentMethodResource($item));
-    }
-
-    public function update(PaymentMethodRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new PaymentMethodResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 0 - 55
app/Http/Controllers/PermissionController.php

@@ -1,55 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\PermissionService;
-use App\Http\Requests\PermissionRequest;
-use App\Http\Resources\PermissionResource;
-use Illuminate\Http\JsonResponse;
-
-class PermissionController extends Controller
-{
-    public function __construct(protected PermissionService $service) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(
-            payload: PermissionResource::collection($items),
-        );
-    }
-
-    public function store(PermissionRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(
-            payload: new PermissionResource($item),
-            message: __("messages.created"),
-            code: 201,
-        );
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new PermissionResource($item));
-    }
-
-    public function update(PermissionRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(
-            payload: new PermissionResource($item),
-            message: __("messages.updated"),
-        );
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(
-            message: __("messages.deleted"),
-            code: 204,
-        );
-    }
-}

+ 59 - 6
app/Http/Controllers/ProductController.php

@@ -4,6 +4,9 @@
 
 
 use App\Services\ProductService;
 use App\Services\ProductService;
 use App\Http\Requests\ProductRequest;
 use App\Http\Requests\ProductRequest;
+use App\Http\Requests\ProductStockRequest;
+use Illuminate\Http\Request;
+use PhpOffice\PhpSpreadsheet\IOFactory;
 use App\Http\Resources\ProductResource;
 use App\Http\Resources\ProductResource;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\JsonResponse;
 
 
@@ -13,12 +16,6 @@ public function __construct(
         protected ProductService $service,
         protected ProductService $service,
     ) {}
     ) {}
 
 
-    public function select(): JsonResponse
-    {
-        $items = $this->service->getAllForSelect();
-        return $this->successResponse(payload: $items);
-    }
-
     public function index(): JsonResponse
     public function index(): JsonResponse
     {
     {
         $items = $this->service->getAll();
         $items = $this->service->getAll();
@@ -48,4 +45,60 @@ public function destroy(int $id): JsonResponse
         $this->service->delete($id);
         $this->service->delete($id);
         return $this->successResponse(message: __('messages.deleted'), code: 204);
         return $this->successResponse(message: __('messages.deleted'), code: 204);
     }
     }
+
+    public function import(Request $request): JsonResponse
+    {
+        $request->validate(['file' => 'required|file|mimes:xlsx,xls,csv']);
+
+        $spreadsheet = IOFactory::load($request->file('file')->getRealPath());
+        $rows        = $spreadsheet->getActiveSheet()->toArray();
+
+        if (empty($rows)) {
+            return $this->errorResponse('Arquivo vazio.', 422);
+        }
+
+        $headers = array_map(fn($h) => mb_strtolower(trim((string) $h)), $rows[0]);
+
+        $colName  = null;
+        $colQty   = null;
+        $colPrice = null;
+
+        foreach ($headers as $i => $h) {
+            if ($colName  === null && str_contains($h, 'descri')) $colName  = $i;
+            if ($colQty   === null && (str_contains($h, 'qtd') || str_contains($h, 'quant'))) $colQty = $i;
+            if ($colPrice === null && (str_contains($h, 'pre') || str_contains($h, 'valor'))) $colPrice = $i;
+        }
+
+        if ($colName === null || $colQty === null || $colPrice === null) {
+            return $this->errorResponse('Colunas não encontradas. O arquivo deve conter: Descrição, Qtd, Preço.', 422);
+        }
+
+        $imported = [];
+
+        for ($i = 1; $i < count($rows); $i++) {
+            $row  = $rows[$i];
+            $name = trim((string) ($row[$colName] ?? ''));
+
+            if ($name === '') continue;
+
+            $qty   = (int)   ($row[$colQty]   ?? 0);
+            $price = (float) ($row[$colPrice]  ?? 0);
+
+            $product = $this->service->importRow($name, $qty, $price);
+            $imported[] = new ProductResource($product);
+        }
+
+        return $this->successResponse(payload: $imported);
+    }
+
+    public function adjustStock(ProductStockRequest $request, int $id): JsonResponse
+    {
+        $result = $this->service->adjustStock($id, $request->type, $request->quantity);
+
+        if (isset($result['error'])) {
+            return $this->errorResponse($result['error'], 422);
+        }
+
+        return $this->successResponse(payload: new ProductResource($result['product']), message: __('messages.updated'));
+    }
 }
 }

+ 0 - 45
app/Http/Controllers/ProductKitController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\ProductKitService;
-use App\Http\Requests\ProductKitRequest;
-use App\Http\Resources\ProductKitResource;
-use Illuminate\Http\JsonResponse;
-
-class ProductKitController extends Controller
-{
-    public function __construct(
-        protected ProductKitService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: ProductKitResource::collection($items));
-    }
-
-    public function store(ProductKitRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new ProductKitResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new ProductKitResource($item));
-    }
-
-    public function update(ProductKitRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new ProductKitResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 0 - 45
app/Http/Controllers/ProductMovementController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\ProductMovementService;
-use App\Http\Requests\ProductMovementRequest;
-use App\Http\Resources\ProductMovementResource;
-use Illuminate\Http\JsonResponse;
-
-class ProductMovementController extends Controller
-{
-    public function __construct(
-        protected ProductMovementService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: ProductMovementResource::collection($items));
-    }
-
-    public function store(ProductMovementRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new ProductMovementResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new ProductMovementResource($item));
-    }
-
-    public function update(ProductMovementRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new ProductMovementResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 0 - 45
app/Http/Controllers/ProductOrderController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\ProductOrderService;
-use App\Http\Requests\ProductOrderRequest;
-use App\Http\Resources\ProductOrderResource;
-use Illuminate\Http\JsonResponse;
-
-class ProductOrderController extends Controller
-{
-    public function __construct(
-        protected ProductOrderService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: ProductOrderResource::collection($items));
-    }
-
-    public function store(ProductOrderRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new ProductOrderResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new ProductOrderResource($item));
-    }
-
-    public function update(ProductOrderRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new ProductOrderResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 0 - 45
app/Http/Controllers/ProductOrderItemController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\ProductOrderItemService;
-use App\Http\Requests\ProductOrderItemRequest;
-use App\Http\Resources\ProductOrderItemResource;
-use Illuminate\Http\JsonResponse;
-
-class ProductOrderItemController extends Controller
-{
-    public function __construct(
-        protected ProductOrderItemService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: ProductOrderItemResource::collection($items));
-    }
-
-    public function store(ProductOrderItemRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new ProductOrderItemResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new ProductOrderItemResource($item));
-    }
-
-    public function update(ProductOrderItemRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new ProductOrderItemResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 0 - 45
app/Http/Controllers/RoyaltiesBaseBracketController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\RoyaltiesBaseBracketService;
-use App\Http\Requests\RoyaltiesBaseBracketRequest;
-use App\Http\Resources\RoyaltiesBaseBracketResource;
-use Illuminate\Http\JsonResponse;
-
-class RoyaltiesBaseBracketController extends Controller
-{
-    public function __construct(
-        protected RoyaltiesBaseBracketService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: RoyaltiesBaseBracketResource::collection($items));
-    }
-
-    public function store(RoyaltiesBaseBracketRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new RoyaltiesBaseBracketResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new RoyaltiesBaseBracketResource($item));
-    }
-
-    public function update(RoyaltiesBaseBracketRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new RoyaltiesBaseBracketResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 171 - 0
app/Http/Controllers/SelectController.php

@@ -0,0 +1,171 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Http\Controllers\Concerns\ResolvesActiveUnit;
+use App\Http\Resources\Select\CitySelectResource;
+use App\Http\Resources\Select\CountrySelectResource;
+use App\Http\Resources\Select\FinancialPlanAccountSelectResource;
+use App\Http\Resources\Select\GroupSelectResource;
+use App\Http\Resources\Select\InhabitantClassificationSelectResource;
+use App\Http\Resources\Select\MunicipalitySizeSelectResource;
+use App\Http\Resources\Select\PackageSelectResource;
+use App\Http\Resources\Select\ProductSelectResource;
+use App\Http\Resources\Select\StateSelectResource;
+use App\Http\Resources\Select\StudentSelectResource;
+use App\Http\Resources\Select\UnitSelectResource;
+use App\Http\Resources\Select\UserSelectResource;
+use App\Http\Resources\Select\UserTypeSelectResource;
+use App\Models\City;
+use App\Models\ClassPackageUnit;
+use App\Models\Country;
+use App\Models\FinancialPlanAccount;
+use App\Models\Group;
+use App\Models\InhabitantClassification;
+use App\Models\MunicipalitySize;
+use App\Models\Product;
+use App\Models\State;
+use App\Models\Student;
+use App\Models\Unit;
+use App\Models\User;
+use App\Models\UserType;
+use Illuminate\Http\JsonResponse;
+
+class SelectController extends Controller
+{
+    use ResolvesActiveUnit;
+
+    public function countries(): JsonResponse
+    {
+        return $this->items(CountrySelectResource::collection(Country::orderBy('name')->get()));
+    }
+
+    public function states(): JsonResponse
+    {
+        $items = State::query()
+            ->when(request()->integer('country_id'), fn ($query, $id) => $query->where('country_id', $id))
+            ->orderBy('name')->get();
+
+        return $this->items(StateSelectResource::collection($items));
+    }
+
+    public function cities(): JsonResponse
+    {
+        $items = City::query()
+            ->when(request()->integer('state_id'), fn ($query, $id) => $query->where('state_id', $id))
+            ->when(request()->integer('country_id'), fn ($query, $id) => $query->where('country_id', $id))
+            ->orderBy('name')->get();
+
+        return $this->items(CitySelectResource::collection($items));
+    }
+
+    public function units(): JsonResponse
+    {
+        return $this->items(UnitSelectResource::collection(Unit::orderBy('fantasy_name')->get()));
+    }
+
+    public function groups(): JsonResponse
+    {
+        return $this->items(GroupSelectResource::collection(Group::with('units:id')->orderBy('name')->get()));
+    }
+
+    public function products(): JsonResponse
+    {
+        $items = Product::query()
+            ->when(request()->boolean('visible_franchisee'), fn ($query) => $query->where('visible_franchisee', true))
+            ->orderBy('name')->get();
+
+        return $this->items(ProductSelectResource::collection($items));
+    }
+
+    public function franchiseePackages(): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+        $items = $unitId
+            ? ClassPackageUnit::where('unit_id', $unitId)->where('visible', true)->orderBy('name')->get()
+            : collect();
+
+        return $this->items(PackageSelectResource::collection($items));
+    }
+
+    public function franchiseeUsers(): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+        $items = $unitId
+            ? User::query()
+                ->join('unit_users', fn ($join) => $join->on('unit_users.user_id', 'users.id')->where('unit_users.unit_id', $unitId))
+                ->leftJoin('user_types', 'user_types.id', 'unit_users.user_type_id')
+                ->orderBy('users.name')
+                ->get([
+                    'users.id',
+                    'users.name',
+                    'user_types.slug as user_type',
+                    'user_types.label as user_type_label',
+                    'user_types.system_key as user_type_system_key',
+                ])
+            : collect();
+
+        return $this->items(UserSelectResource::collection($items));
+    }
+
+    public function franchiseeStudents(): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+        $items = $unitId
+            ? Student::where('unit_id', $unitId)->orderBy('name')->get()
+            : collect();
+
+        return $this->items(StudentSelectResource::collection($items));
+    }
+
+    public function userTypes(): JsonResponse
+    {
+        $scope = request()->input('access_scope', auth()->user()?->access_scope);
+        $unitId = request()->integer('unit_id') ?: $this->activeUnitId();
+        $isFranchisorAssignment = request()->boolean('include_system_admin');
+
+        $items = UserType::query()
+            ->when($scope === 'unit' && $unitId, fn ($query) => $query->where('unit_id', $unitId))
+            ->when($scope === 'unit' && ! $unitId, fn ($query) => $query->where('system_key', 'ADMIN_FRANCHISEE'))
+            ->when($scope === 'franchisor', fn ($query) => $query->whereNull('unit_id'))
+            ->when(
+                $scope === 'unit' && $isFranchisorAssignment,
+                fn ($query) => $query->where(fn ($types) => $types->where('is_system', false)->orWhere('system_key', 'ADMIN_FRANCHISEE')),
+                fn ($query) => $query->where('is_system', false),
+            )
+            ->orderBy('label')->get();
+
+        return $this->items(UserTypeSelectResource::collection($items));
+    }
+
+    public function inhabitantClassifications(): JsonResponse
+    {
+        return $this->items(InhabitantClassificationSelectResource::collection(
+            InhabitantClassification::with('municipalitySize')->orderBy('description')->get()
+        ));
+    }
+
+    public function municipalitySizes(): JsonResponse
+    {
+        return $this->items(MunicipalitySizeSelectResource::collection(MunicipalitySize::orderBy('id')->get()));
+    }
+
+    public function financialPlanAccounts(): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+        $items = FinancialPlanAccount::query()
+            ->when(
+                $unitId,
+                fn ($query) => $query->where(fn ($scope) => $scope->where('unit_id', $unitId)->orWhereNull('unit_id')),
+                fn ($query) => $query->whereNull('unit_id'),
+            )
+            ->orderBy('code')->get();
+
+        return $this->items(FinancialPlanAccountSelectResource::collection($items));
+    }
+
+    private function items($items): JsonResponse
+    {
+        return $this->successResponse(payload: $items);
+    }
+}

+ 112 - 10
app/Http/Controllers/StudentContractController.php

@@ -2,57 +2,159 @@
 
 
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
-use App\Services\StudentContractService;
+use App\Http\Controllers\Concerns\ResolvesActiveUnit;
 use App\Http\Requests\StudentContractRequest;
 use App\Http\Requests\StudentContractRequest;
+use App\Http\Resources\StudentContractInstallmentResource;
 use App\Http\Resources\StudentContractResource;
 use App\Http\Resources\StudentContractResource;
+use App\Models\UnitFinancial;
+use App\Services\StudentContractService;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\Auth;
 
 
 class StudentContractController extends Controller
 class StudentContractController extends Controller
 {
 {
+    use ResolvesActiveUnit;
+
     public function __construct(
     public function __construct(
         protected StudentContractService $service,
         protected StudentContractService $service,
     ) {}
     ) {}
 
 
     public function index(): JsonResponse
     public function index(): JsonResponse
     {
     {
-        $unitId    = Auth::user()->load('units')->units->first()?->id;
+        $unitId = $this->activeUnitId();
         $studentId = request()->integer('student_id') ?: null;
         $studentId = request()->integer('student_id') ?: null;
-        $items     = $this->service->getAll($unitId, $studentId);
+        $items = $this->service->getAll($unitId, $studentId);
+
         return $this->successResponse(payload: StudentContractResource::collection($items));
         return $this->successResponse(payload: StudentContractResource::collection($items));
     }
     }
 
 
     public function store(StudentContractRequest $request): JsonResponse
     public function store(StudentContractRequest $request): JsonResponse
     {
     {
-        $unitId = Auth::user()->load('units')->units->first()?->id;
-        $data   = array_merge($request->validated(), ['unit_id' => $unitId]);
-        $item   = $this->service->create($data);
+        $unitId = $this->activeUnitId();
+        $data = array_merge($request->validated(), ['unit_id' => $unitId]);
+        $item = $this->service->create($data);
+
         return $this->successResponse(payload: new StudentContractResource($item), message: __('messages.created'), code: 201);
         return $this->successResponse(payload: new StudentContractResource($item), message: __('messages.created'), code: 201);
     }
     }
 
 
     public function show(int $id): JsonResponse
     public function show(int $id): JsonResponse
     {
     {
         $item = $this->service->findById($id);
         $item = $this->service->findById($id);
+
         return $this->successResponse(payload: new StudentContractResource($item));
         return $this->successResponse(payload: new StudentContractResource($item));
     }
     }
 
 
     public function update(StudentContractRequest $request, int $id): JsonResponse
     public function update(StudentContractRequest $request, int $id): JsonResponse
     {
     {
-        $item = $this->service->update($id, $request->validated());
+        $item = $this->service->update($id, $request->validated(), Auth::id());
+
         return $this->successResponse(payload: new StudentContractResource($item), message: __('messages.updated'));
         return $this->successResponse(payload: new StudentContractResource($item), message: __('messages.updated'));
     }
     }
 
 
+    public function destroy(int $id): JsonResponse
+    {
+        $this->service->delete($id);
+
+        return $this->successResponse(message: __('messages.deleted'), code: 204);
+    }
+
+    //
+
+    public function franchisorSummary(): JsonResponse
+    {
+        $unitIds = array_filter((array) request()->input('unit_ids', []));
+
+        return $this->successResponse(payload: $this->service->getFranchisorSummary($unitIds));
+    }
+
+    public function franchisorCancelled(): JsonResponse
+    {
+        $unitIds = array_filter((array) request()->input('unit_ids', []));
+
+        $items = $this->service->getFranchisorByStatus('cancelled', $unitIds);
+
+        return $this->successResponse(payload: StudentContractResource::collection($items));
+    }
+
+    public function franchisorFrozen(): JsonResponse
+    {
+        $unitIds = array_filter((array) request()->input('unit_ids', []));
+
+        $items = $this->service->getFranchisorByStatus('frozen', $unitIds);
+
+        return $this->successResponse(payload: StudentContractResource::collection($items));
+    }
+
+    //
+
     public function attachFile(Request $request, int $id): JsonResponse
     public function attachFile(Request $request, int $id): JsonResponse
     {
     {
         $request->validate(['file' => 'required|file|mimes:jpeg,png,gif,pdf,mp4,mov,avi|max:20480']);
         $request->validate(['file' => 'required|file|mimes:jpeg,png,gif,pdf,mp4,mov,avi|max:20480']);
         $item = $this->service->attachFile($id, $request->file('file'));
         $item = $this->service->attachFile($id, $request->file('file'));
+
         return $this->successResponse(payload: new StudentContractResource($item), message: __('messages.updated'));
         return $this->successResponse(payload: new StudentContractResource($item), message: __('messages.updated'));
     }
     }
 
 
-    public function destroy(int $id): JsonResponse
+    public function attachSignedFile(Request $request, int $id): JsonResponse
     {
     {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
+        $request->validate(['file' => 'required|file|mimes:jpeg,png,gif,pdf,mp4,mov,avi|max:20480']);
+        $item = $this->service->attachFile($id, $request->file('file'), signed: true);
+
+        return $this->successResponse(payload: new StudentContractResource($item), message: __('messages.updated'));
+    }
+
+   //
+
+    public function installments(int $id): JsonResponse
+    {
+        $items = $this->service->getInstallments($id);
+
+        return $this->successResponse(payload: StudentContractInstallmentResource::collection($items));
+    }
+
+    //
+
+    public function freeze(int $id): JsonResponse
+    {
+        $months = request()->integer('months', 0);
+
+        if ($months < 1) {
+            return $this->errorResponse(message: 'Informe o tempo de trancamento em meses.', code: 422);
+        }
+
+        $contract = $this->service->findById($id);
+        if (! $contract) {
+            return $this->errorResponse(message: 'Contrato não encontrado.', code: 404);
+        }
+
+        $financial = UnitFinancial::where('unit_id', $contract->unit_id)->first();
+
+        if ($financial && $financial->max_freeze_count !== null && $months > $financial->max_freeze_count) {
+            return $this->errorResponse(
+                message: "Não é possível trancar por mais de {$financial->max_freeze_count} mês(es).",
+                code: 422,
+            );
+        }
+
+        $item = $this->service->freeze($id, $months, Auth::id());
+
+        return $this->successResponse(payload: new StudentContractResource($item), message: __('messages.updated'));
+    }
+
+    //
+
+    public function cancel(int $id): JsonResponse
+    {
+        $item = $this->service->cancel($id, Auth::id());
+
+        return $this->successResponse(payload: new StudentContractResource($item), message: __('messages.updated'));
+    }
+
+    public function reactivate(int $id): JsonResponse
+    {
+        $item = $this->service->reactivate($id, Auth::id());
+
+        return $this->successResponse(payload: new StudentContractResource($item), message: __('messages.updated'));
     }
     }
 }
 }

+ 37 - 3
app/Http/Controllers/StudentController.php

@@ -2,9 +2,12 @@
 
 
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
-use App\Services\StudentService;
+use App\Http\Requests\StoreStudentRequest;
+use App\Http\Requests\StudentRegistrationDraftRequest;
 use App\Http\Requests\StudentRequest;
 use App\Http\Requests\StudentRequest;
 use App\Http\Resources\StudentResource;
 use App\Http\Resources\StudentResource;
+use App\Services\StudentRegistrationDraftService;
+use App\Services\StudentService;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\Auth;
 
 
@@ -12,15 +15,46 @@ class StudentController extends Controller
 {
 {
     public function __construct(
     public function __construct(
         protected StudentService $service,
         protected StudentService $service,
+        protected StudentRegistrationDraftService $registrationDraftService,
     ) {}
     ) {}
 
 
     public function index(): JsonResponse
     public function index(): JsonResponse
     {
     {
-        $items = $this->service->getAll(Auth::user());
+        $items = $this->service->getAll(Auth::user(), request()->only([
+            'contract_start_date',
+            'contract_end_date',
+        ]));
+        return $this->successResponse(payload: StudentResource::collection($items));
+    }
+
+    public function franchisorActive(): JsonResponse
+    {
+        $unitIds = array_filter((array) request()->input('unit_ids', []));
+        $items   = $this->service->getFranchisorActive($unitIds);
         return $this->successResponse(payload: StudentResource::collection($items));
         return $this->successResponse(payload: StudentResource::collection($items));
     }
     }
 
 
-    public function store(StudentRequest $request): JsonResponse
+    public function franchisorStudentDetail(int $id): JsonResponse
+    {
+        $detail = $this->service->getFranchisorStudentDetail($id);
+        return $this->successResponse(payload: $detail);
+    }
+
+    public function franchisorSummary(): JsonResponse
+    {
+        $unitIds = array_filter((array) request()->input('unit_ids', []));
+        $summary = $this->service->getFranchisorSummary($unitIds);
+        return $this->successResponse(payload: $summary);
+    }
+
+    public function storeRegistrationDraft(StudentRegistrationDraftRequest $request): JsonResponse
+    {
+        $draft = $this->registrationDraftService->store(Auth::user(), $request->validated());
+
+        return $this->successResponse(payload: $draft);
+    }
+
+    public function store(StoreStudentRequest $request): JsonResponse
     {
     {
         $item = $this->service->create(Auth::user(), $request->validated());
         $item = $this->service->create(Auth::user(), $request->validated());
         return $this->successResponse(
         return $this->successResponse(

+ 40 - 0
app/Http/Controllers/StudentMediaController.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Services\StudentMediaService;
+use App\Http\Requests\StudentMediaRequest;
+use App\Http\Resources\StudentMediaResource;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+
+class StudentMediaController extends Controller
+{
+    public function __construct(
+        protected StudentMediaService $service,
+    ) {}
+
+    public function index(Request $request): JsonResponse
+    {
+        $items = $this->service->getAll($request->only(['student_id', 'student_contract_id']));
+        return $this->successResponse(payload: StudentMediaResource::collection($items));
+    }
+
+    public function store(StudentMediaRequest $request): JsonResponse
+    {
+        $item = $this->service->create($request->validated());
+        return $this->successResponse(payload: new StudentMediaResource($item), message: __('messages.created'), code: 201);
+    }
+
+    public function update(StudentMediaRequest $request, int $id): JsonResponse
+    {
+        $item = $this->service->update($id, $request->validated());
+        return $this->successResponse(payload: new StudentMediaResource($item), message: __('messages.updated'));
+    }
+
+    public function destroy(int $id): JsonResponse
+    {
+        $this->service->delete($id);
+        return $this->successResponse(message: __('messages.deleted'), code: 204);
+    }
+}

+ 2 - 2
app/Http/Controllers/StudentResponsibleController.php

@@ -15,8 +15,8 @@ public function __construct(
 
 
     public function getByStudent(int $studentId): JsonResponse
     public function getByStudent(int $studentId): JsonResponse
     {
     {
-        $item = $this->service->getByStudentId($studentId);
-        return $this->successResponse(payload: $item ? new StudentResponsibleResource($item) : null);
+        $items = $this->service->getByStudentId($studentId);
+        return $this->successResponse(payload: StudentResponsibleResource::collection($items));
     }
     }
 
 
     public function store(StudentResponsibleRequest $request): JsonResponse
     public function store(StudentResponsibleRequest $request): JsonResponse

+ 0 - 45
app/Http/Controllers/SupplierController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\SupplierService;
-use App\Http\Requests\SupplierRequest;
-use App\Http\Resources\SupplierResource;
-use Illuminate\Http\JsonResponse;
-
-class SupplierController extends Controller
-{
-    public function __construct(
-        protected SupplierService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: SupplierResource::collection($items));
-    }
-
-    public function store(SupplierRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new SupplierResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new SupplierResource($item));
-    }
-
-    public function update(SupplierRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new SupplierResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 0 - 45
app/Http/Controllers/SupportMovementController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\SupportMovementService;
-use App\Http\Requests\SupportMovementRequest;
-use App\Http\Resources\SupportMovementResource;
-use Illuminate\Http\JsonResponse;
-
-class SupportMovementController extends Controller
-{
-    public function __construct(
-        protected SupportMovementService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: SupportMovementResource::collection($items));
-    }
-
-    public function store(SupportMovementRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new SupportMovementResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new SupportMovementResource($item));
-    }
-
-    public function update(SupportMovementRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new SupportMovementResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 80 - 0
app/Http/Controllers/SupportReplyController.php

@@ -0,0 +1,80 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Enums\NotificationType;
+use App\Models\SupportTicket;
+use App\Services\NotificationService;
+use App\Services\SupportReplyService;
+use App\Http\Requests\SupportReplyRequest;
+use App\Http\Resources\SupportReplyResource;
+use Illuminate\Http\JsonResponse;
+
+class SupportReplyController extends Controller
+{
+    public function __construct(
+        protected SupportReplyService $service,
+        protected NotificationService $notifications,
+    ) {}
+
+    public function index(int $ticketId): JsonResponse
+    {
+        $replies = $this->service->getByTicket($ticketId);
+        return $this->successResponse(payload: SupportReplyResource::collection($replies));
+    }
+
+    public function store(SupportReplyRequest $request, int $ticketId): JsonResponse
+    {
+        $reply = $this->service->create($ticketId, auth()->id(), $request->validated()['reply']);
+        $this->notifyReplyCreated($ticketId);
+        return $this->successResponse(payload: new SupportReplyResource($reply), message: __('messages.created'), code: 201);
+    }
+
+    /**
+     * Notifica o outro lado do chamado quando há uma nova resposta.
+     * Autor da matriz → unidade do chamado; autor da unidade → matriz.
+     */
+    private function notifyReplyCreated(int $ticketId): void
+    {
+        $ticket = SupportTicket::find($ticketId);
+        if (!$ticket) {
+            return;
+        }
+
+        $author         = auth()->user();
+        $authorIsMatriz = $author->user_type === 'ADMIN';
+        $unitId         = $ticket->applicant_unit_id ?? $ticket->target_unit_id;
+
+        $recipients = $authorIsMatriz
+            ? $this->notifications->unitUserIds($unitId)
+            : $this->notifications->matrizUserIds();
+
+        // Não notifica o próprio autor da resposta.
+        $recipients = array_values(array_diff($recipients, [$author->id]));
+
+        $this->notifications->dispatch([
+            'origin_table'       => 'support_replies',
+            'origin_id'          => $ticket->id,
+            'unit_id'            => $unitId,
+            'type'               => NotificationType::SUPPORT_TICKET_REPLY->value,
+            'title'              => 'Nova resposta no chamado',
+            'message'            => $ticket->title,
+            'url'                => '/support',
+            'action_text'        => 'Ver chamado',
+            'created_by_user_id' => $author->id,
+            'recipient_user_ids' => $recipients,
+        ]);
+    }
+
+    public function update(SupportReplyRequest $request, int $ticketId, int $id): JsonResponse
+    {
+        $reply = $this->service->update($ticketId, $id, $request->validated()['reply']);
+        return $this->successResponse(payload: new SupportReplyResource($reply), message: __('messages.updated'));
+    }
+
+    public function destroy(int $ticketId, int $id): JsonResponse
+    {
+        $this->service->delete($ticketId, $id);
+        return $this->successResponse(message: __('messages.deleted'), code: 204);
+    }
+}

+ 0 - 45
app/Http/Controllers/SupportStatusController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\SupportStatusService;
-use App\Http\Requests\SupportStatusRequest;
-use App\Http\Resources\SupportStatusResource;
-use Illuminate\Http\JsonResponse;
-
-class SupportStatusController extends Controller
-{
-    public function __construct(
-        protected SupportStatusService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: SupportStatusResource::collection($items));
-    }
-
-    public function store(SupportStatusRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new SupportStatusResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new SupportStatusResource($item));
-    }
-
-    public function update(SupportStatusRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new SupportStatusResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 130 - 4
app/Http/Controllers/SupportTicketController.php

@@ -2,6 +2,9 @@
 
 
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
+use App\Http\Controllers\Concerns\ResolvesActiveUnit;
+use App\Enums\NotificationType;
+use App\Services\NotificationService;
 use App\Services\SupportTicketService;
 use App\Services\SupportTicketService;
 use App\Http\Requests\SupportTicketRequest;
 use App\Http\Requests\SupportTicketRequest;
 use App\Http\Resources\SupportTicketResource;
 use App\Http\Resources\SupportTicketResource;
@@ -9,20 +12,112 @@
 
 
 class SupportTicketController extends Controller
 class SupportTicketController extends Controller
 {
 {
+    use ResolvesActiveUnit;
+
     public function __construct(
     public function __construct(
         protected SupportTicketService $service,
         protected SupportTicketService $service,
+        protected NotificationService $notifications,
     ) {}
     ) {}
 
 
     public function index(): JsonResponse
     public function index(): JsonResponse
     {
     {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: SupportTicketResource::collection($items));
+        $user = auth()->user();
+        $query = \App\Models\SupportTicket::query()
+            ->with(['applicantUnit', 'targetUnit'])
+            ->orderBy('created_at', 'desc');
+
+        if ($this->isMatriz($user)) {
+            $query->where(fn($q) => $q->where('origin', '!=', 'unit')->orWhere('scope', '!=', 'internal'));
+        } else {
+            $query->visibleToUnit($this->activeUnitId($user));
+        }
+
+        return $this->successResponse(
+            payload: SupportTicketResource::collection($query->get())
+        );
     }
     }
 
 
     public function store(SupportTicketRequest $request): JsonResponse
     public function store(SupportTicketRequest $request): JsonResponse
     {
     {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new SupportTicketResource($item), message: __('messages.created'), code: 201);
+        $data = $request->validated();
+        $user = auth()->user();
+        $isMatriz = $this->isMatriz($user);
+
+        $data['origin'] = $isMatriz ? 'matriz' : 'unit';
+        $data['applicant_user_id'] = $user->id;
+        $data['responsable_user_id'] = $user->id;
+        $data['applicant_unit_id'] = $isMatriz ? null : $this->activeUnitId($user);
+        $data['status'] = 'in_progress';
+
+        // Broadcast: Matriz para todas as unidades
+        if ($isMatriz && ($data['scope'] ?? null) === 'all') {
+            $batchId = (string) \Illuminate\Support\Str::uuid();
+            $unitIds = \App\Models\Unit::query()->pluck('id');
+            $created = [];
+            foreach ($unitIds as $unitId) {
+                $ticket = $this->service->create(array_merge($data, [
+                    'target_unit_id' => $unitId,
+                    'batch_id' => $batchId,
+                ]));
+                $this->notifyTicketCreated($ticket);
+                $created[] = $ticket;
+            }
+            return $this->successResponse(
+                payload: SupportTicketResource::collection($created),
+                message: __('messages.created'),
+                code: 201
+            );
+        }
+
+        // Resolução de target_unit_id por cenário
+        if ($isMatriz) {
+            if (($data['scope'] ?? null) === 'internal') {
+                $data['target_unit_id'] = null;
+            }
+            // scope='specific' → target_unit_id já veio do request
+        } else {
+            // Franchisee
+            $data['target_unit_id'] = (($data['scope'] ?? null) === 'internal')
+                ? $this->activeUnitId($user)
+                : null; // 'specific' do Franchisee = "para Matriz"
+        }
+
+        $item = $this->service->create($data);
+        $this->notifyTicketCreated($item);
+        return $this->successResponse(
+            payload: new SupportTicketResource($item),
+            message: __('messages.created'),
+            code: 201
+        );
+    }
+
+    /**
+     * Notifica o outro lado quando um chamado é criado:
+     * franqueada → matriz; matriz → unidade alvo.
+     */
+    private function notifyTicketCreated(\App\Models\SupportTicket $ticket): void
+    {
+        if ($ticket->origin === 'unit') {
+            $unitId     = $ticket->applicant_unit_id;
+            $recipients = $this->notifications->matrizUserIds();
+        } else {
+            $unitId     = $ticket->target_unit_id;
+            $recipients = $this->notifications->unitUserIds($unitId);
+        }
+
+        $this->notifications->dispatch([
+            'origin_table'       => 'support_tickets',
+            'origin_id'          => $ticket->id,
+            'unit_id'            => $unitId,
+            'type'               => NotificationType::SUPPORT_TICKET_CREATED->value,
+            'title'              => 'Novo chamado de suporte',
+            'message'            => $ticket->title,
+            'url'                => '/support',
+            'action_text'        => 'Ver chamado',
+            'priority'           => $ticket->severity === 'high' ? 'high' : 'normal',
+            'created_by_user_id' => $ticket->applicant_user_id,
+            'recipient_user_ids' => $recipients,
+        ]);
     }
     }
 
 
     public function show(int $id): JsonResponse
     public function show(int $id): JsonResponse
@@ -33,13 +128,44 @@ public function show(int $id): JsonResponse
 
 
     public function update(SupportTicketRequest $request, int $id): JsonResponse
     public function update(SupportTicketRequest $request, int $id): JsonResponse
     {
     {
+        $user = auth()->user();
+        $item = $this->service->findById($id);
+
+        if (!$this->canManage($user, $item)) {
+            return $this->errorResponse(message: __('messages.unauthorized'), code: 403);
+        }
+
         $item = $this->service->update($id, $request->validated());
         $item = $this->service->update($id, $request->validated());
         return $this->successResponse(payload: new SupportTicketResource($item), message: __('messages.updated'));
         return $this->successResponse(payload: new SupportTicketResource($item), message: __('messages.updated'));
     }
     }
 
 
     public function destroy(int $id): JsonResponse
     public function destroy(int $id): JsonResponse
     {
     {
+        $user = auth()->user();
+        $item = $this->service->findById($id);
+
+        if (!$this->canManage($user, $item)) {
+            return $this->errorResponse(message: __('messages.unauthorized'), code: 403);
+        }
+
         $this->service->delete($id);
         $this->service->delete($id);
         return $this->successResponse(message: __('messages.deleted'), code: 204);
         return $this->successResponse(message: __('messages.deleted'), code: 204);
     }
     }
+
+    private function isMatriz(\App\Models\User $user): bool
+    {
+        return $user->user_type === 'ADMIN';
+    }
+
+    private function canManage(\App\Models\User $user, \App\Models\SupportTicket $ticket): bool
+    {
+        if ($this->isMatriz($user)) {
+            return true;
+        }
+
+        // Franchisee só pode gerenciar tickets internos que ela mesma criou
+        return $ticket->origin === 'unit'
+            && $ticket->scope === 'internal'
+            && $ticket->applicant_unit_id === $this->activeUnitId($user);
+    }
 }
 }

+ 49 - 2
app/Http/Controllers/TbrCalculationController.php

@@ -2,9 +2,11 @@
 
 
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
-use App\Services\TbrCalculationService;
+use App\Http\Requests\TbrCalculationBatchRequest;
 use App\Http\Requests\TbrCalculationRequest;
 use App\Http\Requests\TbrCalculationRequest;
+use App\Http\Resources\FranchiseeAccountReceiveResource;
 use App\Http\Resources\TbrCalculationResource;
 use App\Http\Resources\TbrCalculationResource;
+use App\Services\TbrCalculationService;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 use Illuminate\Http\Request;
 
 
@@ -16,10 +18,25 @@ public function __construct(
 
 
     public function index(Request $request): JsonResponse
     public function index(Request $request): JsonResponse
     {
     {
-        $items = $this->service->paginate($request->integer('per_page', 15));
+        $items = $this->service->listAll($request->integer('limit', 100));
         return $this->successResponse(payload: TbrCalculationResource::collection($items));
         return $this->successResponse(payload: TbrCalculationResource::collection($items));
     }
     }
 
 
+    public function preview(TbrCalculationRequest $request): JsonResponse
+    {
+        $payload = $this->service->preview($request->validated());
+        return $this->successResponse(payload: $payload);
+    }
+
+    public function previewBatch(TbrCalculationBatchRequest $request): JsonResponse
+    {
+        $payload = $this->service->previewBatch(
+            (int) $request->validated('reference_year'),
+            (int) $request->validated('reference_month'),
+        );
+        return $this->successResponse(payload: $payload);
+    }
+
     public function store(TbrCalculationRequest $request): JsonResponse
     public function store(TbrCalculationRequest $request): JsonResponse
     {
     {
         $item = $this->service->calculate($request->validated());
         $item = $this->service->calculate($request->validated());
@@ -31,4 +48,34 @@ public function show(int $id): JsonResponse
         $item = $this->service->findById($id);
         $item = $this->service->findById($id);
         return $this->successResponse(payload: new TbrCalculationResource($item));
         return $this->successResponse(payload: new TbrCalculationResource($item));
     }
     }
+
+    public function generateReceivable(int $id): JsonResponse
+    {
+        $receive = $this->service->generateReceivable($id);
+        return $this->successResponse(
+            payload: new FranchiseeAccountReceiveResource($receive),
+            message: 'Conta a Receber gerada com sucesso.',
+            code: 201,
+        );
+    }
+
+    public function generateBatch(TbrCalculationBatchRequest $request): JsonResponse
+    {
+        $result = $this->service->generateBatch(
+            (int) $request->validated('reference_year'),
+            (int) $request->validated('reference_month'),
+            $request->validated('unit_ids'),
+        );
+
+        return $this->successResponse(
+            payload: $result,
+            message: sprintf(
+                '%d título(s) gerado(s), %d pulado(s), %d erro(s).',
+                $result['generated_count'],
+                $result['skipped_count'],
+                $result['error_count'],
+            ),
+            code: 201,
+        );
+    }
 }
 }

+ 63 - 2
app/Http/Controllers/TreasuryAccountController.php

@@ -2,20 +2,81 @@
 
 
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
+use App\Http\Controllers\Concerns\ResolvesActiveUnit;
 use App\Services\TreasuryAccountService;
 use App\Services\TreasuryAccountService;
 use App\Http\Requests\TreasuryAccountRequest;
 use App\Http\Requests\TreasuryAccountRequest;
 use App\Http\Resources\TreasuryAccountResource;
 use App\Http\Resources\TreasuryAccountResource;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
 
 
 class TreasuryAccountController extends Controller
 class TreasuryAccountController extends Controller
 {
 {
+    use ResolvesActiveUnit;
+
     public function __construct(
     public function __construct(
         protected TreasuryAccountService $service,
         protected TreasuryAccountService $service,
     ) {}
     ) {}
 
 
-    public function index(): JsonResponse
+    // --- Unidade ativa (Franchisee) ---
+
+    public function indexMe(): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+        if (!$unitId) {
+            return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
+        }
+
+        return $this->successResponse(
+            payload: TreasuryAccountResource::collection($this->service->getAll($unitId)),
+        );
+    }
+
+    public function storeMe(TreasuryAccountRequest $request): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+        if (!$unitId) {
+            return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
+        }
+
+        $data = $request->validated();
+        $data['unit_id'] = $unitId;
+
+        $item = $this->service->create($data);
+
+        return $this->successResponse(payload: new TreasuryAccountResource($item), message: __('messages.created'), code: 201);
+    }
+
+    public function updateMe(TreasuryAccountRequest $request, int $id): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+        if (!$unitId || !$this->service->findByIdForUnit($id, $unitId)) {
+            return $this->errorResponse(message: 'Conta não encontrada', code: 404);
+        }
+
+        $data = $request->validated();
+        unset($data['unit_id']);
+
+        $item = $this->service->update($id, $data);
+
+        return $this->successResponse(payload: new TreasuryAccountResource($item), message: __('messages.updated'));
+    }
+
+    public function destroyMe(int $id): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+        if (!$unitId || !$this->service->findByIdForUnit($id, $unitId)) {
+            return $this->errorResponse(message: 'Conta não encontrada', code: 404);
+        }
+
+        $this->service->delete($id);
+
+        return $this->successResponse(message: __('messages.deleted'), code: 204);
+    }
+
+    public function index(Request $request): JsonResponse
     {
     {
-        $items = $this->service->getAll();
+        $unitId = $request->filled('unit_id') ? (int) $request->input('unit_id') : null;
+        $items = $this->service->getAll($unitId);
         return $this->successResponse(payload: TreasuryAccountResource::collection($items));
         return $this->successResponse(payload: TreasuryAccountResource::collection($items));
     }
     }
 
 

+ 0 - 45
app/Http/Controllers/TreasuryImportLineController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\TreasuryImportLineService;
-use App\Http\Requests\TreasuryImportLineRequest;
-use App\Http\Resources\TreasuryImportLineResource;
-use Illuminate\Http\JsonResponse;
-
-class TreasuryImportLineController extends Controller
-{
-    public function __construct(
-        protected TreasuryImportLineService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: TreasuryImportLineResource::collection($items));
-    }
-
-    public function store(TreasuryImportLineRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new TreasuryImportLineResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new TreasuryImportLineResource($item));
-    }
-
-    public function update(TreasuryImportLineRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new TreasuryImportLineResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 0 - 45
app/Http/Controllers/TreasuryImportsController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\TreasuryImportsService;
-use App\Http\Requests\TreasuryImportsRequest;
-use App\Http\Resources\TreasuryImportsResource;
-use Illuminate\Http\JsonResponse;
-
-class TreasuryImportsController extends Controller
-{
-    public function __construct(
-        protected TreasuryImportsService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: TreasuryImportsResource::collection($items));
-    }
-
-    public function store(TreasuryImportsRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new TreasuryImportsResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new TreasuryImportsResource($item));
-    }
-
-    public function update(TreasuryImportsRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new TreasuryImportsResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 90 - 3
app/Http/Controllers/TreasuryLaunchController.php

@@ -2,44 +2,131 @@
 
 
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
-use App\Services\TreasuryLaunchService;
+use App\Http\Controllers\Concerns\ResolvesActiveUnit;
 use App\Http\Requests\TreasuryLaunchRequest;
 use App\Http\Requests\TreasuryLaunchRequest;
 use App\Http\Resources\TreasuryLaunchResource;
 use App\Http\Resources\TreasuryLaunchResource;
+use App\Services\TreasuryAccountService;
+use App\Services\TreasuryLaunchService;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
 
 
 class TreasuryLaunchController extends Controller
 class TreasuryLaunchController extends Controller
 {
 {
+    use ResolvesActiveUnit;
+
     public function __construct(
     public function __construct(
         protected TreasuryLaunchService $service,
         protected TreasuryLaunchService $service,
+        protected TreasuryAccountService $accountService,
     ) {}
     ) {}
 
 
-    public function index(): JsonResponse
+    // --- Unidade ativa (Franchisee): extrato e movimentação manual ---
+
+    public function indexMe(Request $request): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+        if (! $unitId) {
+            return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
+        }
+
+        $accountId = $request->filled('account_id') ? (int) $request->input('account_id') : null;
+
+        // Extrato só de uma conta da própria unidade.
+        if ($accountId !== null && ! $this->accountService->findByIdForUnit($accountId, $unitId)) {
+            return $this->errorResponse(message: 'Conta não encontrada', code: 404);
+        }
+
+        return $this->successResponse(
+            payload: TreasuryLaunchResource::collection($this->service->getAll($accountId)),
+        );
+    }
+
+    public function storeMe(TreasuryLaunchRequest $request): JsonResponse
     {
     {
-        $items = $this->service->getAll();
+        $unitId = $this->activeUnitId();
+        if (! $unitId) {
+            return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
+        }
+
+        $data = $request->validated();
+
+        // A conta movimentada precisa pertencer à unidade ativa.
+        if (! $this->accountService->findByIdForUnit((int) $data['account_id'], $unitId)) {
+            return $this->errorResponse(message: 'Conta não encontrada', code: 404);
+        }
+
+        $item = $this->service->create($data);
+
+        return $this->successResponse(payload: new TreasuryLaunchResource($item), message: __('messages.created'), code: 201);
+    }
+
+    public function updateMe(TreasuryLaunchRequest $request, int $id): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+        $item = $this->service->findById($id);
+        $currentAccountId = $item?->transaction_type === 'entrada'
+            ? $item?->to_account_id
+            : $item?->from_account_id;
+        $accountId = $request->integer('account_id') ?: $currentAccountId;
+
+        if (! $unitId || ! $item || ! $currentAccountId
+            || ! $this->accountService->findByIdForUnit($currentAccountId, $unitId)
+            || ! $accountId || ! $this->accountService->findByIdForUnit($accountId, $unitId)) {
+            return $this->errorResponse(message: 'Movimentação não encontrada', code: 404);
+        }
+
+        $updated = $this->service->update($id, $request->validated());
+
+        return $this->successResponse(payload: new TreasuryLaunchResource($updated), message: __('messages.updated'));
+    }
+
+    public function destroyMe(int $id): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+        $item = $this->service->findById($id);
+        $accountId = $item?->transaction_type === 'entrada' ? $item?->to_account_id : $item?->from_account_id;
+
+        if (! $unitId || ! $item || ! $accountId || ! $this->accountService->findByIdForUnit($accountId, $unitId)) {
+            return $this->errorResponse(message: 'Movimentação não encontrada', code: 404);
+        }
+
+        $this->service->delete($id);
+
+        return $this->successResponse(message: __('messages.deleted'), code: 204);
+    }
+
+    public function index(Request $request): JsonResponse
+    {
+        $accountId = $request->filled('account_id') ? (int) $request->input('account_id') : null;
+        $items = $this->service->getAll($accountId);
+
         return $this->successResponse(payload: TreasuryLaunchResource::collection($items));
         return $this->successResponse(payload: TreasuryLaunchResource::collection($items));
     }
     }
 
 
     public function store(TreasuryLaunchRequest $request): JsonResponse
     public function store(TreasuryLaunchRequest $request): JsonResponse
     {
     {
         $item = $this->service->create($request->validated());
         $item = $this->service->create($request->validated());
+
         return $this->successResponse(payload: new TreasuryLaunchResource($item), message: __('messages.created'), code: 201);
         return $this->successResponse(payload: new TreasuryLaunchResource($item), message: __('messages.created'), code: 201);
     }
     }
 
 
     public function show(int $id): JsonResponse
     public function show(int $id): JsonResponse
     {
     {
         $item = $this->service->findById($id);
         $item = $this->service->findById($id);
+
         return $this->successResponse(payload: new TreasuryLaunchResource($item));
         return $this->successResponse(payload: new TreasuryLaunchResource($item));
     }
     }
 
 
     public function update(TreasuryLaunchRequest $request, int $id): JsonResponse
     public function update(TreasuryLaunchRequest $request, int $id): JsonResponse
     {
     {
         $item = $this->service->update($id, $request->validated());
         $item = $this->service->update($id, $request->validated());
+
         return $this->successResponse(payload: new TreasuryLaunchResource($item), message: __('messages.updated'));
         return $this->successResponse(payload: new TreasuryLaunchResource($item), message: __('messages.updated'));
     }
     }
 
 
     public function destroy(int $id): JsonResponse
     public function destroy(int $id): JsonResponse
     {
     {
         $this->service->delete($id);
         $this->service->delete($id);
+
         return $this->successResponse(message: __('messages.deleted'), code: 204);
         return $this->successResponse(message: __('messages.deleted'), code: 204);
     }
     }
 }
 }

+ 95 - 0
app/Http/Controllers/UnitAccountPayableController.php

@@ -0,0 +1,95 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Http\Controllers\Concerns\ResolvesActiveUnit;
+use App\Http\Requests\SettlePayableRequest;
+use App\Http\Requests\StoreUnitPayableRequest;
+use App\Http\Resources\UnitAccountPayableResource;
+use App\Services\UnitAccountPayableService;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+
+class UnitAccountPayableController extends Controller
+{
+    use ResolvesActiveUnit;
+
+    public function __construct(
+        protected UnitAccountPayableService $service,
+    ) {}
+
+    public function indexMe(Request $request): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+
+        if (!$unitId) {
+            return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
+        }
+
+        $filters = array_filter($request->only(['status', 'origin']), fn ($v) => $v !== null);
+        $items   = $this->service->list($unitId, $filters);
+
+        return $this->successResponse(payload: UnitAccountPayableResource::collection($items));
+    }
+
+    public function storeMe(StoreUnitPayableRequest $request): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+
+        if (!$unitId) {
+            return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
+        }
+
+        $item = $this->service->createManual($unitId, $request->validated());
+
+        return $this->successResponse(
+            payload: new UnitAccountPayableResource($item),
+            message: __('messages.created'),
+            code: 201,
+        );
+    }
+
+    public function settleMe(SettlePayableRequest $request, int $id): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+
+        if (!$unitId) {
+            return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
+        }
+
+        $payable = $this->service->findForUnit($id, $unitId);
+
+        if (!$payable) {
+            return $this->errorResponse(message: 'Conta a pagar não encontrada', code: 404);
+        }
+
+        $item = $this->service->settle($payable, $request->validated());
+
+        return $this->successResponse(
+            payload: new UnitAccountPayableResource($item),
+            message: 'Baixa realizada com sucesso',
+        );
+    }
+
+    public function reopenMe(int $id): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+
+        if (!$unitId) {
+            return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
+        }
+
+        $payable = $this->service->findForUnit($id, $unitId);
+
+        if (!$payable) {
+            return $this->errorResponse(message: 'Conta a pagar não encontrada', code: 404);
+        }
+
+        $item = $this->service->reopen($payable);
+
+        return $this->successResponse(
+            payload: new UnitAccountPayableResource($item),
+            message: 'Conta reaberta',
+        );
+    }
+}

+ 9 - 19
app/Http/Controllers/UnitController.php

@@ -2,14 +2,16 @@
 
 
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
+use App\Http\Controllers\Concerns\ResolvesActiveUnit;
 use App\Services\UnitService;
 use App\Services\UnitService;
 use App\Http\Requests\UnitRequest;
 use App\Http\Requests\UnitRequest;
 use App\Http\Resources\UnitResource;
 use App\Http\Resources\UnitResource;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\JsonResponse;
-use Illuminate\Support\Facades\Auth;
 
 
 class UnitController extends Controller
 class UnitController extends Controller
 {
 {
+    use ResolvesActiveUnit;
+
     public function __construct(
     public function __construct(
         protected UnitService $service,
         protected UnitService $service,
     ) {}
     ) {}
@@ -48,40 +50,28 @@ public function destroy(int $id): JsonResponse
 
 
     public function showMe(): JsonResponse
     public function showMe(): JsonResponse
     {
     {
-        $user = Auth::user()->load('units');
-
-        $unit = $user->units->first();
+        $unitId = $this->activeUnitId();
 
 
-        if (!$unit) {
+        if (!$unitId) {
             return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
             return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
         }
         }
 
 
-        $item = $this->service->findById($unit->id);
+        $item = $this->service->findById($unitId);
 
 
         return $this->successResponse(payload: new UnitResource($item));
         return $this->successResponse(payload: new UnitResource($item));
     }
     }
 
 
     public function updateMe(UnitRequest $request): JsonResponse
     public function updateMe(UnitRequest $request): JsonResponse
     {
     {
-        $user = Auth::user()->load('units');
+        $unitId = $this->activeUnitId();
 
 
-        $unit = $user->units->first();
-
-        if (!$unit) {
+        if (!$unitId) {
             return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
             return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
         }
         }
 
 
-        $item = $this->service->update($unit->id, $request->safe()->only(['avatar']));
+        $item = $this->service->update($unitId, $request->safe()->only(['avatar']));
 
 
         return $this->successResponse(payload: new UnitResource($item), message: __('messages.updated'));
         return $this->successResponse(payload: new UnitResource($item), message: __('messages.updated'));
     }
     }
 
 
-    //
-
-    public function selectList(): JsonResponse
-    {
-        $items = $this->service->getSelectList();
-
-        return $this->successResponse(payload: $items);
-    }
 }
 }

+ 20 - 4
app/Http/Controllers/UnitFinancialController.php

@@ -2,15 +2,17 @@
 
 
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
+use App\Http\Controllers\Concerns\ResolvesActiveUnit;
 use App\Services\UnitFinancialService;
 use App\Services\UnitFinancialService;
 use App\Http\Requests\UnitFinancialRequest;
 use App\Http\Requests\UnitFinancialRequest;
 use App\Http\Resources\UnitFinancialResource;
 use App\Http\Resources\UnitFinancialResource;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Auth;
 
 
 class UnitFinancialController extends Controller
 class UnitFinancialController extends Controller
 {
 {
+    use ResolvesActiveUnit;
+
     public function __construct(
     public function __construct(
         protected UnitFinancialService $service,
         protected UnitFinancialService $service,
     ) {}
     ) {}
@@ -21,11 +23,9 @@ public function show(Request $request): JsonResponse
         return $this->successResponse(payload: $item ? new UnitFinancialResource($item) : null);
         return $this->successResponse(payload: $item ? new UnitFinancialResource($item) : null);
     }
     }
 
 
-    //
-
     public function showMe(): JsonResponse
     public function showMe(): JsonResponse
     {
     {
-        $unitId = Auth::user()?->load('units')->units->first()?->id;
+        $unitId = $this->activeUnitId();
 
 
         if (!$unitId) {
         if (!$unitId) {
             return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
             return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
@@ -47,4 +47,20 @@ public function upsert(UnitFinancialRequest $request): JsonResponse
 
 
         return $this->successResponse(payload: new UnitFinancialResource($item), message: __('messages.updated'));
         return $this->successResponse(payload: new UnitFinancialResource($item), message: __('messages.updated'));
     }
     }
+
+    public function upsertMe(UnitFinancialRequest $request): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+
+        if (!$unitId) {
+            return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
+        }
+
+        $validated = $request->validated();
+        unset($validated['unit_id']);
+
+        $item = $this->service->upsert($unitId, $validated);
+
+        return $this->successResponse(payload: new UnitFinancialResource($item), message: __('messages.updated'));
+    }
 }
 }

+ 27 - 0
app/Http/Controllers/UnitFinancialDashboardController.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Http\Controllers\Concerns\ResolvesActiveUnit;
+use App\Services\UnitFinancialDashboardService;
+use Illuminate\Http\JsonResponse;
+
+class UnitFinancialDashboardController extends Controller
+{
+    use ResolvesActiveUnit;
+
+    public function __construct(
+        protected UnitFinancialDashboardService $service,
+    ) {}
+
+    public function overviewMe(): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+
+        if (!$unitId) {
+            return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
+        }
+
+        return $this->successResponse(payload: $this->service->overview($unitId));
+    }
+}

+ 4 - 4
app/Http/Controllers/UnitHistoryController.php

@@ -2,15 +2,17 @@
 
 
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
+use App\Http\Controllers\Concerns\ResolvesActiveUnit;
 use App\Services\UnitHistoryService;
 use App\Services\UnitHistoryService;
 use App\Http\Requests\UnitHistoryRequest;
 use App\Http\Requests\UnitHistoryRequest;
 use App\Http\Resources\UnitHistoryResource;
 use App\Http\Resources\UnitHistoryResource;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Auth;
 
 
 class UnitHistoryController extends Controller
 class UnitHistoryController extends Controller
 {
 {
+    use ResolvesActiveUnit;
+
     public function __construct(
     public function __construct(
         protected UnitHistoryService $service,
         protected UnitHistoryService $service,
     ) {}
     ) {}
@@ -64,8 +66,6 @@ public function indexMe(): JsonResponse
 
 
     private function currentUnitId(): ?int
     private function currentUnitId(): ?int
     {
     {
-        $user = Auth::user()?->load('units');
-
-        return $user?->units->first()?->id;
+        return $this->activeUnitId();
     }
     }
 }
 }

+ 0 - 53
app/Http/Controllers/UnitInhabitantClassificationController.php

@@ -1,53 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\UnitInhabitantClassificationService;
-use App\Http\Requests\UnitInhabitantClassificationRequest;
-use App\Http\Resources\UnitInhabitantClassificationResource;
-use Illuminate\Http\JsonResponse;
-
-class UnitInhabitantClassificationController extends Controller
-{
-    public function __construct(
-        protected UnitInhabitantClassificationService $service,
-    ) {}
-
-    public function index(int $unitId): JsonResponse
-    {
-        $items = $this->service->getByUnit($unitId);
-        return $this->successResponse(payload: UnitInhabitantClassificationResource::collection($items));
-    }
-
-    public function store(UnitInhabitantClassificationRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new UnitInhabitantClassificationResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new UnitInhabitantClassificationResource($item));
-    }
-
-    public function update(UnitInhabitantClassificationRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new UnitInhabitantClassificationResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-
-    //
-
-    public function selectList(int $unitId): JsonResponse
-    {
-        $items = $this->service->getSelectList($unitId);
-        return $this->successResponse(payload: $items);
-    }
-}

+ 4 - 4
app/Http/Controllers/UnitMediaController.php

@@ -2,15 +2,17 @@
 
 
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
+use App\Http\Controllers\Concerns\ResolvesActiveUnit;
 use App\Services\UnitMediaService;
 use App\Services\UnitMediaService;
 use App\Http\Requests\UnitMediaRequest;
 use App\Http\Requests\UnitMediaRequest;
 use App\Http\Resources\UnitMediaResource;
 use App\Http\Resources\UnitMediaResource;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Auth;
 
 
 class UnitMediaController extends Controller
 class UnitMediaController extends Controller
 {
 {
+    use ResolvesActiveUnit;
+
     public function __construct(
     public function __construct(
         protected UnitMediaService $service,
         protected UnitMediaService $service,
     ) {}
     ) {}
@@ -83,8 +85,6 @@ public function storeMe(UnitMediaRequest $request): JsonResponse
 
 
     private function currentUnitId(): ?int
     private function currentUnitId(): ?int
     {
     {
-        $user = Auth::user()?->load('units');
-
-        return $user?->units->first()?->id;
+        return $this->activeUnitId();
     }
     }
 }
 }

+ 4 - 4
app/Http/Controllers/UnitPartnerController.php

@@ -2,15 +2,17 @@
 
 
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
+use App\Http\Controllers\Concerns\ResolvesActiveUnit;
 use App\Services\UnitPartnerService;
 use App\Services\UnitPartnerService;
 use App\Http\Requests\UnitPartnerRequest;
 use App\Http\Requests\UnitPartnerRequest;
 use App\Http\Resources\UnitPartnerResource;
 use App\Http\Resources\UnitPartnerResource;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Auth;
 
 
 class UnitPartnerController extends Controller
 class UnitPartnerController extends Controller
 {
 {
+    use ResolvesActiveUnit;
+
     public function __construct(
     public function __construct(
         protected UnitPartnerService $service,
         protected UnitPartnerService $service,
     ) {}
     ) {}
@@ -120,8 +122,6 @@ public function destroyMe(int $id): JsonResponse
 
 
     private function currentUnitId(): ?int
     private function currentUnitId(): ?int
     {
     {
-        $user = Auth::user()?->load('units');
-
-        return $user?->units->first()?->id;
+        return $this->activeUnitId();
     }
     }
 }
 }

+ 49 - 0
app/Http/Controllers/UnitPaymentAccountController.php

@@ -0,0 +1,49 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Http\Controllers\Concerns\ResolvesActiveUnit;
+use App\Http\Requests\UnitPaymentAccountRequest;
+use App\Http\Resources\UnitPaymentAccountResource;
+use App\Services\UnitPaymentAccountService;
+use Illuminate\Http\JsonResponse;
+
+class UnitPaymentAccountController extends Controller
+{
+    use ResolvesActiveUnit;
+
+    public function __construct(
+        protected UnitPaymentAccountService $service,
+    ) {}
+
+    public function showMe(): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+
+        if (!$unitId) {
+            return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
+        }
+
+        $item = $this->service->getByUnitId($unitId);
+
+        return $this->successResponse(
+            payload: $item ? new UnitPaymentAccountResource($item) : null,
+        );
+    }
+
+    public function upsertMe(UnitPaymentAccountRequest $request): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+
+        if (!$unitId) {
+            return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
+        }
+
+        $item = $this->service->upsertAsaasApiKey($unitId, $request->validated('asaas_api_key'));
+
+        return $this->successResponse(
+            payload: new UnitPaymentAccountResource($item),
+            message: __('messages.updated'),
+        );
+    }
+}

+ 173 - 0
app/Http/Controllers/UnitReceivableController.php

@@ -0,0 +1,173 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Http\Controllers\Concerns\ResolvesActiveUnit;
+use App\Http\Requests\SettleReceivableRequest;
+use App\Http\Requests\StoreUnitReceivableRequest;
+use App\Http\Resources\UnitAccountReceivableResource;
+use App\Http\Resources\UnitReceivableResource;
+use App\Services\UnitReceivableService;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+
+class UnitReceivableController extends Controller
+{
+    use ResolvesActiveUnit;
+
+    public function __construct(
+        protected UnitReceivableService $service,
+    ) {}
+
+    public function indexMe(Request $request): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+
+        if (!$unitId) {
+            return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
+        }
+
+        $filters = array_filter($request->only(['status', 'student_id', 'type']), fn ($v) => $v !== null);
+
+        // Parcelas de aluno + recebíveis avulsos convivem na mesma listagem,
+        // normalizados e ordenados por vencimento. 'source' distingue a origem.
+        $installments = UnitReceivableResource::collection($this->service->list($unitId, $filters))
+            ->toArray($request);
+        $manual = UnitAccountReceivableResource::collection($this->service->listManual($unitId, $filters))
+            ->toArray($request);
+
+        $merged = collect(array_merge($installments, $manual))
+            ->sortBy('due_date')
+            ->values()
+            ->all();
+
+        return $this->successResponse(payload: $merged);
+    }
+
+    public function storeManualMe(StoreUnitReceivableRequest $request): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+
+        if (!$unitId) {
+            return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
+        }
+
+        $item = $this->service->createManual($unitId, $request->validated());
+
+        return $this->successResponse(
+            payload: new UnitAccountReceivableResource($item),
+            message: __('messages.created'),
+            code: 201,
+        );
+    }
+
+    public function settleManualMe(SettleReceivableRequest $request, int $id): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+
+        if (!$unitId) {
+            return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
+        }
+
+        $receivable = $this->service->findManualForUnit($id, $unitId);
+
+        if (!$receivable) {
+            return $this->errorResponse(message: 'Recebível não encontrado', code: 404);
+        }
+
+        $item = $this->service->settleManual($receivable, $request->validated());
+
+        return $this->successResponse(
+            payload: new UnitAccountReceivableResource($item),
+            message: 'Baixa realizada com sucesso',
+        );
+    }
+
+    public function reopenManualMe(int $id): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+
+        if (!$unitId) {
+            return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
+        }
+
+        $receivable = $this->service->findManualForUnit($id, $unitId);
+
+        if (!$receivable) {
+            return $this->errorResponse(message: 'Recebível não encontrado', code: 404);
+        }
+
+        $item = $this->service->reopenManual($receivable);
+
+        return $this->successResponse(
+            payload: new UnitAccountReceivableResource($item),
+            message: 'Recebível reaberto',
+        );
+    }
+
+    public function settleMe(SettleReceivableRequest $request, int $id): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+
+        if (!$unitId) {
+            return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
+        }
+
+        $installment = $this->service->findForUnit($id, $unitId);
+
+        if (!$installment) {
+            return $this->errorResponse(message: 'Recebível não encontrado', code: 404);
+        }
+
+        $item = $this->service->settle($installment, $request->validated());
+
+        return $this->successResponse(
+            payload: new UnitReceivableResource($item),
+            message: 'Baixa realizada com sucesso',
+        );
+    }
+
+    public function chargeMe(int $id): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+
+        if (!$unitId) {
+            return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
+        }
+
+        $installment = $this->service->findForUnit($id, $unitId);
+
+        if (!$installment) {
+            return $this->errorResponse(message: 'Recebível não encontrado', code: 404);
+        }
+
+        $item = $this->service->generateCharge($installment);
+
+        return $this->successResponse(
+            payload: new UnitReceivableResource($item),
+            message: 'Cobrança gerada no Asaas',
+        );
+    }
+
+    public function reopenMe(int $id): JsonResponse
+    {
+        $unitId = $this->activeUnitId();
+
+        if (!$unitId) {
+            return $this->errorResponse(message: 'Unidade não encontrada', code: 404);
+        }
+
+        $installment = $this->service->findForUnit($id, $unitId);
+
+        if (!$installment) {
+            return $this->errorResponse(message: 'Recebível não encontrado', code: 404);
+        }
+
+        $item = $this->service->reopen($installment);
+
+        return $this->successResponse(
+            payload: new UnitReceivableResource($item),
+            message: 'Recebível reaberto',
+        );
+    }
+}

+ 0 - 45
app/Http/Controllers/UnitUserController.php

@@ -1,45 +0,0 @@
-<?php
-
-namespace App\Http\Controllers;
-
-use App\Services\UnitUserService;
-use App\Http\Requests\UnitUserRequest;
-use App\Http\Resources\UnitUserResource;
-use Illuminate\Http\JsonResponse;
-
-class UnitUserController extends Controller
-{
-    public function __construct(
-        protected UnitUserService $service,
-    ) {}
-
-    public function index(): JsonResponse
-    {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: UnitUserResource::collection($items));
-    }
-
-    public function store(UnitUserRequest $request): JsonResponse
-    {
-        $item = $this->service->create($request->validated());
-        return $this->successResponse(payload: new UnitUserResource($item), message: __('messages.created'), code: 201);
-    }
-
-    public function show(int $id): JsonResponse
-    {
-        $item = $this->service->findById($id);
-        return $this->successResponse(payload: new UnitUserResource($item));
-    }
-
-    public function update(UnitUserRequest $request, int $id): JsonResponse
-    {
-        $item = $this->service->update($id, $request->validated());
-        return $this->successResponse(payload: new UnitUserResource($item), message: __('messages.updated'));
-    }
-
-    public function destroy(int $id): JsonResponse
-    {
-        $this->service->delete($id);
-        return $this->successResponse(message: __('messages.deleted'), code: 204);
-    }
-}

+ 51 - 14
app/Http/Controllers/UserController.php

@@ -2,15 +2,17 @@
 
 
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
+use App\Http\Controllers\Concerns\ResolvesActiveUnit;
 use App\Services\UserService;
 use App\Services\UserService;
 use App\Http\Requests\UserRequest;
 use App\Http\Requests\UserRequest;
 use App\Http\Resources\UserResource;
 use App\Http\Resources\UserResource;
-use App\Http\Resources\UserTypeResource;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\Auth;
 
 
 class UserController extends Controller
 class UserController extends Controller
 {
 {
+    use ResolvesActiveUnit;
+
     public function __construct(protected UserService $service) {}
     public function __construct(protected UserService $service) {}
 
 
     public function authUser(): JsonResponse
     public function authUser(): JsonResponse
@@ -28,9 +30,23 @@ public function index(): JsonResponse
         );
         );
     }
     }
 
 
+    public function indexByUnit(): JsonResponse
+    {
+        $items = $this->service->getAllByUnit();
+
+        return $this->successResponse(
+            payload: UserResource::collection($items),
+        );
+    }
+
     public function store(UserRequest $request): JsonResponse
     public function store(UserRequest $request): JsonResponse
     {
     {
-        $item = $this->service->create($request->validated());
+        $data = $request->validated();
+        if ($this->isFranchiseeUsersRequest()) {
+            $data['unit_id'] = $this->activeUnitId();
+            $data['access_scope'] = 'unit';
+        }
+        $item = $this->service->create($data);
 
 
         return $this->successResponse(
         return $this->successResponse(
             payload: new UserResource($item),
             payload: new UserResource($item),
@@ -42,12 +58,20 @@ public function store(UserRequest $request): JsonResponse
     public function show(int $id): JsonResponse
     public function show(int $id): JsonResponse
     {
     {
         $item = $this->service->findById($id);
         $item = $this->service->findById($id);
+        $this->authorizeFranchiseeUser($item);
         return $this->successResponse(payload: new UserResource($item));
         return $this->successResponse(payload: new UserResource($item));
     }
     }
 
 
     public function update(UserRequest $request, int $id): JsonResponse
     public function update(UserRequest $request, int $id): JsonResponse
     {
     {
-        $item = $this->service->update($id, $request->validated());
+        $current = $this->service->findById($id);
+        $this->authorizeFranchiseeUser($current);
+        $data = $request->validated();
+        if ($this->isFranchiseeUsersRequest()) {
+            $data['unit_id'] = $this->activeUnitId();
+            $data['access_scope'] = 'unit';
+        }
+        $item = $this->service->update($id, $data);
         return $this->successResponse(
         return $this->successResponse(
             payload: new UserResource($item),
             payload: new UserResource($item),
             message: __("messages.updated"),
             message: __("messages.updated"),
@@ -56,6 +80,7 @@ public function update(UserRequest $request, int $id): JsonResponse
 
 
     public function destroy(int $id): JsonResponse
     public function destroy(int $id): JsonResponse
     {
     {
+        $this->authorizeFranchiseeUser($this->service->findById($id));
         $this->service->delete($id);
         $this->service->delete($id);
 
 
         return $this->successResponse(
         return $this->successResponse(
@@ -63,17 +88,6 @@ public function destroy(int $id): JsonResponse
         );
         );
     }
     }
 
 
-    public function getUserTypes(): JsonResponse
-    {
-        $user_types = $this->service->getUserTypes();
-
-        return $this->successResponse(
-            payload: new UserTypeResource($user_types),
-        );
-    }
-
-    //
-
     public function updateMe(UserRequest $request): JsonResponse
     public function updateMe(UserRequest $request): JsonResponse
     {
     {
         $user = Auth::user();
         $user = Auth::user();
@@ -85,4 +99,27 @@ public function updateMe(UserRequest $request): JsonResponse
             message: __("messages.updated"),
             message: __("messages.updated"),
         );
         );
     }
     }
+
+    private function isFranchiseeUsersRequest(): bool
+    {
+        return request()->is('api/franchisee/users*');
+    }
+
+    private function authorizeFranchiseeUser(?\App\Models\User $user): void
+    {
+        if (!$this->isFranchiseeUsersRequest()) {
+            return;
+        }
+
+        abort_unless($user, 404);
+        $unitId = request()->input('active_unit_id');
+        abort_unless($unitId && $user->units()->where('units.id', $unitId)->exists(), 404);
+
+        $role = \App\Models\UnitUser::query()
+            ->where('unit_id', $unitId)
+            ->where('user_id', $user->id)
+            ->with('userType')
+            ->first()?->userType;
+        abort_if($role?->system_key === 'ADMIN_FRANCHISEE', 403, 'Administradores do sistema não podem ser editados.');
+    }
 }
 }

+ 189 - 0
app/Http/Controllers/UserTypeController.php

@@ -0,0 +1,189 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Services\PermissionService;
+use App\Services\UserTypePermissionService;
+use App\Services\UserTypeService;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+use Illuminate\Validation\Rule;
+use App\Models\Permission;
+
+class UserTypeController extends Controller
+{
+    public function __construct(
+        protected UserTypeService $service,
+        protected PermissionService $permissionService,
+        protected UserTypePermissionService $userTypePermissionService,
+    ) {}
+
+    public function index(): JsonResponse
+    {
+        $items = $this->service->getAll();
+
+        return $this->successResponse(
+            payload: $items->map(fn($t) => [
+                'id'        => $t->id,
+                'slug'      => $t->slug,
+                'label'     => $t->label,
+                'is_system' => $t->is_system,
+                'access_scope' => $t->access_scope,
+            ])->values(),
+        );
+    }
+
+    public function store(Request $request): JsonResponse
+    {
+        $data = $request->validate([
+            'label' => 'required|string|max:100',
+            'slug'  => [
+                'nullable', 'string', 'max:100', 'regex:/^[A-Za-z0-9_]+$/',
+                Rule::unique('user_types', 'slug')->where(
+                    fn ($query) => $query->where('scope_key', $this->scopeKey()),
+                ),
+            ],
+        ]);
+
+        $item = $this->service->create($data);
+
+        return $this->successResponse(
+            payload: ['id' => $item->id, 'slug' => $item->slug, 'label' => $item->label, 'is_system' => $item->is_system],
+            message: __('messages.created'),
+            code:    201,
+        );
+    }
+
+    public function update(Request $request, int $id): JsonResponse
+    {
+        $data = $request->validate([
+            'label' => 'required|string|max:100',
+            'slug'  => [
+                'required',
+                'string',
+                'max:100',
+                'regex:/^[A-Za-z0-9_]+$/',
+                Rule::unique('user_types', 'slug')->where(
+                    fn ($query) => $query->where('scope_key', $this->scopeKey()),
+                )->ignore($id),
+            ],
+        ]);
+
+        $item = $this->service->update($id, $data);
+
+        if (!$item) {
+            return $this->errorResponse(
+                message: 'Grupo não encontrado ou não pode ser editado.',
+                code:    422,
+            );
+        }
+
+        return $this->successResponse(
+            payload: ['id' => $item->id, 'slug' => $item->slug, 'label' => $item->label, 'is_system' => $item->is_system],
+            message: __('messages.updated'),
+        );
+    }
+
+    public function destroy(int $id): JsonResponse
+    {
+        $deleted = $this->service->delete($id);
+
+        if (!$deleted) {
+            return $this->errorResponse(message: 'Tipo de usuário não encontrado ou não pode ser removido.', code: 422);
+        }
+
+        return $this->successResponse(message: __('messages.deleted'), code: 204);
+    }
+
+    /**
+     * Matriz de permissões do perfil: árvore de módulos + bits atribuídos ao perfil.
+     */
+    public function permissions(int $id): JsonResponse
+    {
+        $userType = $this->service->findById($id);
+
+        if (!$userType) {
+            return $this->errorResponse(message: 'Tipo de usuário não encontrado.', code: 404);
+        }
+
+        $assigned = $this->userTypePermissionService->bitsByPermissionIdForType($userType->id);
+
+        $permissions = $this->permissionService->getTree()
+            ->when($this->isUnitContext(), fn ($items) => $items->filter(
+                fn ($permission) => str_starts_with($permission->scope, 'franchisee_'),
+            ))
+            ->when(!$this->isUnitContext(), fn ($items) => $items->reject(
+                fn ($permission) => str_starts_with($permission->scope, 'franchisee_'),
+            ))
+            ->map(fn ($perm) => [
+            'id'             => $perm->id,
+            'scope'          => $perm->scope,
+            'description'    => $perm->description,
+            'parent_id'      => $perm->parent_id,
+            'depth'          => $perm->depth,
+            'available_bits' => $perm->bits,
+            'bits'           => $assigned[$perm->id] ?? 0,
+        ])->values();
+
+        return $this->successResponse(payload: [
+            'user_type' => [
+                'id'        => $userType->id,
+                'slug'      => $userType->slug,
+                'label'     => $userType->label,
+                'is_system' => $userType->is_system,
+            ],
+            'permissions' => $permissions,
+        ]);
+    }
+
+    /**
+     * Sincroniza a matriz de permissões do perfil.
+     */
+    public function updatePermissions(Request $request, int $id): JsonResponse
+    {
+        $userType = $this->service->findById($id);
+
+        if (!$userType) {
+            return $this->errorResponse(message: 'Tipo de usuário não encontrado.', code: 404);
+        }
+
+        $data = $request->validate([
+            'permissions'                 => 'present|array',
+            'permissions.*.permission_id' => 'required|integer|exists:permissions,id',
+            'permissions.*.bits'          => 'required|integer|min:0|max:511',
+        ]);
+
+        abort_if($userType->is_system, 422, 'As permissões de administradores do sistema não podem ser alteradas.');
+
+        $permissionIds = collect($data['permissions'])->pluck('permission_id')->unique();
+        $validPermissionIds = Permission::query()
+            ->whereIn('id', $permissionIds)
+            ->when($this->isUnitContext(), fn ($query) => $query->where('scope', 'like', 'franchisee\_%'))
+            ->when(!$this->isUnitContext(), fn ($query) => $query->where('scope', 'not like', 'franchisee\_%'))
+            ->pluck('id');
+
+        abort_if($permissionIds->diff($validPermissionIds)->isNotEmpty(), 422, 'Há permissões fora do contexto ativo.');
+
+        $this->userTypePermissionService->syncPermissionsForType($userType, $data['permissions']);
+
+        return $this->successResponse(message: __('messages.updated'));
+    }
+
+    private function scopeKey(): string
+    {
+        $unitId = request()->input('active_unit_id');
+
+        return $unitId ? "unit:{$unitId}" : 'franchisor';
+    }
+
+    private function isUnitContext(): bool
+    {
+        if (request()->is('api/franchisee/permissions*')) {
+            abort_unless(request()->filled('active_unit_id'), 403, 'Selecione uma unidade válida.');
+
+            return true;
+        }
+
+        return request()->filled('active_unit_id');
+    }
+}

+ 4 - 1
app/Http/Controllers/UserTypePermissionController.php

@@ -22,7 +22,10 @@ public function allGuestPermissions(): JsonResponse
     public function allPermissionsByUserType(): JsonResponse
     public function allPermissionsByUserType(): JsonResponse
     {
     {
         $user = Auth::user();
         $user = Auth::user();
-        $userTypePermission = $this->userTypePermissionService->allPermissionsByUserType($user->user_type);
+        $userType = $user->activeUserType();
+        $userTypePermission = $userType
+            ? $this->userTypePermissionService->allPermissionsByUserTypeId($userType->id)
+            : collect();
         return $this->successResponse(payload: UserTypePermissionResource::collection($userTypePermission));
         return $this->successResponse(payload: UserTypePermissionResource::collection($userTypePermission));
     }
     }
 }
 }

+ 44 - 0
app/Http/Controllers/Webhooks/AsaasWebhookController.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace App\Http\Controllers\Webhooks;
+
+use App\Http\Controllers\Controller;
+use App\Jobs\ProcessAsaasWebhookJob;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Log;
+
+class AsaasWebhookController extends Controller
+{
+    public function handle(Request $request): JsonResponse
+    {
+        $expectedToken = config('services.asaas.webhook_token');
+
+        if (empty($expectedToken)) {
+            Log::warning('Asaas Webhook: ASAAS_WEBHOOK_TOKEN não está configurado no .env');
+            return response()->json(['error' => 'Webhook não configurado'], 500);
+        }
+
+        $receivedToken = $request->header('asaas-access-token');
+
+        if ($receivedToken !== $expectedToken) {
+            Log::warning('Asaas Webhook: token inválido recebido', [
+                'received' => substr($receivedToken ?? '', 0, 10) . '...',
+                'ip' => $request->ip(),
+            ]);
+
+            return response()->json(['error' => 'Unauthorized'], 401);
+        }
+
+        $payload = $request->all();
+
+        Log::info('Asaas Webhook recebido', [
+            'event' => $payload['event'] ?? 'unknown',
+            'payment_id' => $payload['payment']['id'] ?? null,
+        ]);
+
+        ProcessAsaasWebhookJob::dispatch($payload);
+
+        return response()->json(['status' => 'received'], 200);
+    }
+}

+ 20 - 0
app/Http/Middleware/ConvertStringNullToNull.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Foundation\Http\Middleware\TransformsRequest;
+
+class ConvertStringNullToNull extends TransformsRequest
+{
+    /**
+     * Transform the given value.
+     *
+     * @param  string  $key
+     * @param  mixed  $value
+     * @return mixed
+     */
+    protected function transform($key, $value)
+    {
+        return $value === 'null' ? null : $value;
+    }
+}

+ 34 - 0
app/Http/Middleware/ResolveActiveUnit.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Closure;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Auth;
+use Symfony\Component\HttpFoundation\Response;
+
+class ResolveActiveUnit
+{
+    /**
+     * Lê o header X-Unit-Id enviado pelo frontend, valida que a unidade
+     * pertence ao usuário autenticado e disponibiliza como active_unit_id
+     * na request para que os serviços possam usá-la ao filtrar dados.
+     */
+    public function handle(Request $request, Closure $next): Response
+    {
+        $unitId = $request->header('X-Unit-Id');
+
+        if ($unitId && Auth::check()) {
+            $user = Auth::user();
+            $user->loadMissing('units');
+
+            $validUnit = $user->units->firstWhere('id', (int) $unitId);
+
+            if ($validUnit) {
+                $request->merge(['active_unit_id' => $validUnit->id]);
+            }
+        }
+
+        return $next($request);
+    }
+}

+ 27 - 0
app/Http/Requests/BaseHolidayRequest.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class BaseHolidayRequest extends FormRequest
+{
+    public function rules(): array
+    {
+        return [
+            'description'  => 'required|string|max:255',
+            'holiday_date' => 'required|date_format:Y-m-d',
+            'type'         => 'nullable|string|in:feriado,facultativo',
+        ];
+    }
+
+    public function messages(): array
+    {
+        return [
+            'description.required'      => 'A descrição do feriado é obrigatória.',
+            'description.max'           => 'A descrição deve ter no máximo 255 caracteres.',
+            'holiday_date.required'     => 'A data do feriado é obrigatória.',
+            'holiday_date.date_format'  => 'A data deve estar no formato YYYY-MM-DD.',
+        ];
+    }
+}

+ 0 - 34
app/Http/Requests/ClassPackageFranchiseeRequest.php

@@ -1,34 +0,0 @@
-<?php
-
-namespace App\Http\Requests;
-
-use Illuminate\Foundation\Http\FormRequest;
-
-class ClassPackageFranchiseeRequest extends FormRequest
-{
-    public function rules(): array
-    {
-        $rules = [
-            // Add your validation rules here
-            //'field' => 'sometimes|string|max:255',
-        ];
-
-        // Different rules for creation
-        //if ($this->isMethod('POST')) {
-            // Make fields required if needed
-            // $rules['field'] = 'required|string|max:255';
-        //}
-
-        return $rules;
-    }
-
-    /**
-    * Add custom messages when needed
-    * public function messages(): array
-    * {
-    *   return [
-    *        'field.required' => __('message.algo'),
-    *    ];
-    * }
-    */
-}

+ 19 - 6
app/Http/Requests/ClassPackageRequest.php

@@ -13,14 +13,27 @@ public function rules(): array
         return [
         return [
             'name'                    => "$required|string|max:255",
             'name'                    => "$required|string|max:255",
             'quantity_classes'        => "$required|integer|min:1",
             'quantity_classes'        => "$required|integer|min:1",
-            'contract_value'          => "$required|numeric|min:0",
-            'contract_register_value' => "$required|numeric|min:0",
+            'contract_value'          => 'nullable|numeric|min:0',
+            'contract_register_value' => 'nullable|numeric|min:0',
             'contrat_discount_value'  => 'nullable|numeric|min:0',
             'contrat_discount_value'  => 'nullable|numeric|min:0',
+            'class_duration_minutes'  => "$required|integer|min:1|max:1440",
 
 
-            'materials'                => 'nullable|array',
-            'materials.*.product_id'   => 'required_with:materials|integer|exists:products,id',
-            'materials.*.quantity'     => 'required_with:materials|integer|min:1',
-            'materials.*.price'        => 'required_with:materials|numeric|min:0',
+            // 'weekday'           => 'nullable|integer|min:0|max:6|required_with:start_time',
+            // 'start_time'        => 'nullable|date_format:H:i|required_with:weekday',
+            // 'second_weekday'    => 'nullable|integer|min:0|max:6|different:weekday|required_with:second_start_time',
+            // 'second_start_time' => 'nullable|date_format:H:i|required_with:second_weekday',
+
+            'materials'              => 'nullable|array',
+            'materials.*.product_id' => 'required_with:materials|integer|exists:products,id',
+            'materials.*.quantity'   => 'required_with:materials|integer|min:1',
+            'materials.*.price'      => 'required_with:materials|numeric|min:0',
+
+            'group_ids'   => 'nullable|array',
+            'group_ids.*' => 'integer|exists:groups,id',
+
+            'unit_visibilities'           => 'nullable|array',
+            'unit_visibilities.*.unit_id' => 'required_with:unit_visibilities|integer|exists:units,id',
+            'unit_visibilities.*.visible' => 'required_with:unit_visibilities|boolean',
         ];
         ];
     }
     }
 }
 }

+ 8 - 2
app/Http/Requests/ClassPackageUnitRequest.php

@@ -13,10 +13,16 @@ public function rules(): array
         return [
         return [
             'name'                    => "$required|string|max:255",
             'name'                    => "$required|string|max:255",
             'quantity_classes'        => "$required|integer|min:1",
             'quantity_classes'        => "$required|integer|min:1",
-            'contract_value'          => "$required|numeric|min:0",
-            'contract_register_value' => "$required|numeric|min:0",
+            'contract_value'          => 'nullable|numeric|min:0',
+            'contract_register_value' => 'nullable|numeric|min:0',
             'contrat_discount_value'  => 'nullable|numeric|min:0',
             'contrat_discount_value'  => 'nullable|numeric|min:0',
             'visible'                 => 'sometimes|boolean',
             'visible'                 => 'sometimes|boolean',
+            'class_duration_minutes'  => "$required|integer|min:1|max:1440",
+
+            // 'weekday'           => 'nullable|integer|min:0|max:6|required_with:start_time',
+            // 'start_time'        => 'nullable|date_format:H:i|required_with:weekday',
+            // 'second_weekday'    => 'nullable|integer|min:0|max:6|different:weekday|required_with:second_start_time',
+            // 'second_start_time' => 'nullable|date_format:H:i|required_with:second_weekday',
 
 
             'materials'              => 'nullable|array',
             'materials'              => 'nullable|array',
             'materials.*.product_id' => 'required_with:materials|integer|exists:products,id',
             'materials.*.product_id' => 'required_with:materials|integer|exists:products,id',

+ 22 - 10
app/Http/Requests/ClassRequest.php

@@ -3,23 +3,35 @@
 namespace App\Http\Requests;
 namespace App\Http\Requests;
 
 
 use Illuminate\Foundation\Http\FormRequest;
 use Illuminate\Foundation\Http\FormRequest;
+use Illuminate\Validation\Rule;
 
 
 class ClassRequest extends FormRequest
 class ClassRequest extends FormRequest
 {
 {
+    public function authorize(): bool
+    {
+        return true;
+    }
+
     public function rules(): array
     public function rules(): array
     {
     {
-        $rules = [
-            // Add your validation rules here
-            //'field' => 'sometimes|string|max:255',
-        ];
+        $required = $this->isMethod('POST') ? 'required' : 'sometimes';
+
+        return [
+            'title' => "{$required}|string|max:255",
 
 
-        // Different rules for creation
-        //if ($this->isMethod('POST')) {
-            // Make fields required if needed
-            // $rules['field'] = 'required|string|max:255';
-        //}
+            'class_package_unit_id' => [
+                $required,
+                'integer',
+                Rule::exists('class_package_units', 'id')
+                    ->where('unit_id', $this->input('active_unit_id')),
+            ],
 
 
-        return $rules;
+            'instructor'      => 'nullable|string|max:255',
+            'room'            => 'nullable|string|max:255',
+            'date_time_start' => "{$required}|date",
+            'date_time_end'   => "{$required}|date|after:date_time_start",
+            'status'          => 'sometimes|string|max:50',
+        ];
     }
     }
 
 
     /**
     /**

+ 15 - 0
app/Http/Requests/CompanyPaymentAccountRequest.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class CompanyPaymentAccountRequest extends FormRequest
+{
+    public function rules(): array
+    {
+        return [
+            'asaas_api_key' => 'present|nullable|string|max:500',
+        ];
+    }
+}

+ 15 - 0
app/Http/Requests/ConfirmPasswordRequest.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class ConfirmPasswordRequest extends FormRequest
+{
+    public function rules(): array
+    {
+        return [
+            "password" => "required|string",
+        ];
+    }
+}

+ 0 - 34
app/Http/Requests/FinancialAccountPayableRequest.php

@@ -1,34 +0,0 @@
-<?php
-
-namespace App\Http\Requests;
-
-use Illuminate\Foundation\Http\FormRequest;
-
-class FinancialAccountPayableRequest extends FormRequest
-{
-    public function rules(): array
-    {
-        $rules = [
-            // Add your validation rules here
-            //'field' => 'sometimes|string|max:255',
-        ];
-
-        // Different rules for creation
-        //if ($this->isMethod('POST')) {
-            // Make fields required if needed
-            // $rules['field'] = 'required|string|max:255';
-        //}
-
-        return $rules;
-    }
-
-    /**
-    * Add custom messages when needed
-    * public function messages(): array
-    * {
-    *   return [
-    *        'field.required' => __('message.algo'),
-    *    ];
-    * }
-    */
-}

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác