<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://mediawiki.comfac.net/index.php?action=history&amp;feed=atom&amp;title=Phloc_Skill_Guide_Comfac_ERPN_Loc</id>
	<title>Phloc Skill Guide Comfac ERPN Loc - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://mediawiki.comfac.net/index.php?action=history&amp;feed=atom&amp;title=Phloc_Skill_Guide_Comfac_ERPN_Loc"/>
	<link rel="alternate" type="text/html" href="https://mediawiki.comfac.net/index.php?title=Phloc_Skill_Guide_Comfac_ERPN_Loc&amp;action=history"/>
	<updated>2026-06-05T11:00:28Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.45.1</generator>
	<entry>
		<id>https://mediawiki.comfac.net/index.php?title=Phloc_Skill_Guide_Comfac_ERPN_Loc&amp;diff=181&amp;oldid=prev</id>
		<title>Justinaquino: &quot;Add Comfac ERPNext Localization documentation from PHlocalization project&quot;</title>
		<link rel="alternate" type="text/html" href="https://mediawiki.comfac.net/index.php?title=Phloc_Skill_Guide_Comfac_ERPN_Loc&amp;diff=181&amp;oldid=prev"/>
		<updated>2026-03-09T10:49:56Z</updated>

		<summary type="html">&lt;p&gt;&amp;quot;Add Comfac ERPNext Localization documentation from PHlocalization project&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;= Phloc Skill Guide — Building &amp;amp; Deploying Frappe/ERPNext Customizations =&lt;br /&gt;
&lt;br /&gt;
A hands-on reference for creating custom reports, client scripts, Charts of Accounts templates, and other ERPNext customizations — then packaging them into the phlocalization Frappe app and deploying to a Frappe Cloud $25/month instance.&lt;br /&gt;
&lt;br /&gt;
== Table of Contents ==&lt;br /&gt;
&lt;br /&gt;
# [[#1. Local Development Environment Setup|Local Development Environment Setup]]&lt;br /&gt;
# [[#2. Adding Custom Reports|Adding Custom Reports]]&lt;br /&gt;
# [[#3. Adding Client Scripts|Adding Client Scripts]]&lt;br /&gt;
# [[#4. Adding Custom Fields to Existing DocTypes|Adding Custom Fields to Existing DocTypes]]&lt;br /&gt;
# [[#5. Adding Chart of Accounts Templates|Adding Chart of Accounts Templates]]&lt;br /&gt;
# [[#6. Adding Custom DocTypes|Adding Custom DocTypes]]&lt;br /&gt;
# [[#7. Adding Print Formats|Adding Print Formats]]&lt;br /&gt;
# [[#8. Adding Property Setters|Adding Property Setters]]&lt;br /&gt;
# [[#9. Adding Server-Side Hooks|Adding Server-Side Hooks]]&lt;br /&gt;
# [[#10. Testing|Testing]]&lt;br /&gt;
# [[#11. Frappe Cloud Deployment ($25/instance)|Frappe Cloud Deployment]]&lt;br /&gt;
# [[#12. Bench Command Reference|Bench Command Reference]]&lt;br /&gt;
# [[#13. Survival Checklist|Survival Checklist]]&lt;br /&gt;
&lt;br /&gt;
== 1. Local Development Environment Setup ==&lt;br /&gt;
&lt;br /&gt;
=== Prerequisites ===&lt;br /&gt;
&lt;br /&gt;
* Python &amp;gt;= 3.10&lt;br /&gt;
* Node.js 18+&lt;br /&gt;
* MariaDB 10.6+ or PostgreSQL 14+&lt;br /&gt;
* Redis&lt;br /&gt;
* wkhtmltopdf&lt;br /&gt;
&lt;br /&gt;
=== Initialize a Bench ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Install bench CLI&lt;br /&gt;
pip install frappe-bench&lt;br /&gt;
&lt;br /&gt;
# Create a new bench (pulls Frappe v15)&lt;br /&gt;
bench init --frappe-branch version-15 frappe-bench&lt;br /&gt;
cd frappe-bench&lt;br /&gt;
&lt;br /&gt;
# Create a site&lt;br /&gt;
bench new-site phloc.localhost --mariadb-root-password &amp;amp;lt;password&amp;amp;gt; --admin-password admin&lt;br /&gt;
&lt;br /&gt;
# Install ERPNext&lt;br /&gt;
bench get-app --branch version-15 erpnext&lt;br /&gt;
bench --site phloc.localhost install-app erpnext&lt;br /&gt;
&lt;br /&gt;
# Install phlocalization&lt;br /&gt;
bench get-app https://github.com/xunema/phlocalization&lt;br /&gt;
bench --site phloc.localhost install-app bureau_of_internal_revenue&lt;br /&gt;
&lt;br /&gt;
# Apply fixtures and migrations&lt;br /&gt;
bench --site phloc.localhost migrate&lt;br /&gt;
&lt;br /&gt;
# Start dev server&lt;br /&gt;
bench start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Access at &amp;lt;code&amp;gt;http://phloc.localhost:8000&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable Developer Mode ===&lt;br /&gt;
&lt;br /&gt;
(required for creating DocTypes/reports via UI)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
bench --site phloc.localhost set-config developer_mode 1&lt;br /&gt;
bench --site phloc.localhost clear-cache&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 2. Adding Custom Reports ==&lt;br /&gt;
&lt;br /&gt;
Custom reports are the primary deliverable in phlocalization. The app uses &amp;#039;&amp;#039;&amp;#039;Script Reports&amp;#039;&amp;#039;&amp;#039; that extend ERPNext&amp;#039;s financial statements.&lt;br /&gt;
&lt;br /&gt;
=== File Structure (one directory per report) ===&lt;br /&gt;
&lt;br /&gt;
All files must share the same snake_case name:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
bureau_of_internal_revenue/&lt;br /&gt;
  bureau_of_internal_revenue/&lt;br /&gt;
    report/&lt;br /&gt;
      your_report_name/&lt;br /&gt;
        __init__.py                    # Empty, required&lt;br /&gt;
        your_report_name.json          # Report metadata (DocType record)&lt;br /&gt;
        your_report_name.py            # Server-side logic&lt;br /&gt;
        your_report_name.js            # Client-side filters &amp;amp; formatting&lt;br /&gt;
        your_report_name.html          # HTML template (optional)&lt;br /&gt;
        test_your_report_name.py       # Tests&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Step-by-Step: Create a New Report ===&lt;br /&gt;
