Files
plugin_recre/projeto_completo.txt
2026-02-17 23:47:16 +01:00

1178 lines
50 KiB
Plaintext

============== ESTRUTURA DO PROJETO ==============
.
./__manifest__.py
./models
./models/financeiro.py
./models/plano.py
./models/__init__.py
./models/crianca.py
./models/diario.py
./models/res_partner.py
./models/report_parser.py
./__init__.py
./reports
./reports/report.xml
./reports/financeiro_report.xml
./reports/historico_template.xml
./reports/financeiro_template.xml
./static
./static/manifest.json
./static/img
./static/img/favicon.png
./static/img/icon_192.png
./static/img/icon_512.png
./static/img/favicon.ico
./wizard
./wizard/financeiro_wizard.py
./wizard/__init__.py
./security
./security/ir.model.access.csv
./views
./views/financeiro_wizard_view.xml
./views/web_layout.xml
./views/crianca_view.xml
./views/plano_view.xml
./views/res_partner_view.xml
./views/diario_view.xml
./views/financeiro_view.xml
==================================================================
ARQUIVO: ./__manifest__.py
==================================================================
# ARQUIVO: ./__manifest__.py
{
'name': 'Gestão de Recreação (Lite)',
'version': '3.0',
'category': 'Services',
'summary': 'Alunos, Pais e Financeiro Simples',
'depends': ['base', 'contacts'], # REMOVIDO 'account'
'data': [
'security/ir.model.access.csv',
'views/plano_view.xml',
'views/diario_view.xml',
'views/crianca_view.xml',
'views/financeiro_view.xml',
'views/res_partner_view.xml',
'views/web_layout.xml',
'views/financeiro_wizard_view.xml', # NOVO
'reports/report.xml',
'reports/historico_template.xml',
'reports/financeiro_template.xml', # NOVO
],
'application': True,
'license': 'LGPL-3',
}
==================================================================
ARQUIVO: ./models/financeiro.py
==================================================================
# ARQUIVO: ./models/financeiro.py
from odoo import models, fields, api
class RecreacaoFinanceiro(models.Model):
_name = 'recreacao.financeiro'
_description = 'Movimentação Financeira'
_order = 'data_vencimento desc'
name = fields.Char(string='Descrição', required=True)
tipo = fields.Selection([
('receita', '🟢 Receita (Entrada)'),
('despesa', '🔴 Despesa (Saída)')
], string='Tipo', required=True, default='receita')
valor = fields.Float(string='Valor (R$)', required=True)
data_vencimento = fields.Date(string='Vencimento', required=True, default=fields.Date.context_today)
data_pagamento = fields.Date(string='Data Pagamento')
# Status do pagamento
status = fields.Selection([
('pendente', 'Pendente'),
('pago', 'Pago'),
('cancelado', 'Cancelado')
], string='Status', default='pendente', tracking=True)
# Relacionamentos
partner_id = fields.Many2one('res.partner', string='Pessoa/Fornecedor')
crianca_id = fields.Many2one('recreacao.crianca', string='Referente ao Aluno')
def action_pagar(self):
for rec in self:
rec.status = 'pago'
rec.data_pagamento = fields.Date.today()
def action_cancelar(self):
for rec in self:
rec.status = 'cancelado'
def action_redefinir(self):
for rec in self:
rec.status = 'pendente'
rec.data_pagamento = False
==================================================================
ARQUIVO: ./models/plano.py
==================================================================
from odoo import models, fields
class RecreacaoPlano(models.Model):
_name = 'recreacao.plano'
_description = 'Planos de Cobrança'
name = fields.Char(string='Nome do Plano', required=True)
# NOVO: Tipo de período
tipo_cobranca = fields.Selection([
('mensal', 'Mensalidade (Recorrente)'),
('diaria', 'Diária / Avulso')
], string='Tipo de Cobrança', default='mensal', required=True)
valor = fields.Float(string='Valor (R$)', required=True)
produto_id = fields.Many2one('product.product', string='Produto/Serviço Vinculado')
horario_inicio = fields.Float(string='Entrada')
horario_fim = fields.Float(string='Saída')
==================================================================
ARQUIVO: ./models/__init__.py
==================================================================
from . import plano
from . import diario
from . import crianca
from . import res_partner
from . import financeiro
from . import report_parser
==================================================================
ARQUIVO: ./models/crianca.py
==================================================================
# ARQUIVO: ./models/crianca.py
from odoo import models, fields, api, _
from datetime import date
class RecreacaoCrianca(models.Model):
_name = 'recreacao.crianca'
_description = 'Aluno / Criança'
_inherit = ['mail.thread'] # Permite o log de mensagens e chat no rodapé
# --- DADOS PESSOAIS ---
name = fields.Char(string='Nome da Criança', required=True, tracking=True)
foto = fields.Binary(string="Foto")
data_nascimento = fields.Date(string='Data de Nascimento')
idade = fields.Char(compute='_compute_idade', string='Idade')
# --- FAMÍLIA ---
pai_id = fields.Many2one('res.partner', string='Pai', domain="[('is_company', '=', False)]")
mae_id = fields.Many2one('res.partner', string='Mãe', domain="[('is_company', '=', False)]")
responsavel_financeiro_id = fields.Many2one('res.partner', string='Responsável Financeiro', required=True)
# --- SAÚDE ---
tem_alergia = fields.Boolean(string="Tem Alergia?", tracking=True)
alergias = fields.Text(string='Quais Alergias?')
toma_remedio = fields.Boolean(string="Toma Remédio?")
medicamentos = fields.Text(string='Quais Medicamentos?')
horario_medicacao = fields.Char(string='Horários')
observacoes_saude = fields.Text(string='Obs. Médicas')
# --- PLANO & FINANCEIRO ---
plano_id = fields.Many2one('recreacao.plano', string='Plano Contratado', tracking=True)
valor_plano = fields.Float(related='plano_id.valor', string='Valor do Plano (R$)', readonly=True)
# Novo relacionamento com o Financeiro Simplificado
financeiro_ids = fields.One2many('recreacao.financeiro', 'crianca_id', string='Histórico de Pagamentos')
# --- HISTÓRICO DE FREQUÊNCIA ---
diario_ids = fields.One2many('recreacao.diario', 'crianca_id', string='Histórico Diário')
# --- CONTROLE DE STATUS (Lógica Corrigida) ---
status_dia = fields.Selection([
('ausente', 'Ausente'),
('presente', 'Na Creche'),
('finalizado', 'Já Saiu')
], compute='_compute_status_dia', string='Status Hoje', store=False)
@api.depends('diario_ids')
def _compute_status_dia(self):
hoje = fields.Date.today()
for rec in self:
# Busca o ÚLTIMO movimento de entrada ou saída de hoje
ultimo_movimento = self.env['recreacao.diario'].search([
('crianca_id', '=', rec.id),
('data', '=', hoje),
('tipo', 'in', ['entrada', 'saida'])
], order='create_date desc', limit=1)
if not ultimo_movimento:
rec.status_dia = 'ausente'
elif ultimo_movimento.tipo == 'entrada':
rec.status_dia = 'presente'
elif ultimo_movimento.tipo == 'saida':
rec.status_dia = 'finalizado'
else:
rec.status_dia = 'ausente'
# --- AÇÕES DE FREQUÊNCIA ---
def action_marcar_entrada(self):
"""Botão Verde do Kanban"""
for rec in self:
self.env['recreacao.diario'].create({
'crianca_id': rec.id,
'tipo': 'entrada',
'descricao': 'Chegou na recreação.'
})
# Força atualização da interface
rec._compute_status_dia()
def action_marcar_saida(self):
"""Botão Vermelho do Kanban"""
for rec in self:
self.env['recreacao.diario'].create({
'crianca_id': rec.id,
'tipo': 'saida',
'descricao': 'Foi embora.'
})
rec._compute_status_dia()
# --- AÇÕES DE UTILIDADE ---
def action_abrir_whatsapp(self):
self.ensure_one()
if not self.responsavel_financeiro_id.phone and not self.responsavel_financeiro_id.mobile:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {'title': 'Erro', 'message': 'Responsável sem telefone cadastrado!', 'type': 'danger'}
}
numero = self.responsavel_financeiro_id.mobile or self.responsavel_financeiro_id.phone
phone = ''.join(filter(str.isdigit, numero))
msg = f"Olá, gostaria de falar sobre: {self.name}"
return {
'type': 'ir.actions.act_url',
'url': f"https://api.whatsapp.com/send?phone={phone}&text={msg}",
'target': 'new',
}
def action_abrir_historico(self):
"""Abre a lista filtrada do diário dessa criança"""
return {
'name': f'Histórico: {self.name}',
'type': 'ir.actions.act_window',
'res_model': 'recreacao.diario',
'domain': [('crianca_id', '=', self.id)],
'view_mode': 'tree,form',
}
# --- NOVA AÇÃO FINANCEIRA (SIMPLIFICADA) ---
def action_gerar_cobranca(self):
"""Gera um lançamento de Receita no Financeiro Simplificado"""
self.ensure_one()
if not self.plano_id:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {'title': 'Atenção', 'message': 'Selecione um Plano antes de gerar cobrança.', 'type': 'warning'}
}
# Cria a cobrança no novo modelo
self.env['recreacao.financeiro'].create({
'name': f"Mensalidade: {self.name} - {fields.Date.today().strftime('%m/%Y')}",
'tipo': 'receita',
'valor': self.valor_plano,
'partner_id': self.responsavel_financeiro_id.id,
'crianca_id': self.id,
'data_vencimento': fields.Date.today(),
'status': 'pendente'
})
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': 'Sucesso',
'message': f'Cobrança de R$ {self.valor_plano} gerada no Financeiro!',
'type': 'success',
'sticky': False,
}
}
# --- CÁLCULO DE IDADE ---
@api.depends('data_nascimento')
def _compute_idade(self):
for rec in self:
if rec.data_nascimento:
today = date.today()
# Lógica precisa de cálculo de idade (considera dia e mês)
years = today.year - rec.data_nascimento.year - ((today.month, today.day) < (rec.data_nascimento.month, rec.data_nascimento.day))
rec.idade = f"{years} anos"
else:
rec.idade = "Sem data"
==================================================================
ARQUIVO: ./models/diario.py
==================================================================
from odoo import models, fields, api
from datetime import datetime
import pytz
class RecreacaoDiario(models.Model):
_name = 'recreacao.diario'
_description = 'Diário de Classe'
_order = 'data desc, create_date desc' # Ordena pelo mais recente
data = fields.Date(string='Data', required=True, default=fields.Date.context_today)
# Campo para guardar a hora exata (para o relatório)
hora_registro = fields.Char(string='Hora', default=lambda self: self._get_hora_atual())
crianca_id = fields.Many2one('recreacao.crianca', string='Criança', required=True)
tipo = fields.Selection([
('entrada', '🟢 Entrada / Chegada'),
('saida', '🔴 Saída / Foi Embora'),
('ocorrencia', '⚠️ Ocorrência / Incidente'),
('saude', '💊 Medicamento / Saúde'),
('rotina', '📝 Rotina / Anotação')
], string='Tipo', required=True, default='rotina')
descricao = fields.Text(string='Observações')
def _get_hora_atual(self):
# Pega a hora atual no fuso de SP (Hardcoded para facilitar)
tz = pytz.timezone('America/Sao_Paulo')
return datetime.now(tz).strftime('%H:%M')
==================================================================
ARQUIVO: ./models/res_partner.py
==================================================================
from odoo import models, fields
class ResPartner(models.Model):
_inherit = 'res.partner'
# CORREÇÃO AQUI: O segundo parâmetro deve ser igual ao nome do campo na criança
crianca_ids = fields.One2many(
'recreacao.crianca',
'responsavel_financeiro_id',
string='Filhos (Responsável Fin.)'
)
==================================================================
ARQUIVO: ./models/report_parser.py
==================================================================
from odoo import models, api
class RelatorioFinanceiroParser(models.AbstractModel):
_name = 'report.plugin_recre.template_financeiro_mensal'
_description = 'Lógica do Relatório Financeiro'
@api.model
def _get_report_values(self, docids, data=None):
start = data.get('data_inicio')
end = data.get('data_fim')
# Busca todos os movimentos no período
movimentos = self.env['recreacao.financeiro'].search([
('data_vencimento', '>=', start),
('data_vencimento', '<=', end),
# Opcional: filtrar só os pagos? Para fluxo de caixa real, sim.
# ('status', '=', 'pago')
], order='data_vencimento asc')
entradas = movimentos.filtered(lambda r: r.tipo == 'receita')
saidas = movimentos.filtered(lambda r: r.tipo == 'despesa')
total_entradas = sum(entradas.mapped('valor'))
total_saidas = sum(saidas.mapped('valor'))
return {
'doc_ids': docids,
'doc_model': 'recreacao.financeiro',
'data_inicio': start,
'data_fim': end,
'entradas': entradas,
'saidas': saidas,
'total_entradas': total_entradas,
'total_saidas': total_saidas,
'saldo_final': total_entradas - total_saidas,
}
==================================================================
ARQUIVO: ./__init__.py
==================================================================
from . import models
from . import wizard
==================================================================
ARQUIVO: ./reports/report.xml
==================================================================
<odoo>
<record id="action_report_historico_aluno" model="ir.actions.report">
<field name="name">Histórico Mensal</field>
<field name="model">recreacao.crianca</field>
<field name="report_type">qweb-pdf</field>
<!-- O nome agora começa com plugin_recre -->
<field name="report_name">plugin_recre.report_historico_aluno</field>
<field name="report_file">plugin_recre.report_historico_aluno</field>
<field name="binding_model_id" ref="model_recreacao_crianca"/>
<field name="binding_type">report</field>
</record>
</odoo>
==================================================================
ARQUIVO: ./reports/historico_template.xml
==================================================================
<odoo>
<template id="report_historico_aluno">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="web.external_layout">
<div class="page">
<h2>Relatório do Aluno: <span t-field="doc.name"/></h2>
<div class="row mt32 mb32">
<div class="col-6">
<strong>Idade:</strong> <span t-field="doc.idade"/><br/>
<strong>Responsável:</strong> <span t-field="doc.responsavel_financeiro_id.name"/>
</div>
</div>
<h3>Diário de Atividades</h3>
<table class="table table-sm table-striped mt-4">
<thead>
<tr>
<th>Data</th>
<th>Hora</th> <!-- Coluna Nova -->
<th>Tipo</th>
<th>Detalhes</th>
</tr>
</thead>
<tbody>
<tr t-foreach="doc.diario_ids" t-as="line">
<td><span t-field="line.data"/></td>
<td><span t-field="line.hora_registro"/></td> <!-- Hora -->
<td>
<!-- Ícones no PDF -->
<span t-if="line.tipo == 'entrada'" style="color:green;">🟢 Entrada</span>
<span t-if="line.tipo == 'saida'" style="color:red;">🔴 Saída</span>
<span t-if="line.tipo == 'ocorrencia'" style="color:orange; font-weight:bold;">⚠️ Ocorrência</span>
<span t-if="line.tipo == 'saude'">💊 Saúde</span>
<span t-if="line.tipo == 'rotina'">Rotina</span>
</td>
<td><span t-field="line.descricao"/></td>
</tr>
</tbody>
</table>
</div>
</t>
</t>
</t>
</template>
</odoo>
==================================================================
ARQUIVO: ./reports/financeiro_template.xml
==================================================================
<odoo>
<!-- AÇÃO DO RELATÓRIO -->
<record id="action_report_financeiro_mensal" model="ir.actions.report">
<field name="name">Fluxo de Caixa Mensal</field>
<field name="model">recreacao.financeiro.wizard</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">plugin_recre.template_financeiro_mensal</field>
<field name="report_file">plugin_recre.template_financeiro_mensal</field>
</record>
<!-- O TEMPLATE VISUAL -->
<template id="template_financeiro_mensal">
<t t-call="web.html_container">
<t t-call="web.external_layout"> <!-- Usa o cabeçalho oficial da empresa -->
<div class="page">
<div class="text-center mt-4 mb-4">
<h2>Relatório de Fluxo de Caixa</h2>
<p>Período: <span t-esc="data_inicio"/> até <span t-esc="data_fim"/></p>
</div>
<!-- BLOCO 1: ENTRADAS (RECEITAS) -->
<div class="mt-4">
<h3 style="color: #28a745; border-bottom: 2px solid #28a745;">
🟢 Entradas (Receitas)
</h3>
<table class="table table-sm table-striped">
<thead>
<tr>
<th>Data</th>
<th>Descrição / Aluno</th>
<th>Responsável</th>
<th>Status</th>
<th class="text-end">Valor (R$)</th>
</tr>
</thead>
<tbody>
<tr t-foreach="entradas" t-as="line">
<td><span t-field="line.data_vencimento"/></td>
<td><span t-field="line.name"/></td>
<td><span t-field="line.partner_id.name"/></td>
<td>
<span t-if="line.status == 'pago'" class="badge rounded-pill text-bg-success">Pago</span>
<span t-if="line.status == 'pendente'" class="badge rounded-pill text-bg-warning">Pendente</span>
</td>
<td class="text-end"><span t-field="line.valor"/></td>
</tr>
</tbody>
<tfoot>
<tr class="fw-bold">
<td colspan="4" class="text-end">TOTAL ENTRADAS:</td>
<td class="text-end" style="color: #28a745;">
R$ <span t-esc="'{:.2f}'.format(total_entradas).replace('.', ',')"/>
</td>
</tr>
</tfoot>
</table>
</div>
<!-- BLOCO 2: SAÍDAS (DESPESAS) -->
<div class="mt-5">
<h3 style="color: #dc3545; border-bottom: 2px solid #dc3545;">
🔴 Saídas (Despesas)
</h3>
<table class="table table-sm table-striped">
<thead>
<tr>
<th>Data</th>
<th>Descrição / Motivo</th>
<th>Fornecedor</th>
<th>Status</th>
<th class="text-end">Valor (R$)</th>
</tr>
</thead>
<tbody>
<tr t-foreach="saidas" t-as="line">
<td><span t-field="line.data_vencimento"/></td>
<td><span t-field="line.name"/></td>
<td><span t-field="line.partner_id.name"/></td>
<td>
<span t-if="line.status == 'pago'" class="badge rounded-pill text-bg-success">Pago</span>
<span t-if="line.status == 'pendente'" class="badge rounded-pill text-bg-warning">Pendente</span>
</td>
<td class="text-end"><span t-field="line.valor"/></td>
</tr>
</tbody>
<tfoot>
<tr class="fw-bold">
<td colspan="4" class="text-end">TOTAL SAÍDAS:</td>
<td class="text-end" style="color: #dc3545;">
R$ <span t-esc="'{:.2f}'.format(total_saidas).replace('.', ',')"/>
</td>
</tr>
</tfoot>
</table>
</div>
<!-- BLOCO 3: RESUMO FINAL -->
<div class="row mt-5">
<div class="col-6 offset-6">
<table class="table table-bordered">
<tr style="background-color: #f8f9fa;">
<td><strong>Total Entradas (+)</strong></td>
<td class="text-end text-success"><span t-esc="'{:.2f}'.format(total_entradas)"/></td>
</tr>
<tr style="background-color: #f8f9fa;">
<td><strong>Total Saídas (-)</strong></td>
<td class="text-end text-danger"><span t-esc="'{:.2f}'.format(total_saidas)"/></td>
</tr>
<tr style="background-color: #333; color: white; font-size: 1.2em;">
<td><strong>SALDO DO PERÍODO (=)</strong></td>
<td class="text-end">
R$ <span t-esc="'{:.2f}'.format(saldo_final).replace('.', ',')"/>
</td>
</tr>
</table>
</div>
</div>
<div class="text-muted text-center mt-5" style="font-size: 0.8em;">
Relatório gerado automaticamente pelo Sistema de Gestão de Recreação.
</div>
</div>
</t>
</t>
</template>
</odoo>
==================================================================
ARQUIVO: ./static/manifest.json
==================================================================
{
"name": "CRM Dente de leão",
"short_name": "Recreação",
"start_url": "/web",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#714B67",
"scope": "/",
"icons": [
{
"src": "/plugin_recre/static/img/icon_192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/plugin_recre/static/img/icon_512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
==================================================================
ARQUIVO: ./wizard/financeiro_wizard.py
==================================================================
from odoo import models, fields, api
class FinanceiroRelatorioWizard(models.TransientModel):
_name = 'recreacao.financeiro.wizard'
_description = 'Wizard de Relatório Financeiro'
data_inicio = fields.Date(string='Data Inicial', required=True, default=lambda self: fields.Date.today().replace(day=1))
data_fim = fields.Date(string='Data Final', required=True, default=fields.Date.today())
def action_gerar_pdf(self):
# Passa as datas para o relatório
data = {
'data_inicio': self.data_inicio,
'data_fim': self.data_fim,
}
return self.env.ref('plugin_recre.action_report_financeiro_mensal').report_action(self, data=data)
==================================================================
ARQUIVO: ./wizard/__init__.py
==================================================================
from . import financeiro_wizard
==================================================================
ARQUIVO: ./security/ir.model.access.csv
==================================================================
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_recreacao_plano,recreacao.plano,model_recreacao_plano,base.group_user,1,1,1,1
access_recreacao_diario,recreacao.diario,model_recreacao_diario,base.group_user,1,1,1,1
access_recreacao_crianca,recreacao.crianca,model_recreacao_crianca,base.group_user,1,1,1,1
access_recreacao_financeiro,recreacao.financeiro,model_recreacao_financeiro,base.group_user,1,1,1,1
access_recreacao_financeiro_wizard,recreacao.financeiro.wizard,model_recreacao_financeiro_wizard,base.group_user,1,1,1,1
==================================================================
ARQUIVO: ./views/financeiro_wizard_view.xml
==================================================================
<odoo>
<record id="view_recreacao_financeiro_wizard_form" model="ir.ui.view">
<field name="name">recreacao.financeiro.wizard.form</field>
<field name="model">recreacao.financeiro.wizard</field>
<field name="arch" type="xml">
<form string="Relatório Financeiro Mensal">
<group>
<group>
<field name="data_inicio"/>
</group>
<group>
<field name="data_fim"/>
</group>
</group>
<footer>
<button name="action_gerar_pdf" string="Imprimir PDF" type="object" class="btn-primary"/>
<button string="Cancelar" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_recreacao_financeiro_wizard" model="ir.actions.act_window">
<field name="name">Relatório Mensal</field>
<field name="res_model">recreacao.financeiro.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<!-- Adiciona o botão no menu Financeiro -->
<menuitem id="menu_recreacao_relatorio"
name="Relatórios"
parent="menu_recreacao_root"
sequence="20"/>
<menuitem id="menu_recreacao_relatorio_mensal"
name="Fluxo de Caixa (PDF)"
parent="menu_recreacao_relatorio"
action="action_recreacao_financeiro_wizard"/>
</odoo>
==================================================================
ARQUIVO: ./views/web_layout.xml
==================================================================
<odoo>
<template id="custom_favicon_backend" inherit_id="web.layout" name="Custom Favicon &amp; Manifest">
<xpath expr="//head" position="inside">
<!-- Ícones de navegador -->
<link rel="shortcut icon" href="/plugin_recre/static/img/favicon.ico" type="image/x-icon"/>
<link rel="icon" href="/plugin_recre/static/img/favicon.ico" type="image/x-icon"/>
<!-- O SEGREDO DO ANDROID: Link para o nosso JSON -->
<link rel="manifest" href="/plugin_recre/static/manifest.json"/>
<!-- Meta tag para pintar a barra do navegador do celular -->
<meta name="theme-color" content="#714B67"/>
</xpath>
</template>
</odoo>
==================================================================
ARQUIVO: ./views/crianca_view.xml
==================================================================
<odoo>
<!-- KANBAN -->
<record id="view_recreacao_crianca_kanban" model="ir.ui.view">
<field name="name">recreacao.crianca.kanban</field>
<field name="model">recreacao.crianca</field>
<field name="arch" type="xml">
<kanban class="o_kanban_mobile">
<field name="id"/>
<field name="name"/>
<field name="foto"/>
<field name="status_dia"/>
<field name="tem_alergia"/>
<field name="plano_id"/>
<field name="idade"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click o_kanban_record_has_image_fill o_res_partner_kanban">
<!-- Foto -->
<t t-if="record.foto.raw_value">
<div class="o_kanban_image">
<img t-att-src="kanban_image('recreacao.crianca', 'foto', record.id.raw_value)" alt="Foto"/>
</div>
</t>
<t t-else="">
<div class="o_kanban_image">
<img alt="Sem Foto" src="/base/static/img/avatar_grey.png"/>
</div>
</t>
<div class="oe_kanban_details">
<div class="o_kanban_record_top">
<div class="o_kanban_record_headings">
<strong class="o_kanban_record_title"><field name="name"/></strong>
</div>
<div class="o_dropdown_kanban dropdown">
<a class="dropdown-toggle o-no-caret btn" role="button" data-bs-toggle="dropdown" href="#" aria-label="Dropdown">
<span class="fa fa-ellipsis-v"/>
</a>
<div class="dropdown-menu" role="menu">
<a role="menuitem" type="edit" class="dropdown-item">Editar</a>
<a role="menuitem" type="object" name="action_abrir_historico" class="dropdown-item">Ver Diário</a>
<!-- CORRIGIDO AQUI: Nome da nova função -->
<a role="menuitem" type="object" name="action_gerar_cobranca" class="dropdown-item">Gerar Cobrança</a>
<a role="menuitem" type="delete" class="dropdown-item text-danger">Excluir</a>
</div>
</div>
</div>
<ul>
<li><span class="text-muted"><field name="idade"/></span></li>
<li><span class="badge text-bg-light"><field name="plano_id"/></span></li>
<li t-if="record.tem_alergia.raw_value">
<span class="badge text-bg-danger">⚠️ ALÉRGICO</span>
</li>
</ul>
<div class="o_kanban_record_bottom" style="margin-top: 8px;">
<div class="oe_kanban_bottom_left">
<button name="action_abrir_whatsapp" type="object" class="btn btn-sm btn-outline-success" style="border-radius: 50%; width: 32px; height: 32px; padding: 0;">
<i class="fa fa-whatsapp" style="font-size: 18px;"/>
</button>
</div>
<div class="oe_kanban_bottom_right">
<t t-if="record.status_dia.raw_value == 'ausente'">
<button name="action_marcar_entrada" type="object" class="btn btn-primary btn-sm">
<i class="fa fa-sign-in"/> Entrada
</button>
</t>
<t t-if="record.status_dia.raw_value == 'presente'">
<button name="action_marcar_saida" type="object" class="btn btn-danger btn-sm">
<i class="fa fa-sign-out"/> Saída
</button>
</t>
<t t-if="record.status_dia.raw_value == 'finalizado'">
<span class="badge rounded-pill text-bg-secondary">
<i class="fa fa-home"/> JÁ SAIU
</span>
</t>
</div>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!-- LISTA GERAL -->
<record id="view_recreacao_crianca_tree" model="ir.ui.view">
<field name="name">recreacao.crianca.tree</field>
<field name="model">recreacao.crianca</field>
<field name="arch" type="xml">
<tree>
<field name="name"/>
<field name="idade"/>
<field name="responsavel_financeiro_id"/>
<field name="plano_id"/>
<field name="status_dia" widget="badge"
decoration-success="status_dia == 'presente'"
decoration-muted="status_dia == 'finalizado'"/>
</tree>
</field>
</record>
<!-- FORMULÁRIO -->
<record id="view_recreacao_crianca_form" model="ir.ui.view">
<field name="name">recreacao.crianca.form</field>
<field name="model">recreacao.crianca</field>
<field name="arch" type="xml">
<form>
<header>
<button name="action_marcar_entrada" string="Registrar Entrada" type="object" class="oe_highlight" invisible="status_dia != 'ausente'"/>
<button name="action_marcar_saida" string="Registrar Saída" type="object" class="btn-danger" invisible="status_dia != 'presente'"/>
<!-- CORRIGIDO AQUI: Botão novo -->
<button name="action_gerar_cobranca" string="Gerar Cobrança" type="object" class="btn-primary"/>
<field name="status_dia" widget="statusbar"/>
</header>
<sheet>
<field name="foto" widget="image" class="oe_avatar"/>
<div class="oe_title">
<h1><field name="name" placeholder="Nome da Criança"/></h1>
</div>
<group>
<group string="Filiação">
<field name="mae_id" context="{'default_is_company': False}"/>
<field name="pai_id" context="{'default_is_company': False}"/>
<field name="responsavel_financeiro_id"/>
</group>
<group string="Plano">
<field name="plano_id"/>
<field name="valor_plano"/>
<field name="data_nascimento"/>
<field name="idade"/>
</group>
</group>
<div class="alert alert-danger" role="alert" invisible="not tem_alergia and not toma_remedio">
<strong>ATENÇÃO:</strong> Criança com restrições médicas!
</div>
<notebook>
<page string="🏥 Saúde">
<group>
<group>
<field name="tem_alergia"/>
<field name="alergias" invisible="not tem_alergia"/>
</group>
<group>
<field name="toma_remedio"/>
<field name="medicamentos" invisible="not toma_remedio"/>
<field name="horario_medicacao" invisible="not toma_remedio"/>
</group>
</group>
<field name="observacoes_saude"/>
</page>
<page string="Histórico / Diário">
<field name="diario_ids">
<tree editable="bottom" default_order="data desc, create_date desc"
decoration-success="tipo=='entrada'"
decoration-danger="tipo=='saida'">
<field name="data"/>
<field name="hora_registro"/>
<field name="tipo"/>
<field name="descricao"/>
</tree>
</field>
</page>
<!-- NOVO: Aba Financeiro -->
<page string="💲 Financeiro">
<field name="financeiro_ids" readonly="1">
<tree decoration-success="status=='pago'" decoration-danger="status=='pendente'">
<field name="data_vencimento"/>
<field name="name"/>
<field name="valor"/>
<field name="status" widget="badge"/>
</tree>
</field>
</page>
</notebook>
</sheet>
<!-- CHATTER (Rodapé para mensagens) -->
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="message_ids"/>
</div>
</form>
</field>
</record>
<record id="action_recreacao_crianca" model="ir.actions.act_window">
<field name="name">Alunos</field>
<field name="res_model">recreacao.crianca</field>
<field name="view_mode">kanban,tree,form</field>
</record>
<menuitem id="menu_recreacao_crianca" name="Painel de Alunos" parent="menu_recreacao_root" action="action_recreacao_crianca" sequence="1"/>
</odoo>
==================================================================
ARQUIVO: ./views/plano_view.xml
==================================================================
<odoo>
<record id="view_recreacao_plano_tree" model="ir.ui.view">
<field name="name">recreacao.plano.tree</field>
<field name="model">recreacao.plano</field>
<field name="arch" type="xml">
<tree editable="bottom">
<field name="name"/>
<field name="tipo_cobranca"/> <!-- Campo Novo -->
<field name="valor"/>
<field name="horario_inicio" widget="float_time"/>
<field name="horario_fim" widget="float_time"/>
</tree>
</field>
</record>
<record id="action_recreacao_plano" model="ir.actions.act_window">
<field name="name">Planos e Preços</field>
<field name="res_model">recreacao.plano</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="menu_recreacao_root" name="Recreação" web_icon="contacts,static/description/icon.png"/>
<menuitem id="menu_recreacao_config" name="Configurações" parent="menu_recreacao_root" sequence="100"/>
<menuitem id="menu_recreacao_plano" name="Planos" parent="menu_recreacao_config" action="action_recreacao_plano"/>
</odoo>
==================================================================
ARQUIVO: ./views/res_partner_view.xml
==================================================================
<odoo>
<record id="view_partner_form_recreacao" model="ir.ui.view">
<field name="name">res.partner.form.recreacao</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<notebook position="inside">
<page string="Filhos na Recreação">
<field name="crianca_ids">
<tree>
<field name="name"/>
<field name="plano_id"/>
<field name="idade"/>
</tree>
</field>
</page>
</notebook>
</field>
</record>
</odoo>
==================================================================
ARQUIVO: ./views/diario_view.xml
==================================================================
<odoo>
<!-- LISTA GERAL DO DIÁRIO -->
<record id="view_recreacao_diario_tree" model="ir.ui.view">
<field name="name">recreacao.diario.tree</field>
<field name="model">recreacao.diario</field>
<field name="arch" type="xml">
<tree string="Diário de Classe"
decoration-success="tipo=='entrada'"
decoration-danger="tipo=='saida'"
decoration-warning="tipo=='ocorrencia'">
<field name="data"/>
<field name="hora_registro"/> <!-- Novo -->
<field name="crianca_id"/>
<field name="tipo" widget="badge"/>
<field name="descricao"/>
</tree>
</field>
</record>
<!-- FORMULÁRIO DO DIÁRIO -->
<record id="view_recreacao_diario_form" model="ir.ui.view">
<field name="name">recreacao.diario.form</field>
<field name="model">recreacao.diario</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<group>
<field name="data"/>
<field name="hora_registro"/>
<field name="crianca_id" options="{'no_create': True}"/>
</group>
<group>
<field name="tipo"/>
</group>
</group>
<field name="descricao" placeholder="Descreva o que aconteceu..."/>
</sheet>
</form>
</field>
</record>
<record id="action_recreacao_diario" model="ir.actions.act_window">
<field name="name">Diário Geral</field>
<field name="res_model">recreacao.diario</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="menu_recreacao_diario" name="Diário Geral" parent="menu_recreacao_root" action="action_recreacao_diario" sequence="10"/>
</odoo>
==================================================================
ARQUIVO: ./views/financeiro_view.xml
==================================================================
<odoo>
<record id="view_recreacao_financeiro_tree" model="ir.ui.view">
<field name="name">recreacao.financeiro.tree</field>
<field name="model">recreacao.financeiro</field>
<field name="arch" type="xml">
<!-- editable="bottom" permite criar e editar direto na linha, igual Excel -->
<tree editable="bottom"
decoration-success="tipo=='receita'"
decoration-danger="tipo=='despesa'"
decoration-muted="status=='cancelado'">
<field name="data_vencimento"/>
<field name="name" placeholder="Ex: Mensalidade João ou Conta de Luz"/>
<!-- AQUI: O campo Tipo agora é editável livremente na lista -->
<field name="tipo" widget="badge"
decoration-success="tipo=='receita'"
decoration-danger="tipo=='despesa'"/>
<field name="partner_id" widget="res_partner_many2one" optional="show"/>
<field name="crianca_id" optional="hide"/>
<!-- Soma total no rodapé da tabela -->
<field name="valor" sum="Total Movimentado"/>
<field name="status" widget="badge"
decoration-success="status=='pago'"
decoration-warning="status=='pendente'"/>
<!-- Botão rápido de pagar -->
<button name="action_pagar" string="Pagar/Receber" type="object"
icon="fa-check" class="btn-success btn-sm"
invisible="status != 'pendente'"/>
</tree>
</field>
</record>
<!-- 2. O GRÁFICO -->
<record id="view_recreacao_financeiro_graph" model="ir.ui.view">
<field name="name">recreacao.financeiro.graph</field>
<field name="model">recreacao.financeiro</field>
<field name="arch" type="xml">
<graph string="Fluxo de Caixa" type="bar" stacked="True">
<field name="data_vencimento" type="row" interval="month"/>
<field name="tipo" type="col"/>
<field name="valor" type="measure"/>
</graph>
</field>
</record>
<!-- 3. FILTROS (SEARCH) -->
<record id="view_recreacao_financeiro_search" model="ir.ui.view">
<field name="name">recreacao.financeiro.search</field>
<field name="model">recreacao.financeiro</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<field name="partner_id"/>
<filter string="Entradas (Receitas)" name="filter_receitas" domain="[('tipo','=','receita')]"/>
<filter string="Saídas (Despesas)" name="filter_despesas" domain="[('tipo','=','despesa')]"/>
<separator/>
<filter string="Pendentes" name="filter_pendentes" domain="[('status','=','pendente')]"/>
<filter string="Pagos" name="filter_pagos" domain="[('status','=','pago')]"/>
<group expand="1" string="Agrupar Por">
<filter string="Mês" name="group_month" context="{'group_by':'data_vencimento:month'}"/>
<filter string="Tipo" name="group_tipo" context="{'group_by':'tipo'}"/>
</group>
</search>
</field>
</record>
<!-- 4. AÇÃO DA LISTA -->
<record id="action_recreacao_financeiro" model="ir.actions.act_window">
<field name="name">Movimentações</field>
<field name="res_model">recreacao.financeiro</field>
<field name="view_mode">tree,graph,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Nenhuma movimentação encontrada!
</p>
<p>
Crie uma nova despesa manualmente ou gere cobranças através dos alunos.
</p>
</field>
</record>
<!-- ======================================================== -->
<!-- ORGANIZAÇÃO DOS MENUS (AQUI ESTAVA A CONFUSÃO) -->
<!-- ======================================================== -->
<!-- Menu Pai: Financeiro -->
<menuitem id="menu_recreacao_financeiro_root"
name="Financeiro"
parent="menu_recreacao_root"
sequence="10"/>
<!-- Submenu 1: Movimentações (A Lista/Histórico) -->
<menuitem id="menu_recreacao_financeiro_movimentos"
name="Movimentações (Caixa)"
parent="menu_recreacao_financeiro_root"
action="action_recreacao_financeiro"
sequence="1"/>
<!-- Submenu 2: Relatórios (O PDF) -->
<menuitem id="menu_recreacao_relatorio_mensal"
name="Imprimir Fluxo (PDF)"
parent="menu_recreacao_financeiro_root"
action="action_recreacao_financeiro_wizard"
sequence="2"/>
</odoo>