Treinamento TΓ©cnico Detalhado - From Frontend to Database
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 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) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
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
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');
}
});
// 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');
}
}
});
// 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);
});
}
});