feat(financeiro+salas): motor financeiro V1/V2, glosas, GTO→financeiro, contratos de convênio e redesenho de Salas
Financeiro V1 (Épicos 1–4): - FKs de origem no lançamento (paciente/profissional/procedimento/agendamento/contrato/realizado), soft delete e cron "Atrasado" - prontuário de procedimentos realizados (fotos antes/depois) + regra sem-comissão (garantia/reabertura do mesmo profissional) - normalizeFinanceiro: corrige CHECK capitalizado vs enforceUppercase (lançamentos não salvavam pela UI) - baixa de pagamento (pago_em), despesas (fornecedor/centro de custo/recorrente), competência por data de conclusão - relatório financeiro (período, paciente, profissional, forma, convênio, glosa) - orçamento Assinado -> contas a receber; contrato vigente -> cobrança recorrente; renovação automática de contrato Financeiro V2 (Comissão/Repasse): - regras de % (padrão/procedimento/override por profissional) + custo de laboratório - base selecionável (recebido/produção), fechamento persistido, pagar (gera despesa) + comprovante + estorno Glosas de convênio: por item de GTO, recurso/protocolo/prazo, recuperar/estornar, impacto no relatório GTO -> Financeiro: finalizar gera RECEITA (convênio a receber); LancarGTO passa a persistir; convênios reais (planos) Contratos: modelo "Atendimento a Convênios / Repasse (Dentista Credenciado)" + variáveis de convênio Salas (redesenho): perfil "Dono de Sala"; locação sempre ativa; menus MINHAS/ALUGAR SALAS; sala como workspace isolado (seletor agrupado Clínicas x Salas, tenantGuard por dono); financeiro de locação (reserva confirmada -> recebível); Painel da Sala (KPIs, agenda visual, recebíveis, relatório por sala) Docs: BACKLOG-FINANCEIRO-V1, AUDITORIA-FUNCIONAL-SCOREODONTO, CHECKLIST-DEPLOY-PRODUCAO Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@ import { Puzzle, Radio, CheckCircle2, XCircle, Settings2, Zap, ChevronDown, Chev
|
||||
import { PageHeader } from '../components/PageHeader.tsx';
|
||||
import { useToast } from '../contexts/ToastContext.tsx';
|
||||
import { HybridBackend } from '../services/backend.ts';
|
||||
import { getActivePlugins, togglePlugin, getPluginConfig, savePluginConfig } from './plugins/pluginRegistry.ts';
|
||||
import { getActivePlugins, togglePlugin, getPluginConfig, savePluginConfig, ALWAYS_ON_PLUGINS } from './plugins/pluginRegistry.ts';
|
||||
|
||||
// ─── Plugin definitions ───────────────────────────────────────────────────────
|
||||
interface ConfigField {
|
||||
@@ -90,7 +90,7 @@ const PLUGINS: PluginDef[] = [
|
||||
version: '1.0',
|
||||
author: 'ScoreOdonto',
|
||||
category: 'MARKETPLACE',
|
||||
color: '#ea580c',
|
||||
color: '#0d9488',
|
||||
sidebarItems: [{ id: 'marketplace-profissionais', label: 'PROFISSIONAIS' }],
|
||||
features: [
|
||||
'Busca por especialidade, cidade, estado e região (CEP)',
|
||||
@@ -171,7 +171,7 @@ const ConfigModal: React.FC<{ plugin: PluginDef; onClose: () => void }> = ({ plu
|
||||
};
|
||||
|
||||
// ─── Plugin Card ──────────────────────────────────────────────────────────────
|
||||
const PluginCard: React.FC<{ plugin: PluginDef; isActive: boolean; price?: number; onToggle: () => void; onConfigure: () => void }> = ({ plugin, isActive, price, onToggle, onConfigure }) => {
|
||||
const PluginCard: React.FC<{ plugin: PluginDef; isActive: boolean; alwaysOn?: boolean; price?: number; onToggle: () => void; onConfigure: () => void }> = ({ plugin, isActive, alwaysOn, price, onToggle, onConfigure }) => {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
return (
|
||||
@@ -251,6 +251,11 @@ const PluginCard: React.FC<{ plugin: PluginDef; isActive: boolean; price?: numbe
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex gap-3 pt-2 border-t border-gray-50">
|
||||
{alwaysOn ? (
|
||||
<div className="flex-1 py-3 rounded-2xl text-xs font-black uppercase tracking-wider flex items-center justify-center gap-2 bg-gray-50 text-gray-400 border border-gray-100 cursor-default" title="Capability de plataforma — sempre incluída">
|
||||
<CheckCircle2 size={15} /> SEMPRE INCLUÍDO
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
onClick={onToggle}
|
||||
className={`flex-1 py-3 rounded-2xl text-xs font-black uppercase tracking-wider transition-all flex items-center justify-center gap-2 ${isActive ? 'border-2 border-red-200 text-red-500 hover:bg-red-50' : 'text-white shadow-md hover:opacity-90'}`}
|
||||
@@ -258,6 +263,7 @@ const PluginCard: React.FC<{ plugin: PluginDef; isActive: boolean; price?: numbe
|
||||
>
|
||||
{isActive ? <><XCircle size={15} /> DESATIVAR PLUGIN</> : <><CheckCircle2 size={15} /> ATIVAR PLUGIN</>}
|
||||
</button>
|
||||
)}
|
||||
{plugin.configFields.length > 0 && (
|
||||
<button
|
||||
onClick={onConfigure}
|
||||
@@ -350,7 +356,8 @@ export const PluginsView: React.FC = () => {
|
||||
<PluginCard
|
||||
key={plugin.id}
|
||||
plugin={{ ...plugin, version: pluginVersions[plugin.id] || plugin.version }}
|
||||
isActive={activePlugins.includes(plugin.id)}
|
||||
isActive={activePlugins.includes(plugin.id) || ALWAYS_ON_PLUGINS.includes(plugin.id)}
|
||||
alwaysOn={ALWAYS_ON_PLUGINS.includes(plugin.id)}
|
||||
price={pluginPrices[plugin.id]}
|
||||
onToggle={() => handleToggle(plugin)}
|
||||
onConfigure={() => setConfiguringPlugin(plugin)}
|
||||
|
||||
Reference in New Issue
Block a user