&lt;br /&gt;
==== A. Create the report JSON definition ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;quot;add_total_row&amp;quot;: 0,&lt;br /&gt;
  &amp;quot;columns&amp;quot;: [],&lt;br /&gt;
  &amp;quot;creation&amp;quot;: &amp;quot;2026-03-09 00:00:00.000000&amp;quot;,&lt;br /&gt;
  &amp;quot;disabled&amp;quot;: 0,&lt;br /&gt;
  &amp;quot;docstatus&amp;quot;: 0,&lt;br /&gt;
  &amp;quot;doctype&amp;quot;: &amp;quot;Report&amp;quot;,&lt;br /&gt;
  &amp;quot;filters&amp;quot;: [],&lt;br /&gt;
  &amp;quot;idx&amp;quot;: 0,&lt;br /&gt;
  &amp;quot;is_standard&amp;quot;: &amp;quot;Yes&amp;quot;,&lt;br /&gt;
  &amp;quot;letterhead&amp;quot;: null,&lt;br /&gt;
  &amp;quot;modified&amp;quot;: &amp;quot;2026-03-09 00:00:00.000000&amp;quot;,&lt;br /&gt;
  &amp;quot;modified_by&amp;quot;: &amp;quot;Administrator&amp;quot;,&lt;br /&gt;
  &amp;quot;module&amp;quot;: &amp;quot;Bureau of Internal Revenue&amp;quot;,&lt;br /&gt;
  &amp;quot;name&amp;quot;: &amp;quot;Your Report Name&amp;quot;,&lt;br /&gt;
  &amp;quot;owner&amp;quot;: &amp;quot;Administrator&amp;quot;,&lt;br /&gt;
  &amp;quot;prepared_report&amp;quot;: 0,&lt;br /&gt;
  &amp;quot;ref_doctype&amp;quot;: &amp;quot;GL Entry&amp;quot;,&lt;br /&gt;
  &amp;quot;report_name&amp;quot;: &amp;quot;Your Report Name&amp;quot;,&lt;br /&gt;
  &amp;quot;report_type&amp;quot;: &amp;quot;Script Report&amp;quot;,&lt;br /&gt;
  &amp;quot;roles&amp;quot;: [&lt;br /&gt;
    {&amp;quot;role&amp;quot;: &amp;quot;Accounts User&amp;quot;},&lt;br /&gt;
    {&amp;quot;role&amp;quot;: &amp;quot;Accounts Manager&amp;quot;},&lt;br /&gt;
    {&amp;quot;role&amp;quot;: &amp;quot;Auditor&amp;quot;}&lt;br /&gt;
  ],&lt;br /&gt;
  &amp;quot;timeout&amp;quot;: 0&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Critical fields:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* &amp;lt;code&amp;gt;report_type&amp;lt;/code&amp;gt;: &amp;lt;code&amp;gt;&amp;quot;Script Report&amp;quot;&amp;lt;/code&amp;gt; — runs your Python&lt;br /&gt;
