From caa4380d49ecd5c1f0435e78b50c5c266b6d19bb Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 26 Dec 2013 11:07:58 +0530 Subject: [PATCH] Merge with 3.3.8 --- .../general_ledger/__init__.py | 0 .../general_ledger/general_ledger.js | 51 ++++++++ .../general_ledger/general_ledger.py | 111 ++++++++++++++++++ .../general_ledger/general_ledger.txt | 21 ++++ .../1311/p07_scheduler_errors_digest.py | 32 +++++ .../1311/p08_email_digest_recipients.py | 11 ++ erpnext/patches/1312/__init__.py | 0 .../1312/p01_delete_old_stock_reports.py | 17 +++ .../p02_update_item_details_in_item_price.py | 10 ++ erpnext/stock/report/stock_ageing/__init__.py | 0 .../stock/report/stock_ageing/stock_ageing.js | 40 +++++++ .../stock/report/stock_ageing/stock_ageing.py | 94 +++++++++++++++ .../report/stock_ageing/stock_ageing.txt | 21 ++++ .../report/stock_projected_qty/__init__.py | 0 .../stock_projected_qty.js | 31 +++++ .../stock_projected_qty.py | 50 ++++++++ .../stock_projected_qty.txt | 22 ++++ 17 files changed, 511 insertions(+) create mode 100644 erpnext/accounts/report/accounts_payable/general_ledger/__init__.py create mode 100644 erpnext/accounts/report/accounts_payable/general_ledger/general_ledger.js create mode 100644 erpnext/accounts/report/accounts_payable/general_ledger/general_ledger.py create mode 100644 erpnext/accounts/report/accounts_payable/general_ledger/general_ledger.txt create mode 100644 erpnext/patches/1311/p07_scheduler_errors_digest.py create mode 100644 erpnext/patches/1311/p08_email_digest_recipients.py create mode 100644 erpnext/patches/1312/__init__.py create mode 100644 erpnext/patches/1312/p01_delete_old_stock_reports.py create mode 100644 erpnext/patches/1312/p02_update_item_details_in_item_price.py create mode 100644 erpnext/stock/report/stock_ageing/__init__.py create mode 100644 erpnext/stock/report/stock_ageing/stock_ageing.js create mode 100644 erpnext/stock/report/stock_ageing/stock_ageing.py create mode 100644 erpnext/stock/report/stock_ageing/stock_ageing.txt create mode 100644 erpnext/stock/report/stock_projected_qty/__init__.py create mode 100644 erpnext/stock/report/stock_projected_qty/stock_projected_qty.js create mode 100644 erpnext/stock/report/stock_projected_qty/stock_projected_qty.py create mode 100644 erpnext/stock/report/stock_projected_qty/stock_projected_qty.txt diff --git a/erpnext/accounts/report/accounts_payable/general_ledger/__init__.py b/erpnext/accounts/report/accounts_payable/general_ledger/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/report/accounts_payable/general_ledger/general_ledger.js b/erpnext/accounts/report/accounts_payable/general_ledger/general_ledger.js new file mode 100644 index 00000000000..7985277115d --- /dev/null +++ b/erpnext/accounts/report/accounts_payable/general_ledger/general_ledger.js @@ -0,0 +1,51 @@ +// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +wn.query_reports["General Ledger"] = { + "filters": [ + { + "fieldname":"company", + "label": wn._("Company"), + "fieldtype": "Link", + "options": "Company", + "default": wn.defaults.get_user_default("company"), + "reqd": 1 + }, + { + "fieldname":"account", + "label": wn._("Account"), + "fieldtype": "Link", + "options": "Account" + }, + { + "fieldname":"voucher_no", + "label": wn._("Voucher No"), + "fieldtype": "Data", + }, + { + "fieldname":"group_by", + "label": wn._("Group by"), + "fieldtype": "Select", + "options": "\nGroup by Account\nGroup by Voucher" + }, + { + "fieldtype": "Break", + }, + { + "fieldname":"from_date", + "label": wn._("From Date"), + "fieldtype": "Date", + "default": wn.datetime.add_months(wn.datetime.get_today(), -1), + "reqd": 1, + "width": "60px" + }, + { + "fieldname":"to_date", + "label": wn._("To Date"), + "fieldtype": "Date", + "default": wn.datetime.get_today(), + "reqd": 1, + "width": "60px" + } + ] +} \ No newline at end of file diff --git a/erpnext/accounts/report/accounts_payable/general_ledger/general_ledger.py b/erpnext/accounts/report/accounts_payable/general_ledger/general_ledger.py new file mode 100644 index 00000000000..575ccda9fa9 --- /dev/null +++ b/erpnext/accounts/report/accounts_payable/general_ledger/general_ledger.py @@ -0,0 +1,111 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import webnotes +from webnotes.utils import flt, add_days +from webnotes import _ +from erpnext.accounts.utils import get_balance_on + +def execute(filters=None): + account_details = webnotes.conn.get_value("Account", filters["account"], + ["debit_or_credit", "group_or_ledger"], as_dict=True) if filters.get("account") else None + validate_filters(filters, account_details) + + columns = get_columns() + data = [] + if filters.get("group_by"): + data += get_grouped_gle(filters) + else: + data += get_gl_entries(filters) + if data: + data.append(get_total_row(data)) + + if account_details: + data = [get_opening_balance_row(filters, account_details.debit_or_credit)] + data + \ + [get_closing_balance_row(filters, account_details.debit_or_credit)] + + return columns, data + +def validate_filters(filters, account_details): + if account_details and account_details.group_or_ledger == "Ledger" \ + and filters.get("group_by") == "Group by Account": + webnotes.throw(_("Can not filter based on Account, if grouped by Account")) + + if filters.get("voucher_no") and filters.get("group_by") == "Group by Voucher": + webnotes.throw(_("Can not filter based on Voucher No, if grouped by Voucher")) + +def get_columns(): + return ["Posting Date:Date:100", "Account:Link/Account:200", "Debit:Float:100", + "Credit:Float:100", "Voucher Type::120", "Voucher No::160", "Link::20", + "Cost Center:Link/Cost Center:100", "Remarks::200"] + +def get_opening_balance_row(filters, debit_or_credit): + opening_balance = get_balance_on(filters["account"], add_days(filters["from_date"], -1)) + return get_balance_row(opening_balance, debit_or_credit, "Opening Balance") + +def get_closing_balance_row(filters, debit_or_credit): + closing_balance = get_balance_on(filters["account"], filters["to_date"]) + return get_balance_row(closing_balance, debit_or_credit, "Closing Balance") + +def get_balance_row(balance, debit_or_credit, balance_label): + if debit_or_credit == "Debit": + return ["", balance_label, balance, 0.0, "", "", ""] + else: + return ["", balance_label, 0.0, balance, "", "", ""] + +def get_gl_entries(filters): + gl_entries = webnotes.conn.sql("""select + posting_date, account, debit, credit, voucher_type, voucher_no, cost_center, remarks + from `tabGL Entry` + where company=%(company)s + and posting_date between %(from_date)s and %(to_date)s + {conditions} + order by posting_date, account"""\ + .format(conditions=get_conditions(filters)), filters, as_list=1) + + for d in gl_entries: + icon = """""" \ + % ("/".join(["#Form", d[4], d[5]]),) + d.insert(6, icon) + + return gl_entries + +def get_conditions(filters): + conditions = [] + if filters.get("account"): + lft, rgt = webnotes.conn.get_value("Account", filters["account"], ["lft", "rgt"]) + conditions.append("""account in (select name from tabAccount + where lft>=%s and rgt<=%s and docstatus<2)""" % (lft, rgt)) + if filters.get("voucher_no"): + conditions.append("voucher_no=%(voucher_no)s") + + return "and {}".format(" and ".join(conditions)) if conditions else "" + +def get_grouped_gle(filters): + gle_map = {} + gle = get_gl_entries(filters) + for d in gle: + gle_map.setdefault(d[1 if filters["group_by"]=="Group by Account" else 5], []).append(d) + + data = [] + for entries in gle_map.values(): + subtotal_debit = subtotal_credit = 0.0 + for entry in entries: + data.append(entry) + subtotal_debit += flt(entry[2]) + subtotal_credit += flt(entry[3]) + + data.append(["", "Total", subtotal_debit, subtotal_credit, "", "", ""]) + + if data: + data.append(get_total_row(gle)) + return data + +def get_total_row(gle): + total_debit = total_credit = 0.0 + for d in gle: + total_debit += flt(d[2]) + total_credit += flt(d[3]) + + return ["", "Total Debit/Credit", total_debit, total_credit, "", "", ""] \ No newline at end of file diff --git a/erpnext/accounts/report/accounts_payable/general_ledger/general_ledger.txt b/erpnext/accounts/report/accounts_payable/general_ledger/general_ledger.txt new file mode 100644 index 00000000000..ef169dbe888 --- /dev/null +++ b/erpnext/accounts/report/accounts_payable/general_ledger/general_ledger.txt @@ -0,0 +1,21 @@ +[ + { + "creation": "2013-12-06 13:22:23", + "docstatus": 0, + "modified": "2013-12-06 13:22:23", + "modified_by": "Administrator", + "owner": "Administrator" + }, + { + "doctype": "Report", + "is_standard": "Yes", + "name": "__common__", + "ref_doctype": "GL Entry", + "report_name": "General Ledger", + "report_type": "Script Report" + }, + { + "doctype": "Report", + "name": "General Ledger" + } +] \ No newline at end of file diff --git a/erpnext/patches/1311/p07_scheduler_errors_digest.py b/erpnext/patches/1311/p07_scheduler_errors_digest.py new file mode 100644 index 00000000000..4527f187259 --- /dev/null +++ b/erpnext/patches/1311/p07_scheduler_errors_digest.py @@ -0,0 +1,32 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import webnotes + +def execute(): + webnotes.reload_doc("setup", "doctype", "email_digest") + + from webnotes.profile import get_system_managers + system_managers = get_system_managers(only_name=True) + if not system_managers: + return + + # no default company + company = webnotes.conn.sql_list("select name from `tabCompany`") + if company: + company = company[0] + if not company: + return + + # scheduler errors digest + edigest = webnotes.new_bean("Email Digest") + edigest.doc.fields.update({ + "name": "Scheduler Errors", + "company": company, + "frequency": "Daily", + "enabled": 1, + "recipient_list": "\n".join(system_managers), + "scheduler_errors": 1 + }) + edigest.insert() diff --git a/erpnext/patches/1311/p08_email_digest_recipients.py b/erpnext/patches/1311/p08_email_digest_recipients.py new file mode 100644 index 00000000000..fad540844f5 --- /dev/null +++ b/erpnext/patches/1311/p08_email_digest_recipients.py @@ -0,0 +1,11 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import webnotes + +def execute(): + from webnotes.utils import extract_email_id + for name, recipients in webnotes.conn.sql("""select name, recipient_list from `tabEmail Digest`"""): + recipients = "\n".join([extract_email_id(r) for r in recipients.split("\n")]) + webnotes.conn.set_value("Email Digest", name, "recipient_list", recipients) \ No newline at end of file diff --git a/erpnext/patches/1312/__init__.py b/erpnext/patches/1312/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/patches/1312/p01_delete_old_stock_reports.py b/erpnext/patches/1312/p01_delete_old_stock_reports.py new file mode 100644 index 00000000000..e8d620ba33f --- /dev/null +++ b/erpnext/patches/1312/p01_delete_old_stock_reports.py @@ -0,0 +1,17 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +def execute(): + import webnotes, os, shutil + from webnotes.utils import get_base_path + + webnotes.delete_doc('Page', 'stock-ledger') + webnotes.delete_doc('Page', 'stock-ageing') + webnotes.delete_doc('Page', 'stock-level') + webnotes.delete_doc('Page', 'general-ledger') + + for d in [["stock", "stock_ledger"], ["stock", "stock_ageing"], + ["stock", "stock_level"], ["accounts", "general_ledger"]]: + path = os.path.join(get_base_path(), "app", d[0], "page", d[1]) + if os.path.exists(path): + shutil.rmtree(path) \ No newline at end of file diff --git a/erpnext/patches/1312/p02_update_item_details_in_item_price.py b/erpnext/patches/1312/p02_update_item_details_in_item_price.py new file mode 100644 index 00000000000..c19988c9ef7 --- /dev/null +++ b/erpnext/patches/1312/p02_update_item_details_in_item_price.py @@ -0,0 +1,10 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import webnotes + +def execute(): + webnotes.conn.sql("""update `tabItem Price` ip INNER JOIN `tabItem` i + ON (ip.item_code = i.name) + set ip.item_name = i.item_name, ip.item_description = i.description""") \ No newline at end of file diff --git a/erpnext/stock/report/stock_ageing/__init__.py b/erpnext/stock/report/stock_ageing/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.js b/erpnext/stock/report/stock_ageing/stock_ageing.js new file mode 100644 index 00000000000..f9e84b8a655 --- /dev/null +++ b/erpnext/stock/report/stock_ageing/stock_ageing.js @@ -0,0 +1,40 @@ +// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +wn.query_reports["Stock Ageing"] = { + "filters": [ + { + "fieldname":"company", + "label": wn._("Company"), + "fieldtype": "Link", + "options": "Company", + "default": wn.defaults.get_user_default("company"), + "reqd": 1 + }, + { + "fieldname":"to_date", + "label": wn._("To Date"), + "fieldtype": "Date", + "default": wn.datetime.get_today(), + "reqd": 1 + }, + { + "fieldname":"warehouse", + "label": wn._("Warehouse"), + "fieldtype": "Link", + "options": "Warehouse" + }, + { + "fieldname":"item_code", + "label": wn._("Item"), + "fieldtype": "Link", + "options": "Item" + }, + { + "fieldname":"brand", + "label": wn._("Brand"), + "fieldtype": "Link", + "options": "Brand" + } + ] +} \ No newline at end of file diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py new file mode 100644 index 00000000000..1a84f939d32 --- /dev/null +++ b/erpnext/stock/report/stock_ageing/stock_ageing.py @@ -0,0 +1,94 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import webnotes +from webnotes.utils import date_diff + +def execute(filters=None): + + columns = get_columns() + item_details = get_fifo_queue(filters) + to_date = filters["to_date"] + data = [] + for item, item_dict in item_details.items(): + fifo_queue = item_dict["fifo_queue"] + details = item_dict["details"] + if not fifo_queue: continue + + average_age = get_average_age(fifo_queue, to_date) + earliest_age = date_diff(to_date, fifo_queue[0][1]) + latest_age = date_diff(to_date, fifo_queue[-1][1]) + + data.append([item, details.item_name, details.description, details.item_group, + details.brand, average_age, earliest_age, latest_age, details.stock_uom]) + + return columns, data + +def get_average_age(fifo_queue, to_date): + batch_age = age_qty = total_qty = 0.0 + for batch in fifo_queue: + batch_age = date_diff(to_date, batch[1]) + age_qty += batch_age * batch[0] + total_qty += batch[0] + + return (age_qty / total_qty) if total_qty else 0.0 + +def get_columns(): + return ["Item Code:Link/Item:100", "Item Name::100", "Description::200", + "Item Group:Link/Item Group:100", "Brand:Link/Brand:100", "Average Age:Float:100", + "Earliest:Int:80", "Latest:Int:80", "UOM:Link/UOM:100"] + +def get_fifo_queue(filters): + item_details = {} + for d in get_stock_ledger_entries(filters): + item_details.setdefault(d.name, {"details": d, "fifo_queue": []}) + fifo_queue = item_details[d.name]["fifo_queue"] + if d.actual_qty > 0: + fifo_queue.append([d.actual_qty, d.posting_date]) + else: + qty_to_pop = abs(d.actual_qty) + while qty_to_pop: + batch = fifo_queue[0] if fifo_queue else [0, None] + if 0 < batch[0] <= qty_to_pop: + # if batch qty > 0 + # not enough or exactly same qty in current batch, clear batch + qty_to_pop -= batch[0] + fifo_queue.pop(0) + else: + # all from current batch + batch[0] -= qty_to_pop + qty_to_pop = 0 + + return item_details + +def get_stock_ledger_entries(filters): + return webnotes.conn.sql("""select + item.name, item.item_name, item_group, brand, description, item.stock_uom, + actual_qty, posting_date + from `tabStock Ledger Entry` sle, + (select name, item_name, description, stock_uom, brand, item_group + from `tabItem` {item_conditions}) item + where item_code = item.name and + company = %(company)s and + posting_date <= %(to_date)s + {sle_conditions} + order by posting_date, posting_time, sle.name"""\ + .format(item_conditions=get_item_conditions(filters), + sle_conditions=get_sle_conditions(filters)), filters, as_dict=True) + +def get_item_conditions(filters): + conditions = [] + if filters.get("item_code"): + conditions.append("item_code=%(item_code)s") + if filters.get("brand"): + conditions.append("brand=%(brand)s") + + return "where {}".format(" and ".join(conditions)) if conditions else "" + +def get_sle_conditions(filters): + conditions = [] + if filters.get("warehouse"): + conditions.append("warehouse=%(warehouse)s") + + return "and {}".format(" and ".join(conditions)) if conditions else "" \ No newline at end of file diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.txt b/erpnext/stock/report/stock_ageing/stock_ageing.txt new file mode 100644 index 00000000000..b88ebce5ed1 --- /dev/null +++ b/erpnext/stock/report/stock_ageing/stock_ageing.txt @@ -0,0 +1,21 @@ +[ + { + "creation": "2013-12-02 17:09:31", + "docstatus": 0, + "modified": "2013-12-02 17:09:31", + "modified_by": "Administrator", + "owner": "Administrator" + }, + { + "doctype": "Report", + "is_standard": "Yes", + "name": "__common__", + "ref_doctype": "Item", + "report_name": "Stock Ageing", + "report_type": "Script Report" + }, + { + "doctype": "Report", + "name": "Stock Ageing" + } +] \ No newline at end of file diff --git a/erpnext/stock/report/stock_projected_qty/__init__.py b/erpnext/stock/report/stock_projected_qty/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js new file mode 100644 index 00000000000..8c25e5d88a5 --- /dev/null +++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js @@ -0,0 +1,31 @@ +// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +wn.query_reports["Stock Projected Qty"] = { + "filters": [ + { + "fieldname":"company", + "label": wn._("Company"), + "fieldtype": "Link", + "options": "Company" + }, + { + "fieldname":"warehouse", + "label": wn._("Warehouse"), + "fieldtype": "Link", + "options": "Warehouse" + }, + { + "fieldname":"item_code", + "label": wn._("Item"), + "fieldtype": "Link", + "options": "Item" + }, + { + "fieldname":"brand", + "label": wn._("Brand"), + "fieldtype": "Link", + "options": "Brand" + } + ] +} \ No newline at end of file diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py new file mode 100644 index 00000000000..d335ebf8462 --- /dev/null +++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py @@ -0,0 +1,50 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import webnotes + +def execute(filters=None): + columns = get_columns() + + data = webnotes.conn.sql("""select + item.name, item.item_name, description, item_group, brand, warehouse, item.stock_uom, + actual_qty, planned_qty, indented_qty, ordered_qty, reserved_qty, + projected_qty, item.re_order_level, item.re_order_qty + from `tabBin` bin, + (select name, company from tabWarehouse + {warehouse_conditions}) wh, + (select name, item_name, description, stock_uom, item_group, + brand, re_order_level, re_order_qty + from `tabItem` {item_conditions}) item + where item_code = item.name and warehouse = wh.name + order by item.name, wh.name"""\ + .format(item_conditions=get_item_conditions(filters), + warehouse_conditions=get_warehouse_conditions(filters)), filters) + + return columns, data + +def get_columns(): + return ["Item Code:Link/Item:140", "Item Name::100", "Description::200", + "Item Group:Link/Item Group:100", "Brand:Link/Brand:100", "Warehouse:Link/Warehouse:120", + "UOM:Link/UOM:100", "Actual Qty:Float:100", "Planned Qty:Float:100", + "Requested Qty:Float:110", "Ordered Qty:Float:100", "Reserved Qty:Float:100", + "Projected Qty:Float:100", "Reorder Level:Float:100", "Reorder Qty:Float:100"] + +def get_item_conditions(filters): + conditions = [] + if filters.get("item_code"): + conditions.append("name=%(item_code)s") + if filters.get("brand"): + conditions.append("brand=%(brand)s") + + return "where {}".format(" and ".join(conditions)) if conditions else "" + +def get_warehouse_conditions(filters): + conditions = [] + if filters.get("company"): + conditions.append("company=%(company)s") + if filters.get("warehouse"): + conditions.append("name=%(warehouse)s") + + return "where {}".format(" and ".join(conditions)) if conditions else "" \ No newline at end of file diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.txt b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.txt new file mode 100644 index 00000000000..1998f7ae740 --- /dev/null +++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.txt @@ -0,0 +1,22 @@ +[ + { + "creation": "2013-12-04 18:21:56", + "docstatus": 0, + "modified": "2013-12-04 18:21:56", + "modified_by": "Administrator", + "owner": "Administrator" + }, + { + "add_total_row": 1, + "doctype": "Report", + "is_standard": "Yes", + "name": "__common__", + "ref_doctype": "Item", + "report_name": "Stock Projected Qty", + "report_type": "Script Report" + }, + { + "doctype": "Report", + "name": "Stock Projected Qty" + } +] \ No newline at end of file