ERPNext Technical Documents =========================== Dev vs Production `````````````````` By default when you install bench {and following instructions from `here `_ }, frappe and erpnext - all of them will be loaded on `development` branch. Following are instructions to have `master` {aka production version installed} .. code-block:: sh git clone https://github.com/frappe/bench bench-repo pipenv --python 3.6 pipenv shell pip3 install --user -e bench-repo bench init frappe-bench && cd frappe-bench bench switch-to-branch version-12 bench get-app erpnext https://github.com/frappe/erpnext --branch version-12 bench update --patch or bench update {run this despite getting error} bench new-site site1.local {make sure we have right db for `site_config.json`} bench --site site1.local install-app erpnext bench start Alternatively this can be achieved using: .. code-block:: sh wget https://raw.githubusercontent.com/frappe/bench/master/playbooks/install.py sudo python install.py --production --user frappe Exporting custom app ```````````````````` 1. Make sure developer mode is activiated 2. Un-check `custom` checkbox for all DocTypes you want to export 3. Ensure all your DocTypes are visible in whatever app you've created using `bench new-app` 4. Initialize empty git repo using `git int .` 5. Commit and push all you changes to repo Deploying custom app ```````````````````` 1. Clone repo to apps 2. Install using pip: `./env/bin/pip install apps/hello_world/ --no-cache-dir` 3. Make sure app name exists in `./sites/app.txt` 4. Install app on associated site: `bench --site site1.local install-app hello_world` {Here name of out custom application is `hello_world`} 5. If you get AttributeError: `WebApplicationClient` issue solve it using: `this solution `_ Code Examples ````````````` Dynamically Create Child Rows ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Following example shows with Default Values {Including Dates} .. code-block:: javascript frappe.ui.form.on("Sales Order", "spot_to_date", function(frm, cdt, cdn) { var d = locals[cdt][cdn]; if(d.spot_from_date === undefined || d.spot_from_date > d.spot_to_date){ frappe.throw(("Please select valid From Date")); }else{ var current = new Date(d.spot_from_date); current = new Date(current.setDate(current.getDate() - 1)) var end = new Date(d.spot_to_date); var i = 0; while(current < end){ var child = cur_frm.add_child("iro_spot_schedule"); frappe.model.set_value(child.doctype, child.name, 'date', frappe.datetime.add_days(d.spot_from_date, i)); frappe.model.set_value(child.doctype, child.name, 'program_name', d.default_program_name); var next = new Date(current.setDate(current.getDate() + 1)) current = next; i += 1; } frm.refresh_field("iro_spot_schedule") } }); The above example gets triggered on DocType `Sales Order` when `spot_to_date` is changed Triggering code just before saving ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Lets say you want to do either of following: 1. Validation 2. Adjust Fields 3. Calculate Values at Runtime following example shows you how .. code-block:: javascript frappe.ui.form.on('Item', { validate: function(frm) { var results = []; var item_code = "Material Code:" + frm.doc.item_code; results.push(item_code); if(frm.doc.manufacturers.length > 0){ var firstItem = frm.doc.manufacturers[0]; var manufacturersCode = "Manufacturers Part No:" + firstItem.manufacturer_part_no; results.push(manufacturersCode); } if(frm.doc.gtl_hazmat){ results.push("

This item is classified as Hazardous Material for shipment

