From 44a7c57960fd0c544556cb531823eef11bcb23d7 Mon Sep 17 00:00:00 2001 From: Priya Date: Thu, 31 Oct 2013 12:26:12 +0530 Subject: [PATCH 01/10] [docs] minor mrp and website --- docs/user/docs.user.md | 1 + docs/user/mfg/docs.user.mfg.planning.md | 46 ++++++++++++-- docs/user/setup/docs.user.setup.taxes.md | 5 ++ ...cs.user.website.add_products_to_website.md | 60 +++++++++++++++++++ docs/user/website/docs.user.website.md | 3 +- 5 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 docs/user/website/docs.user.website.add_products_to_website.md diff --git a/docs/user/docs.user.md b/docs/user/docs.user.md index 1b79ee7606..5593dae819 100644 --- a/docs/user/docs.user.md +++ b/docs/user/docs.user.md @@ -143,6 +143,7 @@ Contents 1. [Style](docs.user.website.style.html) 1. [Blog](docs.user.website.blog.html) 1. [Shopping Cart](docs.user.website.shopping_cart.html) + 1. [Add Products](docs.user.website.add_products_to_website.html) 1. [Tools](docs.user.tools.html) 1. [To Do](docs.user.tools.todo.html) 1. [Calendar](docs.user.tools.calendar.html) diff --git a/docs/user/mfg/docs.user.mfg.planning.md b/docs/user/mfg/docs.user.mfg.planning.md index 8d43ab8142..a8075e162d 100644 --- a/docs/user/mfg/docs.user.mfg.planning.md +++ b/docs/user/mfg/docs.user.mfg.planning.md @@ -15,19 +15,57 @@ To use the Production Planning Tool, go to:
 > Manufacturing > Production Planning Tool -![Production Planning Tool](img/production-planning-tool.png) +![Material Requisition Planning](img/mrp.png) + +
