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 fcbc469950..98a972b18e 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 cd28c88e8d..9516f24524 100644 --- a/erpnext/buying/doctype/purchase_common/purchase_common.py +++ b/erpnext/buying/doctype/purchase_common/purchase_common.py @@ -72,15 +72,15 @@ class PurchaseCommon(BuyingController): 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 0b8b8efbe5..5088f2ea66 100644 --- a/erpnext/buying/doctype/supplier/supplier.js +++ b/erpnext/buying/doctype/supplier/supplier.js @@ -7,7 +7,7 @@ frappe.ui.form.on("Supplier", { 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 5c241fb6c6..cb26154ce4 100755 --- a/erpnext/hr/doctype/employee/employee.js +++ b/erpnext/hr/doctype/employee/employee.js @@ -25,7 +25,7 @@ erpnext.hr.EmployeeController = frappe.ui.form.Controller.extend({ 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 0000000000..4476b16dbc --- /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 14e3674b2e..3b71b7637b 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, @@ -305,6 +305,33 @@ "set_only_once": 0, "unique": 0 }, + { + "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, @@ -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 88ee997d46..6c8dfd5a6d 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.projects.doctype.time_log.time_log import OverlapError 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 @@ form_grid_templates = { 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 @@ class ProductionOrder(Document): 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 @@ class ProductionOrder(Document): 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 @@ class ProductionOrder(Document): 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 @@ class ProductionOrder(Document): 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 @@ class ProductionOrder(Document): 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 @@ class ProductionOrder(Document): 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 @@ def make_stock_entry(production_order_id, purpose, qty=None): 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 3b558a9f42..1879e78e57 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 @@ class TestProductionOrder(unittest.TestCase): 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 @@ def make_prod_order_test_record(**args): 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 0000000000..e69de29bb2 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 0000000000..2b93549086 --- /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 0000000000..c18338ceb8 --- /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 13073777fe..208b47639b 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 21e68cc49c..050fc5472b 100644 --- a/erpnext/projects/doctype/time_log/time_log.json +++ b/erpnext/projects/doctype/time_log/time_log.json @@ -10,6 +10,32 @@ "doctype": "DocType", "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, @@ -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 557a392ab8..3c87dab3e9 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -68,5 +68,20 @@ erpnext.financial_statements = { "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 95bed474a7..d30924e67f 100644 --- a/erpnext/selling/doctype/customer/customer.js +++ b/erpnext/selling/doctype/customer/customer.js @@ -7,7 +7,7 @@ frappe.ui.form.on("Customer", { 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 0000000000..40411b68b4 --- /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 9eb3995cc9..bb0de3ff5f 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", @@ -214,6 +222,31 @@ "set_only_once": 0, "unique": 0 }, + { + "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, @@ -222,6 +255,7 @@ "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 ae7c6e7832..a1580d52f0 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -65,12 +65,15 @@ class Bin(Document): 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 @@ class Bin(Document): """, (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 900c40c689..4a4de4c921 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -16,12 +16,6 @@ frappe.ui.form.on("Item", { }, - 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 @@ frappe.ui.form.on("Item", { 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 f0e5f29863..649f97be2d 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -633,8 +633,8 @@ def get_timeline_data(name): 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 a002a50fee..cef209a666 100644 --- a/erpnext/stock/doctype/item/item_dashboard.html +++ b/erpnext/stock/doctype/item/item_dashboard.html @@ -1,12 +1,39 @@ -