diff --git a/src/main/java/com/seconectai/condocontab/master/http/MasterHandlers.java b/src/main/java/com/seconectai/condocontab/master/http/MasterHandlers.java index 0e0d105..8cce172 100644 --- a/src/main/java/com/seconectai/condocontab/master/http/MasterHandlers.java +++ b/src/main/java/com/seconectai/condocontab/master/http/MasterHandlers.java @@ -131,9 +131,10 @@ public final class MasterHandlers implements HttpHandler { else HttpResponses.methodNotAllowed(ex); return; } - if (parts.length == 2 && "planos".equals(parts[0]) && "PUT".equals(method)) { - atualizarPlano(ex, parseId(parts[1])); - return; + if (parts.length == 2 && "planos".equals(parts[0])) { + if ("PUT".equals(method)) { atualizarPlano(ex, parseId(parts[1])); return; } + else if ("DELETE".equals(method)) { excluirPlano(ex, parseId(parts[1])); return; } + else { HttpResponses.methodNotAllowed(ex); return; } } if (parts.length == 1 && "logs-envio".equals(parts[0]) && "GET".equals(method)) { listarLogsEnvio(ex); @@ -526,6 +527,28 @@ public final class MasterHandlers implements HttpHandler { private void criarPlano(HttpExchange ex) throws Exception { salvarPlano(ex, null); } private void atualizarPlano(HttpExchange ex, long id) throws Exception { salvarPlano(ex, id); } + private void excluirPlano(HttpExchange ex, long id) throws Exception { + try (Connection conn = dataSourceProvider.getMasterConnection()) { + conn.setAutoCommit(false); + if (exists(conn, "SELECT 1 FROM assinaturas WHERE plano_id = ?", id)) { + try (PreparedStatement ps = conn.prepareStatement("UPDATE planos SET ativo=false WHERE id=?")) { + ps.setLong(1, id); + ps.executeUpdate(); + } + } else { + try (PreparedStatement ps = conn.prepareStatement("DELETE FROM plano_funcionalidades WHERE plano_id=?")) { + ps.setLong(1, id); + ps.executeUpdate(); + } + try (PreparedStatement ps = conn.prepareStatement("DELETE FROM planos WHERE id=?")) { + ps.setLong(1, id); + ps.executeUpdate(); + } + } + conn.commit(); + HttpResponses.json(ex, 200, Map.of("sucesso", true)); + } + } private void salvarPlano(HttpExchange ex, Long id) throws Exception { String raw = rawBody(ex); @@ -669,7 +692,7 @@ public final class MasterHandlers implements HttpHandler { private int diaVencimento(Connection conn,long empresaId)throws SQLException{Mapa=one(conn,"SELECT dia_vencimento FROM assinaturas WHERE empresa_id=? AND status='ativa' ORDER BY id DESC LIMIT 1",List.of(empresaId));if(a==null||a.get("dia_vencimento")==null)return 7;int dia=((Number)a.get("dia_vencimento")).intValue();return Math.max(1,Math.min(28,dia));} private long inserirPlano(Connection c, Mapb)throws SQLException{try(PreparedStatement ps=c.prepareStatement("INSERT INTO planos (nome,valor_mensal_centavos,descricao,ativo) VALUES (?,?,?,true) RETURNING id")){ps.setString(1,required(b,"nome"));ps.setLong(2,Long.parseLong(required(b,"valor_mensal_centavos")));ps.setString(3,b.get("descricao"));try(ResultSet rs=ps.executeQuery()){rs.next();return rs.getLong(1);}}} - private long atualizarPlanoRow(Connection c,long id,Mapb)throws SQLException{try(PreparedStatement ps=c.prepareStatement("UPDATE planos SET nome=COALESCE(?,nome), valor_mensal_centavos=COALESCE(?,valor_mensal_centavos), descricao=COALESCE(?,descricao) WHERE id=?")){ps.setString(1,b.get("nome")); if(b.get("valor_mensal_centavos")==null)ps.setNull(2,Types.BIGINT);else ps.setLong(2,Long.parseLong(b.get("valor_mensal_centavos"))); ps.setString(3,b.get("descricao")); ps.setLong(4,id); ps.executeUpdate(); return id;}} + private long atualizarPlanoRow(Connection c,long id,Mapb)throws SQLException{String sql="UPDATE planos SET nome=COALESCE(?,nome), valor_mensal_centavos=COALESCE(?,valor_mensal_centavos), descricao=COALESCE(?,descricao)"+(b.containsKey("ativo")?", ativo=?":"")+" WHERE id=?";try(PreparedStatement ps=c.prepareStatement(sql)){ps.setString(1,b.get("nome")); if(b.get("valor_mensal_centavos")==null)ps.setNull(2,Types.BIGINT);else ps.setLong(2,Long.parseLong(b.get("valor_mensal_centavos"))); ps.setString(3,b.get("descricao")); int idx=4; if(b.containsKey("ativo")){ps.setBoolean(idx++,Boolean.parseBoolean(b.get("ativo")));} ps.setLong(idx,id); ps.executeUpdate(); return id;}} private void sincronizarPlanoFuncionalidades(Connection c,long planoId,List chaves)throws SQLException{try(PreparedStatement del=c.prepareStatement("DELETE FROM plano_funcionalidades WHERE plano_id=?")){del.setLong(1,planoId);del.executeUpdate();}try(PreparedStatement ins=c.prepareStatement("INSERT INTO plano_funcionalidades (plano_id,funcionalidade_id) SELECT ?, id FROM funcionalidades WHERE chave=?")){for(String chave:chaves){ins.setLong(1,planoId);ins.setString(2,chave);ins.addBatch();}ins.executeBatch();}} private void copiarFuncionalidadesPlano(Connection c,long empresaId,long planoId)throws SQLException{try(PreparedStatement ps=c.prepareStatement("INSERT INTO empresa_funcionalidades (empresa_id,funcionalidade_id,ativa) SELECT ?, funcionalidade_id, true FROM plano_funcionalidades WHERE plano_id=? ON CONFLICT (empresa_id,funcionalidade_id) DO UPDATE SET ativa=true")){ps.setLong(1,empresaId);ps.setLong(2,planoId);ps.executeUpdate();}} private void salvarAssinatura(Connection c,long empresaId,long planoId)throws SQLException{Map p=one(c,"SELECT valor_mensal_centavos FROM planos WHERE id=?",List.of(planoId));if(p==null)throw new IllegalArgumentException("Plano nao encontrado");try(PreparedStatement ps=c.prepareStatement("UPDATE assinaturas SET status='inativa' WHERE empresa_id=? AND status='ativa'")){ps.setLong(1,empresaId);ps.executeUpdate();}try(PreparedStatement ps=c.prepareStatement("INSERT INTO assinaturas (empresa_id,plano_id,valor_mensal_centavos,dia_vencimento,status) VALUES (?,?,?,?, 'ativa')")){ps.setLong(1,empresaId);ps.setLong(2,planoId);ps.setLong(3,((Number)p.get("valor_mensal_centavos")).longValue());ps.setInt(4,7);ps.executeUpdate();}} diff --git a/src/main/webapp/master/planos.html b/src/main/webapp/master/planos.html index 3f4b36d..e9bf916 100644 --- a/src/main/webapp/master/planos.html +++ b/src/main/webapp/master/planos.html @@ -1 +1,83 @@ -Planos

Planos

Novo plano

Planos cadastrados

IDNomeValorDescricaoStatus
\ No newline at end of file + + + + + + Planos + + + + + + + +
+
+

Planos

+
+
+
+

Planos cadastrados

+
+ +
+
+
+ + + + + + + + + + + + +
IDNomeValorDescriçãoStatusAções
+
+
+ + + +
+ + + + \ No newline at end of file diff --git a/src/main/webapp/master/planos.js b/src/main/webapp/master/planos.js index 45ef09e..4dfbe15 100644 --- a/src/main/webapp/master/planos.js +++ b/src/main/webapp/master/planos.js @@ -1 +1,105 @@ -const form=document.querySelector('#plano-form'),rows=document.querySelector('#rows');async function load(){try{const d=await Api.get('/api/master/planos');rows.innerHTML=(d.planos||[]).map(p=>`${p.id}${p.nome}${Util.money(p.valor_mensal_centavos)}${p.descricao||''}${Util.badge(p.ativo?'ativo':'inativo')}`).join('')}catch(e){Util.toast(e.message,true)}}form.onsubmit=async e=>{e.preventDefault();const b=Util.formData(form);b.funcionalidades=[];try{await Api.post('/api/master/planos',b);form.reset();Util.toast('Plano salvo');load()}catch(err){Util.toast(err.message,true)}};load(); \ No newline at end of file +const form = document.querySelector('#plano-form'); +const rows = document.querySelector('#rows'); +const modal = document.querySelector('#plano-modal'); +const modalTitle = document.querySelector('#plano-modal-title'); +const campoStatus = document.querySelector('#campo-status-plano'); +let planos = []; +let planoEditandoId = null; + +const Icons = { + edit: '', + trash: '' +}; + +function esc(v){ + return String(v??'').replace(/[&<>'"]/g,c=>({'&':'&','<':'<','>':'>',"'":''','"':'"'}[c])); +} + +async function load() { + try { + const d = await Api.get('/api/master/planos'); + planos = d.planos || []; + render(); + } catch (e) { + Util.toast(e.message, true); + } +} + +function render() { + rows.innerHTML = planos.length ? planos.map(p => ` + ${p.id} + ${esc(p.nome)} + ${Util.money(p.valor_mensal_centavos)} + ${esc(p.descricao || '-')} + ${Util.badge(p.ativo ? 'ativo' : 'inativo')} + +
+ + +
+ + `).join('') : 'Nenhum plano cadastrado.'; +} + +window.abrirModalPlano = () => { + planoEditandoId = null; + form.reset(); + modalTitle.textContent = 'Novo plano'; + campoStatus.style.display = 'none'; + modal?.classList.remove('hidden'); + form.querySelector('input[name="nome"]')?.focus(); +}; + +window.fecharModalPlano = () => { + modal?.classList.add('hidden'); +}; + +window.editarPlano = (id) => { + const p = planos.find(x => Number(x.id) === Number(id)); + if (!p) return; + planoEditandoId = p.id; + form.reset(); + modalTitle.textContent = 'Editar plano'; + form.querySelector('input[name="nome"]').value = p.nome || ''; + form.querySelector('input[name="valor_mensal_centavos"]').value = p.valor_mensal_centavos ?? ''; + form.querySelector('input[name="descricao"]').value = p.descricao || ''; + form.querySelector('#plano-ativo').value = p.ativo ? 'true' : 'false'; + campoStatus.style.display = 'block'; + modal?.classList.remove('hidden'); + form.querySelector('input[name="nome"]')?.focus(); +}; + +window.excluirPlano = async (id) => { + const p = planos.find(x => Number(x.id) === Number(id)); + const nome = p ? ` "${p.nome}"` : ''; + if (!await Util.confirm(`Tem certeza que deseja excluir o plano${nome}?`, 'Excluir plano')) return; + try { + await Api.request(`/api/master/planos/${id}`, { method: 'DELETE' }); + Util.toast('Plano excluído com sucesso'); + load(); + } catch (err) { + Util.toast(err.message, true); + } +}; + +form.onsubmit = async (e) => { + e.preventDefault(); + const b = Util.formData(form); + b.funcionalidades = []; + try { + if (planoEditandoId) { + await Api.put(`/api/master/planos/${planoEditandoId}`, b); + Util.toast('Plano atualizado com sucesso'); + } else { + await Api.post('/api/master/planos', b); + Util.toast('Plano cadastrado com sucesso'); + } + form.reset(); + fecharModalPlano(); + load(); + } catch (err) { + Util.toast(err.message, true); + } +}; + +load(); \ No newline at end of file