+#### Step 1: Select and get Sales Order + +Select sales orders for MRP using filters (Time, Item, and Customer) + + +![Production Planning Tool](img/mrp-1.png) + +Click on Get Sales Order to generate a list. + +![Production Planning Tool](img/mrp-1.1.png) + +
+ + +#### Step 2: Get Item from Sales Orders. + + You can add/remove or change quantity of these Items. + + ![Production Planning Tool](img/mrp-2.png) + + +
+ +#### Step 3: Create Production Orders + +![Production Planning Tool](img/mrp-3.png) + +
+ +#### Step 4: Create Material Request + +Create Material Request for Items with projected shortfall. + + +![Production Planning Tool](img/mrp-4.png) + +
The Production Planning Tool is used in two stages: - Selection of Open Sales Orders for the period based on “Expected Delivery Date”. - Selection of Items from those Sales Orders. -- Click on “Raise Production Orders” -The tool will update if you have already created Production Orders for this Item against this Sales Order (“Planned Quantity”). + +The tool will update if you have already created Production Orders for a particular Item against its Sales Order (“Planned Quantity”). You can always edit the Item list and increase / reduce quantities to plan your production. > Note: How do you change a Production Plan? The output of the Production Planning Tool is the Production Order. Once your orders are created, you can change them by amending the Production Orders. - diff --git a/docs/user/setup/docs.user.setup.taxes.md b/docs/user/setup/docs.user.setup.taxes.md index 7043429096..ef3bbd5527 100644 --- a/docs/user/setup/docs.user.setup.taxes.md +++ b/docs/user/setup/docs.user.setup.taxes.md @@ -17,6 +17,11 @@ If some of your Items require different tax rates as compared to others, mention - **Inclusive and Exclusive Tax**: ERPNext allows you to enter Item rates which are tax inclusive. + +![Inclusive Tax](img/inclusive-tax.png) + + + - **Exception to the rule**: Item tax settings are required only if a particular Item has a different tax rate than the rate defined in the standard tax Account - **Item tax is overwrite-able**: You can overwrite or change the item tax rate by going to the Item master in the Item tax table. diff --git a/docs/user/website/docs.user.website.add_products_to_website.md b/docs/user/website/docs.user.website.add_products_to_website.md new file mode 100644 index 0000000000..9259ec6634 --- /dev/null +++ b/docs/user/website/docs.user.website.add_products_to_website.md @@ -0,0 +1,60 @@ +--- +{ + "_label": "Add Products to Website" +} +--- +### Add Products to the Website + +To list your Item on the Website, fill the Item details and save the file. Once the file is saved, a plus (+) button will appear next to the Image icon. Click on the plus button and add your Item image. The html code will be generated automatically. + +##### Step 1: Save Image + +![Webimage](img/item-webimage.png) + +
+ +##### Step 2: Check the 'Show in Website' box. + +Under the Website section, please check the box that says 'show in Website'. Once the box is checked, the page will display other fields for entering information. + +![Webimage](img/item-webimage-1.png) + +
+ + +##### Step 3: Enter Website Details + +![Webimage](img/item-webimage-2.png) + + +The page name will be generated automatically. Mention the Item-Group under which the Item will be displayed. + +#### Item Groups + +Mention the Item Group under this column. If you wish to list your Item under the broad category products, name your Item Group as Products. In case you have various varieties of Item and want to classify them under different names, make Item Groups with those names and check the box that says 'show in Website'. For Example, if you wish to create a category called 'Bags', create a Item Group named Bags. + + +![Item Group](img/itemgroup-webimage-bags.png) + +Once the Item Group is created go to the Website Settings page under Website. Enter the Label, Url, and Parent Label. + + +![Item Group](img/itemgroup-website-settings.png) + +
+ +#### Webpage labels + +![Webpage](img/webpage-labels.png) + +Add more Items under a particular Item Group. + +To add more Items under a certain Label, mention the Item Group on the Item Page. The Items will be added automatically on the Webpage, under the Item Group Label. For Example, To add Item-Kiddies Bag and Butterfly Print Bag, check the 'Show in Website'box. The Items will be placed under the Label Bags on the Webpage. + +![Item Group](img/itemgroup-websettings.png) + +
+ +Item Group Display + +![Item Group Display](img/webpage-itemgroup-display.png) \ No newline at end of file diff --git a/docs/user/website/docs.user.website.md b/docs/user/website/docs.user.website.md index b628e710c1..2e9cfc2772 100644 --- a/docs/user/website/docs.user.website.md +++ b/docs/user/website/docs.user.website.md @@ -5,7 +5,8 @@ "docs.user.website.setup", "docs.user.website.web_page", "docs.user.website.style", - "docs.user.website.blog" + "docs.user.website.blog", + "docs.user.website.add_products_to_website" ] } --- From 8728f26442be846b7a2cfebeebf6554d9f7671b4 Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Wed, 6 Nov 2013 14:19:09 +0530 Subject: [PATCH 02/10] Refactored plugin architecture to allow custom script reports --- patches/october_2013/p10_plugins_refactor.py | 24 ++++++++++++++++++++ patches/patch_list.py | 1 + utilities/demo/make_erpnext_demo.py | 4 ++-- 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 patches/october_2013/p10_plugins_refactor.py diff --git a/patches/october_2013/p10_plugins_refactor.py b/patches/october_2013/p10_plugins_refactor.py new file mode 100644 index 0000000000..b37bef4374 --- /dev/null +++ b/patches/october_2013/p10_plugins_refactor.py @@ -0,0 +1,24 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import webnotes, os, shutil + +def execute(): + # changed cache key for plugin code files + for doctype in webnotes.conn.sql_list("""select name from `tabDocType`"""): + webnotes.cache().delete_value("_server_script:"+doctype) + + # move custom script reports to plugins folder + for name in webnotes.conn.sql_list("""select name from `tabReport` + where report_type="Script Report" and is_standard="No" """): + bean = webnotes.bean("Report", name) + bean.save() + + module = webnotes.conn.get_value("DocType", bean.doc.ref_doctype, "module") + path = webnotes.modules.get_doc_path(module, "Report", name) + for extn in ["py", "js"]: + file_path = os.path.join(path, name + "." + extn) + plugins_file_path = webnotes.plugins.get_path(module, "Report", name, extn=extn) + if not os.path.exists(plugins_file_path) and os.path.exists(file_path): + shutil.copyfile(file_path, plugins_file_path) \ No newline at end of file diff --git a/patches/patch_list.py b/patches/patch_list.py index 1581841872..f882739cdc 100644 --- a/patches/patch_list.py +++ b/patches/patch_list.py @@ -237,4 +237,5 @@ patch_list = [ "patches.october_2013.p07_rename_for_territory", "patches.june_2013.p07_taxes_price_list_for_territory", "patches.october_2013.p08_cleanup_after_item_price_module_change", + "patches.october_2013.p10_plugins_refactor", ] \ No newline at end of file diff --git a/utilities/demo/make_erpnext_demo.py b/utilities/demo/make_erpnext_demo.py index cda38d690d..a094239c4b 100644 --- a/utilities/demo/make_erpnext_demo.py +++ b/utilities/demo/make_erpnext_demo.py @@ -106,8 +106,8 @@ def make_demo_login_page(): def make_demo_on_login_script(): import shutil - from core.doctype.custom_script.custom_script import get_custom_server_script_path - custom_script_path = get_custom_server_script_path("Control Panel") + import webnotes.plugins + custom_script_path = webnotes.plugins.get_path("Core", "DocType", "Control Panel") webnotes.create_folder(os.path.dirname(custom_script_path)) shutil.copyfile(os.path.join(os.path.dirname(__file__), "demo_control_panel.py"), custom_script_path) From 04f65a0c564a819cbe29bb734abcb7b9ef94edd0 Mon Sep 17 00:00:00 2001 From: Akhilesh Darjee Date: Wed, 6 Nov 2013 15:45:26 +0530 Subject: [PATCH 03/10] [fix] added owner permission for query report --- selling/report/lead_details/lead_details.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selling/report/lead_details/lead_details.txt b/selling/report/lead_details/lead_details.txt index 6da9b79c49..1383a79550 100644 --- a/selling/report/lead_details/lead_details.txt +++ b/selling/report/lead_details/lead_details.txt @@ -2,7 +2,7 @@ { "creation": "2013-10-22 11:58:16", "docstatus": 0, - "modified": "2013-10-22 12:08:18", + "modified": "2013-11-06 15:01:09", "modified_by": "Administrator", "owner": "Administrator" }, @@ -10,7 +10,7 @@ "doctype": "Report", "is_standard": "Yes", "name": "__common__", - "query": "SELECT\n `tabLead`.name as \"Lead Id:Link/Lead:120\",\n `tabLead`.lead_name as \"Lead Name::120\",\n\t`tabLead`.company_name as \"Company Name::120\",\n\t`tabLead`.status as \"Status::120\",\n\tconcat_ws(', ', \n\t\ttrim(',' from `tabAddress`.address_line1), \n\t\ttrim(',' from tabAddress.address_line2), \n\t\ttabAddress.state, tabAddress.pincode, tabAddress.country\n\t) as 'Address::180',\n\t`tabLead`.phone as \"Phone::100\",\n\t`tabLead`.mobile_no as \"Mobile No::100\",\n\t`tabLead`.email_id as \"Email Id::120\",\n\t`tabLead`.lead_owner as \"Lead Owner::120\",\n\t`tabLead`.source as \"Source::120\",\n\t`tabLead`.territory as \"Territory::120\"\nFROM\n\t`tabLead`\n\tleft join `tabAddress` on (\n\t\t`tabAddress`.lead=`tabLead`.name\n\t)\nWHERE\n\t`tabLead`.docstatus<2\nORDER BY\n\t`tabLead`.name asc", + "query": "SELECT\n `tabLead`.name as \"Lead Id:Link/Lead:120\",\n `tabLead`.lead_name as \"Lead Name::120\",\n\t`tabLead`.company_name as \"Company Name::120\",\n\t`tabLead`.status as \"Status::120\",\n\tconcat_ws(', ', \n\t\ttrim(',' from `tabAddress`.address_line1), \n\t\ttrim(',' from tabAddress.address_line2), \n\t\ttabAddress.state, tabAddress.pincode, tabAddress.country\n\t) as 'Address::180',\n\t`tabLead`.phone as \"Phone::100\",\n\t`tabLead`.mobile_no as \"Mobile No::100\",\n\t`tabLead`.email_id as \"Email Id::120\",\n\t`tabLead`.lead_owner as \"Lead Owner::120\",\n\t`tabLead`.source as \"Source::120\",\n\t`tabLead`.territory as \"Territory::120\",\n `tabLead`.owner as \"Owner:Link/Profile:120\"\nFROM\n\t`tabLead`\n\tleft join `tabAddress` on (\n\t\t`tabAddress`.lead=`tabLead`.name\n\t)\nWHERE\n\t`tabLead`.docstatus<2\nORDER BY\n\t`tabLead`.name asc", "ref_doctype": "Lead", "report_name": "Lead Details", "report_type": "Query Report" From e3af1eaf47810b86fa7b7ef4548cb5cd2c099a17 Mon Sep 17 00:00:00 2001 From: Akhilesh Darjee Date: Wed, 6 Nov 2013 16:59:38 +0530 Subject: [PATCH 04/10] [fix] fixed landed cost wizard for cancel_pr() --- stock/doctype/landed_cost_wizard/landed_cost_wizard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stock/doctype/landed_cost_wizard/landed_cost_wizard.py b/stock/doctype/landed_cost_wizard/landed_cost_wizard.py index 89a3b81d57..071e1dda1f 100644 --- a/stock/doctype/landed_cost_wizard/landed_cost_wizard.py +++ b/stock/doctype/landed_cost_wizard/landed_cost_wizard.py @@ -84,7 +84,7 @@ class DocType: for pr in purchase_receipts: pr_bean = webnotes.bean("Purchase Receipt", pr) - pr_bean.run_method("update_ordered_qty", is_cancelled="Yes") + pr_bean.run_method("update_ordered_qty") webnotes.conn.sql("""delete from `tabStock Ledger Entry` where voucher_type='Purchase Receipt' and voucher_no=%s""", pr) From 6b3358a172d3e9245e87339fc3b60b92aa4fccca Mon Sep 17 00:00:00 2001 From: Priya Date: Thu, 31 Oct 2013 12:26:12 +0530 Subject: [PATCH 05/10] [docs] minor mrp and website --- docs/user/docs.user.md | 1 + docs/user/mfg/docs.user.mfg.planning.md | 46 ++++++++++++-- docs/user/setup/docs.user.setup.taxes.md | 5 ++ ...cs.user.website.add_products_to_website.md | 60 +++++++++++++++++++ docs/user/website/docs.user.website.md | 3 +- 5 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 docs/user/website/docs.user.website.add_products_to_website.md diff --git a/docs/user/docs.user.md b/docs/user/docs.user.md index 1b79ee7606..5593dae819 100644 --- a/docs/user/docs.user.md +++ b/docs/user/docs.user.md @@ -143,6 +143,7 @@ Contents 1. [Style](docs.user.website.style.html) 1. [Blog](docs.user.website.blog.html) 1. [Shopping Cart](docs.user.website.shopping_cart.html) + 1. [Add Products](docs.user.website.add_products_to_website.html) 1. [Tools](docs.user.tools.html) 1. [To Do](docs.user.tools.todo.html) 1. [Calendar](docs.user.tools.calendar.html) diff --git a/docs/user/mfg/docs.user.mfg.planning.md b/docs/user/mfg/docs.user.mfg.planning.md index 8d43ab8142..a8075e162d 100644 --- a/docs/user/mfg/docs.user.mfg.planning.md +++ b/docs/user/mfg/docs.user.mfg.planning.md @@ -15,19 +15,57 @@ To use the Production Planning Tool, go to:
 > Manufacturing > Production Planning Tool -![Production Planning Tool](img/production-planning-tool.png) +![Material Requisition Planning](img/mrp.png) + +
