[enhancement] added reserved_qty_for_production in bin and updated item dashboard
diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js
index fcbc469..98a972b 100644
--- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js
+++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js
@@ -3,10 +3,13 @@
frappe.require("assets/erpnext/js/financial_statements.js");
-frappe.query_reports["Profit and Loss Statement"] = erpnext.financial_statements;
+frappe.query_reports["Profit and Loss Statement"] = $.extend({}, erpnext.financial_statements);
frappe.query_reports["Profit and Loss Statement"]["filters"].push({
"fieldname": "accumulated_values",
"label": __("Accumulated Values"),
"fieldtype": "Check"
-})
\ No newline at end of file
+});
+
+console.log(frappe.query_reports["Profit and Loss Statement"]);
+
diff --git a/erpnext/buying/doctype/purchase_common/purchase_common.py b/erpnext/buying/doctype/purchase_common/purchase_common.py
index cd28c88..9516f24 100644
--- a/erpnext/buying/doctype/purchase_common/purchase_common.py
+++ b/erpnext/buying/doctype/purchase_common/purchase_common.py
@@ -72,15 +72,15 @@
if items and len(items) != len(set(items)) and \
not cint(frappe.db.get_single_value("Buying Settings", "allow_multiple_items") or 0):
- frappe.msgprint(_("Warning: Same item has been entered multiple times."))
+ frappe.msgprint(_("Warning: Same item has been entered multiple times."), small=True)
def check_for_closed_status(self, doctype, docname):
status = frappe.db.get_value(doctype, docname, "status")
-
+
if status == "Closed":
frappe.throw(_("{0} {1} status is {2}").format(doctype, docname, status), frappe.InvalidStatusError)
-
+
def check_docstatus(self, check, doctype, docname, detail_doctype = ''):
if check == 'Next':
submitted = frappe.db.sql("""select t1.name from `tab%s` t1,`tab%s` t2
diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js
index 0b8b8ef..5088f2e 100644
--- a/erpnext/buying/doctype/supplier/supplier.js
+++ b/erpnext/buying/doctype/supplier/supplier.js
@@ -7,7 +7,7 @@
frappe.setup_language_field(frm);
},
refresh: function(frm) {
- frm.dashboard.show_links();
+ frm.dashboard.show_dashboard();
if(frappe.defaults.get_default("supp_master_name")!="Naming Series") {
frm.toggle_display("naming_series", false);
diff --git a/erpnext/hr/doctype/employee/employee.js b/erpnext/hr/doctype/employee/employee.js
index 5c241fb..cb26154 100755
--- a/erpnext/hr/doctype/employee/employee.js
+++ b/erpnext/hr/doctype/employee/employee.js
@@ -25,7 +25,7 @@
refresh: function() {
var me = this;
erpnext.toggle_naming_series();
- this.frm.dashboard.show_links();
+ this.frm.dashboard.show_dashboard();
},
date_of_birth: function() {
diff --git a/erpnext/manufacturing/doctype/production_order/.py b/erpnext/manufacturing/doctype/production_order/.py
new file mode 100644
index 0000000..4476b16
--- /dev/null
+++ b/erpnext/manufacturing/doctype/production_order/.py
@@ -0,0 +1,8 @@
+import frappe
+
+def set_required_items(production_order):
+ pass
+
+def reserve_for_production(production_order):
+ '''Reserve pending raw materials for production'''
+ pass
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/production_order/production_order.json b/erpnext/manufacturing/doctype/production_order/production_order.json
index 14e3674..3b71b76 100644
--- a/erpnext/manufacturing/doctype/production_order/production_order.json
+++ b/erpnext/manufacturing/doctype/production_order/production_order.json
@@ -78,7 +78,7 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "\nDraft\nSubmitted\nStopped\nIn Process\nCompleted\nCancelled",
+ "options": "\nDraft\nSubmitted\nNot Started\nStopped\nIn Process\nCompleted\nCancelled",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
@@ -309,6 +309,33 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "description": "Warehouse for reserving items",
+ "fieldname": "source_warehouse",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Source Warehouse",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Warehouse",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
"fieldname": "wip_warehouse",
"fieldtype": "Link",
"hidden": 0,
@@ -1024,6 +1051,32 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "required_items",
+ "fieldtype": "Table",
+ "hidden": 1,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Required Items",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Production Order Item",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
}
],
"hide_heading": 0,
@@ -1036,7 +1089,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-04-06 05:44:08.681263",
+ "modified": "2016-04-18 08:42:47.582203",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Order",
@@ -1083,6 +1136,7 @@
"write": 0
}
],
+ "quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"sort_order": "ASC",
diff --git a/erpnext/manufacturing/doctype/production_order/production_order.py b/erpnext/manufacturing/doctype/production_order/production_order.py
index 88ee997..6c8dfd5 100644
--- a/erpnext/manufacturing/doctype/production_order/production_order.py
+++ b/erpnext/manufacturing/doctype/production_order/production_order.py
@@ -15,6 +15,8 @@
from erpnext.stock.doctype.stock_entry.stock_entry import get_additional_costs
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
from erpnext.stock.stock_balance import get_planned_qty, update_bin_qty
+from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
+from erpnext.stock.utils import get_bin
class OverProductionError(frappe.ValidationError): pass
class StockOverProductionError(frappe.ValidationError): pass
@@ -27,13 +29,6 @@
class ProductionOrder(Document):
def validate(self):
- if self.docstatus == 0:
- self.status = "Draft"
-
- from erpnext.controllers.status_updater import validate_status
- validate_status(self.status, ["Draft", "Submitted", "Stopped",
- "In Process", "Completed", "Cancelled"])
-
self.validate_production_item()
if self.bom_no:
validate_bom_no(self.production_item, self.bom_no)
@@ -43,6 +38,7 @@
self.calculate_operating_cost()
self.validate_qty()
self.validate_operation_time()
+ self.status = self.get_status()
from erpnext.utilities.transaction_base import validate_uom_is_integer
validate_uom_is_integer(self, "stock_uom", ["qty", "produced_qty"])
@@ -65,7 +61,7 @@
def validate_warehouse(self):
from erpnext.stock.utils import validate_warehouse_company
- for w in [self.fg_warehouse, self.wip_warehouse]:
+ for w in [self.source_warehouse, self.fg_warehouse, self.wip_warehouse]:
validate_warehouse_company(w, self.company)
def calculate_operating_cost(self):
@@ -113,23 +109,36 @@
def update_status(self, status=None):
+ '''Update status of production order'''
+ status = self.get_status()
+ if status != self.status:
+ self.db_set("status", status)
+
+ self.update_required_items()
+
+ def get_status(self, status=None):
+ '''Return the status based on stock entries against this production order'''
if not status:
status = self.status
- if status != 'Stopped':
- stock_entries = frappe._dict(frappe.db.sql("""select purpose, sum(fg_completed_qty)
- from `tabStock Entry` where production_order=%s and docstatus=1
- group by purpose""", self.name))
+ if self.docstatus==0:
+ status = 'Draft'
+ elif self.docstatus==1:
+ if status != 'Stopped':
+ stock_entries = frappe._dict(frappe.db.sql("""select purpose, sum(fg_completed_qty)
+ from `tabStock Entry` where production_order=%s and docstatus=1
+ group by purpose""", self.name))
- status = "Submitted"
- if stock_entries:
- status = "In Process"
- produced_qty = stock_entries.get("Manufacture")
- if flt(produced_qty) == flt(self.qty):
- status = "Completed"
+ status = "Not Started"
+ if stock_entries:
+ status = "In Process"
+ produced_qty = stock_entries.get("Manufacture")
+ if flt(produced_qty) == flt(self.qty):
+ status = "Completed"
+ else:
+ status = 'Cancelled'
- if status != self.status:
- self.db_set("status", status)
+ return status
def update_production_order_qty(self):
"""Update **Manufactured Qty** and **Material Transferred for Qty** in Production Order
@@ -147,13 +156,16 @@
self.db_set(fieldname, qty)
+ def before_submit(self):
+ self.set_required_items()
+
def on_submit(self):
if not self.wip_warehouse:
frappe.throw(_("Work-in-Progress Warehouse is required before Submit"))
if not self.fg_warehouse:
frappe.throw(_("For Warehouse is required before Submit"))
- frappe.db.set(self,'status', 'Submitted')
+ self.update_reserved_qty_for_production()
self.make_time_logs()
self.update_completed_qty_in_material_request()
self.update_planned_qty()
@@ -162,6 +174,7 @@
self.validate_cancel()
frappe.db.set(self,'status', 'Cancelled')
+ self.clear_required_items()
self.delete_time_logs()
self.update_completed_qty_in_material_request()
self.update_planned_qty()
@@ -342,6 +355,74 @@
if not d.time_in_mins > 0:
frappe.throw(_("Operation Time must be greater than 0 for Operation {0}".format(d.operation)))
+ def update_required_items(self):
+ '''
+ update bin reserved_qty_for_production
+ called from Stock Entry for production, after submit, cancel
+ '''
+ if self.docstatus==1 and self.source_warehouse:
+ if self.material_transferred_for_manufacturing == self.produced_qty:
+ # clear required items table and save document
+ self.clear_required_items()
+ else:
+ # calculate transferred qty based on submitted
+ # stock entries
+ self.update_transaferred_qty_for_required_items()
+
+ # update in bin
+ self.update_reserved_qty_for_production()
+
+ def clear_required_items(self):
+ '''Remove the required_items table and update the bins'''
+ items = [d.item_code for d in self.required_items]
+ self.required_items = []
+
+ self.update_child_table('required_items')
+
+ # completed, update reserved qty in bin
+ self.update_reserved_qty_for_production(items)
+
+ def update_reserved_qty_for_production(self, items=None):
+ '''update reserved_qty_for_production in bins'''
+ if not self.source_warehouse:
+ return
+
+ if not items:
+ items = [d.item_code for d in self.required_items]
+
+ for item in items:
+ stock_bin = get_bin(item, self.source_warehouse)
+ stock_bin.update_reserved_qty_for_production()
+
+ def set_required_items(self):
+ '''set required_items for production to keep track of reserved qty'''
+ if self.source_warehouse:
+ item_dict = get_bom_items_as_dict(self.bom_no, self.company, qty=self.qty,
+ fetch_exploded = self.use_multi_level_bom)
+
+ for item in item_dict.values():
+ self.append('required_items', {'item_code': item.item_code,
+ 'required_qty': item.qty})
+
+ #print frappe.as_json(self.required_items)
+
+ def update_transaferred_qty_for_required_items(self):
+ '''update transferred qty from submitted stock entries for that item against
+ the production order'''
+
+ for d in self.required_items:
+ transferred_qty = frappe.db.sql('''select count(qty)
+ from `tabStock Entry` entry, `tabStock Entry Detail` detail
+ where
+ entry.production_order = %s
+ entry.purpose = "Material Transfer for Manufacture"
+ and entry.docstatus = 1
+ and detail.parent = entry.name
+ and detail.item_code = %s''', (self.name, d.item_code))[0][0]
+
+ d.db_set('transferred_qty', transferred_qty, update_modified = False)
+
+
@frappe.whitelist()
def get_item_details(item):
res = frappe.db.sql("""select stock_uom, description
@@ -372,6 +453,8 @@
stock_entry.fg_completed_qty = qty or (flt(production_order.qty) - flt(production_order.produced_qty))
if purpose=="Material Transfer for Manufacture":
+ if production_order.source_warehouse:
+ stock_entry.from_warehouse = production_order.source_warehouse
stock_entry.to_warehouse = production_order.wip_warehouse
else:
stock_entry.from_warehouse = production_order.wip_warehouse
diff --git a/erpnext/manufacturing/doctype/production_order/test_production_order.py b/erpnext/manufacturing/doctype/production_order/test_production_order.py
index 3b558a9..1879e78 100644
--- a/erpnext/manufacturing/doctype/production_order/test_production_order.py
+++ b/erpnext/manufacturing/doctype/production_order/test_production_order.py
@@ -5,14 +5,19 @@
from __future__ import unicode_literals
import unittest
import frappe
-from frappe.utils import flt, time_diff_in_hours, now, add_days
+from frappe.utils import flt, time_diff_in_hours, now, add_days, cint
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
from erpnext.manufacturing.doctype.production_order.production_order \
import make_stock_entry, ItemHasVariantError
from erpnext.stock.doctype.stock_entry import test_stock_entry
from erpnext.projects.doctype.time_log.time_log import OverProductionLoggedError
+from erpnext.stock.utils import get_bin
class TestProductionOrder(unittest.TestCase):
+ def setUp(self):
+ self.warehouse = '_Test Warehouse 2 - _TC'
+ self.item = '_Test Item'
+
def check_planned_qty(self):
set_perpetual_inventory(0)
@@ -140,6 +145,73 @@
prod_order = make_prod_order_test_record(item="_Test Variant Item", qty=1, do_not_save=True)
self.assertRaises(ItemHasVariantError, prod_order.save)
+ def test_reserved_qty_for_production_submit(self):
+ self.bin1_at_start = get_bin(self.item, self.warehouse)
+
+ # reset to correct value
+ self.bin1_at_start.update_reserved_qty_for_production()
+
+ self.pro_order = make_prod_order_test_record(item="_Test FG Item", qty=2,
+ source_warehouse=self.warehouse)
+
+ self.bin1_on_submit = get_bin(self.item, self.warehouse)
+
+ # reserved qty for production is updated
+ self.assertEqual(cint(self.bin1_at_start.reserved_qty_for_production) + 2,
+ cint(self.bin1_on_submit.reserved_qty_for_production))
+ self.assertEqual(cint(self.bin1_at_start.projected_qty),
+ cint(self.bin1_on_submit.projected_qty) + 2)
+
+ def test_reserved_qty_for_production_cancel(self):
+ self.test_reserved_qty_for_production_submit()
+
+ self.pro_order.cancel()
+
+ bin1_on_cancel = get_bin(self.item, self.warehouse)
+
+ # reserved_qty_for_producion updated
+ self.assertEqual(cint(self.bin1_at_start.reserved_qty_for_production),
+ cint(bin1_on_cancel.reserved_qty_for_production))
+ self.assertEqual(self.bin1_at_start.projected_qty,
+ cint(bin1_on_cancel.projected_qty))
+
+ def test_reserved_qty_for_production_on_stock_entry(self):
+ test_stock_entry.make_stock_entry(item_code="_Test Item",
+ target= self.warehouse, qty=100, basic_rate=100)
+ test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
+ target= self.warehouse, qty=100, basic_rate=100)
+
+ self.test_reserved_qty_for_production_submit()
+
+ s = frappe.get_doc(make_stock_entry(self.pro_order.name,
+ "Material Transfer for Manufacture", 2))
+
+ s.submit()
+
+ bin1_on_start_production = get_bin(self.item, self.warehouse)
+
+ # reserved_qty_for_producion updated
+ self.assertEqual(cint(self.bin1_at_start.reserved_qty_for_production),
+ cint(bin1_on_start_production.reserved_qty_for_production))
+
+ # projected qty will now be 2 less (becuase of item movement)
+ self.assertEqual(cint(self.bin1_at_start.projected_qty),
+ cint(bin1_on_start_production.projected_qty) + 2)
+
+ s = frappe.get_doc(make_stock_entry(self.pro_order.name, "Manufacture", 2))
+
+ bin1_on_end_production = get_bin(self.item, self.warehouse)
+
+ # no change in reserved / projected
+ self.assertEqual(cint(bin1_on_end_production.reserved_qty_for_production),
+ cint(bin1_on_start_production.reserved_qty_for_production))
+ self.assertEqual(cint(bin1_on_end_production.projected_qty),
+ cint(bin1_on_end_production.projected_qty))
+
+ # required_items removed
+ self.pro_order.reload()
+ self.assertEqual(len(self.pro_order.required_items), 0)
+
def make_prod_order_test_record(**args):
args = frappe._dict(args)
@@ -152,6 +224,10 @@
pro_order.fg_warehouse = args.fg_warehouse or "_Test Warehouse 1 - _TC"
pro_order.company = args.company or "_Test Company"
pro_order.stock_uom = "_Test UOM"
+
+ if args.source_warehouse:
+ pro_order.source_warehouse = args.source_warehouse
+
if args.planned_start_date:
pro_order.planned_start_date = args.planned_start_date
diff --git a/erpnext/manufacturing/doctype/production_order_item/__init__.py b/erpnext/manufacturing/doctype/production_order_item/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/doctype/production_order_item/__init__.py
diff --git a/erpnext/manufacturing/doctype/production_order_item/production_order_item.json b/erpnext/manufacturing/doctype/production_order_item/production_order_item.json
new file mode 100644
index 0000000..2b93549
--- /dev/null
+++ b/erpnext/manufacturing/doctype/production_order_item/production_order_item.json
@@ -0,0 +1,110 @@
+{
+ "allow_copy": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "creation": "2016-04-18 07:38:26.314642",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "fields": [
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Item Code",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Item",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "required_qty",
+ "fieldtype": "Float",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Required Qty",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "transferred_qty",
+ "fieldtype": "Float",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Transferred Qty",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ }
+ ],
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "in_create": 0,
+ "in_dialog": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 1,
+ "max_attachments": 0,
+ "modified": "2016-04-18 07:38:26.314642",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Production Order Item",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/production_order_item/production_order_item.py b/erpnext/manufacturing/doctype/production_order_item/production_order_item.py
new file mode 100644
index 0000000..c18338c
--- /dev/null
+++ b/erpnext/manufacturing/doctype/production_order_item/production_order_item.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+
+class ProductionOrderItem(Document):
+ pass
diff --git a/erpnext/patches/v7_0/update_party_status.py b/erpnext/patches/v7_0/update_party_status.py
index 1307377..208b476 100644
--- a/erpnext/patches/v7_0/update_party_status.py
+++ b/erpnext/patches/v7_0/update_party_status.py
@@ -1,9 +1,21 @@
import frappe
-from erpnext.accounts.party_status import update_status
+from erpnext.accounts.party_status import status_depends_on, default_status
+from frappe.desk.notifications import get_filters_for
def execute():
- for doctype in ('Customer', 'Supplier'):
- frappe.reload_doctype(doctype)
- for doc in frappe.get_all(doctype):
- doc = frappe.get_doc(doctype, doc.name)
- update_status(doc)
\ No newline at end of file
+ for party_type in ('Customer', 'Supplier'):
+ frappe.reload_doctype(party_type)
+
+ # set all as default status
+ frappe.db.sql('update `tab{0}` set status=%s'.format(party_type), default_status[party_type])
+
+ for doctype in status_depends_on[party_type]:
+ filters = get_filters_for(doctype)
+ parties = frappe.get_all(doctype, fields="{0} as party".format(party_type.lower()),
+ filters=filters, limit_page_length=1)
+
+ parties = filter(None, [p.party for p in parties])
+
+ if parties:
+ frappe.db.sql('update `tab{0}` set status="Open" where name in ({1})'.format(party_type,
+ ', '.join(len(parties) * ['%s'])), parties)
\ No newline at end of file
diff --git a/erpnext/projects/doctype/time_log/time_log.json b/erpnext/projects/doctype/time_log/time_log.json
index 21e68cc..050fc54 100644
--- a/erpnext/projects/doctype/time_log/time_log.json
+++ b/erpnext/projects/doctype/time_log/time_log.json
@@ -11,6 +11,32 @@
"document_type": "Setup",
"fields": [
{
+ "allow_on_submit": 1,
+ "bold": 1,
+ "collapsible": 0,
+ "depends_on": "__islocal",
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Title",
+ "length": 0,
+ "no_copy": 1,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
"allow_on_submit": 0,
"bold": 1,
"collapsible": 0,
@@ -896,31 +922,6 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
- },
- {
- "allow_on_submit": 1,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "title",
- "fieldtype": "Data",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "label": "Title",
- "length": 0,
- "no_copy": 1,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
}
],
"hide_heading": 0,
@@ -933,7 +934,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-03-29 15:55:12.780956",
+ "modified": "2016-04-15 07:51:03.097280",
"modified_by": "Administrator",
"module": "Projects",
"name": "Time Log",
@@ -953,8 +954,6 @@
"print": 1,
"read": 1,
"report": 1,
- "restrict": 0,
- "restricted": 1,
"role": "Projects User",
"set_user_permissions": 0,
"share": 1,
@@ -975,8 +974,6 @@
"print": 1,
"read": 1,
"report": 1,
- "restrict": 0,
- "restricted": 0,
"role": "Projects Manager",
"set_user_permissions": 0,
"share": 1,
@@ -984,6 +981,7 @@
"write": 1
}
],
+ "quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"sort_order": "ASC",
diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js
index 557a392..3c87dab 100644
--- a/erpnext/public/js/financial_statements.js
+++ b/erpnext/public/js/financial_statements.js
@@ -68,5 +68,20 @@
"tree": true,
"name_field": "account",
"parent_field": "parent_account",
- "initial_depth": 3
+ "initial_depth": 3,
+ onload: function(report) {
+ // dropdown for links to other financial statements
+ report.page.add_inner_button(__("Balance Sheet"), function() {
+ var filters = report.get_values();
+ frappe.set_route('query-report', 'Balance Sheet', {company: filters.company});
+ }, 'Financial Statements');
+ report.page.add_inner_button(__("Profit and Loss"), function() {
+ var filters = report.get_values();
+ frappe.set_route('query-report', 'Profit and Loss Statement', {company: filters.company});
+ }, 'Financial Statements');
+ report.page.add_inner_button(__("Cash Flow Statement"), function() {
+ var filters = report.get_values();
+ frappe.set_route('query-report', 'Cash Flow', {company: filters.company});
+ }, 'Financial Statements');
+ },
};
diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js
index 95bed47..d30924e 100644
--- a/erpnext/selling/doctype/customer/customer.js
+++ b/erpnext/selling/doctype/customer/customer.js
@@ -7,7 +7,7 @@
frappe.setup_language_field(frm);
},
refresh: function(frm) {
- frm.dashboard.show_links();
+ frm.dashboard.show_dashboard();
if(frappe.defaults.get_default("cust_master_name")!="Naming Series") {
frm.toggle_display("naming_series", false);
diff --git a/erpnext/stock/doctype/bin/bin.js b/erpnext/stock/doctype/bin/bin.js
new file mode 100644
index 0000000..40411b6
--- /dev/null
+++ b/erpnext/stock/doctype/bin/bin.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Bin', {
+ refresh: function(frm) {
+
+ }
+});
diff --git a/erpnext/stock/doctype/bin/bin.json b/erpnext/stock/doctype/bin/bin.json
index 9eb3995..bb0de3f 100644
--- a/erpnext/stock/doctype/bin/bin.json
+++ b/erpnext/stock/doctype/bin/bin.json
@@ -16,6 +16,7 @@
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
"in_filter": 1,
"in_list_view": 1,
"label": "Warehouse",
@@ -42,6 +43,7 @@
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
"in_filter": 1,
"in_list_view": 1,
"label": "Item Code",
@@ -69,6 +71,7 @@
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Reserved Quantity",
@@ -95,6 +98,7 @@
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
"in_filter": 1,
"in_list_view": 1,
"label": "Actual Quantity",
@@ -121,6 +125,7 @@
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Ordered Quantity",
@@ -147,6 +152,7 @@
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Requested Quantity",
@@ -172,6 +178,7 @@
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Planned Qty",
@@ -197,6 +204,7 @@
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Projected Qty",
@@ -218,10 +226,36 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "fieldname": "reserved_qty_for_production",
+ "fieldtype": "Float",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Reserved Qty for Production",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
"fieldname": "ma_rate",
"fieldtype": "Float",
"hidden": 1,
"ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Moving Average Rate",
@@ -247,6 +281,7 @@
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
"in_filter": 1,
"in_list_view": 0,
"label": "UOM",
@@ -273,6 +308,7 @@
"fieldtype": "Float",
"hidden": 1,
"ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "FCFS Rate",
@@ -298,6 +334,7 @@
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Valuation Rate",
@@ -323,6 +360,7 @@
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Stock Value",
@@ -350,7 +388,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-02-10 02:39:45.738623",
+ "modified": "2016-04-18 08:12:57.341517",
"modified_by": "Administrator",
"module": "Stock",
"name": "Bin",
@@ -417,8 +455,10 @@
"write": 0
}
],
+ "quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"search_fields": "item_code,warehouse",
- "sort_order": "ASC"
+ "sort_order": "ASC",
+ "track_seen": 0
}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py
index ae7c6e7..a1580d5 100644
--- a/erpnext/stock/doctype/bin/bin.py
+++ b/erpnext/stock/doctype/bin/bin.py
@@ -65,12 +65,15 @@
self.indented_qty = flt(self.indented_qty) + flt(args.get("indented_qty"))
self.planned_qty = flt(self.planned_qty) + flt(args.get("planned_qty"))
- self.projected_qty = flt(self.actual_qty) + flt(self.ordered_qty) + \
- flt(self.indented_qty) + flt(self.planned_qty) - flt(self.reserved_qty)
-
+ self.set_projected_qty()
self.save()
update_item_projected_qty(self.item_code)
+ def set_projected_qty(self):
+ self.projected_qty = (flt(self.actual_qty) + flt(self.ordered_qty)
+ + flt(self.indented_qty) + flt(self.planned_qty) - flt(self.reserved_qty)
+ - flt(self.reserved_qty_for_production))
+
def get_first_sle(self):
sle = frappe.db.sql("""
select * from `tabStock Ledger Entry`
@@ -81,8 +84,25 @@
""", (self.item_code, self.warehouse), as_dict=1)
return sle and sle[0] or None
+ def update_reserved_qty_for_production(self):
+ '''Update qty reserved for production from Production Item tables
+ in open production orders'''
+ self.reserved_qty_for_production = frappe.db.sql('''select sum(required_qty - transferred_qty)
+ from `tabProduction Order` pro, `tabProduction Order Item` item
+ where
+ item.item_code = %s
+ and item.parent = pro.name
+ and pro.docstatus = 1
+ and pro.source_warehouse = %s''', (self.item_code, self.warehouse))[0][0]
+
+ self.set_projected_qty()
+
+ self.db_set('reserved_qty_for_production', self.reserved_qty_for_production)
+ self.db_set('projected_qty', self.projected_qty)
+
+
def update_item_projected_qty(item_code):
- '''Set Item project qty'''
+ '''Set total_projected_qty in Item as sum of projected qty in all warehouses'''
frappe.db.sql('''update tabItem set
total_projected_qty = ifnull((select sum(projected_qty) from tabBin where item_code=%s), 0)
where name=%s''', (item_code, item_code))
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 900c40c..4a4de4c 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -16,12 +16,6 @@
},
- dashboard_update: function(frm) {
- if(frm.dashboard_data.stock_data && frm.dashboard_data.stock_data.length) {
- frm.dashboard.add_stats(frappe.render_template('item_dashboard', {data: frm.dashboard_data.stock_data}))
- }
- },
-
refresh: function(frm) {
if(frm.doc.is_stock_item) {
@@ -82,7 +76,25 @@
erpnext.item.toggle_attributes(frm);
- frm.dashboard.show_links();
+ frm.dashboard.show_dashboard();
+ },
+
+ dashboard_update: function(frm) {
+ if(frm.dashboard_data.stock_data && frm.dashboard_data.stock_data.length) {
+ var max_count = 0;
+ frm.dashboard_data.stock_data.forEach(function(d) {
+ d.actual_or_pending = d.projected_qty - d.reserved_qty;
+ d.pending_qty = 0;
+ if(d.actual_or_pending > d.actual_qty) {
+ d.pending_qty = d.actual_or_pending - d.actual_qty;
+ }
+
+ max_count = Math.max(d.actual_or_pending, d.actual_qty,
+ d.reserved_qty, max_count);
+ })
+ frm.dashboard.add_stats(frappe.render_template('item_dashboard',
+ {data: frm.dashboard_data.stock_data, max_count: max_count}));
+ }
},
validate: function(frm){
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index f0e5f29..649f97b 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -633,8 +633,8 @@
group by posting_date''', name))
def get_stock_data(name):
- return frappe.get_all('Bin', fields=['warehouse', 'actual_qty', 'projected_qty'],
- filters={'item_code': name})
+ return frappe.get_all('Bin', fields=['warehouse', 'actual_qty', 'projected_qty', 'reserved_qty'],
+ filters={'item_code': name}, order_by = 'warehouse asc')
def validate_end_of_life(item_code, end_of_life=None, disabled=None, verbose=1):
if (not end_of_life) or (disabled is None):
diff --git a/erpnext/stock/doctype/item/item_dashboard.html b/erpnext/stock/doctype/item/item_dashboard.html
index a002a50..cef209a 100644
--- a/erpnext/stock/doctype/item/item_dashboard.html
+++ b/erpnext/stock/doctype/item/item_dashboard.html
@@ -1,12 +1,39 @@
-<div style="padding-left: 15px;">
+<div style="padding-left: 15px; padding-right: 15px;">
<h5>Stock Levels</h5>
- <div class="row">
- <div class="col-md-6 col-xs-12">
- <ul class="list-unstyled">
- {% data.every(function(d) { %}
- <li class="small">{{ d.warehouse }}: {{ d.actual_qty }} ({{ d.projected_qty }})</li>
- {% }) %}
- </ul>
- </div>
- </div>
+ <ul class="list-group">
+ {% for(var i=0; i < data.length; i++) { var d = data[i]; %}
+ <li class="list-group-item" style="background-color: inherit;">
+ <div class="row">
+ <div class="col-sm-8 small" style="margin-top: 8px;">{{ d.warehouse }}</div>
+ <div class="col-sm-4 small">
+ <span class="inline-graph">
+ <span class="inline-graph-half" title="{{ __("Reserved Qty") }}">
+ <span class="inline-graph-count">{{ d.reserved_qty }}</span>
+ <span class="inline-graph-bar">
+ <span class="inline-graph-bar-inner"
+ style="width: {{ cint(Math.abs(d.reserved_qty)/max_count * 100) || 5 }}%">
+ </span>
+ </span>
+ </span>
+ <span class="inline-graph-half" title="{{ __("Acutal Qty {0} / Waiting Qty {1}", [d.actual_qty, d.pending_qty]) }}">
+ <span class="inline-graph-count">
+ {{ d.actual_qty }} {{ (d.pending_qty > 0) ? ("(" + d.pending_qty+ ")") : "" }}
+ </span>
+ <span class="inline-graph-bar">
+ <span class="inline-graph-bar-inner dark"
+ style="width: {{ cint(d.actual_qty/max_count * 100) }}%">
+ </span>
+ {% if(d.pending_qty > 0) { %}
+ <span class="inline-graph-bar-inner" title="{{ __("Projected Qty") }}"
+ style="width: {{ cint(d.pending_qty/max_count * 100) }}%">
+ </span>
+ {% } %}
+ </span>
+ </span>
+ </span>
+ </div>
+ </div>
+ </li>
+ {% } %}
+ </ul>
</div>
\ No newline at end of file
diff --git a/erpnext/stock/doctype/warehouse/test_records.json b/erpnext/stock/doctype/warehouse/test_records.json
index d5df175..e2162d2 100644
--- a/erpnext/stock/doctype/warehouse/test_records.json
+++ b/erpnext/stock/doctype/warehouse/test_records.json
@@ -13,6 +13,12 @@
},
{
"company": "_Test Company",
+ "create_account_under": "Fixed Assets - _TC",
+ "doctype": "Warehouse",
+ "warehouse_name": "_Test Warehouse 2"
+ },
+ {
+ "company": "_Test Company",
"create_account_under": "Stock Assets - _TC",
"doctype": "Warehouse",
"warehouse_name": "_Test Rejected Warehouse"
diff --git a/erpnext/stock/doctype/warehouse/warehouse.json b/erpnext/stock/doctype/warehouse/warehouse.json
index 00fe999..4e9dd07 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.json
+++ b/erpnext/stock/doctype/warehouse/warehouse.json
@@ -91,6 +91,54 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "fieldname": "disabled",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Disabled",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
"depends_on": "eval:sys_defaults.auto_accounting_for_stock",
"description": "Account for the warehouse (Perpetual Inventory) will be created under this Account.",
"fieldname": "create_account_under",
@@ -117,32 +165,8 @@
{
"allow_on_submit": 0,
"bold": 0,
- "collapsible": 0,
- "fieldname": "disabled",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "label": "Disabled",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "description": "For Reference Only.",
+ "collapsible": 1,
+ "description": "",
"fieldname": "warehouse_contact_info",
"fieldtype": "Section Break",
"hidden": 0,
@@ -408,7 +432,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-03-30 03:31:43.954827",
+ "modified": "2016-04-18 05:44:24.837579",
"modified_by": "Administrator",
"module": "Stock",
"name": "Warehouse",
@@ -535,6 +559,7 @@
"write": 0
}
],
+ "quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"sort_order": "DESC",
diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
index b21b402..89963ab 100644
--- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
+++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
@@ -14,7 +14,8 @@
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",
+ _("Requested Qty") + ":Float:110", _("Ordered Qty") + ":Float:100",
+ _("Reserved Qty") + ":Float:100", _("Reserved Qty for Production") + ":Float:100",
_("Projected Qty") + ":Float:100", _("Reorder Level") + ":Float:100", _("Reorder Qty") + ":Float:100",
_("Shortage Qty") + ":Float:100"]
@@ -51,7 +52,7 @@
data.append([item.name, item.item_name, item.description, item.item_group, item.brand, bin.warehouse,
item.stock_uom, bin.actual_qty, bin.planned_qty, bin.indented_qty, bin.ordered_qty,
- bin.reserved_qty, bin.projected_qty, re_order_level, re_order_qty, shortage_qty])
+ bin.reserved_qty, bin.reserved_qty_for_production, bin.projected_qty, re_order_level, re_order_qty, shortage_qty])
return data
@@ -63,7 +64,8 @@
bin_filters.warehouse = filters.warehouse
bin_list = frappe.get_all("Bin", fields=["item_code", "warehouse",
- "actual_qty", "planned_qty", "indented_qty", "ordered_qty", "reserved_qty", "projected_qty"],
+ "actual_qty", "planned_qty", "indented_qty", "ordered_qty", "reserved_qty",
+ "reserved_qty_for_production", "projected_qty"],
filters=bin_filters, order_by="item_code, warehouse")
return bin_list
diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py
index 7247d9e..7d92813 100644
--- a/erpnext/stock/stock_balance.py
+++ b/erpnext/stock/stock_balance.py
@@ -148,8 +148,9 @@
mismatch = True
if mismatch:
- bin.projected_qty = flt(bin.actual_qty) + flt(bin.ordered_qty) + \
+ bin.projected_qty = (flt(bin.actual_qty) + flt(bin.ordered_qty) +
flt(bin.indented_qty) + flt(bin.planned_qty) - flt(bin.reserved_qty)
+ - flt(bin.reserved_qty_for_production))
bin.save()