πŸš€ GUIA COMPLETO: COMO O FRAPPE FUNCIONA

Treinamento TΓ©cnico Detalhado - From Frontend to Database

πŸ“‹ ÍNDICE


πŸ—οΈ 1. VISΓƒO GERAL DA ARQUITETURA {#arquitetura}

Stack TecnolΓ³gico:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     FRAPPE STACK                            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Frontend   β”‚ JavaScript (ES6+) + jQuery + Bootstrap         β”‚
β”‚ Backend    β”‚ Python 3.x + Flask/Werkzeug                    β”‚
β”‚ Database   β”‚ MariaDB/MySQL + SQLAlchemy-like ORM            β”‚
β”‚ Cache      β”‚ Redis (sessΓ΅es, cache, real-time)              β”‚
β”‚ Queue      β”‚ Redis + RQ (background jobs)                   β”‚
β”‚ WebServer  β”‚ Nginx (proxy) + Gunicorn (WSGI)                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Arquitetura MVC Estendida:

Frontend (View)           Backend (Controller)         Database (Model)
     β”‚                           β”‚                           β”‚
     β”œβ”€ form.js                  β”œβ”€ document.py              β”œβ”€ tabDocType
     β”œβ”€ frappe.ui.form.on()      β”œβ”€ hooks.py                 β”œβ”€ tabSingles
     β”œβ”€ triggers/events          β”œβ”€ @frappe.whitelist()      β”œβ”€ tab[CustomDocType]
     └─ frappe.call()            └─ DocType classes          └─ Child Tables


🎭 2. FRONTEND: FORM SCRIPTS E TRIGGERS {#frontend}

2.1 Como frappe.ui.form.on() Funciona:

// Estrutura bΓ‘sica de um form script
frappe.ui.form.on('DocType Name', {
    // Eventos do documento principal
    refresh: function(frm) {
        // Chamado sempre que o form Γ© carregado/refrescado
        console.log("Form refreshed:", frm.doc);
    },

    validate: function(frm) {
        // Chamado antes de salvar (client-side validation)
        if (!frm.doc.required_field) {
            frappe.throw("Campo obrigatΓ³rio nΓ£o preenchido");
        }
    },

    fieldname: function(frm) {
        // Chamado quando 'fieldname' muda
        frm.set_value('dependent_field', frm.doc.fieldname * 2);
    }
});

// Eventos para tabelas filhas
frappe.ui.form.on('Child DocType', {
    fieldname: function(frm, cdt, cdn) {
        // cdt = Child DocType
        // cdn = Child Document Name (unique ID)
        let row = locals[cdt][cdn];
        row.calculated_field = row.qty * row.rate;
        frm.refresh_field('child_table');
    }
});

2.2 Ordem de ExecuΓ§Γ£o dos Triggers:

// SequΓͺncia cronolΓ³gica de eventos:
/*
1. setup()           - Primeira vez que o form Γ© criado
2. onload()          - Toda vez que um documento Γ© carregado
3. refresh()         - ApΓ³s onload, sempre que form atualiza
4. [field_change]()  - Quando campo especΓ­fico muda
5. validate()        - Antes de salvar (client-side)
6. before_save()     - Antes de enviar para servidor
7. after_save()      - ApΓ³s salvar com sucesso
*/

frappe.ui.form.on('Nota Fiscal - Servico', {
    setup: function(frm) {
        // ConfiguraΓ§Γ΅es iniciais do form
        frm.set_query('customer', function() {
            return { filters: { disabled: 0 } };
        });
    },

    onload: function(frm) {
        // Executado quando documento Γ© carregado
        if (frm.doc.__islocal) {
            frm.set_value('date', frappe.datetime.now_date());
        }
    },

    refresh: function(frm) {
        // Sempre executado apΓ³s onload
        frm.toggle_display('section_break', !frm.doc.is_simple);

        // BotΓ΅es customizados
        if (frm.doc.docstatus === 1) {
            frm.add_custom_button('Custom Action', function() {
                // AΓ§Γ£o customizada
            });
        }
    },

    valor_servicos: function(frm) {
        // Trigger especΓ­fico do campo
        if (frm.doc.valor_servicos) {
            frm.set_value('base_calculo', frm.doc.valor_servicos);

            // Chamar funΓ§Γ£o Python
            frappe.call({
                method: 'nxlite.scripts.calculo_imposto.calculo_imposto_nfse',
                args: { doc: frm.doc },
                callback: function(r) {
                    if (r.message) {
                        frm.set_value(r.message);
                    }
                }
            });
        }
    },

    validate: function(frm) {
        // ValidaΓ§Γ£o client-side
        if (frm.doc.valor_servicos <= 0) {
            frappe.throw('Valor dos serviΓ§os deve ser maior que zero');
        }
    }
});

2.3 Gerenciamento de Estado no Frontend:

// Como o Frappe gerencia estado
/*
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     FRONTEND STATE                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ cur_frm.doc      β”‚ Documento atual (objeto principal)       β”‚
β”‚ locals           β”‚ Cache de todos os documentos carregados  β”‚
β”‚ frappe.model     β”‚ FunΓ§Γ΅es para manipular documentos        β”‚
β”‚ frm.dirty()      β”‚ Verifica se hΓ‘ mudanΓ§as nΓ£o salvas       β”‚
β”‚ frm.is_new()     β”‚ Documento Γ© novo (__islocal = true)      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
*

// Exemplo de manipulaΓ§Γ£o de estado
frappe.ui.form.on('Nota Fiscal - Servico', {
    refresh: function(frm) {
        console.log("Documento:", frm.doc);
        console.log("Γ‰ novo?", frm.is_new());
        console.log("EstΓ‘ sujo?", frm.is_dirty());
        console.log("Status:", frm.doc.docstatus);

        // Acessar tabela filha
        frm.doc.tab_servicos.forEach(function(row) {
            console.log("Item:", row.item, "Valor:", row.valor_total);
        });
    }
});


πŸ”„ 3. COMUNICAÇÃO FRONTEND ↔ BACKEND {#comunicacao}