"); } if(!frm.doc.gtl_description.includes("Material Code")){ results.push(frm.doc.gtl_description); frm.set_value("gtl_description", results.join("
")); } } }); Iterating over items in Child Table ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: javascript frappe.ui.form.on("Channel", "onload", function(frm) { console.log("Cool"); $.each(cur_frm.doc.markets_served, function(index, item){ console.log(item); console.log(item.market_served); var result = frappe.model.get_doc("Market", "Faridabad"); console.log(result); }); console.log("Great"); }); Trigger for change in form field ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: javascript frappe.ui.form.on("Channel", { markets: function(frm){ console.log("channel changed"); } }); Trigger for change in child table field ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: javascript frappe.ui.form.on('Channel Market Table', { market_served: function(frm){ console.log("market served changed"); console.log(frm); } }) Fetch value from Child Table ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: javascript frappe.ui.form.on("Channel Market Table", "market_served", function(frm, cdt, cdn) { var d = locals[cdt][cdn]; frappe.db.get_value("Market", {"name": d.market_served}, "district_connectivity", function(value) { frappe.model.set_value(cdt, cdn, 'total_connectivity', value.district_connectivity); console.log(value.district_connectivity); }); }); Calculating based on intermediate fields ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: javascript frappe.ui.form.on("Channel Market Table", "reach", function(frm, cdt, cdn) { var d = locals[cdt][cdn]; if(d.total_connectivity > 0 && d.reach > 0){ var result = ((d.reach * d.total_connectivity)/100).toFixed(2); frappe.model.set_value(cdt, cdn, 'channel_connectivity', result); } }); Adding custom buttons ~~~~~~~~~~~~~~~~~~~~~ .. code-block:: javascript frappe.ui.form.on("Channel", "refresh", function(frm) { frm.add_custom_button(__("Do Something"), function() { console.log("Cool"); }); }); Dynamically add Rows to Child Table ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: javascript frappe.ui.form.on('Sales Order', { refresh(frm) { console.log("this is cool"); var child = cur_frm.add_child("iro_spot_schedule"); var today = new Date(); frappe.model.set_value(child.doctype, child.name, 'date', today); var child = cur_frm.add_child("iro_spot_schedule"); frappe.model.set_value(child.doctype, child.name, 'date', today); } }) Populate Child Rows based on Date Range Selection with Validation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: javascript frappe.ui.form.on("Sales Order", "spot_to_date", function(frm, cdt, cdn) { var d = locals[cdt][cdn]; if(d.spot_from_date === undefined || d.spot_from_date > d.spot_to_date){ frappe.throw(("Please select valid From Date")); }else{ var current = new Date(d.spot_from_date); current = new Date(current.setDate(current.getDate() - 1)) var end = new Date(d.spot_to_date); while(current < end){ console.log(current); var child = cur_frm.add_child("iro_spot_schedule"); frappe.model.set_value(child.doctype, child.name, 'date', current); var next = new Date(current.setDate(current.getDate() + 1)) current = next; } frm.refresh_field("iro_spot_schedule") } }); Updation of Total field based on change in Child Form ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: javascript cur_frm.add_fetch('customer_name', 'customer_terms_and_conditions', 'customer_terms_and_conditions_template'); frappe.ui.form.on('Sales Order', 'refresh', function(frm, cdt, cdn) { var d = locals[cdt][cdn]; console.log(d.items); var total = 0; d.items.forEach(function(item){ console.log(item.gtl_total_price_usd); total += item.gtl_total_price_usd }); frappe.model.set_value(cdt, cdn, 'gtl_total_amount_usd', total); }); frappe.ui.form.on("Sales Order Item", "gtl_unit_price_usd", function(frm, cdt, cdn) { var d = locals[cdt][cdn]; frappe.model.set_value(cdt, cdn, 'gtl_total_price_usd', d.qty * d.gtl_unit_price_usd); console.log(d); }); Import files from Item master and attach to current DocType ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Example of code from file `apps/gtl_custom/gtl_custom/api.py` .. code-block:: python import json import frappe from frappe.utils.file_manager import save_url @frappe.whitelist() def attach_all_docs(document, method=None): """This function attaches drawings to the purchase order based on the items being ordered""" document = json.loads(document) count = 0 for item_doc in document["items"]: item = frappe.get_doc("Item",item_doc["item_code"]) attachments = [] # Get the path for the attachments if item.item_attachment_1: attachments.append(item.item_attachment_1) if item.item_attachment_2: attachments.append(item.item_attachment_2) if item.item_attachment_3: attachments.append(item.item_attachment_3) if item.item_attachment_4: attachments.append(item.item_attachment_4) for attach in attachments: count = count + 1 save_url(attach, attach.split("/")[-1], document["doctype"], document["name"], "Home/Attachments", True) frappe.msgprint("Attached {0} files".format(count)) JS part of this functionality .. code-block:: javascript frappe.ui.form.on("Quotation", { refresh: function(frm) { frm.add_custom_button(__("Load Attachments"), function(foo) { console.log("loading documents"); frappe.call({ method: "gtl_custom.api.attach_all_docs", args: { document: cur_frm.doc }, callback: function(r) { frm.reload_doc(); } }); }); } }); Downloading Child Table as TSV ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: javascript frappe.ui.form.on("Sales Order", { refresh: function(frm) { // code to hide buttons setTimeout(() => { frm.remove_custom_button('Subscription', 'Create'); frm.remove_custom_button('Pick List', 'Create'); frm.remove_custom_button('Delivery Note', 'Create'); frm.remove_custom_button('Work Order', 'Create'); frm.remove_custom_button('Material Request', 'Create'); frm.remove_custom_button('Request for Raw Materials', 'Create'); frm.remove_custom_button('Project', 'Create'); frm.remove_custom_button('Subscription', 'Create'); },10); frm.doc.items.forEach(function(item){ if(item.qty){ item.ua_quantity = item.qty * 10;} else{ item.ua_quantity = 0; } }); frm.add_custom_button(__("Export MB (CSV)"), function() { var URL = "/api/method/updateads.api.fetch_media_buying?iro_name=" + frm.doc.name; window.open(URL, '_blank'); }); } }); Key thing to note is `frm.add_custom_button(__("Export MB (CSV)"), function()` section. Which contains code for button and linkage to backend. Following is Python code for Server Side: .. code-block:: python def get_media_buying(iro_name): q = "SELECT state,mso,channel_name,channel_connectivity FROM `tabIRO Channel Table` WHERE parent='{0}' ORDER BY\ idx".format(iro_name) q1='SELECT s_days,s_fct from `tabSummary Item Table` where parent="{0}" limit 1'.format(iro_name) results = frappe.db.sql(q1) df = pd.DataFrame(frappe.db.sql(q, as_list=True), columns=['State','MSO','Channel Name','Connectivity (HH)']) if results: df['Days'] = results[0][0] df['Total FCT'] = results[0][1] df.to_csv("/tmp/media_buying.csv", index=False) @frappe.whitelist() def fetch_media_buying(iro_name): get_media_buying(iro_name) frappe.local.response.filename = "{0}-Media-Buying.csv".format(iro_name) frappe.local.response.filecontent = open("/tmp/media_buying.csv").read() frappe.local.response.type = "download" Customization References ```````````````````````` 1. `Frappe Guide `_ is a great collection of articles for development 2. `Customization Articles `_ 3. `Customize ERPNext `_ 1. This is a meta-list explaining how to customize different aspects 4. `Form Scripting in Frappe `_ 5. `Web Hooks `_ can be defined to make a call back on certain DocType events 6. `1st Step for new devloper? `_ 7. `Working with Notifications in ERPNext `_ 8. `Explanation of Cutomising Report {Specifically w.r.t. filtering} `_ 9. Running script when something is `selected `_ 10. Adding `Custom Button `_ 11. `Custom Button & Icon `_ 12. Tutorials 1. `Tutorial 1 `_ 2. `Tutorial 2 `_ Development References `````````````````````` 1. `frappe.get_doc `_ is also used to create a new doc 2. Jinja2 templating engine is used {for HTML templates} 3. It is recommended of adding customization by `Customize Form` option as opposed to updating the `DocType` 4. `Custom CSS `_ 5. `frappe.call `_ is used to make Ajax calls 6. `locals cdt cdn `_ are cache values 1. Current Data Type 2. Current Data Name 7. `frappe.form_dict` gives all request parameters 8. `frappe.get_doc` used to get intance of a doctype {which is model} 9. `frappe.get_all` similar to `objects.all()` of django 10. `frappe.get_list` similar to `get_all` but only show records that current user has permission to 11. On python end testing is done using `unittest` module and on JS end `Qunit` is used 12. `Hooks `_ and `hooks.py `_ allow you to plugin to different part of ERPNext. This will also allows you to `Rewrite APIs using Hooks `_ 13. `Background Services `_ talks about different services Frappe uses. Redis is basically used as broker for Celery {which in turn is used for background jobs} 14. `DocType Events `_ is equivalent of `def save` for django. This will allow you to write custom code on various events 15. `Making Public API `_ is simple you need to add decorator `@frappe.whitelist()`, which will make API accessible from `/api/method/myapp.api.get_last_project` 16. On client side `cur_frm` will allow you to access current form's object 17. `Custom scripts `_ allow you to modify certain aspect of Form only on client side. Some usecase for Custom Scripts include 1. Form validation 2. Fetching values from Masters or Linked DocTypes 18. One can also write `call back `_ for value changes 19. `Community developed custom scripts `_ good reference for custom scripts 20. Document name is like Primary Key {this is used while accessing API}. `Example usage of APIs `_ explains it 21. `frappe UI Dialog `_ is used for showing dialog 22. `Print format builder `_ will allow you to create reports/change their formatting 23. `Frappe Cheatsheet `_ 1. Contains information about different methods for Frappe 24. `Frappe Confusions `_ gives more clarity from third-person prespective 25. `Frappe Database API `_ 26. `Advanced Database queries in Frappe `_ 27. `hooks.py` allow interaction between core and custom application 1. doc_events: E.g. {Sales Order} saving of sales order, you want to run a custom function in customization_shipping 2. Define what event to call on`valiate`: `erpnext_shipping.manage_shipping.sales_order_custom.calculate_total_weight` 28. `from frappe.utils import flt` <- converts None to 0 float 29. Developer console also gives full stack trace of error 30. All callbacks across all app are calculated in 31. Good way to write client side scripting {this allows you to do version control} 1. `fixtures/custom_scripts/` whatever doctype name e.g. `Customer.js` this is where you write custom logic in JS 2. It needs to be added to hooks.py. Make sure `fixtures` should be having `Custom Field` and `Custom Script` are present 32. Exporting print format using custom app is possible, and recommended to store in custom app for maintenance REST APIs ````````` 1. `Filtering in REST APIs `_ 2. `Example of calling ERPNext's API Call via Requests `_ 3. `Frappe Client `_ is a good alternative that allows you to interact with Frappe's installations without having to write raw REST APIs 4. `Token Authentication for APIs `_ 1. Tokens are associated with users which has created 2. This implies that attribution {to assicated user} would be right when creating via APIs Reporting `````````` 1. `Custom Reports in ERPNext `_, following are some of the report types 1. Report Builders - using the GUI 2. `Query Report `_ 3. `Script Report `_ talks about creating custom reports 2. `How to hide field from print format `_ 3. Setting up new Query Report 1. Duplicate Report 2. For Module Select Custom Application 3. This will save JSON file with SQL Queries {This gets saved in `report` folder} 4. To show this report edit `config.py` in Custom Application {E.g. stock.py} 1. Update get_data() function {Use existing files as a reference} 2. Update `name` and `doctype` attributes 5. Don’t forget to reload the site, restart bench and clear cache