* &amp;lt;code&amp;gt;ref_doctype&amp;lt;/code&amp;gt;: &amp;lt;code&amp;gt;&amp;quot;GL Entry&amp;quot;&amp;lt;/code&amp;gt; for financial reports (determines base permissions)&lt;br /&gt;
* &amp;lt;code&amp;gt;module&amp;lt;/code&amp;gt;: Must match your module name exactly&lt;br /&gt;
* &amp;lt;code&amp;gt;is_standard&amp;lt;/code&amp;gt;: &amp;lt;code&amp;gt;&amp;quot;Yes&amp;quot;&amp;lt;/code&amp;gt; — marks it as part of the app (not user-created)&lt;br /&gt;
&lt;br /&gt;
==== B. Create the Python report (&amp;lt;code&amp;gt;your_report_name.py&amp;lt;/code&amp;gt;) ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import frappe&lt;br /&gt;
from frappe import _&lt;br /&gt;
from frappe.utils import flt&lt;br /&gt;
&lt;br /&gt;
from erpnext.accounts.report.financial_statements import (&lt;br /&gt;
    get_columns,&lt;br /&gt;
    get_data,&lt;br /&gt;
    get_period_list,&lt;br /&gt;
)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def execute(filters=None):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Main entry point. Frappe calls this automatically.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    period_list = get_period_list(&lt;br /&gt;
        filters.from_fiscal_year,&lt;br /&gt;
        filters.to_fiscal_year,&lt;br /&gt;
        filters.period_start_date,&lt;br /&gt;
        filters.period_end_date,&lt;br /&gt;
        filters.filter_based_on,&lt;br /&gt;
        filters.periodicity,&lt;br /&gt;
        company=filters.company,&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    # Fetch account data from ERPNext&lt;br /&gt;
    asset = get_data(&lt;br /&gt;
        filters.company,&lt;br /&gt;
        &amp;quot;Asset&amp;quot;,&lt;br /&gt;
        &amp;quot;Debit&amp;quot;,&lt;br /&gt;
        period_list,&lt;br /&gt;
        only_current_fiscal_year=False,&lt;br /&gt;
        filters=filters,&lt;br /&gt;
        accumulated_values=filters.accumulated_values,&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    columns = get_columns(&lt;br /&gt;
        filters.periodicity, period_list, filters.accumulated_values, filters.company&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    data = []&lt;br /&gt;
    data.extend(asset or [])&lt;br /&gt;
&lt;br /&gt;
    # Return tuple: (columns, data, message, chart, report_summary)&lt;br /&gt;
    return columns, data, None, None, None&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Return value is always a tuple:&amp;#039;&amp;#039;&amp;#039; &amp;lt;code&amp;gt;(columns, data, message, chart, report_summary)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== C. Create the JavaScript file (&amp;lt;code&amp;gt;your_report_name.js&amp;lt;/code&amp;gt;) ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;frappe.query_reports[&amp;quot;Your Report Name&amp;quot;] = $.extend(&lt;br /&gt;
    {},&lt;br /&gt;
    erpnext.financial_statements,&lt;br /&gt;
    {&lt;br /&gt;
        // Override the formatter to customize display&lt;br /&gt;
        formatter(value, row, column, data, df) {&lt;br /&gt;
            const formatted = erpnext.financial_statements.formatter&lt;br /&gt;
                ? erpnext.financial_statements.formatter(value, row, column, data, df)&lt;br /&gt;
                : value;&lt;br /&gt;
&lt;br /&gt;
            // Hide numeric values for top-level group rows&lt;br /&gt;
            if (data &amp;amp;&amp;amp; data.indent === 0 &amp;amp;&amp;amp; typeof value === &amp;quot;number&amp;quot;) {&lt;br /&gt;
                return &amp;quot;&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
            return formatted;&lt;br /&gt;
        },&lt;br /&gt;
    }&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
// Add ERPNext dimension filters (Cost Center, Project, etc.)&lt;br /&gt;
erpnext.utils.add_dimensions(&amp;quot;Your Report Name&amp;quot;, 10);&lt;br /&gt;
&lt;br /&gt;
// Add custom filters&lt;br /&gt;
frappe.query_reports[&amp;quot;Your Report Name&amp;quot;][&amp;quot;filters&amp;quot;].push({&lt;br /&gt;
    fieldname: &amp;quot;accumulated_values&amp;quot;,&lt;br /&gt;
    label: __(&amp;quot;Accumulated Values&amp;quot;),&lt;br /&gt;
    fieldtype: &amp;quot;Check&amp;quot;,&lt;br /&gt;
    default: 1,&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
frappe.query_reports[&amp;quot;Your Report Name&amp;quot;][&amp;quot;filters&amp;quot;].push({&lt;br /&gt;
    fieldname: &amp;quot;level&amp;quot;,&lt;br /&gt;
    label: __(&amp;quot;Show Levels Upto&amp;quot;),&lt;br /&gt;
    fieldtype: &amp;quot;Select&amp;quot;,&lt;br /&gt;
    options: [&amp;quot;1&amp;quot;, &amp;quot;2&amp;quot;, &amp;quot;3&amp;quot;, &amp;quot;4&amp;quot;, &amp;quot;All&amp;quot;],&lt;br /&gt;
    default: &amp;quot;All&amp;quot;,&lt;br /&gt;
});&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Patterns:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* &amp;lt;code&amp;gt;$.extend({}, erpnext.financial_statements, {...})&amp;lt;/code&amp;gt; — inherit standard financial statement behavior&lt;br /&gt;
* Push additional filters after the extend&lt;br /&gt;
* Filter object: &amp;lt;code&amp;gt;{fieldname, label, fieldtype, default, options?, reqd?}&amp;lt;/code&amp;gt;&lt;br /&gt;
* To remove inherited filters: &amp;lt;code&amp;gt;.filter(f =&amp;amp;gt; ![&amp;quot;cost_center&amp;quot;].includes(f.fieldname))&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== D. Create the HTML template (&amp;lt;code&amp;gt;your_report_name.html&amp;lt;/code&amp;gt;) ====&lt;br /&gt;
&lt;br /&gt;
For financial reports, reuse ERPNext&amp;#039;s template:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;{% include &amp;quot;accounts/report/financial_statements.html&amp;quot; %}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or write custom Jinja HTML for non-financial reports.&lt;br /&gt;
&lt;br /&gt;
==== E. Register the report ====&lt;br /&gt;
&lt;br /&gt;
No hooks.py change needed. Frappe auto-discovers reports by the JSON file in the module&amp;#039;s &amp;lt;code&amp;gt;report/&amp;lt;/code&amp;gt; directory. Just run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
bench --site phloc.localhost migrate&lt;br /&gt;
bench --site phloc.localhost clear-cache&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The report appears under: &amp;#039;&amp;#039;&amp;#039;Accounting &amp;amp;gt; Reports &amp;amp;gt; Your Report Name&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
== 3. Adding Client Scripts ==&lt;br /&gt;
&lt;br /&gt;
Client scripts inject JavaScript into existing DocType forms. This is how phlocalization adds the Schedule field toggle to the Account form.&lt;br /&gt;
&lt;br /&gt;
=== File Location ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
bureau_of_internal_revenue/&lt;br /&gt;
  public/&lt;br /&gt;
    js/&lt;br /&gt;
      your_doctype.js&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Register in hooks.py ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;doctype_js = {&lt;br /&gt;
    &amp;quot;Account&amp;quot;: &amp;quot;public/js/account.js&amp;quot;,&lt;br /&gt;
    &amp;quot;Sales Invoice&amp;quot;: &amp;quot;public/js/sales_invoice.js&amp;quot;,   # add more as needed&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Client Script Pattern ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;frappe.ui.form.on(&amp;quot;Sales Invoice&amp;quot;, {&lt;br /&gt;
    // Runs on form load/refresh&lt;br /&gt;
    refresh(frm) {&lt;br /&gt;
        frm.trigger(&amp;quot;setup_custom_fields&amp;quot;);&lt;br /&gt;
    },&lt;br /&gt;
&lt;br /&gt;
    // Runs when a specific field changes&lt;br /&gt;
    customer(frm) {&lt;br /&gt;
        if (frm.doc.customer) {&lt;br /&gt;
            frappe.call({&lt;br /&gt;
                method: &amp;quot;frappe.client.get_value&amp;quot;,&lt;br /&gt;
                args: {&lt;br /&gt;
                    doctype: &amp;quot;Customer&amp;quot;,&lt;br /&gt;
                    filters: { name: frm.doc.customer },&lt;br /&gt;
                    fieldname: &amp;quot;tax_id&amp;quot;&lt;br /&gt;
                },&lt;br /&gt;
                callback(r) {&lt;br /&gt;
                    if (r.message) {&lt;br /&gt;
                        frm.set_value(&amp;quot;custom_tax_id&amp;quot;, r.message.tax_id);&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    },&lt;br /&gt;
&lt;br /&gt;
    // Custom event (triggered by frm.trigger)&lt;br /&gt;
    setup_custom_fields(frm) {&lt;br /&gt;
        frm.toggle_display(&amp;quot;custom_tax_id&amp;quot;, !!frm.doc.customer);&lt;br /&gt;
    }&lt;br /&gt;
});&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Key frm methods:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* &amp;lt;code&amp;gt;frm.toggle_display(fieldname, show)&amp;lt;/code&amp;gt; — show/hide field&lt;br /&gt;
* &amp;lt;code&amp;gt;frm.set_value(fieldname, value)&amp;lt;/code&amp;gt; — set value&lt;br /&gt;
* &amp;lt;code&amp;gt;frm.trigger(event_name)&amp;lt;/code&amp;gt; — fire custom event&lt;br /&gt;
* &amp;lt;code&amp;gt;frm.doc.fieldname&amp;lt;/code&amp;gt; — read current document values&lt;br /&gt;
* &amp;lt;code&amp;gt;frm.add_custom_button(label, callback, group)&amp;lt;/code&amp;gt; — add toolbar button&lt;br /&gt;
* &amp;lt;code&amp;gt;frm.set_query(fieldname, filters)&amp;lt;/code&amp;gt; — set link field filters&lt;br /&gt;
&lt;br /&gt;
=== Apply changes ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
bench build --app bureau_of_internal_revenue&lt;br /&gt;
bench --site phloc.localhost clear-cache&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4. Adding Custom Fields to Existing DocTypes ==&lt;br /&gt;
&lt;br /&gt;
Two approaches, both valid. The phlocalization app currently uses &amp;#039;&amp;#039;&amp;#039;fixtures&amp;#039;&amp;#039;&amp;#039;; the recommended upgrade-safe pattern is &amp;#039;&amp;#039;&amp;#039;programmatic creation&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
=== Approach A: Fixtures (current phlocalization pattern) ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;1. Create the field via Frappe UI&amp;#039;&amp;#039;&amp;#039; (with developer mode on):&lt;br /&gt;
* Go to Customize Form &amp;amp;gt; select DocType &amp;amp;gt; add field &amp;amp;gt; save&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;2. Register in hooks.py:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;fixtures = [&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;doctype&amp;quot;: &amp;quot;Custom Field&amp;quot;,&lt;br /&gt;
        &amp;quot;filters&amp;quot;: [&lt;br /&gt;
            [&amp;quot;Custom Field&amp;quot;, &amp;quot;module&amp;quot;, &amp;quot;=&amp;quot;, &amp;quot;Bureau of Internal Revenue&amp;quot;]&lt;br /&gt;
        ]&lt;br /&gt;
    }&lt;br /&gt;
]&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;3. Export:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
bench --site phloc.localhost export-fixtures --app bureau_of_internal_revenue&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This writes &amp;lt;code&amp;gt;fixtures/custom_field.json&amp;lt;/code&amp;gt;. The JSON contains the full field definition including &amp;lt;code&amp;gt;dt&amp;lt;/code&amp;gt; (target DocType), &amp;lt;code&amp;gt;fieldname&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fieldtype&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;label&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;insert_after&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;options&amp;lt;/code&amp;gt;, etc.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;4. Fields are re-imported on every &amp;lt;code&amp;gt;bench migrate&amp;lt;/code&amp;gt;.&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
=== Approach B: Programmatic (recommended for production) ===&lt;br /&gt;
&lt;br /&gt;
This is the pattern used by India Compliance, HRMS, and other official Frappe apps. It survives bench updates more reliably.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;1. Add hooks:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;after_install = &amp;quot;bureau_of_internal_revenue.setup.after_install&amp;quot;&lt;br /&gt;
after_migrate = &amp;quot;bureau_of_internal_revenue.setup.after_migrate&amp;quot;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;2. Create &amp;lt;code&amp;gt;bureau_of_internal_revenue/setup.py&amp;lt;/code&amp;gt;:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;from frappe.custom.doctype.custom_field.custom_field import create_custom_fields&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def after_install():&lt;br /&gt;
    create_custom_fields(get_custom_fields())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def after_migrate():&lt;br /&gt;
    create_custom_fields(get_custom_fields())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def get_custom_fields():&lt;br /&gt;
    return {&lt;br /&gt;
        &amp;quot;Account&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;fieldname&amp;quot;: &amp;quot;schedule&amp;quot;,&lt;br /&gt;
                &amp;quot;fieldtype&amp;quot;: &amp;quot;Select&amp;quot;,&lt;br /&gt;
                &amp;quot;label&amp;quot;: &amp;quot;Schedule&amp;quot;,&lt;br /&gt;
                &amp;quot;insert_after&amp;quot;: &amp;quot;parent_account&amp;quot;,&lt;br /&gt;
                &amp;quot;options&amp;quot;: &amp;quot;\n&amp;quot;.join([&amp;quot;&amp;quot;] + [f&amp;quot;SCHED {i}&amp;quot; for i in range(1, 24)]),&lt;br /&gt;
                &amp;quot;translatable&amp;quot;: 1,&lt;br /&gt;
            }&lt;br /&gt;
        ],&lt;br /&gt;
        &amp;quot;Sales Invoice&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;fieldname&amp;quot;: &amp;quot;bir_tax_category&amp;quot;,&lt;br /&gt;
                &amp;quot;fieldtype&amp;quot;: &amp;quot;Link&amp;quot;,&lt;br /&gt;
                &amp;quot;label&amp;quot;: &amp;quot;BIR Tax Category&amp;quot;,&lt;br /&gt;
                &amp;quot;options&amp;quot;: &amp;quot;Tax Category&amp;quot;,&lt;br /&gt;
                &amp;quot;insert_after&amp;quot;: &amp;quot;taxes_and_charges&amp;quot;,&lt;br /&gt;
            }&lt;br /&gt;
        ],&lt;br /&gt;
    }&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Why &amp;lt;code&amp;gt;after_migrate&amp;lt;/code&amp;gt; matters:&amp;#039;&amp;#039;&amp;#039; It runs on every &amp;lt;code&amp;gt;bench migrate&amp;lt;/code&amp;gt; (which happens on every &amp;lt;code&amp;gt;bench update&amp;lt;/code&amp;gt;), re-asserting your fields even if something reset them.&lt;br /&gt;
&lt;br /&gt;
== 5. Adding Chart of Accounts Templates ==&lt;br /&gt;
&lt;br /&gt;
ERPNext allows custom Chart of Accounts (COA) templates. For BIR compliance, you need a Philippine COA with schedule assignments.&lt;br /&gt;
&lt;br /&gt;
=== COA Template File Location ===&lt;br /&gt;
&lt;br /&gt;
Place your template inside the app:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
bureau_of_internal_revenue/&lt;br /&gt;
  bureau_of_internal_revenue/&lt;br /&gt;
    chart_of_accounts/&lt;br /&gt;
      __init__.py&lt;br /&gt;
      verified/&lt;br /&gt;
        __init__.py&lt;br /&gt;
        philippines_bir_standard.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Register via hooks.py ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;regional_overrides = {&lt;br /&gt;
    &amp;quot;Philippines&amp;quot;: {&lt;br /&gt;
        &amp;quot;erpnext.accounts.doctype.chart_of_accounts.chart_of_accounts.get_charts_for_country&amp;quot;:&lt;br /&gt;
            &amp;quot;bureau_of_internal_revenue.bureau_of_internal_revenue.chart_of_accounts.get_charts_for_country&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== COA Template Format (Python dict) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;# philippines_bir_standard.py&lt;br /&gt;
&lt;br /&gt;
chart_data = {&lt;br /&gt;
    &amp;quot;name&amp;quot;: &amp;quot;Philippines - BIR Standard&amp;quot;,&lt;br /&gt;
    &amp;quot;country_code&amp;quot;: &amp;quot;ph&amp;quot;,&lt;br /&gt;
    &amp;quot;tree&amp;quot;: {&lt;br /&gt;
        &amp;quot;Assets&amp;quot;: {&lt;br /&gt;
            &amp;quot;account_type&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
            &amp;quot;is_group&amp;quot;: 1,&lt;br /&gt;
            &amp;quot;root_type&amp;quot;: &amp;quot;Asset&amp;quot;,&lt;br /&gt;
            &amp;quot;Current Assets&amp;quot;: {&lt;br /&gt;
                &amp;quot;is_group&amp;quot;: 1,&lt;br /&gt;
                &amp;quot;account_type&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
                &amp;quot;Cash and Cash Equivalents&amp;quot;: {&lt;br /&gt;
                    &amp;quot;is_group&amp;quot;: 1,&lt;br /&gt;
                    &amp;quot;account_type&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
                    &amp;quot;Cash on Hand&amp;quot;: {&lt;br /&gt;
                        &amp;quot;account_type&amp;quot;: &amp;quot;Cash&amp;quot;,&lt;br /&gt;
                    },&lt;br /&gt;
                    &amp;quot;Cash in Bank&amp;quot;: {&lt;br /&gt;
                        &amp;quot;account_type&amp;quot;: &amp;quot;Bank&amp;quot;,&lt;br /&gt;
                    },&lt;br /&gt;
                },&lt;br /&gt;
                &amp;quot;Trade and Other Receivables&amp;quot;: {&lt;br /&gt;
                    &amp;quot;is_group&amp;quot;: 1,&lt;br /&gt;
                    &amp;quot;Accounts Receivable&amp;quot;: {&lt;br /&gt;
                        &amp;quot;account_type&amp;quot;: &amp;quot;Receivable&amp;quot;,&lt;br /&gt;
                    },&lt;br /&gt;
                },&lt;br /&gt;
            },&lt;br /&gt;
        },&lt;br /&gt;
        &amp;quot;Liabilities&amp;quot;: {&lt;br /&gt;
            &amp;quot;is_group&amp;quot;: 1,&lt;br /&gt;
            &amp;quot;root_type&amp;quot;: &amp;quot;Liability&amp;quot;,&lt;br /&gt;
            &amp;quot;Current Liabilities&amp;quot;: {&lt;br /&gt;
                &amp;quot;is_group&amp;quot;: 1,&lt;br /&gt;
                &amp;quot;Accounts Payable&amp;quot;: {&lt;br /&gt;
                    &amp;quot;account_type&amp;quot;: &amp;quot;Payable&amp;quot;,&lt;br /&gt;
                },&lt;br /&gt;
                &amp;quot;BIR Withholding Tax Payable&amp;quot;: {&lt;br /&gt;
                    &amp;quot;account_type&amp;quot;: &amp;quot;Tax&amp;quot;,&lt;br /&gt;
                },&lt;br /&gt;
            },&lt;br /&gt;
        },&lt;br /&gt;
        &amp;quot;Equity&amp;quot;: {&lt;br /&gt;
            &amp;quot;is_group&amp;quot;: 1,&lt;br /&gt;
            &amp;quot;root_type&amp;quot;: &amp;quot;Equity&amp;quot;,&lt;br /&gt;
            &amp;quot;Share Capital&amp;quot;: {},&lt;br /&gt;
            &amp;quot;Retained Earnings&amp;quot;: {&lt;br /&gt;
                &amp;quot;account_type&amp;quot;: &amp;quot;Equity&amp;quot;,&lt;br /&gt;
            },&lt;br /&gt;
        },&lt;br /&gt;
        &amp;quot;Income&amp;quot;: {&lt;br /&gt;
            &amp;quot;is_group&amp;quot;: 1,&lt;br /&gt;
            &amp;quot;root_type&amp;quot;: &amp;quot;Income&amp;quot;,&lt;br /&gt;
            &amp;quot;Sales Revenue&amp;quot;: {&lt;br /&gt;
                &amp;quot;account_type&amp;quot;: &amp;quot;Income Account&amp;quot;,&lt;br /&gt;
            },&lt;br /&gt;
        },&lt;br /&gt;
        &amp;quot;Expenses&amp;quot;: {&lt;br /&gt;
            &amp;quot;is_group&amp;quot;: 1,&lt;br /&gt;
            &amp;quot;root_type&amp;quot;: &amp;quot;Expense&amp;quot;,&lt;br /&gt;
            &amp;quot;Cost of Goods Sold&amp;quot;: {&lt;br /&gt;
                &amp;quot;account_type&amp;quot;: &amp;quot;Cost of Goods Sold&amp;quot;,&lt;br /&gt;
            },&lt;br /&gt;
            &amp;quot;Operating Expenses&amp;quot;: {&lt;br /&gt;
                &amp;quot;is_group&amp;quot;: 1,&lt;br /&gt;
                &amp;quot;Salaries and Wages&amp;quot;: {&lt;br /&gt;
                    &amp;quot;account_type&amp;quot;: &amp;quot;Expense Account&amp;quot;,&lt;br /&gt;
                },&lt;br /&gt;
            },&lt;br /&gt;
        },&lt;br /&gt;
    },&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Alternative: JSON-based COA ===&lt;br /&gt;
&lt;br /&gt;
Place a JSON file at:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
bureau_of_internal_revenue/&lt;br /&gt;
  bureau_of_internal_revenue/&lt;br /&gt;
    chart_of_accounts/&lt;br /&gt;
      verified/&lt;br /&gt;
        ph_bir_standard.json&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Same tree structure as above but in JSON format. Frappe discovers it automatically if the &amp;lt;code&amp;gt;get_charts_for_country&amp;lt;/code&amp;gt; function is set up to scan this directory.&lt;br /&gt;
&lt;br /&gt;
== 6. Adding Custom DocTypes ==&lt;br /&gt;
&lt;br /&gt;
For app-specific data models (e.g., &amp;quot;BIR Tax Schedule&amp;quot;, &amp;quot;E-Invoice Log&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
=== Create via Bench (Developer Mode) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# In the Frappe UI: DocType List &amp;amp;gt; + New DocType&lt;br /&gt;
# OR via code:&lt;br /&gt;
bench --site phloc.localhost new-doctype &amp;quot;BIR Tax Schedule&amp;quot; \&lt;br /&gt;
    --module &amp;quot;Bureau of Internal Revenue&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== File structure created automatically ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
bureau_of_internal_revenue/&lt;br /&gt;
  bureau_of_internal_revenue/&lt;br /&gt;
    doctype/&lt;br /&gt;
      bir_tax_schedule/&lt;br /&gt;
        __init__.py&lt;br /&gt;
        bir_tax_schedule.json       # Schema definition&lt;br /&gt;
        bir_tax_schedule.py         # Controller (server logic)&lt;br /&gt;
        bir_tax_schedule.js         # Form script (client logic)&lt;br /&gt;
        test_bir_tax_schedule.py    # Tests&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Controller pattern ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;# bir_tax_schedule.py&lt;br /&gt;
import frappe&lt;br /&gt;
from frappe.model.document import Document&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class BIRTaxSchedule(Document):&lt;br /&gt;
    def validate(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Runs before save.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        if not self.schedule_code:&lt;br /&gt;
            frappe.throw(&amp;quot;Schedule code is required&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    def on_submit(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Runs when document is submitted.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    def on_cancel(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Runs when document is cancelled.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        pass&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== No hooks.py change needed ===&lt;br /&gt;
&lt;br /&gt;
Frappe auto-discovers DocTypes from the module&amp;#039;s &amp;lt;code&amp;gt;doctype/&amp;lt;/code&amp;gt; directory during &amp;lt;code&amp;gt;bench migrate&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== 7. Adding Print Formats ==&lt;br /&gt;
&lt;br /&gt;
=== Standard Jinja Print Format (in-app, upgrade-safe) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
bureau_of_internal_revenue/&lt;br /&gt;
  bureau_of_internal_revenue/&lt;br /&gt;
    print_format/&lt;br /&gt;
      bir_sales_invoice/&lt;br /&gt;
        bir_sales_invoice.json&lt;br /&gt;
        bir_sales_invoice.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;bir_sales_invoice.json:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
  &amp;quot;doctype&amp;quot;: &amp;quot;Print Format&amp;quot;,&lt;br /&gt;
  &amp;quot;name&amp;quot;: &amp;quot;BIR Sales Invoice&amp;quot;,&lt;br /&gt;
  &amp;quot;doc_type&amp;quot;: &amp;quot;Sales Invoice&amp;quot;,&lt;br /&gt;
  &amp;quot;module&amp;quot;: &amp;quot;Bureau of Internal Revenue&amp;quot;,&lt;br /&gt;
  &amp;quot;standard&amp;quot;: &amp;quot;Yes&amp;quot;,&lt;br /&gt;
  &amp;quot;print_format_type&amp;quot;: &amp;quot;Jinja&amp;quot;,&lt;br /&gt;
  &amp;quot;raw_printing&amp;quot;: 0,&lt;br /&gt;
  &amp;quot;disabled&amp;quot;: 0&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;bir_sales_invoice.html:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&amp;amp;lt;div class=&amp;quot;page-break&amp;quot;&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;h2&amp;amp;gt;{{ doc.company }}&amp;amp;lt;/h2&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;p&amp;amp;gt;TIN: {{ doc.tax_id }}&amp;amp;lt;/p&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;table&amp;amp;gt;&lt;br /&gt;
        &amp;amp;lt;tr&amp;amp;gt;&lt;br /&gt;
            &amp;amp;lt;th&amp;amp;gt;Item&amp;amp;lt;/th&amp;amp;gt;&amp;amp;lt;th&amp;amp;gt;Qty&amp;amp;lt;/th&amp;amp;gt;&amp;amp;lt;th&amp;amp;gt;Rate&amp;amp;lt;/th&amp;amp;gt;&amp;amp;lt;th&amp;amp;gt;Amount&amp;amp;lt;/th&amp;amp;gt;&lt;br /&gt;
        &amp;amp;lt;/tr&amp;amp;gt;&lt;br /&gt;
        {% for item in doc.items %}&lt;br /&gt;
        &amp;amp;lt;tr&amp;amp;gt;&lt;br /&gt;
            &amp;amp;lt;td&amp;amp;gt;{{ item.item_name }}&amp;amp;lt;/td&amp;amp;gt;&lt;br /&gt;
            &amp;amp;lt;td&amp;amp;gt;{{ item.qty }}&amp;amp;lt;/td&amp;amp;gt;&lt;br /&gt;
            &amp;amp;lt;td&amp;amp;gt;{{ frappe.format_value(item.rate, {&amp;quot;fieldtype&amp;quot;: &amp;quot;Currency&amp;quot;}) }}&amp;amp;lt;/td&amp;amp;gt;&lt;br /&gt;
            &amp;amp;lt;td&amp;amp;gt;{{ frappe.format_value(item.amount, {&amp;quot;fieldtype&amp;quot;: &amp;quot;Currency&amp;quot;}) }}&amp;amp;lt;/td&amp;amp;gt;&lt;br /&gt;
        &amp;amp;lt;/tr&amp;amp;gt;&lt;br /&gt;
        {% endfor %}&lt;br /&gt;
    &amp;amp;lt;/table&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;p&amp;amp;gt;&amp;amp;lt;strong&amp;amp;gt;Grand Total: {{ frappe.format_value(doc.grand_total, {&amp;quot;fieldtype&amp;quot;: &amp;quot;Currency&amp;quot;}) }}&amp;amp;lt;/strong&amp;amp;gt;&amp;amp;lt;/p&amp;amp;gt;&lt;br /&gt;
&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Export via fixtures (alternative) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;# hooks.py&lt;br /&gt;
fixtures = [&lt;br /&gt;
    # ... existing fixtures ...&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;doctype&amp;quot;: &amp;quot;Print Format&amp;quot;,&lt;br /&gt;
        &amp;quot;filters&amp;quot;: [[&amp;quot;Print Format&amp;quot;, &amp;quot;module&amp;quot;, &amp;quot;=&amp;quot;, &amp;quot;Bureau of Internal Revenue&amp;quot;]]&lt;br /&gt;
    }&lt;br /&gt;
]&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 8. Adding Property Setters ==&lt;br /&gt;
&lt;br /&gt;
Property Setters modify properties of existing DocType fields (e.g., making a field mandatory, changing its default, hiding it) without modifying the DocType source.&lt;br /&gt;
&lt;br /&gt;
=== Programmatic approach (in setup.py) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;import frappe&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def after_migrate():&lt;br /&gt;
    create_custom_fields(get_custom_fields())&lt;br /&gt;
    create_property_setters()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def create_property_setters():&lt;br /&gt;
    property_setters = [&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;doctype&amp;quot;: &amp;quot;Sales Invoice&amp;quot;,&lt;br /&gt;
            &amp;quot;fieldname&amp;quot;: &amp;quot;tax_id&amp;quot;,&lt;br /&gt;
            &amp;quot;property&amp;quot;: &amp;quot;reqd&amp;quot;,&lt;br /&gt;
            &amp;quot;value&amp;quot;: &amp;quot;1&amp;quot;,&lt;br /&gt;
            &amp;quot;property_type&amp;quot;: &amp;quot;Check&amp;quot;,&lt;br /&gt;
        },&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;doctype&amp;quot;: &amp;quot;Sales Invoice&amp;quot;,&lt;br /&gt;
            &amp;quot;fieldname&amp;quot;: &amp;quot;tax_id&amp;quot;,&lt;br /&gt;
            &amp;quot;property&amp;quot;: &amp;quot;default&amp;quot;,&lt;br /&gt;
            &amp;quot;value&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
            &amp;quot;property_type&amp;quot;: &amp;quot;Data&amp;quot;,&lt;br /&gt;
        },&lt;br /&gt;
    ]&lt;br /&gt;
    for ps in property_setters:&lt;br /&gt;
        frappe.make_property_setter(ps, is_system_generated=False)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Fixture approach ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;# hooks.py&lt;br /&gt;
fixtures = [&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;doctype&amp;quot;: &amp;quot;Property Setter&amp;quot;,&lt;br /&gt;
        &amp;quot;filters&amp;quot;: [[&amp;quot;Property Setter&amp;quot;, &amp;quot;module&amp;quot;, &amp;quot;=&amp;quot;, &amp;quot;Bureau of Internal Revenue&amp;quot;]]&lt;br /&gt;
    }&lt;br /&gt;
]&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 9. Adding Server-Side Hooks ==&lt;br /&gt;
&lt;br /&gt;
=== Document Event Hooks ===&lt;br /&gt;
&lt;br /&gt;
Trigger your code when documents are saved, submitted, or cancelled:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;# hooks.py&lt;br /&gt;
doc_events = {&lt;br /&gt;
    &amp;quot;Sales Invoice&amp;quot;: {&lt;br /&gt;
        &amp;quot;validate&amp;quot;: &amp;quot;bureau_of_internal_revenue.overrides.sales_invoice.validate&amp;quot;,&lt;br /&gt;
        &amp;quot;on_submit&amp;quot;: &amp;quot;bureau_of_internal_revenue.overrides.sales_invoice.on_submit&amp;quot;,&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;Purchase Invoice&amp;quot;: {&lt;br /&gt;
        &amp;quot;validate&amp;quot;: &amp;quot;bureau_of_internal_revenue.overrides.purchase_invoice.validate&amp;quot;,&lt;br /&gt;
    },&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;# bureau_of_internal_revenue/overrides/sales_invoice.py&lt;br /&gt;
import frappe&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def validate(doc, method):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Called before every Sales Invoice save.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    if doc.company_address:&lt;br /&gt;
        # Add BIR validation logic&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def on_submit(doc, method):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Called when Sales Invoice is submitted.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    pass&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Whitelisted API Methods ===&lt;br /&gt;
&lt;br /&gt;
Expose Python functions as REST API endpoints:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;# hooks.py (uncomment and modify)&lt;br /&gt;
override_whitelisted_methods = {&lt;br /&gt;
    &amp;quot;erpnext.accounts.utils.get_balance_on&amp;quot;:&lt;br /&gt;
        &amp;quot;bureau_of_internal_revenue.overrides.accounts.get_balance_on&amp;quot;&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or create standalone API endpoints:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;# bureau_of_internal_revenue/api.py&lt;br /&gt;
import frappe&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@frappe.whitelist()&lt;br /&gt;
def get_bir_schedule(account):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Callable via /api/method/bureau_of_internal_revenue.api.get_bir_schedule&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    return frappe.db.get_value(&amp;quot;Account&amp;quot;, account, &amp;quot;schedule&amp;quot;)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Scheduled Tasks ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;# hooks.py&lt;br /&gt;
scheduler_events = {&lt;br /&gt;
    &amp;quot;daily&amp;quot;: [&lt;br /&gt;
        &amp;quot;bureau_of_internal_revenue.tasks.daily_compliance_check&amp;quot;&lt;br /&gt;
    ],&lt;br /&gt;
    &amp;quot;monthly&amp;quot;: [&lt;br /&gt;
        &amp;quot;bureau_of_internal_revenue.tasks.generate_monthly_bir_summary&amp;quot;&lt;br /&gt;
    ],&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 10. Testing ==&lt;br /&gt;
&lt;br /&gt;
=== Test file pattern ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;# test_your_report_name.py&lt;br /&gt;
import frappe&lt;br /&gt;
from frappe.tests.utils import FrappeTestCase&lt;br /&gt;
from unittest.mock import patch&lt;br /&gt;
&lt;br /&gt;
from bureau_of_internal_revenue.bureau_of_internal_revenue.report.your_report_name.your_report_name import execute&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class TestYourReportName(FrappeTestCase):&lt;br /&gt;
&lt;br /&gt;
    def test_basic_execution(self):&lt;br /&gt;
        filters = frappe._dict(&lt;br /&gt;
            company=&amp;quot;_Test Company&amp;quot;,&lt;br /&gt;
            from_fiscal_year=&amp;quot;2025&amp;quot;,&lt;br /&gt;
            to_fiscal_year=&amp;quot;2025&amp;quot;,&lt;br /&gt;
            period_start_date=&amp;quot;2025-01-01&amp;quot;,&lt;br /&gt;
            period_end_date=&amp;quot;2025-12-31&amp;quot;,&lt;br /&gt;
            filter_based_on=&amp;quot;Fiscal Year&amp;quot;,&lt;br /&gt;
            periodicity=&amp;quot;Yearly&amp;quot;,&lt;br /&gt;
            accumulated_values=1,&lt;br /&gt;
        )&lt;br /&gt;
        columns, data, *_ = execute(filters)&lt;br /&gt;
        self.assertTrue(len(columns) &amp;amp;gt; 0)&lt;br /&gt;
&lt;br /&gt;
    @patch(&amp;quot;bureau_of_internal_revenue.bureau_of_internal_revenue.report.your_report_name.your_report_name.get_data&amp;quot;)&lt;br /&gt;
    def test_with_mocked_data(self, mock_get_data):&lt;br /&gt;
        mock_get_data.return_value = [&lt;br /&gt;
            frappe._dict(account=&amp;quot;Cash - TC&amp;quot;, account_name=&amp;quot;Cash&amp;quot;, indent=1, total=500),&lt;br /&gt;
        ]&lt;br /&gt;
        filters = frappe._dict(company=&amp;quot;_Test Company&amp;quot;)&lt;br /&gt;
        columns, data, *_ = execute(filters)&lt;br /&gt;
        self.assertEqual(data[0].total, 500)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Run tests ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# All tests for the app&lt;br /&gt;
bench --site phloc.localhost run-tests --app bureau_of_internal_revenue&lt;br /&gt;
&lt;br /&gt;
# Specific test file&lt;br /&gt;
bench --site phloc.localhost run-tests \&lt;br /&gt;
    --module bureau_of_internal_revenue.bureau_of_internal_revenue.report.balance_sheet_bir.test_balance_sheet_bir&lt;br /&gt;
&lt;br /&gt;
# With verbose output&lt;br /&gt;
bench --site phloc.localhost run-tests --app bureau_of_internal_revenue -v&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 11. Frappe Cloud Deployment ($25/instance) ==&lt;br /&gt;
&lt;br /&gt;
=== Frappe Cloud Pricing (as of 2026) ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Plan !! Price !! What you get&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;$25/mo&amp;#039;&amp;#039;&amp;#039; (Starter/Basic) || $25 USD/month || 1 site, shared bench, limited resources, custom apps supported&lt;br /&gt;
|-&lt;br /&gt;
| $50/mo (Standard) || $50 USD/month || More CPU/RAM, priority support&lt;br /&gt;
|-&lt;br /&gt;
| Dedicated || Custom || Dedicated server, full control&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The $25/mo plan is sufficient for testing and small deployments. It supports custom app installation.&lt;br /&gt;
&lt;br /&gt;
=== Step-by-Step: Deploy to Frappe Cloud ===&lt;br /&gt;
&lt;br /&gt;
==== 1. Prepare your repository ====&lt;br /&gt;
&lt;br /&gt;
Ensure your repo has:&lt;br /&gt;
* &amp;lt;code&amp;gt;pyproject.toml&amp;lt;/code&amp;gt; with &amp;lt;code&amp;gt;[tool.bench.frappe-dependencies]&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;app_name&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;hooks.py&amp;lt;/code&amp;gt; matches the top-level directory name&lt;br /&gt;
* &amp;lt;code&amp;gt;__init__.py&amp;lt;/code&amp;gt; in the app root&lt;br /&gt;
* Repo is public on GitHub (or you grant Frappe Cloud access to private repo)&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Fix the placeholder in pyproject.toml first:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;[project]&lt;br /&gt;
name = &amp;quot;bureau_of_internal_revenue&amp;quot;&lt;br /&gt;
authors = [&lt;br /&gt;
    { name = &amp;quot;Ambibuzz Technologies LLP&amp;quot;, email = &amp;quot;buzz.us@ambibuzz.com&amp;quot; }&lt;br /&gt;
]&lt;br /&gt;
description = &amp;quot;Philippine BIR Localization for ERPNext&amp;quot;&lt;br /&gt;
requires-python = &amp;quot;&amp;gt;=3.10&amp;quot;&lt;br /&gt;
readme = &amp;quot;README.md&amp;quot;&lt;br /&gt;
dynamic = [&amp;quot;version&amp;quot;]&lt;br /&gt;
dependencies = [&lt;br /&gt;
    &amp;quot;frappe~=15.95.0&amp;quot;&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
[tool.bench.frappe-dependencies]&lt;br /&gt;
frappe = &amp;quot;~=15.95.0&amp;quot;&lt;br /&gt;
erpnext = &amp;quot;~=15.0.0&amp;quot;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 2. Sign up and create a bench on Frappe Cloud ====&lt;br /&gt;
&lt;br /&gt;
# Go to [frappecloud.com](https://frappecloud.com) and sign up&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Dashboard &amp;amp;gt; Benches &amp;amp;gt; New Bench&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
# Select Frappe version: &amp;#039;&amp;#039;&amp;#039;Version 15&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
# Add apps:&lt;br /&gt;
#* &amp;#039;&amp;#039;&amp;#039;ERPNext&amp;#039;&amp;#039;&amp;#039; (from Frappe&amp;#039;s official list)&lt;br /&gt;
#* &amp;#039;&amp;#039;&amp;#039;Your custom app&amp;#039;&amp;#039;&amp;#039;: click &amp;quot;Add App&amp;quot; &amp;amp;gt; paste GitHub URL &amp;lt;code&amp;gt;https://github.com/xunema/phlocalization&amp;lt;/code&amp;gt; &amp;amp;gt; select branch&lt;br /&gt;
# Choose region (nearest to Philippines: Singapore)&lt;br /&gt;
# Create the bench — Frappe Cloud builds it (takes 5-15 minutes)&lt;br /&gt;
&lt;br /&gt;
==== 3. Create a site ====&lt;br /&gt;
&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Dashboard &amp;amp;gt; Sites &amp;amp;gt; New Site&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
# Select your bench&lt;br /&gt;
# Choose the $25/mo plan&lt;br /&gt;
# Set subdomain: &amp;lt;code&amp;gt;phloc.frappe.cloud&amp;lt;/code&amp;gt; (or custom domain)&lt;br /&gt;
# Select apps to install: &amp;#039;&amp;#039;&amp;#039;ERPNext&amp;#039;&amp;#039;&amp;#039; + &amp;#039;&amp;#039;&amp;#039;Bureau of Internal Revenue&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
# Create site — Frappe Cloud provisions it and runs &amp;lt;code&amp;gt;bench migrate&amp;lt;/code&amp;gt; (applies fixtures)&lt;br /&gt;
&lt;br /&gt;
==== 4. Verify deployment ====&lt;br /&gt;
&lt;br /&gt;
# Log in at &amp;lt;code&amp;gt;https://phloc.frappe.cloud&amp;lt;/code&amp;gt;&lt;br /&gt;
# Go to &amp;#039;&amp;#039;&amp;#039;Search Bar &amp;amp;gt; Balance Sheet BIR&amp;#039;&amp;#039;&amp;#039; — report should appear&lt;br /&gt;
# Go to &amp;#039;&amp;#039;&amp;#039;Accounting &amp;amp;gt; Chart of Accounts&amp;#039;&amp;#039;&amp;#039; — verify Schedule field on group accounts&lt;br /&gt;
# Go to &amp;#039;&amp;#039;&amp;#039;Customize Form &amp;amp;gt; Account&amp;#039;&amp;#039;&amp;#039; — verify Schedule custom field exists&lt;br /&gt;
&lt;br /&gt;
==== 5. Update workflow ====&lt;br /&gt;
&lt;br /&gt;
When you push changes to GitHub:&lt;br /&gt;
&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Dashboard &amp;amp;gt; Benches &amp;amp;gt; your bench &amp;amp;gt; Updates&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
# Click &amp;#039;&amp;#039;&amp;#039;&amp;quot;Deploy&amp;quot;&amp;#039;&amp;#039;&amp;#039; or enable auto-deploy&lt;br /&gt;
# Frappe Cloud pulls latest code, runs &amp;lt;code&amp;gt;bench migrate&amp;lt;/code&amp;gt;, restarts&lt;br /&gt;
# Your fixtures, reports, and hooks are re-applied automatically&lt;br /&gt;
&lt;br /&gt;
=== Frappe Cloud CLI (alternative) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Install FC CLI&lt;br /&gt;
pip install press-cli&lt;br /&gt;
&lt;br /&gt;
# Login&lt;br /&gt;
fc login&lt;br /&gt;
&lt;br /&gt;
# List your benches&lt;br /&gt;
fc bench list&lt;br /&gt;
&lt;br /&gt;
# Deploy (trigger update)&lt;br /&gt;
fc bench deploy &amp;amp;lt;bench-name&amp;amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 12. Bench Command Reference ==&lt;br /&gt;
&lt;br /&gt;
=== Daily Development Workflow ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Start dev server (Frappe + Redis + workers)&lt;br /&gt;
bench start&lt;br /&gt;
&lt;br /&gt;
# After editing Python files — apply DB changes&lt;br /&gt;
bench --site phloc.localhost migrate&lt;br /&gt;
&lt;br /&gt;
# After editing JS/CSS files — rebuild assets&lt;br /&gt;
bench build --app bureau_of_internal_revenue&lt;br /&gt;
&lt;br /&gt;
# Quick cache clear (when UI doesn&amp;#039;t reflect changes)&lt;br /&gt;
bench --site phloc.localhost clear-cache&lt;br /&gt;
&lt;br /&gt;
# Export fixtures after creating custom fields via UI&lt;br /&gt;
bench --site phloc.localhost export-fixtures --app bureau_of_internal_revenue&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Site Management ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Create new site&lt;br /&gt;
bench new-site &amp;amp;lt;site-name&amp;amp;gt; --mariadb-root-password &amp;amp;lt;pw&amp;amp;gt; --admin-password &amp;amp;lt;pw&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
# Install app on site&lt;br /&gt;
bench --site &amp;amp;lt;site-name&amp;amp;gt; install-app bureau_of_internal_revenue&lt;br /&gt;
&lt;br /&gt;
# Uninstall app&lt;br /&gt;
bench --site &amp;amp;lt;site-name&amp;amp;gt; uninstall-app bureau_of_internal_revenue&lt;br /&gt;
&lt;br /&gt;
# Drop site entirely&lt;br /&gt;
bench drop-site &amp;amp;lt;site-name&amp;amp;gt; --force&lt;br /&gt;
&lt;br /&gt;
# Backup&lt;br /&gt;
bench --site &amp;amp;lt;site-name&amp;amp;gt; backup --with-files&lt;br /&gt;
&lt;br /&gt;
# Restore&lt;br /&gt;
bench --site &amp;amp;lt;site-name&amp;amp;gt; restore &amp;amp;lt;backup-file&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
# Open console (Python shell with frappe context)&lt;br /&gt;
bench --site &amp;amp;lt;site-name&amp;amp;gt; console&lt;br /&gt;
&lt;br /&gt;
# Open mariadb shell&lt;br /&gt;
bench --site &amp;amp;lt;site-name&amp;amp;gt; mariadb&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== App Management ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Get app from GitHub&lt;br /&gt;
bench get-app &amp;amp;lt;url&amp;amp;gt; --branch &amp;amp;lt;branch&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
# Remove app from bench (not from site)&lt;br /&gt;
bench remove-app &amp;amp;lt;app-name&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
# Check installed apps on site&lt;br /&gt;
bench --site &amp;amp;lt;site-name&amp;amp;gt; list-apps&lt;br /&gt;
&lt;br /&gt;
# Update all apps&lt;br /&gt;
bench update&lt;br /&gt;
&lt;br /&gt;
# Update specific app only&lt;br /&gt;
bench update --apps bureau_of_internal_revenue&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Development Tools ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Run tests&lt;br /&gt;
bench --site &amp;amp;lt;site-name&amp;amp;gt; run-tests --app bureau_of_internal_revenue&lt;br /&gt;
&lt;br /&gt;
# Run tests verbose&lt;br /&gt;
bench --site &amp;amp;lt;site-name&amp;amp;gt; run-tests --app bureau_of_internal_revenue -v&lt;br /&gt;
&lt;br /&gt;
# Export fixtures&lt;br /&gt;
bench --site &amp;amp;lt;site-name&amp;amp;gt; export-fixtures --app bureau_of_internal_revenue&lt;br /&gt;
&lt;br /&gt;
# Build assets&lt;br /&gt;
bench build --app bureau_of_internal_revenue&lt;br /&gt;
&lt;br /&gt;
# Clear cache&lt;br /&gt;
bench --site &amp;amp;lt;site-name&amp;amp;gt; clear-cache&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 13. Survival Checklist — Will My Changes Survive &amp;lt;code&amp;gt;bench update&amp;lt;/code&amp;gt;? ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Check !! Safe Pattern !! Unsafe Pattern&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Custom fields on standard DocTypes&amp;#039;&amp;#039;&amp;#039; || Fixtures or &amp;lt;code&amp;gt;create_custom_fields()&amp;lt;/code&amp;gt; in setup.py || Manual changes in &amp;quot;Customize Form&amp;quot; without exporting&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Client scripts&amp;#039;&amp;#039;&amp;#039; || &amp;lt;code&amp;gt;doctype_js&amp;lt;/code&amp;gt; in hooks.py + JS files in &amp;lt;code&amp;gt;public/js/&amp;lt;/code&amp;gt; || &amp;quot;Custom Script&amp;quot; records in Frappe UI&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Reports&amp;#039;&amp;#039;&amp;#039; || Script Report JSON + .py/.js/.html files in app directory || &amp;quot;Query Report&amp;quot; or &amp;quot;Report Builder&amp;quot; saved in UI&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Print formats&amp;#039;&amp;#039;&amp;#039; || Standard print format JSON + HTML in app directory || Custom Print Format created via UI&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;DocType modifications&amp;#039;&amp;#039;&amp;#039; || Custom Fields, Property Setters || Modifying standard DocType source code&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Server logic&amp;#039;&amp;#039;&amp;#039; || &amp;lt;code&amp;gt;doc_events&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;override_whitelisted_methods&amp;lt;/code&amp;gt; in hooks.py || Editing ERPNext .py files directly&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Rule of thumb:&amp;#039;&amp;#039;&amp;#039; If you created it through the Frappe UI and it&amp;#039;s not exported to a file in your app directory, it will &amp;#039;&amp;#039;&amp;#039;not&amp;#039;&amp;#039;&amp;#039; survive a &amp;lt;code&amp;gt;bench update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Category:ERPNext]]&lt;br /&gt;
[[Category:Frappe]]&lt;br /&gt;
[[Category:Developer Guide]]&lt;br /&gt;
[[Category:Tutorial]]&lt;br /&gt;
[[Category:Comfac ERPN Loc]]&lt;/div&gt;</summary>
		<author><name>Justinaquino</name></author>
	</entry>
</feed>