Merge with 3.3.8
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 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/report/accounts_payable/general_ledger/__init__.py
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 0000000..7985277
--- /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 0000000..575ccda
--- /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 = """<a href="%s"><i class="icon icon-share" style="cursor: pointer;"></i></a>""" \
+ % ("/".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 0000000..ef169db
--- /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 0000000..4527f18
--- /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 0000000..fad5408
--- /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 0000000..e69de29
--- /dev/null
+++ b/erpnext/patches/1312/__init__.py
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 0000000..e8d620b
--- /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 0000000..c19988c
--- /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 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/report/stock_ageing/__init__.py
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 0000000..f9e84b8
--- /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 0000000..1a84f93
--- /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 0000000..b88ebce
--- /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 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/report/stock_projected_qty/__init__.py
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 0000000..8c25e5d
--- /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 0000000..d335ebf
--- /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 0000000..1998f7a
--- /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