+#### Step 1: Select and get Sales Order + +Select sales orders for MRP using filters (Time, Item, and Customer) + + +![Production Planning Tool](img/mrp-1.png) + +Click on Get Sales Order to generate a list. + +![Production Planning Tool](img/mrp-1.1.png) + +
+ + +#### Step 2: Get Item from Sales Orders. + + You can add/remove or change quantity of these Items. + + ![Production Planning Tool](img/mrp-2.png) + + +
+ +#### Step 3: Create Production Orders + +![Production Planning Tool](img/mrp-3.png) + +
+ +#### Step 4: Create Material Request + +Create Material Request for Items with projected shortfall. + + +![Production Planning Tool](img/mrp-4.png) + +
The Production Planning Tool is used in two stages: - Selection of Open Sales Orders for the period based on “Expected Delivery Date”. - Selection of Items from those Sales Orders. -- Click on “Raise Production Orders” -The tool will update if you have already created Production Orders for this Item against this Sales Order (“Planned Quantity”). + +The tool will update if you have already created Production Orders for a particular Item against its Sales Order (“Planned Quantity”). You can always edit the Item list and increase / reduce quantities to plan your production. > Note: How do you change a Production Plan? The output of the Production Planning Tool is the Production Order. Once your orders are created, you can change them by amending the Production Orders. - diff --git a/docs/user/setup/docs.user.setup.taxes.md b/docs/user/setup/docs.user.setup.taxes.md index 7043429096..ef3bbd5527 100644 --- a/docs/user/setup/docs.user.setup.taxes.md +++ b/docs/user/setup/docs.user.setup.taxes.md @@ -17,6 +17,11 @@ If some of your Items require different tax rates as compared to others, mention - **Inclusive and Exclusive Tax**: ERPNext allows you to enter Item rates which are tax inclusive. + +![Inclusive Tax](img/inclusive-tax.png) + + + - **Exception to the rule**: Item tax settings are required only if a particular Item has a different tax rate than the rate defined in the standard tax Account - **Item tax is overwrite-able**: You can overwrite or change the item tax rate by going to the Item master in the Item tax table. diff --git a/docs/user/website/docs.user.website.add_products_to_website.md b/docs/user/website/docs.user.website.add_products_to_website.md new file mode 100644 index 0000000000..9259ec6634 --- /dev/null +++ b/docs/user/website/docs.user.website.add_products_to_website.md @@ -0,0 +1,60 @@ +--- +{ + "_label": "Add Products to Website" +} +--- +### Add Products to the Website + +To list your Item on the Website, fill the Item details and save the file. Once the file is saved, a plus (+) button will appear next to the Image icon. Click on the plus button and add your Item image. The html code will be generated automatically. + +##### Step 1: Save Image + +![Webimage](img/item-webimage.png) + +
+ +##### Step 2: Check the 'Show in Website' box. + +Under the Website section, please check the box that says 'show in Website'. Once the box is checked, the page will display other fields for entering information. + +![Webimage](img/item-webimage-1.png) + +
+ + +##### Step 3: Enter Website Details + +![Webimage](img/item-webimage-2.png) + + +The page name will be generated automatically. Mention the Item-Group under which the Item will be displayed. + +#### Item Groups + +Mention the Item Group under this column. If you wish to list your Item under the broad category products, name your Item Group as Products. In case you have various varieties of Item and want to classify them under different names, make Item Groups with those names and check the box that says 'show in Website'. For Example, if you wish to create a category called 'Bags', create a Item Group named Bags. + + +![Item Group](img/itemgroup-webimage-bags.png) + +Once the Item Group is created go to the Website Settings page under Website. Enter the Label, Url, and Parent Label. + + +![Item Group](img/itemgroup-website-settings.png) + +
+ +#### Webpage labels + +![Webpage](img/webpage-labels.png) + +Add more Items under a particular Item Group. + +To add more Items under a certain Label, mention the Item Group on the Item Page. The Items will be added automatically on the Webpage, under the Item Group Label. For Example, To add Item-Kiddies Bag and Butterfly Print Bag, check the 'Show in Website'box. The Items will be placed under the Label Bags on the Webpage. + +![Item Group](img/itemgroup-websettings.png) + +
+ +Item Group Display + +![Item Group Display](img/webpage-itemgroup-display.png) \ No newline at end of file diff --git a/docs/user/website/docs.user.website.md b/docs/user/website/docs.user.website.md index b628e710c1..2e9cfc2772 100644 --- a/docs/user/website/docs.user.website.md +++ b/docs/user/website/docs.user.website.md @@ -5,7 +5,8 @@ "docs.user.website.setup", "docs.user.website.web_page", "docs.user.website.style", - "docs.user.website.blog" + "docs.user.website.blog", + "docs.user.website.add_products_to_website" ] } --- From 427935f2a6b3ece94c00da3a4114a73ae51b0e3a Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Thu, 7 Nov 2013 15:53:09 +0530 Subject: [PATCH 06/10] [minor] [fix] demo timezone --- utilities/demo/make_demo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utilities/demo/make_demo.py b/utilities/demo/make_demo.py index be42395031..4637836612 100644 --- a/utilities/demo/make_demo.py +++ b/utilities/demo/make_demo.py @@ -18,7 +18,7 @@ company = "Wind Power LLC" company_abbr = "WP" country = "United States" currency = "USD" -time_zone = "America/New York" +time_zone = "America/New_York" start_date = '2013-01-01' bank_name = "Citibank" runs_for = None From eb0ea44f2cc6a28df29f41c5eef4013c84b03a73 Mon Sep 17 00:00:00 2001 From: Priya Date: Thu, 7 Nov 2013 17:24:51 +0530 Subject: [PATCH 07/10] [docs] minor sales return --- .../selling/docs.user.selling.sales_order.md | 4 +-- .../stock/docs.user.stock.sales_return.md | 33 +++++++++++++++++-- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/docs/user/selling/docs.user.selling.sales_order.md b/docs/user/selling/docs.user.selling.sales_order.md index 882964b9c6..fc53780bf1 100644 --- a/docs/user/selling/docs.user.selling.sales_order.md +++ b/docs/user/selling/docs.user.selling.sales_order.md @@ -3,7 +3,7 @@ "_label": "Sales Order" } --- -The Sales Order confirms your sales and triggers purchase (**Purchase Request**) shipment (**Delivery Note**), billing (**Sales Invoice**) and manufacturing (**Production Plan**) +The Sales Order confirms your sales and triggers purchase (**Material Request**) shipment (**Delivery Note**), billing (**Sales Invoice**) and manufacturing (**Production Plan**) A Sales Order is usually a binding Contract with your Customer. @@ -41,7 +41,7 @@ The “Packing List” table will be automatically updated when you “Save” t #### Reservation and Warehouses -If your Sales Order contains Items for which inventory is tracked (Is Stock Item is “Yes”). ERPNext will ask you for “Reservation Warehouse”. If you have set a default Warehouse for the Item, it will automatically set this Warehouse here. +If your Sales Order contains Items for which inventory is tracked (Is Stock Item is “Yes”) then, ERPNext will ask you for “Reservation Warehouse”. If you have set a default Warehouse for the Item, it will automatically set this Warehouse here. This “reserved” quantity will help you project what is the quantity you need to purchase based on all your commitments. diff --git a/docs/user/stock/docs.user.stock.sales_return.md b/docs/user/stock/docs.user.stock.sales_return.md index 3198db0f0e..3bf6db0f26 100644 --- a/docs/user/stock/docs.user.stock.sales_return.md +++ b/docs/user/stock/docs.user.stock.sales_return.md @@ -7,15 +7,42 @@ Goods sold being returned is quite a common practice in Business. They could be > Stock > Stock Entry > New Stock Entry +#### Step 1: Select Purpose as "Sales Return" -![Sales Return](img/sales-return.png) +#### Step 2: Enter the Delivery Note No. or Sales Invoice No. + +
+![Sales Return](img/sales-return-1.png) + +
+ +#### Step 3: Enter Item Details + + +![Sales Return](img/sales-return-2.png) + +
+ +#### Step 4: Mention Contact Information of the Customer. + + +![Sales Return](img/sales-return-3.png) + +
- For Sales Return click on Stock Entry - Select Sales Return under Purpose -- Mention the Delivery Note number and the Sales Invoice number. +- Mention the Delivery Note number or the Sales Invoice number. - Mention Contact Information of the Customer. -- Save the file. \ No newline at end of file +- Save the file and Submit the Stock Entry. + +#### Credit Note + +Once the Stock Entry is submitted, you can click on Make Credit Note button and a new Journal Voucher will be created prefilled with the customer's account and Items' income account. + +![Sales Return](img/sales-return-4.png) + From 9ff22fd508f99a52527ca9badee2b3afeb1e448b Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Thu, 7 Nov 2013 20:44:30 +0530 Subject: [PATCH 08/10] [minor] [refactor] Accounts Receivable --- .../accounts_receivable.py | 298 +++++++++--------- 1 file changed, 153 insertions(+), 145 deletions(-) diff --git a/accounts/report/accounts_receivable/accounts_receivable.py b/accounts/report/accounts_receivable/accounts_receivable.py index 86a2475359..18fd35cef7 100644 --- a/accounts/report/accounts_receivable/accounts_receivable.py +++ b/accounts/report/accounts_receivable/accounts_receivable.py @@ -3,150 +3,158 @@ from __future__ import unicode_literals import webnotes -from webnotes import msgprint, _ -from webnotes.utils import getdate, nowdate, flt, cstr +from webnotes import _ +from webnotes.utils import getdate, nowdate, flt + +class AccountsReceivableReport(object): + def __init__(self, filters=None): + self.filters = webnotes._dict(filters or {}) + self.filters.report_date = getdate(self.filters.report_date or nowdate()) + self.age_as_on = getdate(nowdate()) \ + if self.filters.report_date > getdate(nowdate()) \ + else self.filters.report_date + + def run(self): + return self.get_columns(), self.get_data() + + def get_columns(self): + return [ + "Posting Date:Date:80", "Account:Link/Account:150", "Customer::150", + "Voucher Type::110", "Voucher No::120", "Remarks::150", + "Due Date:Date:80", "Territory:Link/Territory:80", + "Invoiced Amount:Currency:100", "Payment Received:Currency:100", + "Outstanding Amount:Currency:100", "Age:Int:50", "0-30:Currency:100", + "30-60:Currency:100", "60-90:Currency:100", "90-Above:Currency:100" + ] + + def get_data(self): + data = [] + future_vouchers = self.get_entries_after(self.filters.report_date) + for gle in self.get_entries_till(self.filters.report_date): + if self.is_receivable(gle, future_vouchers): + outstanding_amount = self.get_outstanding_amount(gle, self.filters.report_date) + if abs(outstanding_amount) > 0.01: + due_date = self.get_due_date(gle) + invoiced_amount = gle.debit if (gle.debit > 0) else 0 + payment_received = invoiced_amount - outstanding_amount + row = [gle.posting_date, gle.account, self.get_customer(gle.account), + gle.voucher_type, gle.voucher_no, gle.remarks, due_date, + self.get_territory(gle.account), invoiced_amount, payment_received, + outstanding_amount] + entry_date = due_date if self.filters.ageing_based_on=="Due Date" \ + else gle.posting_date + row += self.get_age(entry_date, outstanding_amount) + data.append(row) + return data + + def get_entries_after(self, report_date): + # returns a distinct list + return list(set([(e.voucher_type, e.voucher_no) for e in self.get_gl_entries() + if getdate(e.posting_date) > report_date])) + + def get_entries_till(self, report_date): + # returns a generator + return (e for e in self.get_gl_entries() + if getdate(e.posting_date) <= report_date) + + def is_receivable(self, gle, future_vouchers): + return ((not gle.against_voucher) or (gle.against_voucher==gle.voucher_no) or + ((gle.against_voucher_type, gle.against_voucher) in future_vouchers)) + + def get_outstanding_amount(self, gle, report_date): + payment_received = 0.0 + for e in self.get_gl_entries_for(gle.account, gle.voucher_type, gle.voucher_no): + if getdate(e.posting_date) <= report_date and e.name!=gle.name: + payment_received += (flt(e.credit) - flt(e.debit)) + + return flt(gle.debit) - flt(gle.credit) - payment_received + + def get_age(self, entry_date, oustanding_amount): + # [0-30, 30-60, 60-90, 90-above] + outstanding_range = [0.0, 0.0, 0.0, 0.0] + if not (self.age_as_on and entry_date): + return [0] + outstanding_range + + age = (self.age_as_on - getdate(entry_date)).days or 0 + index = None + for i, days in enumerate([30, 60, 90]): + if age <= days: + index = i + break + + if index is None: index = 3 + outstanding_range[index] = oustanding_amount + + return [age] + outstanding_range + + def get_customer(self, account): + return self.get_account_map().get(account).get("customer_name") or "" + + def get_territory(self, account): + return self.get_account_map().get(account).get("territory") or "" + + def get_account_map(self): + if not hasattr(self, "account_map"): + self.account_map = dict(((r.name, r) for r in webnotes.conn.sql("""select + account.name, customer.customer_name, customer.territory + from `tabAccount` account, `tabCustomer` customer + where account.master_type="Customer" + and customer.name=account.master_name""", as_dict=True))) + + return self.account_map + + def get_due_date(self, gle): + if not hasattr(self, "invoice_due_date_map"): + # TODO can be restricted to posting date + self.invoice_due_date_map = dict(webnotes.conn.sql("""select name, due_date + from `tabSales Invoice` where docstatus=1""")) + + return gle.voucher_type == "Sales Invoice" \ + and self.invoice_due_date_map.get(gle.voucher_no) or "" + + def get_gl_entries(self): + if not hasattr(self, "gl_entries"): + conditions, values = self.prepare_conditions() + self.gl_entries = webnotes.conn.sql("""select * from `tabGL Entry` + where docstatus < 2 {} order by posting_date, account""".format(conditions), + values, as_dict=True) + + return self.gl_entries + + def prepare_conditions(self): + conditions = [""] + values = {} + + if self.filters.company: + conditions.append("company=%(company)s") + values["company"] = self.filters.company + + if self.filters.account: + conditions.append("account=%(account)s") + values["account"] = self.filters.account + else: + account_map = self.get_account_map() + if not account_map: + webnotes.throw(_("No Customer Accounts found.")) + else: + accounts_list = ['"{}"'.format(ac) for ac in account_map] + conditions.append("account in ({})".format(", ".join(accounts_list))) + + return " and ".join(conditions), values + + def get_gl_entries_for(self, account, against_voucher_type, against_voucher): + if not hasattr(self, "gl_entries_map"): + self.gl_entries_map = {} + for gle in self.get_gl_entries(): + if gle.against_voucher_type and gle.against_voucher: + self.gl_entries_map.setdefault(gle.account, {})\ + .setdefault(gle.against_voucher_type, {})\ + .setdefault(gle.against_voucher, [])\ + .append(gle) + + return self.gl_entries_map.get(account, {})\ + .get(against_voucher_type, {})\ + .get(against_voucher, []) def execute(filters=None): - if not filters: filters = {} - columns = get_columns() - entries = get_gl_entries(filters) - account_customer = dict(webnotes.conn.sql("""select account.name, customer.customer_name - from `tabAccount` account, `tabCustomer` customer - where account.master_type="Customer" and customer.name=account.master_name""")) - - entries_after_report_date = [[gle.voucher_type, gle.voucher_no] - for gle in get_gl_entries(filters, upto_report_date=False)] - - account_territory_map = get_account_territory_map() - si_due_date_map = get_si_due_date_map() - - # Age of the invoice on this date - age_on = getdate(filters.get("report_date")) > getdate(nowdate()) \ - and nowdate() or filters.get("report_date") - - data = [] - for gle in entries: - if cstr(gle.against_voucher) == gle.voucher_no or not gle.against_voucher \ - or [gle.against_voucher_type, gle.against_voucher] in entries_after_report_date: - - due_date = (gle.voucher_type == "Sales Invoice") \ - and si_due_date_map.get(gle.voucher_no) or "" - - invoiced_amount = gle.debit > 0 and gle.debit or 0 - outstanding_amount = get_outstanding_amount(gle, - filters.get("report_date") or nowdate()) - - if abs(flt(outstanding_amount)) > 0.01: - payment_amount = invoiced_amount - outstanding_amount - row = [gle.posting_date, gle.account, account_customer.get(gle.account, ""), - gle.voucher_type, gle.voucher_no, - gle.remarks, due_date, account_territory_map.get(gle.account), - invoiced_amount, payment_amount, outstanding_amount] - # Ageing - if filters.get("ageing_based_on") == "Due Date": - ageing_based_on_date = due_date - else: - ageing_based_on_date = gle.posting_date - row += get_ageing_data(ageing_based_on_date, age_on, outstanding_amount) - - data.append(row) - - return columns, data - -def get_columns(): - return [ - "Posting Date:Date:80", "Account:Link/Account:150", "Customer::150", "Voucher Type::110", - "Voucher No::120", "Remarks::150", "Due Date:Date:80", "Territory:Link/Territory:80", - "Invoiced Amount:Currency:100", "Payment Received:Currency:100", - "Outstanding Amount:Currency:100", "Age:Int:50", "0-30:Currency:100", - "30-60:Currency:100", "60-90:Currency:100", "90-Above:Currency:100" - ] - -def get_gl_entries(filters, upto_report_date=True): - conditions, customer_accounts = get_conditions(filters, upto_report_date) - return webnotes.conn.sql("""select * from `tabGL Entry` - where docstatus < 2 %s order by posting_date, account""" % - (conditions), tuple(customer_accounts), as_dict=1) - -def get_conditions(filters, upto_report_date=True): - conditions = "" - if filters.get("company"): - conditions += " and company='%s'" % filters["company"] - - customer_accounts = [] - if filters.get("account"): - customer_accounts = [filters["account"]] - else: - customer_accounts = webnotes.conn.sql_list("""select name from `tabAccount` - where ifnull(master_type, '') = 'Customer' and docstatus < 2 %s""" % - conditions, filters) - - if customer_accounts: - conditions += " and account in (%s)" % (", ".join(['%s']*len(customer_accounts))) - else: - msgprint(_("No Customer Accounts found. Customer Accounts are identified based on \ - 'Master Type' value in account record."), raise_exception=1) - - if filters.get("report_date"): - if upto_report_date: - conditions += " and posting_date<='%s'" % filters["report_date"] - else: - conditions += " and posting_date>'%s'" % filters["report_date"] - - return conditions, customer_accounts - -def get_account_territory_map(): - account_territory_map = {} - for each in webnotes.conn.sql("""select t2.name, t1.territory from `tabCustomer` t1, - `tabAccount` t2 where t1.name = t2.master_name"""): - account_territory_map[each[0]] = each[1] - - return account_territory_map - -def get_si_due_date_map(): - """ get due_date from sales invoice """ - si_due_date_map = {} - for t in webnotes.conn.sql("""select name, due_date from `tabSales Invoice`"""): - si_due_date_map[t[0]] = t[1] - - return si_due_date_map - -def get_outstanding_amount(gle, report_date): - payment_amount = webnotes.conn.sql(""" - select sum(ifnull(credit, 0)) - sum(ifnull(debit, 0)) - from `tabGL Entry` - where account = %s and posting_date <= %s and against_voucher_type = %s - and against_voucher = %s and name != %s""", - (gle.account, report_date, gle.voucher_type, gle.voucher_no, gle.name))[0][0] - - return flt(gle.debit) - flt(gle.credit) - flt(payment_amount) - -def get_payment_amount(gle, report_date, entries_after_report_date): - payment_amount = 0 - if flt(gle.credit) > 0 and (not gle.against_voucher or - [gle.against_voucher_type, gle.against_voucher] in entries_after_report_date): - payment_amount = gle.credit - elif flt(gle.debit) > 0: - payment_amount = webnotes.conn.sql(""" - select sum(ifnull(credit, 0)) - sum(ifnull(debit, 0)) from `tabGL Entry` - where account = %s and posting_date <= %s and against_voucher_type = %s - and against_voucher = %s and name != %s""", - (gle.account, report_date, gle.voucher_type, gle.voucher_no, gle.name))[0][0] - - return flt(payment_amount) - -def get_ageing_data(ageing_based_on_date, age_on, outstanding_amount): - val1 = val2 = val3 = val4 = diff = 0 - diff = age_on and ageing_based_on_date \ - and (getdate(age_on) - getdate(ageing_based_on_date)).days or 0 - - if diff <= 30: - val1 = outstanding_amount - elif 30 < diff <= 60: - val2 = outstanding_amount - elif 60 < diff <= 90: - val3 = outstanding_amount - elif diff > 90: - val4 = outstanding_amount - - return [diff, val1, val2, val3, val4] \ No newline at end of file + return AccountsReceivableReport(filters).run() \ No newline at end of file From 3990104e9bf24b5e64b172771d4e5317e0ed7340 Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Thu, 7 Nov 2013 21:04:01 +0530 Subject: [PATCH 09/10] [minor] [refactor] Accounts Receivable --- .../accounts_receivable.py | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/accounts/report/accounts_receivable/accounts_receivable.py b/accounts/report/accounts_receivable/accounts_receivable.py index 18fd35cef7..02a9a5e6b2 100644 --- a/accounts/report/accounts_receivable/accounts_receivable.py +++ b/accounts/report/accounts_receivable/accounts_receivable.py @@ -43,7 +43,7 @@ class AccountsReceivableReport(object): outstanding_amount] entry_date = due_date if self.filters.ageing_based_on=="Due Date" \ else gle.posting_date - row += self.get_age(entry_date, outstanding_amount) + row += get_age(self.age_as_on, entry_date, outstanding_amount) data.append(row) return data @@ -69,24 +69,6 @@ class AccountsReceivableReport(object): return flt(gle.debit) - flt(gle.credit) - payment_received - def get_age(self, entry_date, oustanding_amount): - # [0-30, 30-60, 60-90, 90-above] - outstanding_range = [0.0, 0.0, 0.0, 0.0] - if not (self.age_as_on and entry_date): - return [0] + outstanding_range - - age = (self.age_as_on - getdate(entry_date)).days or 0 - index = None - for i, days in enumerate([30, 60, 90]): - if age <= days: - index = i - break - - if index is None: index = 3 - outstanding_range[index] = oustanding_amount - - return [age] + outstanding_range - def get_customer(self, account): return self.get_account_map().get(account).get("customer_name") or "" @@ -157,4 +139,22 @@ class AccountsReceivableReport(object): .get(against_voucher, []) def execute(filters=None): - return AccountsReceivableReport(filters).run() \ No newline at end of file + return AccountsReceivableReport(filters).run() + +def get_ageing_data(age_as_on, entry_date, oustanding_amount): + # [0-30, 30-60, 60-90, 90-above] + outstanding_range = [0.0, 0.0, 0.0, 0.0] + if not (self.age_as_on and entry_date): + return [0] + outstanding_range + + age = (self.age_as_on - getdate(entry_date)).days or 0 + index = None + for i, days in enumerate([30, 60, 90]): + if age <= days: + index = i + break + + if index is None: index = 3 + outstanding_range[index] = oustanding_amount + + return [age] + outstanding_range \ No newline at end of file From ce7ed98e3132d7aa0323a050ae2e713ad1b626ca Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Thu, 7 Nov 2013 21:09:06 +0530 Subject: [PATCH 10/10] [minor] [refactor] Accounts Receivable --- accounts/report/accounts_receivable/accounts_receivable.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/accounts/report/accounts_receivable/accounts_receivable.py b/accounts/report/accounts_receivable/accounts_receivable.py index 02a9a5e6b2..87ca4c7ad9 100644 --- a/accounts/report/accounts_receivable/accounts_receivable.py +++ b/accounts/report/accounts_receivable/accounts_receivable.py @@ -43,7 +43,7 @@ class AccountsReceivableReport(object): outstanding_amount] entry_date = due_date if self.filters.ageing_based_on=="Due Date" \ else gle.posting_date - row += get_age(self.age_as_on, entry_date, outstanding_amount) + row += get_ageing_data(self.age_as_on, entry_date, outstanding_amount) data.append(row) return data @@ -144,10 +144,10 @@ def execute(filters=None): def get_ageing_data(age_as_on, entry_date, oustanding_amount): # [0-30, 30-60, 60-90, 90-above] outstanding_range = [0.0, 0.0, 0.0, 0.0] - if not (self.age_as_on and entry_date): + if not (age_as_on and entry_date): return [0] + outstanding_range - age = (self.age_as_on - getdate(entry_date)).days or 0 + age = (age_as_on - getdate(entry_date)).days or 0 index = None for i, days in enumerate([30, 60, 90]): if age <= days: