From 0e9cd2e00abd093b4ca99e1f3e2b62ec144d45cf Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 8 Apr 2019 18:30:03 +0530 Subject: [PATCH 01/44] feat: Production plan enhancements --- erpnext/manufacturing/doctype/bom/bom.py | 5 +- .../material_request_plan_item.json | 111 +++- .../production_plan/production_plan.js | 115 +++- .../production_plan/production_plan.json | 282 +++++--- .../production_plan/production_plan.py | 167 ++++- .../production_plan_item.json | 611 ++++++++++++------ 6 files changed, 963 insertions(+), 328 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index ffa3962640..46e7e0b0ff 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -682,6 +682,9 @@ def get_children(doctype, parent=None, is_root=False, **filters): frappe.msgprint(_('Please select a BOM')) return + if parent: + frappe.form_dict.parent = parent + if frappe.form_dict.parent: bom_doc = frappe.get_doc("BOM", frappe.form_dict.parent) frappe.has_permission("BOM", doc=bom_doc, throw=True) @@ -694,7 +697,7 @@ def get_children(doctype, parent=None, is_root=False, **filters): item_names = tuple(d.get('item_code') for d in bom_items) items = frappe.get_list('Item', - fields=['image', 'description', 'name'], + fields=['image', 'description', 'name', 'stock_uom', 'item_name'], filters=[['name', 'in', item_names]]) # to get only required item dicts for bom_item in bom_items: diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json index 11b85238af..39d59f006b 100644 --- a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json +++ b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, @@ -14,10 +15,12 @@ "fields": [ { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "item_code", "fieldtype": "Link", "hidden": 0, @@ -41,14 +44,17 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "item_name", "fieldtype": "Data", "hidden": 0, @@ -72,14 +78,17 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "warehouse", "fieldtype": "Link", "hidden": 0, @@ -103,14 +112,51 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, + "fieldname": "material_request_type", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Material Request Type", + "length": 0, + "no_copy": 0, + "options": "\nPurchase\nMaterial Transfer\nMaterial Issue\nManufacture\nCustomer Provided", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_4", "fieldtype": "Column Break", "hidden": 0, @@ -132,14 +178,17 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "quantity", "fieldtype": "Float", "hidden": 0, @@ -149,7 +198,7 @@ "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 0, - "label": "Quantity", + "label": "Required Quantity", "length": 0, "no_copy": 1, "permlevel": 0, @@ -162,14 +211,52 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, + "fieldname": "projected_qty", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Projected Qty", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": "", + "columns": 0, + "depends_on": "", + "fetch_if_empty": 0, "fieldname": "actual_qty", "fieldtype": "Float", "hidden": 0, @@ -192,14 +279,17 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "min_order_qty", "fieldtype": "Float", "hidden": 0, @@ -222,14 +312,17 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, "columns": 0, + "fetch_if_empty": 0, "fieldname": "section_break_8", "fieldtype": "Section Break", "hidden": 0, @@ -252,14 +345,17 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "sales_order", "fieldtype": "Link", "hidden": 0, @@ -283,14 +379,18 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "", + "fetch_if_empty": 0, "fieldname": "requested_qty", "fieldtype": "Float", "hidden": 0, @@ -313,20 +413,19 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 } ], "has_web_view": 0, - "hide_heading": 0, "hide_toolbar": 0, "idx": 0, - "image_view": 0, "in_create": 0, "is_submittable": 0, "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-02-15 13:08:30.535963", + "modified": "2019-04-08 18:15:26.849602", "modified_by": "Administrator", "module": "Manufacturing", "name": "Material Request Plan Item", @@ -335,10 +434,10 @@ "permissions": [], "quick_entry": 1, "read_only": 0, - "read_only_onload": 0, "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", "track_changes": 1, - "track_seen": 0 + "track_seen": 0, + "track_views": 0 } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index ad3c0f12d7..58fc29e493 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -11,6 +11,23 @@ frappe.ui.form.on('Production Plan', { } } + frm.set_query('for_warehouse', function(doc) { + return { + filters: { + company: doc.company + } + } + }); + + frm.fields_dict['po_items'].grid.get_field('item_code').get_query = function(doc) { + return { + query: "erpnext.controllers.queries.item_query", + filters:{ + 'is_stock_item': 1, + } + } + } + frm.fields_dict['po_items'].grid.get_field('bom_no').get_query = function(doc, cdt, cdn) { var d = locals[cdt][cdn]; if (d.item_code) { @@ -50,6 +67,51 @@ frappe.ui.form.on('Production Plan', { } frm.trigger("material_requirement"); + + const projected_qty_formula = ` + +
+
+

+ + ${__("Projected Quantity Formula")} + +

+
+

+ (Actual Qty + Planned Qty + Requested Qty + Ordered Qty) - (Reserved Qty + Reserved for Production + Reserved for Subcontract) +

+
+
+
+
    +
  • + ${__("Actual Qty: Quantity available in the warehouse.")} +
  • +
  • + ${__("Planned Qty: Quantity, for which, Work Order has been raised, but is pending to be manufactured.")} +
  • +
  • + ${__('Requested Qty: Quantity requested for purchase, but not ordered.')} +
  • +
  • + ${__('Ordered Qty: Quantity ordered for purchase, but not received.')} +
  • +
  • + ${__("Reserved Qty: Quantity ordered for sale, but not delivered.")} +
  • +
  • + ${__('Reserved Qty for Production: Raw materials quantity to make manufacturing items.')} +
  • +
  • + ${__('Reserved Qty for Subcontract: Raw materials quantity to make subcotracted items.')} +
  • +
+
+
+
`; + + set_field_options("projected_qty_formula", projected_qty_formula); }, make_work_order: function(frm) { @@ -106,6 +168,8 @@ frappe.ui.form.on('Production Plan', { }, get_items_for_mr: function(frm) { + const set_fields = ['actual_qty', 'item_code', + 'item_name', 'min_order_qty', 'quantity', 'sales_order', 'warehouse', 'projected_qty', 'material_request_type']; frappe.call({ method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_items_for_material_requests", freeze: true, @@ -115,13 +179,11 @@ frappe.ui.form.on('Production Plan', { frm.set_value('mr_items', []); $.each(r.message, function(i, d) { var item = frm.add_child('mr_items'); - item.actual_qty = d.actual_qty; - item.item_code = d.item_code; - item.item_name = d.item_name; - item.min_order_qty = d.min_order_qty; - item.quantity = d.quantity; - item.sales_order = d.sales_order; - item.warehouse = d.warehouse; + for (let key in d) { + if (d[key] && in_list(set_fields, key)) { + item[key] = d[key]; + } + } }); } refresh_field('mr_items'); @@ -129,6 +191,16 @@ frappe.ui.form.on('Production Plan', { }); }, + for_warehouse: function(frm) { + if (frm.doc.mr_items) { + frm.trigger("get_items_for_mr"); + } + }, + + download_materials_required: function(frm) { + $c_obj_csv(frm.doc, 'download_raw_materials', '', ''); + }, + show_progress: function(frm) { var bars = []; var message = ''; @@ -163,6 +235,25 @@ frappe.ui.form.on('Production Plan', { }, }); +frappe.ui.form.on("Production Plan Item", { + item_code: function(frm, cdt, cdn) { + const row = locals[cdt][cdn]; + if (row.item_code) { + frappe.call({ + method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_item_data", + args: { + item_code: row.item_code + }, + callback: function(r) { + for (let key in r.message) { + frappe.model.set_value(cdt, cdn, key, r.message[key]); + } + } + }); + } + } +}); + frappe.ui.form.on("Material Request Plan Item", { warehouse: function(frm, cdt, cdn) { const row = locals[cdt][cdn]; @@ -170,12 +261,16 @@ frappe.ui.form.on("Material Request Plan Item", { frappe.call({ method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_bin_details", args: { - row: row + row: row, + for_warehouse: row.warehouse }, callback: function(r) { - frappe.model.set_value(cdt, cdn, 'actual_qty', r.message[1]) + let {projected_qty, actual_qty} = r.message; + + frappe.model.set_value(cdt, cdn, 'projected_qty', projected_qty); + frappe.model.set_value(cdt, cdn, 'actual_qty', actual_qty); } }) } } -}) +}); diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index c864976033..0be9f1a8ee 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, @@ -22,6 +23,7 @@ "collapsible": 0, "columns": 0, "default": "", + "fetch_if_empty": 0, "fieldname": "naming_series", "fieldtype": "Select", "hidden": 0, @@ -56,6 +58,7 @@ "collapsible": 0, "columns": 0, "depends_on": "", + "fetch_if_empty": 0, "fieldname": "company", "fieldtype": "Link", "hidden": 0, @@ -90,6 +93,7 @@ "collapsible": 0, "columns": 0, "default": "", + "fetch_if_empty": 0, "fieldname": "get_items_from", "fieldtype": "Select", "hidden": 0, @@ -123,6 +127,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break1", "fieldtype": "Column Break", "hidden": 0, @@ -155,6 +160,7 @@ "collapsible": 0, "columns": 0, "default": "Today", + "fetch_if_empty": 0, "fieldname": "posting_date", "fieldtype": "Date", "hidden": 0, @@ -190,6 +196,7 @@ "columns": 0, "depends_on": "eval: doc.get_items_from", "description": "", + "fetch_if_empty": 0, "fieldname": "filters", "fieldtype": "Section Break", "hidden": 0, @@ -222,6 +229,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "item_code", "fieldtype": "Link", "hidden": 0, @@ -256,6 +264,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval: doc.get_items_from == \"Sales Order\"", + "fetch_if_empty": 0, "fieldname": "customer", "fieldtype": "Link", "hidden": 0, @@ -290,6 +299,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval: doc.get_items_from == \"Material Request\"", + "fetch_if_empty": 0, "fieldname": "warehouse", "fieldtype": "Link", "hidden": 0, @@ -324,6 +334,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval: doc.get_items_from == \"Sales Order\"", + "fetch_if_empty": 0, "fieldname": "project", "fieldtype": "Link", "hidden": 0, @@ -357,6 +368,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break2", "fieldtype": "Column Break", "hidden": 0, @@ -389,6 +401,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "from_date", "fieldtype": "Date", "hidden": 0, @@ -421,6 +434,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "to_date", "fieldtype": "Date", "hidden": 0, @@ -455,6 +469,7 @@ "collapsible_depends_on": "eval: doc.__islocal", "columns": 0, "depends_on": "eval: doc.get_items_from == \"Sales Order\"", + "fetch_if_empty": 0, "fieldname": "sales_orders_detail", "fieldtype": "Section Break", "hidden": 0, @@ -489,6 +504,7 @@ "collapsible": 0, "columns": 0, "description": "", + "fetch_if_empty": 0, "fieldname": "get_sales_orders", "fieldtype": "Button", "hidden": 0, @@ -522,6 +538,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "sales_orders", "fieldtype": "Table", "hidden": 0, @@ -557,6 +574,7 @@ "collapsible_depends_on": "eval: doc.__islocal", "columns": 0, "depends_on": "eval: doc.get_items_from == \"Material Request\"", + "fetch_if_empty": 0, "fieldname": "material_request_detail", "fieldtype": "Section Break", "hidden": 0, @@ -590,6 +608,7 @@ "collapsible": 0, "columns": 0, "description": "", + "fetch_if_empty": 0, "fieldname": "get_material_request", "fieldtype": "Button", "hidden": 0, @@ -623,6 +642,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "material_requests", "fieldtype": "Table", "hidden": 0, @@ -657,7 +677,8 @@ "collapsible": 0, "columns": 0, "description": "", - "fieldname": "items_for_production", + "fetch_if_empty": 0, + "fieldname": "select_items_to_manufacture_section", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, @@ -666,7 +687,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Select Items", + "label": "Select Items to Manufacture", "length": 0, "no_copy": 0, "permlevel": 0, @@ -690,6 +711,7 @@ "collapsible": 0, "columns": 0, "depends_on": "get_items_from", + "fetch_if_empty": 0, "fieldname": "get_items", "fieldtype": "Button", "hidden": 0, @@ -723,6 +745,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "po_items", "fieldtype": "Table", "hidden": 0, @@ -732,7 +755,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Items", + "label": "", "length": 0, "no_copy": 1, "options": "Production Plan Item", @@ -757,6 +780,7 @@ "collapsible": 0, "columns": 0, "depends_on": "", + "fetch_if_empty": 0, "fieldname": "material_request_planning", "fieldtype": "Section Break", "hidden": 0, @@ -790,6 +814,7 @@ "collapsible": 0, "columns": 0, "default": "1", + "fetch_if_empty": 0, "fieldname": "include_non_stock_items", "fieldtype": "Check", "hidden": 0, @@ -815,69 +840,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "ignore_existing_ordered_qty", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Ignore Existing Ordered Quantity", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_25", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -886,6 +848,7 @@ "collapsible": 0, "columns": 0, "default": "1", + "fetch_if_empty": 0, "fieldname": "include_subcontracted_items", "fieldtype": "Check", "hidden": 0, @@ -918,9 +881,43 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "", - "fieldname": "section_break_27", - "fieldtype": "Section Break", + "description": "If enabled, then system will create the material even if the raw materials are available", + "fetch_if_empty": 0, + "fieldname": "ignore_existing_ordered_qty", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Ignore Existing Projected Quantity", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "column_break_25", + "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -950,6 +947,74 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, + "fieldname": "for_warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "For Warehouse", + "length": 0, + "no_copy": 0, + "options": "Warehouse", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "download_materials_required", + "fieldtype": "Button", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Download Materials Required", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, "fieldname": "get_items_for_mr", "fieldtype": "Button", "hidden": 0, @@ -982,6 +1047,40 @@ "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "", + "fetch_if_empty": 0, + "fieldname": "section_break_27", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, "fieldname": "mr_items", "fieldtype": "Table", "hidden": 0, @@ -1008,6 +1107,40 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "fetch_if_empty": 0, + "fieldname": "projected_qty_formula", + "fieldtype": "HTML", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Projected Qty Formula", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -1015,6 +1148,7 @@ "bold": 0, "collapsible": 1, "columns": 0, + "fetch_if_empty": 0, "fieldname": "other_details", "fieldtype": "Section Break", "hidden": 0, @@ -1048,6 +1182,7 @@ "collapsible": 0, "columns": 0, "default": "0", + "fetch_if_empty": 0, "fieldname": "total_planned_qty", "fieldtype": "Float", "hidden": 0, @@ -1081,6 +1216,7 @@ "collapsible": 0, "columns": 0, "default": "0", + "fetch_if_empty": 0, "fieldname": "total_produced_qty", "fieldtype": "Float", "hidden": 0, @@ -1113,6 +1249,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_32", "fieldtype": "Column Break", "hidden": 0, @@ -1145,6 +1282,7 @@ "collapsible": 0, "columns": 0, "default": "Draft", + "fetch_if_empty": 0, "fieldname": "status", "fieldtype": "Select", "hidden": 0, @@ -1178,6 +1316,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "amended_from", "fieldtype": "Link", "hidden": 0, @@ -1205,17 +1344,15 @@ } ], "has_web_view": 0, - "hide_heading": 0, "hide_toolbar": 0, "icon": "fa fa-calendar", "idx": 0, - "image_view": 0, "in_create": 0, "is_submittable": 1, "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-08-21 14:44:25.071991", + "modified": "2019-04-09 12:05:14.300886", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", @@ -1244,7 +1381,6 @@ ], "quick_entry": 0, "read_only": 0, - "read_only_onload": 0, "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "ASC", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index f439588f5a..490555ce47 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe, json from frappe import msgprint, _ from frappe.model.document import Document -from erpnext.manufacturing.doctype.bom.bom import validate_bom_no +from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_children from frappe.utils import cstr, flt, cint, nowdate, add_days, comma_and, now_datetime, ceil from erpnext.manufacturing.doctype.work_order.work_order import get_item_details from six import string_types, iteritems @@ -262,7 +262,8 @@ class ProductionPlan(Document): "fg_warehouse" : d.warehouse, "production_plan" : self.name, "production_plan_item" : d.name, - "product_bundle_item" : d.product_bundle_item + "product_bundle_item" : d.product_bundle_item, + "make_work_order_for_sub_assembly_items": d.get("make_work_order_for_sub_assembly_items", 0) } item_details.update({ @@ -293,6 +294,10 @@ class ProductionPlan(Document): if work_order: wo_list.append(work_order) + if item.get("make_work_order_for_sub_assembly_items"): + work_orders = self.make_work_order_for_sub_assembly_items(item) + wo_list.extend(work_orders) + frappe.flags.mute_messages = False if wo_list: @@ -302,11 +307,35 @@ class ProductionPlan(Document): else : msgprint(_("No Work Orders created")) + def make_work_order_for_sub_assembly_items(self, item): + work_orders = [] + bom_data = {} + + get_sub_assembly_items(item.get("bom_no"), bom_data) + + for key, data in bom_data.items(): + data.update({ + 'qty': data.get("stock_qty") * item.get("qty"), + 'production_plan': self.name, + 'company': self.company, + 'fg_warehouse': item.get("fg_warehouse") + }) + + work_order = self.create_work_order(data) + if work_order: + work_orders.append(work_order) + + return work_orders + def create_work_order(self, item): from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError, get_default_warehouse warehouse = get_default_warehouse() wo = frappe.new_doc("Work Order") wo.update(item) + + if item.get("warehouse"): + wo.fg_warehouse = item.get("warehouse") + wo.set_work_order_operations() if not wo.fg_warehouse: @@ -325,8 +354,10 @@ class ProductionPlan(Document): for item in self.mr_items: item_doc = frappe.get_cached_doc('Item', item.item_code) + material_request_type = item.material_request_type or item_doc.default_material_request_type + # key for Sales Order:Material Request Type:Customer - key = '{}:{}:{}'.format(item.sales_order, item_doc.default_material_request_type,item_doc.customer or '') + key = '{}:{}:{}'.format(item.sales_order, material_request_type, item_doc.customer or '') schedule_date = add_days(nowdate(), cint(item_doc.lead_time_days)) if not key in material_request_map: @@ -338,7 +369,7 @@ class ProductionPlan(Document): "status": "Draft", "company": self.company, "requested_by": frappe.session.user, - 'material_request_type': item_doc.default_material_request_type, + 'material_request_type': material_request_type, 'customer': item_doc.customer or '' }) material_request_list.append(material_request) @@ -373,6 +404,26 @@ class ProductionPlan(Document): else : msgprint(_("No material request created")) + def download_raw_materials(self): + item_list = [['Item Code', 'Description', 'Stock UOM', 'Required Qty', 'Warehouse', + 'projected Qty', 'Actual Qty']] + + doc = self.as_dict() + for d in get_items_for_material_requests(doc, ignore_existing_ordered_qty=True): + item_list.append([d.get('item_code'), d.get('description'), d.get('stock_uom'), d.get('quantity'), + d.get('warehouse'), d.get('projected_qty'), d.get('actual_qty')]) + + if not self.for_warehouse: + row = {'item_code': d.get('item_code')} + for bin_dict in get_bin_details(row, self.company, all_warehouse=True): + if d.get("warehouse") == bin_dict.get('warehouse'): + continue + + item_list.append(['', '', '', '', bin_dict.get('warehouse'), + bin_dict.get('projected_qty'), bin_dict.get('actual_qty')]) + + return item_list + def get_exploded_items(item_details, company, bom_no, include_non_stock_items, planned_qty=1): for d in frappe.db.sql("""select bei.item_code, item.default_bom as bom, ifnull(sum(bei.stock_qty/ifnull(bom.quantity, 1)), 0)*%s as qty, item.item_name, @@ -439,17 +490,17 @@ def get_subitems(doc, data, item_details, bom_no, company, include_non_stock_ite include_non_stock_items, include_subcontracted_items, d.qty) return item_details -def get_material_request_items(row, sales_order, company, ignore_existing_ordered_qty, warehouse): +def get_material_request_items(row, sales_order, + company, ignore_existing_ordered_qty, warehouse, bin_dict): total_qty = row['qty'] - projected_qty, actual_qty = get_bin_details(row) - requested_qty = 0 - if ignore_existing_ordered_qty: - requested_qty = total_qty - elif total_qty > projected_qty: - requested_qty = total_qty - projected_qty - if requested_qty > 0 and requested_qty < row['min_order_qty']: - requested_qty = row['min_order_qty'] + required_qty = 0 + if ignore_existing_ordered_qty or bin_dict.get("projected_qty") < 0: + required_qty = total_qty + elif total_qty > bin_dict.get("projected_qty"): + required_qty = total_qty - bin_dict.get("projected_qty") + if required_qty > 0 and required_qty < row['min_order_qty']: + required_qty = row['min_order_qty'] item_group_defaults = get_item_group_defaults(row.item_code, company) if not row['purchase_uom']: @@ -459,20 +510,24 @@ def get_material_request_items(row, sales_order, company, ignore_existing_ordere if not row['conversion_factor']: frappe.throw(_("UOM Conversion factor ({0} -> {1}) not found for item: {2}") .format(row['purchase_uom'], row['stock_uom'], row.item_code)) - requested_qty = requested_qty / row['conversion_factor'] + required_qty = required_qty / row['conversion_factor'] if frappe.db.get_value("UOM", row['purchase_uom'], "must_be_whole_number"): - requested_qty = ceil(requested_qty) + required_qty = ceil(required_qty) - if requested_qty > 0: + if required_qty > 0: return { 'item_code': row.item_code, 'item_name': row.item_name, - 'quantity': requested_qty, + 'quantity': required_qty, + 'description': row.description, + 'stock_uom': row.get("stock_uom"), 'warehouse': warehouse or row.get('source_warehouse') \ or row.get('default_warehouse') or item_group_defaults.get("default_warehouse"), - 'actual_qty': actual_qty, + 'actual_qty': bin_dict.get("actual_qty", 0), + 'projected_qty': bin_dict.get("projected_qty", 0), 'min_order_qty': row['min_order_qty'], + 'material_request_type': row.get("default_material_request_type"), 'sales_order': sales_order } @@ -515,36 +570,44 @@ def get_sales_orders(self): return open_so @frappe.whitelist() -def get_bin_details(row): +def get_bin_details(row, company, for_warehouse=None, all_warehouse=False): if isinstance(row, string_types): row = frappe._dict(json.loads(row)) - conditions = "" - warehouse = row.get('source_warehouse') or row.get('default_warehouse') + company = frappe.db.escape(company) + conditions, warehouse = "", "" + + conditions = " and warehouse in (select name from `tabWarehouse` where company = {0})".format(company) + if not all_warehouse: + warehouse = for_warehouse or row.get('source_warehouse') or row.get('default_warehouse') + if warehouse: lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"]) - conditions = " and exists(select name from `tabWarehouse` where lft >= {0} and rgt <= {1} and name=`tabBin`.warehouse)".format(lft, rgt) + conditions = """ and warehouse in (select name from `tabWarehouse` + where lft >= {0} and rgt <= {1} and name=`tabBin`.warehouse and company = {2}) + """.format(lft, rgt, company) - item_projected_qty = frappe.db.sql(""" select ifnull(sum(projected_qty),0) as projected_qty, - ifnull(sum(actual_qty),0) as actual_qty from `tabBin` + return frappe.db.sql(""" select ifnull(sum(projected_qty),0) as projected_qty, + ifnull(sum(actual_qty),0) as actual_qty, warehouse from `tabBin` where item_code = %(item_code)s {conditions} - """.format(conditions=conditions), { "item_code": row['item_code'] }, as_list=1) - - return item_projected_qty and item_projected_qty[0] or (0,0) + group by item_code, warehouse + """.format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1) @frappe.whitelist() -def get_items_for_material_requests(doc, sales_order=None, company=None): +def get_items_for_material_requests(doc, ignore_existing_ordered_qty=None): if isinstance(doc, string_types): doc = frappe._dict(json.loads(doc)) doc['mr_items'] = [] po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items') company = doc.get('company') + warehouse = doc.get('for_warehouse') + + if not ignore_existing_ordered_qty: + ignore_existing_ordered_qty = doc.get('ignore_existing_ordered_qty') so_item_details = frappe._dict() for data in po_items: - warehouse = data.get('for_warehouse') - ignore_existing_ordered_qty = data.get('ignore_existing_ordered_qty') or doc.get('ignore_existing_ordered_qty') planned_qty = data.get('required_qty') or data.get('planned_qty') item_details = {} if data.get("bom") or data.get("bom_no"): @@ -592,8 +655,8 @@ def get_items_for_material_requests(doc, sales_order=None, company=None): 'conversion_factor' : conversion_factor, } ) - if not sales_order: - sales_order = doc.get("sales_order") + + sales_order = doc.get("sales_order") for item_code, details in iteritems(item_details): so_item_details.setdefault(sales_order, frappe._dict()) @@ -606,10 +669,48 @@ def get_items_for_material_requests(doc, sales_order=None, company=None): for sales_order, item_code in iteritems(so_item_details): item_dict = so_item_details[sales_order] for details in item_dict.values(): + bin_dict = get_bin_details(details, doc.company, warehouse) + bin_dict = bin_dict[0] if bin_dict else {} + if details.qty > 0: items = get_material_request_items(details, sales_order, company, - ignore_existing_ordered_qty, warehouse) + ignore_existing_ordered_qty, warehouse, bin_dict) if items: mr_items.append(items) + if not mr_items: + frappe.msgprint(_("""As raw materials projected quantity is more than required quantity, there is no need to create material request. + Still if you want to make material request, kindly enable Ignore Existing Projected Quantity checkbox""")) + return mr_items + +@frappe.whitelist() +def get_item_data(item_code): + item_details = get_item_details(item_code) + + return { + "bom_no": item_details.get("bom_no"), + "stock_uom": item_details.get("stock_uom"), + "description": item_details.get("description") + } + +def get_sub_assembly_items(bom_no, bom_data): + data = get_children('BOM', parent = bom_no) + for d in data: + if d.expandable: + key = (d.name, d.value) + if key not in bom_data: + bom_data.setdefault(key, { + 'stock_qty': 0, + 'description': d.description, + 'production_item': d.item_code, + 'item_name': d.item_name, + 'stock_uom': d.stock_uom, + 'uom': d.stock_uom, + 'bom_no': d.value + }) + + bom_item = bom_data.get(key) + bom_item["stock_qty"] += d.stock_qty + + get_sub_assembly_items(bom_item.get("bom_no"), bom_data) diff --git a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json index 4e78f61f8f..d0dce53437 100644 --- a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json +++ b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, @@ -13,10 +14,12 @@ "fields": [ { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 2, + "fetch_if_empty": 0, "fieldname": "include_exploded_items", "fieldtype": "Check", "hidden": 0, @@ -44,10 +47,12 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 2, + "fetch_if_empty": 0, "fieldname": "item_code", "fieldtype": "Link", "hidden": 0, @@ -79,10 +84,12 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 2, + "fetch_if_empty": 0, "fieldname": "bom_no", "fieldtype": "Link", "hidden": 0, @@ -114,10 +121,12 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "planned_qty", "fieldtype": "Float", "hidden": 0, @@ -148,11 +157,114 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "column_break_6", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "description": "If enabled, system will create the work order for the exploded items against which BOM is available.", + "fetch_if_empty": 0, + "fieldname": "make_work_order_for_sub_assembly_items", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Make Work Order for Sub Assembly Items", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "", + "fetch_if_empty": 0, + "fieldname": "warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "For Warehouse", + "length": 0, + "no_copy": 0, + "options": "Warehouse", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, "default": "Today", + "fetch_if_empty": 0, "fieldname": "planned_start_date", "fieldtype": "Datetime", "hidden": 0, @@ -180,12 +292,14 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "column_break_6", - "fieldtype": "Column Break", + "fetch_if_empty": 0, + "fieldname": "section_break_9", + "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -193,6 +307,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, + "label": "Quantity and Description", "length": 0, "no_copy": 0, "permlevel": 0, @@ -210,169 +325,13 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "description": "Reserved Warehouse in Sales Order / Finished Goods Warehouse", - "fieldname": "warehouse", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "produced_qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Produced Qty", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sales_order", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sales Order", - "length": 0, - "no_copy": 0, - "oldfieldname": "source_docname", - "oldfieldtype": "Data", - "options": "Sales Order", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sales_order_item", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sales Order Item", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "material_request", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Material Request", - "length": 0, - "no_copy": 0, - "options": "Material Request", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", + "fetch_if_empty": 0, "fieldname": "pending_qty", "fieldtype": "Float", "hidden": 0, @@ -403,10 +362,148 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "default": "0", + "fetch_if_empty": 0, + "fieldname": "ordered_qty", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Ordered Qty", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fetch_if_empty": 0, + "fieldname": "produced_qty", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Produced Qty", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "column_break_17", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "description", + "fieldtype": "Text Editor", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Description", + "length": 0, + "no_copy": 0, + "oldfieldname": "description", + "oldfieldtype": "Text", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": "200px", + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0, + "width": "200px" + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, "fieldname": "stock_uom", "fieldtype": "Link", "hidden": 0, @@ -438,12 +535,14 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "description", - "fieldtype": "Text Editor", + "fetch_if_empty": 0, + "fieldname": "reference_section", + "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -451,15 +550,48 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Description", + "label": "Reference", "length": 0, "no_copy": 0, - "oldfieldname": "description", - "oldfieldtype": "Text", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "sales_order", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Sales Order", + "length": 0, + "no_copy": 0, + "oldfieldname": "source_docname", + "oldfieldtype": "Data", + "options": "Sales Order", "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, - "print_width": "200px", "read_only": 1, "remember_last_selected_value": 0, "report_hide": 0, @@ -467,15 +599,115 @@ "search_index": 0, "set_only_once": 0, "translatable": 0, - "unique": 0, - "width": "200px" + "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, + "fieldname": "sales_order_item", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Sales Order Item", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "column_break_19", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "material_request", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Material Request", + "length": 0, + "no_copy": 0, + "options": "Material Request", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, "fieldname": "material_request_item", "fieldtype": "Data", "hidden": 1, @@ -503,41 +735,12 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "ordered_qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Ordered Qty", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "fetch_if_empty": 0, "fieldname": "product_bundle_item", "fieldtype": "Link", "hidden": 0, @@ -566,16 +769,14 @@ } ], "has_web_view": 0, - "hide_heading": 0, "hide_toolbar": 0, "idx": 1, - "image_view": 0, "in_create": 0, "is_submittable": 0, "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-04-25 17:19:24.572528", + "modified": "2019-04-08 23:09:57.199423", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan Item", @@ -583,9 +784,9 @@ "permissions": [], "quick_entry": 0, "read_only": 0, - "read_only_onload": 0, "show_name_in_global_search": 0, "sort_order": "ASC", "track_changes": 0, - "track_seen": 0 + "track_seen": 0, + "track_views": 0 } \ No newline at end of file From cb278975f3ade6dc32ce58c5820e482e80024418 Mon Sep 17 00:00:00 2001 From: Anoop Date: Tue, 9 Apr 2019 18:10:41 +0530 Subject: [PATCH 02/44] fix: misleading condition example in field options condition example corrected - gender=\"Male\" to gender==\"Male\" --- .../hr/doctype/taxable_salary_slab/taxable_salary_slab.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/taxable_salary_slab/taxable_salary_slab.json b/erpnext/hr/doctype/taxable_salary_slab/taxable_salary_slab.json index 41aa97eb8f..a094f8a197 100644 --- a/erpnext/hr/doctype/taxable_salary_slab/taxable_salary_slab.json +++ b/erpnext/hr/doctype/taxable_salary_slab/taxable_salary_slab.json @@ -189,7 +189,7 @@ "in_standard_filter": 0, "length": 0, "no_copy": 0, - "options": "

Condition Examples

\n
    \n
  1. Applying tax if employee born between 31-12-1937 and 01-01-1958 (Employees aged 60 to 80)
    \nCondition: date_of_birth>date(1937, 12, 31) and date_of_birth<date(1958, 01, 01)

  2. Applying tax by employee gender
    \nCondition: gender=\"Male\"

  3. \n
  4. Applying tax by Salary Component
    \nCondition: base > 10000
", + "options": "

Condition Examples

\n
    \n
  1. Applying tax if employee born between 31-12-1937 and 01-01-1958 (Employees aged 60 to 80)
    \nCondition: date_of_birth>date(1937, 12, 31) and date_of_birth<date(1958, 01, 01)

  2. Applying tax by employee gender
    \nCondition: gender==\"Male\"

  3. \n
  4. Applying tax by Salary Component
    \nCondition: base > 10000
", "permlevel": 0, "precision": "", "print_hide": 0, @@ -229,4 +229,4 @@ "sort_order": "DESC", "track_changes": 1, "track_seen": 0 -} \ No newline at end of file +} From 05458b7fc68ed5815312e31fe1e49aab473bd201 Mon Sep 17 00:00:00 2001 From: Basawaraj Savalagi Date: Wed, 10 Apr 2019 13:33:43 +0530 Subject: [PATCH 03/44] Fixed Company Abbr Update Was Not Able To Update Account --- erpnext/setup/doctype/company/company.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 52c9c5a802..08fbc51fe6 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -372,7 +372,7 @@ def replace_abbr(company, old, new): def _rename_record(doc): parts = doc[0].rsplit(" - ", 1) if len(parts) == 1 or parts[1].lower() == old.lower(): - frappe.rename_doc(dt, doc[0], parts[0] + " - " + new) + frappe.rename_doc(dt, doc[0], parts[0] + " - " + new, force=True) def _rename_records(dt): # rename is expensive so let's be economical with memory usage From 29997e26316816cd292c63671be797acb447c338 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 15 Apr 2019 21:02:16 +0530 Subject: [PATCH 04/44] fix: Invoice against partially returned DN/PR --- .../doctype/delivery_note/delivery_note.py | 26 ++++------------- .../delivery_note/test_delivery_note.py | 2 +- .../purchase_receipt/purchase_receipt.py | 29 ++++--------------- .../purchase_receipt/test_purchase_receipt.py | 2 +- 4 files changed, 14 insertions(+), 45 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 90d1d0a401..cd69dd43b0 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -396,19 +396,7 @@ def get_invoiced_qty_map(delivery_note): return invoiced_qty_map -def get_returned_qty_map_against_so(sales_orders): - """returns a map: {so_detail: returned_qty}""" - returned_qty_map = {} - - for name, returned_qty in frappe.get_all('Sales Order Item', fields = ["name", "returned_qty"], - filters = {'parent': ('in', sales_orders), 'docstatus': 1}, as_list=1): - if not returned_qty_map.get(name): - returned_qty_map[name] = 0 - returned_qty_map[name] += returned_qty - - return returned_qty_map - -def get_returned_qty_map_against_dn(delivery_note): +def get_returned_qty_map(delivery_note): """returns a map: {so_detail: returned_qty}""" returned_qty_map = frappe._dict(frappe.db.sql("""select dn_item.item_code, sum(abs(dn_item.qty)) as qty from `tabDelivery Note Item` dn_item, `tabDelivery Note` dn @@ -425,8 +413,7 @@ def get_returned_qty_map_against_dn(delivery_note): def make_sales_invoice(source_name, target_doc=None): doc = frappe.get_doc('Delivery Note', source_name) sales_orders = [d.against_sales_order for d in doc.items] - returned_qty_map_against_so = get_returned_qty_map_against_so(sales_orders) - returned_qty_map_against_dn = get_returned_qty_map_against_dn(source_name) + returned_qty_map = get_returned_qty_map(source_name) invoiced_qty_map = get_invoiced_qty_map(source_name) def set_missing_values(source, target): @@ -447,17 +434,16 @@ def make_sales_invoice(source_name, target_doc=None): def update_item(source_doc, target_doc, source_parent): target_doc.qty, returned_qty = get_pending_qty(source_doc) - if not source_doc.so_detail: - returned_qty_map_against_dn[source_doc.item_code] = returned_qty + returned_qty_map[source_doc.item_code] = returned_qty if source_doc.serial_no and source_parent.per_billed > 0: target_doc.serial_no = get_delivery_note_serial_no(source_doc.item_code, target_doc.qty, source_parent.name) def get_pending_qty(item_row): - pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0) - returned_qty_map_against_so.get(item_row.so_detail, 0) - returned_qty = flt(returned_qty_map_against_dn.get(item_row.item_code, 0)) - if not item_row.so_detail: + pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0) + returned_qty = flt(returned_qty_map.get(item_row.item_code, 0)) + if returned_qty: if returned_qty >= pending_qty: pending_qty = 0 returned_qty -= pending_qty diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 5c031213d8..bc95c965bc 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -655,7 +655,7 @@ class TestDeliveryNote(unittest.TestCase): si = make_sales_invoice(dn.name) self.assertEquals(si.items[0].qty, 1) - def test_make_sales_invoice_from_dn_with_returned_qty_against_dn(self): + def test_make_sales_invoice_from_dn_with_returned_qty_duplicate_items(self): from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice dn = create_delivery_note(qty=8, do_not_submit=True) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index e3877fb425..71cf1dab79 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -407,10 +407,7 @@ def update_billed_amount_based_on_po(po_detail, update_modified=True): def make_purchase_invoice(source_name, target_doc=None): from frappe.model.mapper import get_mapped_doc doc = frappe.get_doc('Purchase Receipt', source_name) - purchase_orders = [d.purchase_order for d in doc.items] - returned_qty_map_against_po = get_returned_qty_map_against_po(purchase_orders) - returned_qty_map_against_pr = get_returned_qty_map_against_pr(source_name) - + returned_qty_map = get_returned_qty_map(source_name) invoiced_qty_map = get_invoiced_qty_map(source_name) def set_missing_values(source, target): @@ -424,14 +421,12 @@ def make_purchase_invoice(source_name, target_doc=None): def update_item(source_doc, target_doc, source_parent): target_doc.qty, returned_qty = get_pending_qty(source_doc) - if not source_doc.purchase_order_item: - returned_qty_map_against_pr[source_doc.item_code] = returned_qty + returned_qty_map[source_doc.item_code] = returned_qty def get_pending_qty(item_row): - pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0) \ - - returned_qty_map_against_po.get(item_row.purchase_order_item, 0) - returned_qty = flt(returned_qty_map_against_pr.get(item_row.item_code, 0)) - if not item_row.purchase_order_item: + pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0) + returned_qty = flt(returned_qty_map.get(item_row.item_code, 0)) + if returned_qty: if returned_qty >= pending_qty: pending_qty = 0 returned_qty -= pending_qty @@ -484,19 +479,7 @@ def get_invoiced_qty_map(purchase_receipt): return invoiced_qty_map -def get_returned_qty_map_against_po(purchase_orders): - """returns a map: {so_detail: returned_qty}""" - returned_qty_map = {} - - for name, returned_qty in frappe.get_all('Purchase Order Item', fields = ["name", "returned_qty"], - filters = {'parent': ('in', purchase_orders), 'docstatus': 1}, as_list=1): - if not returned_qty_map.get(name): - returned_qty_map[name] = 0 - returned_qty_map[name] += returned_qty - - return returned_qty_map - -def get_returned_qty_map_against_pr(purchase_receipt): +def get_returned_qty_map(purchase_receipt): """returns a map: {so_detail: returned_qty}""" returned_qty_map = frappe._dict(frappe.db.sql("""select pr_item.item_code, sum(abs(pr_item.qty)) as qty from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index c5ae838c8f..d124ae4747 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -418,7 +418,7 @@ class TestPurchaseReceipt(unittest.TestCase): pi = make_purchase_invoice(pr.name) self.assertEquals(pi.items[0].qty, 3) - def test_make_purchase_invoice_from_dn_with_returned_qty_against_dn(self): + def test_make_purchase_invoice_from_pr_with_returned_qty_duplicate_items(self): pr1 = make_purchase_receipt(qty=8, do_not_submit=True) pr1.append("items", { "item_code": "_Test Item", From 1c2eb51a918ab7cdf36baf7dc29ab18d08bad489 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 16 Apr 2019 11:12:13 +0530 Subject: [PATCH 05/44] fix(api) --- .../account_balance_timeline.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py index 3b0893119c..b047ccade7 100644 --- a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py +++ b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py @@ -3,9 +3,10 @@ from __future__ import unicode_literals import frappe, json -from frappe.core.page.dashboard.dashboard import cache_source, get_from_date_from_timespan -from frappe.utils import add_to_date, date_diff, getdate, nowdate +from frappe.utils import add_to_date, date_diff, getdate, nowdate, get_last_day from erpnext.accounts.report.general_ledger.general_ledger import execute +from frappe.core.page.dashboard.dashboard import cache_source, get_from_date_from_timespan +from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_period_beginning, get_period_ending from frappe.utils.nestedset import get_descendants_of @@ -23,13 +24,14 @@ def get(chart_name=None, from_date = None, to_date = None): if not to_date: to_date = nowdate() if not from_date: - from_date = get_from_date_from_timespan(to_date, timespan) + if timegrain in ('Monthly', 'Quarterly'): + from_date = get_from_date_from_timespan(to_date, timespan) # fetch dates to plot dates = get_dates_from_timegrain(from_date, to_date, timegrain) # get all the entries for this account and its descendants - gl_entries = get_gl_entries(account, to_date) + gl_entries = get_gl_entries(account, get_period_ending(to_date, timegrain)) # compile balance values result = build_result(account, dates, gl_entries) @@ -94,7 +96,8 @@ def get_dates_from_timegrain(from_date, to_date, timegrain): elif "Quarterly" == timegrain: months = 3 - dates = [from_date] - while getdate(dates[-1]) <= getdate(to_date): - dates.append(add_to_date(dates[-1], years=years, months=months, days=days)) + dates = [get_period_ending(from_date, timegrain)] + while getdate(dates[-1]) < getdate(to_date): + date = get_period_ending(add_to_date(dates[-1], years=years, months=months, days=days), timegrain) + dates.append(date) return dates From 810dde2e0be3c1269167619761d92bfbb613f9ce Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 16 Apr 2019 12:20:35 +0530 Subject: [PATCH 06/44] fix: show get items even if note has been amended --- .../doctype/delivery_note/delivery_note.js | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index 0ac53c5cf0..fd941c7a6c 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -101,6 +101,32 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend( refresh: function(doc, dt, dn) { var me = this; this._super(); + + if ((!doc.is_return) && (doc.status!="Closed" || doc.is_new())) { + this.frm.items = []; + this.frm.refresh(); + if (this.frm.doc.docstatus===0) { + this.frm.add_custom_button(__('Sales Order'), + function() { + erpnext.utils.map_current_doc({ + method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note", + source_doctype: "Sales Order", + target: me.frm, + setters: { + customer: me.frm.doc.customer || undefined, + }, + get_query_filters: { + docstatus: 1, + status: ["not in", ["Closed", "On Hold"]], + per_delivered: ["<", 99.99], + company: me.frm.doc.company, + project: me.frm.doc.project || undefined, + } + }) + }, __("Get items from")); + } + } + if (!doc.is_return && doc.status!="Closed") { if(flt(doc.per_installed, 2) < 100 && doc.docstatus==1) this.frm.add_custom_button(__('Installation Note'), function() { @@ -127,27 +153,6 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend( if (!doc.__islocal && doc.docstatus==1) { this.frm.page.set_inner_btn_group_as_primary(__('Create')); } - - if (this.frm.doc.docstatus===0) { - this.frm.add_custom_button(__('Sales Order'), - function() { - erpnext.utils.map_current_doc({ - method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note", - source_doctype: "Sales Order", - target: me.frm, - setters: { - customer: me.frm.doc.customer || undefined, - }, - get_query_filters: { - docstatus: 1, - status: ["not in", ["Closed", "On Hold"]], - per_delivered: ["<", 99.99], - company: me.frm.doc.company, - project: me.frm.doc.project || undefined, - } - }) - }, __("Get items from")); - } } if (doc.docstatus==1) { From b98612ff5b0ae3931696d01c0cf2908c45197467 Mon Sep 17 00:00:00 2001 From: scmmishra Date: Tue, 16 Apr 2019 12:20:42 +0530 Subject: [PATCH 07/44] feat: Added auto fetch logic and button for client --- .../js/utils/serial_no_batch_selector.js | 57 ++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index b94cdd8c4c..0e82b180b2 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -344,9 +344,27 @@ erpnext.SerialNoBatchSelector = Class.extend({ this.serial_list = []; return [ {fieldtype: 'Section Break', label: __('Serial No')}, + { + fieldname: 'auto_fetch_toggle', + fieldtype:'Check', + label: __('Fetch based on FIFO'), + default: 0, + onchange: function(e) { + const show_auto_fetch = this.get_value(); + if (show_auto_fetch) { + this.layout.set_df_property('auto_qty', 'hidden', 0); + this.layout.set_df_property('auto_fetch_button', 'hidden', 0); + this.layout.set_df_property('serial_no_select', 'hidden', 1); + } else { + this.layout.set_df_property('serial_no_select', 'hidden', 0); + this.layout.set_df_property('auto_qty', 'hidden', 1); + this.layout.set_df_property('auto_fetch_button', 'hidden', 1); + } + } + }, { fieldtype: 'Link', fieldname: 'serial_no_select', options: 'Serial No', - label: __('Select'), + label: __('Select to add serial no.'), get_query: function() { return { filters: {item_code: me.item_code, warehouse: me.warehouse_details.name}}; }, @@ -379,6 +397,43 @@ erpnext.SerialNoBatchSelector = Class.extend({ this.in_local_change = 0; } }, + { + fieldname: 'auto_qty', + fieldtype:'Float', + read_only: 0, + hidden: 1, + label: __('Auto Fetch Quantity'), + default: 0, + }, + { + fieldname: 'auto_fetch_button', + fieldtype:'Button', + label: __('Fetch Serial Numbers'), + read_only: 0, + hidden: 1, + click: (e) => { + // window.abc = this.dialog + console.log(this.dialog.fields_dict.auto_qty.get_value()) + let auto_fetch_qty_field = this.dialog.fields_dict.auto_qty; + let fetch_qty = auto_fetch_qty_field.get_value(); + let numbers = frappe.call({ + method: "erpnext.stock.doctype.serial_no.serial_no.auto_fetch_serial_number", + args: { + qty: fetch_qty, + item_code: me.item_code, + warehouse: me.warehouse_details.name + } + }); + + numbers.then((data) => { + let auto_fetched_serial_numbers = data.message; + let serial_no_list_field = this.layout.fields_dict.serial_no; + numbers = auto_fetched_serial_numbers.join('\n'); + serial_no_list_field.set_value(numbers); + auto_fetch_qty_field.set_value(""); + }) + } + }, {fieldtype: 'Column Break'}, { fieldname: 'serial_no', From 8ec54c2214b9f408859d52d41ea2b9b087080a97 Mon Sep 17 00:00:00 2001 From: scmmishra Date: Tue, 16 Apr 2019 12:20:55 +0530 Subject: [PATCH 08/44] feat: Added auto fetch serial numbers API --- erpnext/stock/doctype/serial_no/serial_no.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index cb1d153244..a421a209bd 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -459,3 +459,18 @@ def get_delivery_note_serial_no(item_code, qty, delivery_note): serial_nos = '\n'.join(dn_serial_nos) return serial_nos + +@frappe.whitelist() +def auto_fetch_serial_number(qty, item_code, warehouse): + serial_numbers = frappe.db.sql_list("""select name from `tabSerial No` + where + item_code = %(item_code)s + and warehouse = %(warehouse)s + and delivery_document_no is null + and sales_invoice is null + limit {0}""".format(cint(qty)), { + 'item_code': item_code, + 'warehouse': warehouse + }) + + return serial_numbers \ No newline at end of file From 41cf945247b0d690032030a311966d5b959ab698 Mon Sep 17 00:00:00 2001 From: scmmishra Date: Tue, 16 Apr 2019 12:47:39 +0530 Subject: [PATCH 09/44] feat (UX): Refactored batch selector --- .../js/utils/serial_no_batch_selector.js | 115 +++++++++--------- 1 file changed, 59 insertions(+), 56 deletions(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 0e82b180b2..93b7ce98bc 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -68,10 +68,38 @@ erpnext.SerialNoBatchSelector = Class.extend({ { fieldname: 'qty', fieldtype:'Float', - read_only: 1, + read_only: me.has_batch ? 1 : 0, label: __(me.has_batch ? 'Total Qty' : 'Qty'), default: 0 }, + { + fieldname: 'auto_fetch_button', + fieldtype:'Button', + hidden: me.has_batch ? 1 : 0, + label: __(me.has_batch ? 'Fetch Batch Numbers' : 'Fetch Serial Numbers'), + click: (e) => { + // window.abc = this.dialog + // console.log(this.dialog.fields_dict.qty.get_value()) + // let auto_fetch_qty_field = this.dialog.fields_dict.qty; + // let fetch_qty = auto_fetch_qty_field.get_value(); + let numbers = frappe.call({ + method: "erpnext.stock.doctype.serial_no.serial_no.auto_fetch_serial_number", + args: { + qty: this.dialog.fields_dict.qty.get_value(), + item_code: me.item_code, + warehouse: me.warehouse_details.name + } + }); + + numbers.then((data) => { + let auto_fetched_serial_numbers = data.message; + let serial_no_list_field = this.dialog.fields_dict.serial_no; + numbers = auto_fetched_serial_numbers.join('\n'); + serial_no_list_field.set_value(numbers); + // auto_fetch_qty_field.set_value(""); + }) + } + } ]; if(this.has_batch) { @@ -234,7 +262,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ var me = this; return [ {fieldtype:'Section Break', label: __('Batches')}, - {fieldname: 'batches', fieldtype: 'Table', + {fieldname: 'batches', fieldtype: 'Table', label: __('Batch Entries'), fields: [ { fieldtype:'Link', @@ -344,24 +372,6 @@ erpnext.SerialNoBatchSelector = Class.extend({ this.serial_list = []; return [ {fieldtype: 'Section Break', label: __('Serial No')}, - { - fieldname: 'auto_fetch_toggle', - fieldtype:'Check', - label: __('Fetch based on FIFO'), - default: 0, - onchange: function(e) { - const show_auto_fetch = this.get_value(); - if (show_auto_fetch) { - this.layout.set_df_property('auto_qty', 'hidden', 0); - this.layout.set_df_property('auto_fetch_button', 'hidden', 0); - this.layout.set_df_property('serial_no_select', 'hidden', 1); - } else { - this.layout.set_df_property('serial_no_select', 'hidden', 0); - this.layout.set_df_property('auto_qty', 'hidden', 1); - this.layout.set_df_property('auto_fetch_button', 'hidden', 1); - } - } - }, { fieldtype: 'Link', fieldname: 'serial_no_select', options: 'Serial No', label: __('Select to add serial no.'), @@ -397,47 +407,40 @@ erpnext.SerialNoBatchSelector = Class.extend({ this.in_local_change = 0; } }, - { - fieldname: 'auto_qty', - fieldtype:'Float', - read_only: 0, - hidden: 1, - label: __('Auto Fetch Quantity'), - default: 0, - }, - { - fieldname: 'auto_fetch_button', - fieldtype:'Button', - label: __('Fetch Serial Numbers'), - read_only: 0, - hidden: 1, - click: (e) => { - // window.abc = this.dialog - console.log(this.dialog.fields_dict.auto_qty.get_value()) - let auto_fetch_qty_field = this.dialog.fields_dict.auto_qty; - let fetch_qty = auto_fetch_qty_field.get_value(); - let numbers = frappe.call({ - method: "erpnext.stock.doctype.serial_no.serial_no.auto_fetch_serial_number", - args: { - qty: fetch_qty, - item_code: me.item_code, - warehouse: me.warehouse_details.name - } - }); + // { + // fieldname: 'auto_fetch_button', + // fieldtype:'Button', + // label: __('Fetch Serial Numbers'), + // read_only: 0, + // hidden: 1, + // click: (e) => { + // // window.abc = this.dialog + // console.log(this.dialog.fields_dict.auto_qty.get_value()) + // let auto_fetch_qty_field = this.dialog.fields_dict.auto_qty; + // let fetch_qty = auto_fetch_qty_field.get_value(); + // let numbers = frappe.call({ + // method: "erpnext.stock.doctype.serial_no.serial_no.auto_fetch_serial_number", + // args: { + // qty: fetch_qty, + // item_code: me.item_code, + // warehouse: me.warehouse_details.name + // } + // }); - numbers.then((data) => { - let auto_fetched_serial_numbers = data.message; - let serial_no_list_field = this.layout.fields_dict.serial_no; - numbers = auto_fetched_serial_numbers.join('\n'); - serial_no_list_field.set_value(numbers); - auto_fetch_qty_field.set_value(""); - }) - } - }, + // numbers.then((data) => { + // let auto_fetched_serial_numbers = data.message; + // let serial_no_list_field = this.layout.fields_dict.serial_no; + // numbers = auto_fetched_serial_numbers.join('\n'); + // serial_no_list_field.set_value(numbers); + // auto_fetch_qty_field.set_value(""); + // }) + // } + // }, {fieldtype: 'Column Break'}, { fieldname: 'serial_no', fieldtype: 'Small Text', + label: __(me.has_batch ? 'Selected Batch Numbers' : 'Selected Serial Numbers'), onchange: function() { me.serial_list = this.get_value() .replace(/\n/g, ' ').match(/\S+/g) || []; From 19a41ed16d2dc76c46054159c733add578c12473 Mon Sep 17 00:00:00 2001 From: scmmishra Date: Tue, 16 Apr 2019 12:51:05 +0530 Subject: [PATCH 10/44] chore: code cleaning --- .../js/utils/serial_no_batch_selector.js | 35 +------------------ 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 93b7ce98bc..6a346933ac 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -76,12 +76,8 @@ erpnext.SerialNoBatchSelector = Class.extend({ fieldname: 'auto_fetch_button', fieldtype:'Button', hidden: me.has_batch ? 1 : 0, - label: __(me.has_batch ? 'Fetch Batch Numbers' : 'Fetch Serial Numbers'), + label: __('Fetch Serial Numbers based on FIFO'), click: (e) => { - // window.abc = this.dialog - // console.log(this.dialog.fields_dict.qty.get_value()) - // let auto_fetch_qty_field = this.dialog.fields_dict.qty; - // let fetch_qty = auto_fetch_qty_field.get_value(); let numbers = frappe.call({ method: "erpnext.stock.doctype.serial_no.serial_no.auto_fetch_serial_number", args: { @@ -407,35 +403,6 @@ erpnext.SerialNoBatchSelector = Class.extend({ this.in_local_change = 0; } }, - // { - // fieldname: 'auto_fetch_button', - // fieldtype:'Button', - // label: __('Fetch Serial Numbers'), - // read_only: 0, - // hidden: 1, - // click: (e) => { - // // window.abc = this.dialog - // console.log(this.dialog.fields_dict.auto_qty.get_value()) - // let auto_fetch_qty_field = this.dialog.fields_dict.auto_qty; - // let fetch_qty = auto_fetch_qty_field.get_value(); - // let numbers = frappe.call({ - // method: "erpnext.stock.doctype.serial_no.serial_no.auto_fetch_serial_number", - // args: { - // qty: fetch_qty, - // item_code: me.item_code, - // warehouse: me.warehouse_details.name - // } - // }); - - // numbers.then((data) => { - // let auto_fetched_serial_numbers = data.message; - // let serial_no_list_field = this.layout.fields_dict.serial_no; - // numbers = auto_fetched_serial_numbers.join('\n'); - // serial_no_list_field.set_value(numbers); - // auto_fetch_qty_field.set_value(""); - // }) - // } - // }, {fieldtype: 'Column Break'}, { fieldname: 'serial_no', From ef848fda2aefd6e7742682eb9a1ecaf2768a377d Mon Sep 17 00:00:00 2001 From: Himanshu Date: Tue, 16 Apr 2019 13:17:20 +0530 Subject: [PATCH 11/44] fix: let user delete the elements of items --- erpnext/stock/doctype/delivery_note/delivery_note.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index fd941c7a6c..78bc06a47b 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -103,8 +103,6 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend( this._super(); if ((!doc.is_return) && (doc.status!="Closed" || doc.is_new())) { - this.frm.items = []; - this.frm.refresh(); if (this.frm.doc.docstatus===0) { this.frm.add_custom_button(__('Sales Order'), function() { From 407496b5282ed70e4af106e1338690b7b05c6b84 Mon Sep 17 00:00:00 2001 From: scmmishra Date: Tue, 16 Apr 2019 13:32:41 +0530 Subject: [PATCH 12/44] chore: minor changes to labels --- erpnext/public/js/utils/serial_no_batch_selector.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 6a346933ac..e8cfc6bf4d 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -76,7 +76,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ fieldname: 'auto_fetch_button', fieldtype:'Button', hidden: me.has_batch ? 1 : 0, - label: __('Fetch Serial Numbers based on FIFO'), + label: __('Fetch based on FIFO'), click: (e) => { let numbers = frappe.call({ method: "erpnext.stock.doctype.serial_no.serial_no.auto_fetch_serial_number", @@ -367,10 +367,10 @@ erpnext.SerialNoBatchSelector = Class.extend({ var me = this; this.serial_list = []; return [ - {fieldtype: 'Section Break', label: __('Serial No')}, + {fieldtype: 'Section Break', label: __('Serial Numbers')}, { fieldtype: 'Link', fieldname: 'serial_no_select', options: 'Serial No', - label: __('Select to add serial no.'), + label: __('Select to add Serial Number.'), get_query: function() { return { filters: {item_code: me.item_code, warehouse: me.warehouse_details.name}}; }, From 59c5a160371b2a5c448c5b3761a7f7d367f51749 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 16 Apr 2019 15:11:32 +0530 Subject: [PATCH 13/44] fix(account_balance_timeline): remove unused import --- .../account_balance_timeline/account_balance_timeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py index b047ccade7..f2abb81dbb 100644 --- a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py +++ b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py @@ -6,7 +6,7 @@ import frappe, json from frappe.utils import add_to_date, date_diff, getdate, nowdate, get_last_day from erpnext.accounts.report.general_ledger.general_ledger import execute from frappe.core.page.dashboard.dashboard import cache_source, get_from_date_from_timespan -from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_period_beginning, get_period_ending +from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_period_ending from frappe.utils.nestedset import get_descendants_of From 61d6b677e4ec66e520654eb9d2a23ae500429157 Mon Sep 17 00:00:00 2001 From: scmmishra Date: Tue, 16 Apr 2019 15:17:27 +0530 Subject: [PATCH 14/44] feat: Show standard serial number selector in item grid form --- erpnext/public/js/utils.js | 38 ++------------------------------------ 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index fb4f35081c..5a6ae323f5 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -58,43 +58,9 @@ $.extend(erpnext, { .css({"margin-bottom": "10px", "margin-top": "10px"}) .appendTo(grid_row.grid_form.fields_dict.serial_no.$wrapper)); + var me = this $btn.on("click", function() { - var d = new frappe.ui.Dialog({ - title: __("Add Serial No"), - fields: [ - { - "fieldtype": "Link", - "fieldname": "serial_no", - "options": "Serial No", - "label": __("Serial No"), - "get_query": function () { - return { - filters: { - item_code:grid_row.doc.item_code, - warehouse:cur_frm.doc.is_return ? null : grid_row.doc.warehouse - } - } - } - }, - { - "fieldtype": "Button", - "fieldname": "add", - "label": __("Add") - } - ] - }); - - d.get_input("add").on("click", function() { - var serial_no = d.get_value("serial_no"); - if(serial_no) { - var val = (grid_row.doc.serial_no || "").split("\n").concat([serial_no]).join("\n"); - grid_row.grid_form.fields_dict.serial_no.set_model_value(val.trim()); - } - d.hide(); - return false; - }); - - d.show(); + me.show_serial_batch_selector(grid_row.frm, grid_row.doc) }); } }); From eee5c38911407189b4d8a67e868fec4df426db6c Mon Sep 17 00:00:00 2001 From: scmmishra Date: Tue, 16 Apr 2019 15:57:51 +0530 Subject: [PATCH 15/44] feat: Use controller for serial no. API --- erpnext/stock/doctype/serial_no/serial_no.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index a421a209bd..c6d1ed24b7 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -462,15 +462,5 @@ def get_delivery_note_serial_no(item_code, qty, delivery_note): @frappe.whitelist() def auto_fetch_serial_number(qty, item_code, warehouse): - serial_numbers = frappe.db.sql_list("""select name from `tabSerial No` - where - item_code = %(item_code)s - and warehouse = %(warehouse)s - and delivery_document_no is null - and sales_invoice is null - limit {0}""".format(cint(qty)), { - 'item_code': item_code, - 'warehouse': warehouse - }) - + serial_numbers = frappe.get_list("Serial No", filters={"item_code": item_code, "warehouse": warehouse, "delivery_document_no": "", "sales_invoice": ""}, limit=qty, order_by="creation") return serial_numbers \ No newline at end of file From c791c3b24b6d27d01510f5337747f9e98afbd1f5 Mon Sep 17 00:00:00 2001 From: scmmishra Date: Tue, 16 Apr 2019 16:07:32 +0530 Subject: [PATCH 16/44] fix: Minor bug in serial no auto fetch api --- erpnext/stock/doctype/serial_no/serial_no.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index c6d1ed24b7..be26a18f44 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -463,4 +463,4 @@ def get_delivery_note_serial_no(item_code, qty, delivery_note): @frappe.whitelist() def auto_fetch_serial_number(qty, item_code, warehouse): serial_numbers = frappe.get_list("Serial No", filters={"item_code": item_code, "warehouse": warehouse, "delivery_document_no": "", "sales_invoice": ""}, limit=qty, order_by="creation") - return serial_numbers \ No newline at end of file + return [item['name'] for item in serial_numbers] \ No newline at end of file From 46901076be3d1cb2f5cda20abba443c82551f976 Mon Sep 17 00:00:00 2001 From: scmmishra Date: Tue, 16 Apr 2019 17:01:01 +0530 Subject: [PATCH 17/44] feat: Use standard serial picker to add as well as update serial numbers --- .../js/utils/serial_no_batch_selector.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index e8cfc6bf4d..f479066964 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -2,15 +2,13 @@ erpnext.SerialNoBatchSelector = Class.extend({ init: function(opts, show_dialog) { $.extend(this, opts); - this.show_dialog = show_dialog; + this.show_dialog = show_dialog // frm, item, warehouse_details, has_batch, oldest let d = this.item; - - // Don't show dialog if batch no or serial no already set if(d && d.has_batch_no && (!d.batch_no || this.show_dialog)) { this.has_batch = 1; this.setup(); - } else if(d && d.has_serial_no && (!d.serial_no || this.show_dialog)) { + } else if(d && d.has_serial_no && !(this.show_dialog == false)) { this.has_batch = 0; this.setup(); } @@ -78,10 +76,11 @@ erpnext.SerialNoBatchSelector = Class.extend({ hidden: me.has_batch ? 1 : 0, label: __('Fetch based on FIFO'), click: (e) => { + let qty = this.dialog.fields_dict.qty.get_value() let numbers = frappe.call({ method: "erpnext.stock.doctype.serial_no.serial_no.auto_fetch_serial_number", args: { - qty: this.dialog.fields_dict.qty.get_value(), + qty: qty, item_code: me.item_code, warehouse: me.warehouse_details.name } @@ -89,10 +88,13 @@ erpnext.SerialNoBatchSelector = Class.extend({ numbers.then((data) => { let auto_fetched_serial_numbers = data.message; + records_length = auto_fetched_serial_numbers.length + if(records_length < qty) { + frappe.msgprint(`Fetched only ${records_length} serial numbers`) + } let serial_no_list_field = this.dialog.fields_dict.serial_no; numbers = auto_fetched_serial_numbers.join('\n'); serial_no_list_field.set_value(numbers); - // auto_fetch_qty_field.set_value(""); }) } } @@ -111,6 +113,10 @@ erpnext.SerialNoBatchSelector = Class.extend({ fields: fields }); + if (this.item.serial_no) { + this.dialog.fields_dict.serial_no.set_value(this.item.serial_no) + } + this.dialog.set_primary_action(__('Insert'), function() { me.values = me.dialog.get_values(); if(me.validate()) { From ed4b052df82dd3a7413ba2aced154cd8e9025f7e Mon Sep 17 00:00:00 2001 From: scmmishra Date: Tue, 16 Apr 2019 17:03:25 +0530 Subject: [PATCH 18/44] style (codacy): code formatting fixes --- erpnext/public/js/utils.js | 4 ++-- erpnext/public/js/utils/serial_no_batch_selector.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 5a6ae323f5..6860d6a238 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -58,9 +58,9 @@ $.extend(erpnext, { .css({"margin-bottom": "10px", "margin-top": "10px"}) .appendTo(grid_row.grid_form.fields_dict.serial_no.$wrapper)); - var me = this + var me = this; $btn.on("click", function() { - me.show_serial_batch_selector(grid_row.frm, grid_row.doc) + me.show_serial_batch_selector(grid_row.frm, grid_row.doc); }); } }); diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index f479066964..e0539383da 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -88,14 +88,14 @@ erpnext.SerialNoBatchSelector = Class.extend({ numbers.then((data) => { let auto_fetched_serial_numbers = data.message; - records_length = auto_fetched_serial_numbers.length + records_length = auto_fetched_serial_numbers.length; if(records_length < qty) { - frappe.msgprint(`Fetched only ${records_length} serial numbers`) + frappe.msgprint(`Fetched only ${records_length} serial numbers.`); } let serial_no_list_field = this.dialog.fields_dict.serial_no; numbers = auto_fetched_serial_numbers.join('\n'); serial_no_list_field.set_value(numbers); - }) + }); } } ]; From 6d3302b0cc2306a6f53296b6ac5f8cffd65c6fc8 Mon Sep 17 00:00:00 2001 From: scmmishra Date: Tue, 16 Apr 2019 18:25:53 +0530 Subject: [PATCH 19/44] style (codacy): code formatting fixes --- erpnext/public/js/utils/serial_no_batch_selector.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index e0539383da..d02991225b 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -2,7 +2,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ init: function(opts, show_dialog) { $.extend(this, opts); - this.show_dialog = show_dialog + this.show_dialog = show_dialog; // frm, item, warehouse_details, has_batch, oldest let d = this.item; if(d && d.has_batch_no && (!d.batch_no || this.show_dialog)) { @@ -76,7 +76,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ hidden: me.has_batch ? 1 : 0, label: __('Fetch based on FIFO'), click: (e) => { - let qty = this.dialog.fields_dict.qty.get_value() + let qty = this.dialog.fields_dict.qty.get_value(); let numbers = frappe.call({ method: "erpnext.stock.doctype.serial_no.serial_no.auto_fetch_serial_number", args: { @@ -88,7 +88,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ numbers.then((data) => { let auto_fetched_serial_numbers = data.message; - records_length = auto_fetched_serial_numbers.length; + let records_length = auto_fetched_serial_numbers.length; if(records_length < qty) { frappe.msgprint(`Fetched only ${records_length} serial numbers.`); } @@ -114,7 +114,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ }); if (this.item.serial_no) { - this.dialog.fields_dict.serial_no.set_value(this.item.serial_no) + this.dialog.fields_dict.serial_no.set_value(this.item.serial_no); } this.dialog.set_primary_action(__('Insert'), function() { From 34ee41a35eed66195786dbddd96840632144e65c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 11 Apr 2019 13:56:40 +0530 Subject: [PATCH 20/44] fix: Pending SO Items For Purchase Request not showing the so quantity correctly if so has duplicate items --- .../pending_so_items_for_purchase_request.py | 57 +++++++++++-------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py b/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py index 87216518c6..670b4e98bf 100644 --- a/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py +++ b/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py @@ -47,9 +47,8 @@ def get_columns(): }, { "label": _("Material Request"), - "options": "Material Request", "fieldname": "material_request", - "fieldtype": "Link", + "fieldtype": "Data", "width": 140 }, { @@ -116,33 +115,43 @@ def get_data(): {"sales_order_item": ("!=",""), "docstatus": 1}, ["parent", "qty", "sales_order", "item_code"]) - grouped_records = {} + materials_request_dict = {} for record in mr_records: - grouped_records.setdefault(record.sales_order, []).append(record) + key = (record.sales_order, record.item_code) + if key not in materials_request_dict: + materials_request_dict.setdefault(key, { + 'qty': 0, + 'material_requests': [record.parent] + }) + + details = materials_request_dict.get(key) + details['qty'] += record.qty + + if record.parent not in details.get('material_requests'): + details['material_requests'].append(record.parent) pending_so=[] for so in sales_order_entry: # fetch all the material request records for a sales order item - mr_list = grouped_records.get(so.name) or [{}] - mr_item_record = ([mr for mr in mr_list if mr.get('item_code') == so.item_code] or [{}]) + key = (so.name, so.item_code) + materials_request = materials_request_dict.get(key) or {} - for mr in mr_item_record: - # check for pending sales order - if cint(so.net_qty) > cint(mr.get('qty')): - so_record = { - "item_code": so.item_code, - "item_name": so.item_name, - "description": so.description, - "sales_order_no": so.name, - "date": so.transaction_date, - "material_request": cstr(mr.get('parent')), - "customer": so.customer, - "territory": so.territory, - "so_qty": so.net_qty, - "requested_qty": cint(mr.get('qty')), - "pending_qty": so.net_qty - cint(mr.get('qty')), - "company": so.company - } - pending_so.append(so_record) + # check for pending sales order + if cint(so.net_qty) > cint(materials_request.get('qty')): + so_record = { + "item_code": so.item_code, + "item_name": so.item_name, + "description": so.description, + "sales_order_no": so.name, + "date": so.transaction_date, + "material_request": ','.join(materials_request.get('material_requests', [])), + "customer": so.customer, + "territory": so.territory, + "so_qty": so.net_qty, + "requested_qty": cint(materials_request.get('qty')), + "pending_qty": so.net_qty - cint(materials_request.get('qty')), + "company": so.company + } + pending_so.append(so_record) return pending_so \ No newline at end of file From 00d73cda508570a047b51e9abc27cb9189061543 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 14 Apr 2019 19:39:11 +0530 Subject: [PATCH 21/44] fix: POS not working if user has access of multiple company --- .../page/point_of_sale/point_of_sale.js | 45 +++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js index 4c0f42d22e..308b8edd3b 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.js +++ b/erpnext/selling/page/point_of_sale/point_of_sale.js @@ -54,8 +54,16 @@ erpnext.pos.PointOfSale = class PointOfSale { this.prepare_menu(); this.set_online_status(); }, - () => this.setup_company(), () => this.make_new_invoice(), + () => { + if(!this.frm.doc.company) { + this.setup_company() + .then((company) => { + this.frm.doc.company = company; + this.get_pos_profile(); + }); + } + }, () => { frappe.dom.unfreeze(); }, @@ -63,6 +71,22 @@ erpnext.pos.PointOfSale = class PointOfSale { ]); } + get_pos_profile() { + return frappe.xcall("erpnext.stock.get_item_details.get_pos_profile", + {'company': this.frm.doc.company}) + .then((r) => { + if(r) { + this.frm.doc.pos_profile = r.name; + this.set_pos_profile_data() + .then(() => { + this.on_change_pos_profile(); + }); + } else { + this.raise_exception_for_pos_profile(); + } + }); + } + set_online_status() { this.connection_status = false; this.page.set_indicator(__("Offline"), "grey"); @@ -77,6 +101,11 @@ erpnext.pos.PointOfSale = class PointOfSale { }); } + raise_exception_for_pos_profile() { + setTimeout(() => frappe.set_route('List', 'POS Profile'), 2000); + frappe.throw(__("POS Profile is required to use Point-of-Sale")); + } + prepare_dom() { this.wrapper.append(`
@@ -489,7 +518,7 @@ erpnext.pos.PointOfSale = class PointOfSale { setup_company() { return new Promise(resolve => { - if(!frappe.sys_defaults.company) { + if(!this.frm.doc.company) { frappe.prompt({fieldname:"company", options: "Company", fieldtype:"Link", label: __("Select Company"), reqd: 1}, (data) => { this.company = data.company; @@ -529,6 +558,10 @@ erpnext.pos.PointOfSale = class PointOfSale { return new Promise(resolve => { if (this.frm) { this.frm = get_frm(this.frm); + if(this.company) { + this.frm.doc.company = this.company; + } + resolve(); } else { frappe.model.with_doctype(doctype, () => { @@ -545,6 +578,7 @@ erpnext.pos.PointOfSale = class PointOfSale { frm.refresh(name); frm.doc.items = []; frm.doc.is_pos = 1; + return frm; } } @@ -554,6 +588,10 @@ erpnext.pos.PointOfSale = class PointOfSale { this.frm.doc.company = this.company; } + if (!this.frm.doc.company) { + return; + } + return new Promise(resolve => { return this.frm.call({ doc: this.frm.doc, @@ -562,8 +600,7 @@ erpnext.pos.PointOfSale = class PointOfSale { if(!r.exc) { if (!this.frm.doc.pos_profile) { frappe.dom.unfreeze(); - setTimeout(() => frappe.set_route('List', 'POS Profile'), 2000); - frappe.throw(__("POS Profile is required to use Point-of-Sale")); + this.raise_exception_for_pos_profile(); } this.frm.script_manager.trigger("update_stock"); frappe.model.set_default_values(this.frm.doc); From 7aee809571d5097bd1c734fe05b9ff602ab2da05 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 16 Apr 2019 19:16:14 +0530 Subject: [PATCH 22/44] fix: If finance book filter is not set then show all the entries --- .../accounts_receivable/accounts_receivable.py | 7 +------ .../asset_depreciation_ledger.py | 5 +---- .../consolidated_financial_statement.py | 13 ++++--------- .../customer_ledger_summary.py | 8 ++------ erpnext/accounts/report/financial_statements.py | 10 ++-------- .../report/general_ledger/general_ledger.py | 6 +----- 6 files changed, 11 insertions(+), 38 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 4932ae1327..eb41ef6459 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -481,13 +481,8 @@ class ReceivablePayableReport(object): conditions.append("company=%s") values.append(self.filters.company) - company_finance_book = erpnext.get_default_finance_book(self.filters.company) - - if not self.filters.finance_book or (self.filters.finance_book == company_finance_book): + if self.filters.finance_book: conditions.append("ifnull(finance_book,'') in (%s, '')") - values.append(company_finance_book) - elif self.filters.finance_book: - conditions.append("ifnull(finance_book,'') = %s") values.append(self.filters.finance_book) if self.filters.get(party_type_field): diff --git a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py index 3613131910..16bef56525 100644 --- a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py +++ b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py @@ -31,11 +31,8 @@ def get_data(filters): filters_data.append(["against_voucher", "in", assets]) - company_finance_book = erpnext.get_default_finance_book(filters.get("company")) - if (not filters.get('finance_book') or (filters.get('finance_book') == company_finance_book)): + if filters.get("finance_book"): filters_data.append(["finance_book", "in", ['', filters.get('finance_book')]]) - elif filters.get("finance_book"): - filters_data.append(["finance_book", "=", filters.get('finance_book')]) gl_entries = frappe.get_all('GL Entry', filters= filters_data, diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index 8428f268bb..002493112d 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -355,7 +355,8 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g "to_date": to_date, "lft": root_lft, "rgt": root_rgt, - "company": d.name + "company": d.name, + "finance_book": filters.get("finance_book") }, as_dict=True) @@ -385,14 +386,8 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters): if from_date: additional_conditions.append("gl.posting_date >= %(from_date)s") - company_finance_book = erpnext.get_default_finance_book(filters.get("company")) - - if not filters.get('finance_book') or (filters.get('finance_book') == company_finance_book): - additional_conditions.append("ifnull(finance_book, '') in (%s, '')" % - frappe.db.escape(company_finance_book)) - elif filters.get("finance_book"): - additional_conditions.append("ifnull(finance_book, '') = %s " % - frappe.db.escape(filters.get("finance_book"))) + if filters.get("finance_book"): + additional_conditions.append("ifnull(finance_book, '') in (%(finance_book)s, '')") return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else "" diff --git a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py index 23b963b759..7872dbe7b1 100644 --- a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py +++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py @@ -184,12 +184,8 @@ class PartyLedgerSummaryReport(object): if self.filters.company: conditions.append("gle.company=%(company)s") - self.filters.company_finance_book = erpnext.get_default_finance_book(self.filters.company) - - if not self.filters.finance_book or (self.filters.finance_book == self.filters.company_finance_book): - conditions.append("ifnull(finance_book,'') in (%(company_finance_book)s, '')") - elif self.filters.finance_book: - conditions.append("ifnull(finance_book,'') = %(finance_book)s") + if self.filters.finance_book: + conditions.append("ifnull(finance_book,'') in (%(finance_book)s, '')") if self.filters.get("party"): conditions.append("party=%(party)s") diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 43fb87c8a4..f358b4a205 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -392,14 +392,8 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters): filters.cost_center = get_cost_centers_with_children(filters.cost_center) additional_conditions.append("cost_center in %(cost_center)s") - company_finance_book = erpnext.get_default_finance_book(filters.get("company")) - - if not filters.get('finance_book') or (filters.get('finance_book') == company_finance_book): - additional_conditions.append("ifnull(finance_book, '') in (%s, '')" % - frappe.db.escape(company_finance_book)) - elif filters.get("finance_book"): - additional_conditions.append("ifnull(finance_book, '') = %s " % - frappe.db.escape(filters.get("finance_book"))) + if filters.get("finance_book"): + additional_conditions.append("ifnull(finance_book, '') in (%(finance_book)s, '')") return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else "" diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index ecb18f78b1..44ca8d3549 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -186,12 +186,8 @@ def get_conditions(filters): if filters.get("project"): conditions.append("project in %(project)s") - company_finance_book = erpnext.get_default_finance_book(filters.get("company")) - if not filters.get("finance_book") or (filters.get("finance_book") == company_finance_book): - filters['finance_book'] = company_finance_book + if filters.get("finance_book"): conditions.append("ifnull(finance_book, '') in (%(finance_book)s, '')") - elif filters.get("finance_book"): - conditions.append("ifnull(finance_book, '') = %(finance_book)s") from frappe.desk.reportview import build_match_conditions match_conditions = build_match_conditions("GL Entry") From e5a869bc45fa64aa4278629d36657639ca3d37d2 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Tue, 16 Apr 2019 20:08:12 +0530 Subject: [PATCH 23/44] fix: GSTR-1 B2CS report fix --- erpnext/regional/report/gstr_1/gstr_1.py | 46 ++++++++++++++++++++---- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 24bd6cf11c..8447210fe3 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -55,18 +55,50 @@ class Gstr1Report(object): return self.columns, self.data def get_data(self): + + if self.filters.get("type_of_business") == "B2C Small": + self.get_b2cs_data() + else: + for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): + invoice_details = self.invoices.get(inv) + for rate, items in items_based_on_rate.items(): + row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items) + + if self.filters.get("type_of_business") == "CDNR": + row.append("Y" if invoice_details.posting_date <= date(2017, 7, 1) else "N") + row.append("C" if invoice_details.return_against else "R") + + self.data.append(row) + + def get_b2cs_data(self): + b2cs_output = {} + for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): invoice_details = self.invoices.get(inv) + print(invoice_details) for rate, items in items_based_on_rate.items(): - row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items) - if self.filters.get("type_of_business") == "B2C Small": - row.append("E" if invoice_details.ecommerce_gstin else "OE") + place_of_supply = invoice_details.get("place_of_supply") + ecommerce_gstin = invoice_details.get("ecommerce_gstin") - if self.filters.get("type_of_business") == "CDNR": - row.append("Y" if invoice_details.posting_date <= date(2017, 7, 1) else "N") - row.append("C" if invoice_details.return_against else "R") + b2cs_output.setdefault((rate, place_of_supply, ecommerce_gstin),{ + "place_of_supply": "", + "ecommerce_gstin": "", + "rate": "", + "taxable_value": 0, + "cess_amount": 0, + "type": 0 + }) - self.data.append(row) + row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin)) + row["place_of_supply"] = place_of_supply + row["ecommerce_gstin"] = ecommerce_gstin + row["rate"] = rate + row["taxable_value"] += sum([abs(net_amount) + for item_code, net_amount in self.invoice_items.get(inv).items() if item_code in items]) + row["type"] = "E" if ecommerce_gstin else "OE" + + for key, value in iteritems(b2cs_output): + self.data.append(value) def get_row_data_for_invoice(self, invoice, invoice_details, tax_rate, items): row = [] From c354a17383e7b74ce0501adf4770218a841e7f7e Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Tue, 16 Apr 2019 20:56:49 +0530 Subject: [PATCH 24/44] fix: Code cleanup --- erpnext/regional/report/gstr_1/gstr_1.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 8447210fe3..af9de3552f 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -39,7 +39,6 @@ class Gstr1Report(object): shipping_bill_date, reason_for_issuing_document """ - # self.customer_type = "Company" if self.filters.get("type_of_business") == "B2B" else "Individual" def run(self): self.get_columns() @@ -146,7 +145,6 @@ class Gstr1Report(object): if self.filters.get(opts[0]): conditions += opts[1] - # customers = frappe.get_all("Customer", filters={"customer_type": self.customer_type}) if self.filters.get("type_of_business") == "B2B": customers = frappe.get_all("Customer", From 9f873e97fa21d6764ce1165e4c2e49467eb8f5f1 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Tue, 16 Apr 2019 20:58:22 +0530 Subject: [PATCH 25/44] fix: Remove print statement --- erpnext/regional/report/gstr_1/gstr_1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index af9de3552f..a29d5b48a9 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -74,7 +74,7 @@ class Gstr1Report(object): for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): invoice_details = self.invoices.get(inv) - print(invoice_details) + for rate, items in items_based_on_rate.items(): place_of_supply = invoice_details.get("place_of_supply") ecommerce_gstin = invoice_details.get("ecommerce_gstin") From 1cf9fa9662e5657d3172dc839f0a713e506d28cc Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 17 Apr 2019 09:45:46 +0530 Subject: [PATCH 26/44] fix: Refactor Upload Attendance (#17241) New API frappe.ui.FileUploader https://github.com/frappe/frappe/pull/7253 --- .../upload_attendance/upload_attendance.js | 29 ++++++------------- .../upload_attendance/upload_attendance.py | 4 +-- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/erpnext/hr/doctype/upload_attendance/upload_attendance.js b/erpnext/hr/doctype/upload_attendance/upload_attendance.js index 776fd3c13b..277c060d6a 100644 --- a/erpnext/hr/doctype/upload_attendance/upload_attendance.js +++ b/erpnext/hr/doctype/upload_attendance/upload_attendance.js @@ -29,19 +29,12 @@ erpnext.hr.AttendanceControlPanel = frappe.ui.form.Controller.extend({ }); }, - show_upload: function() { - var me = this; + show_upload() { var $wrapper = $(cur_frm.fields_dict.upload_html.wrapper).empty(); - - // upload - frappe.upload.make({ - parent: $wrapper, - args: { - method: 'erpnext.hr.doctype.upload_attendance.upload_attendance.upload' - }, - no_socketio: true, - sample_url: "e.g. http://example.com/somefile.csv", - callback: function(attachment, r) { + new frappe.ui.FileUploader({ + wrapper: $wrapper, + method: 'erpnext.hr.doctype.upload_attendance.upload_attendance.upload', + on_success(file_doc, r) { var $log_wrapper = $(cur_frm.fields_dict.import_log.wrapper).empty(); if(!r.messages) r.messages = []; @@ -59,10 +52,10 @@ erpnext.hr.AttendanceControlPanel = frappe.ui.form.Controller.extend({ }); r.messages = ["

"+__("Import Failed!")+"

"] - .concat(r.messages) + .concat(r.messages); } else { - r.messages = ["

"+__("Import Successful!")+"

"]. - concat(r.message.messages) + r.messages = ["

"+__("Import Successful!")+"

"] + .concat(r.message.messages); } $.each(r.messages, function(i, v) { @@ -79,11 +72,7 @@ erpnext.hr.AttendanceControlPanel = frappe.ui.form.Controller.extend({ }); } }); - - // rename button - $wrapper.find('form input[type="submit"]') - .attr('value', 'Upload and Import') - } + }, }) cur_frm.cscript = new erpnext.hr.AttendanceControlPanel({frm: cur_frm}); diff --git a/erpnext/hr/doctype/upload_attendance/upload_attendance.py b/erpnext/hr/doctype/upload_attendance/upload_attendance.py index db74b102a7..6a4ea6a570 100644 --- a/erpnext/hr/doctype/upload_attendance/upload_attendance.py +++ b/erpnext/hr/doctype/upload_attendance/upload_attendance.py @@ -116,10 +116,10 @@ def upload(): if not frappe.has_permission("Attendance", "create"): raise frappe.PermissionError - from frappe.utils.csvutils import read_csv_content_from_uploaded_file + from frappe.utils.csvutils import read_csv_content from frappe.modules import scrub - rows = read_csv_content_from_uploaded_file() + rows = read_csv_content(frappe.local.uploaded_file) rows = list(filter(lambda x: x and any(x), rows)) if not rows: msg = [_("Please select a csv file")] From 5522cf8c26c85111b482982bd9c63f60659f7ee4 Mon Sep 17 00:00:00 2001 From: scmmishra Date: Wed, 17 Apr 2019 10:26:21 +0530 Subject: [PATCH 27/44] fix: codacy linting --- erpnext/public/js/utils/serial_no_batch_selector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index d02991225b..5287942819 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -75,7 +75,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ fieldtype:'Button', hidden: me.has_batch ? 1 : 0, label: __('Fetch based on FIFO'), - click: (e) => { + click: () => { let qty = this.dialog.fields_dict.qty.get_value(); let numbers = frappe.call({ method: "erpnext.stock.doctype.serial_no.serial_no.auto_fetch_serial_number", From 63921ebbc97d84e95e322a36799afa46938771db Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 17 Apr 2019 15:29:29 +0530 Subject: [PATCH 28/44] style: fixed formatting Fixed formatting for for auto_fetch_serial_number --- erpnext/stock/doctype/serial_no/serial_no.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index be26a18f44..c1aef95216 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -462,5 +462,10 @@ def get_delivery_note_serial_no(item_code, qty, delivery_note): @frappe.whitelist() def auto_fetch_serial_number(qty, item_code, warehouse): - serial_numbers = frappe.get_list("Serial No", filters={"item_code": item_code, "warehouse": warehouse, "delivery_document_no": "", "sales_invoice": ""}, limit=qty, order_by="creation") - return [item['name'] for item in serial_numbers] \ No newline at end of file + serial_numbers = frappe.get_list("Serial No", filters={ + "item_code": item_code, + "warehouse": warehouse, + "delivery_document_no": "", + "sales_invoice": "" + }, limit=qty, order_by="creation") + return [item['name'] for item in serial_numbers] From fed4fd0f02bda5f5c61487722ad9ed132d102dde Mon Sep 17 00:00:00 2001 From: scmmishra Date: Wed, 17 Apr 2019 15:46:15 +0530 Subject: [PATCH 29/44] style: linting issue fixed --- erpnext/public/js/utils/serial_no_batch_selector.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 5287942819..b22d5ca4c4 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -5,9 +5,10 @@ erpnext.SerialNoBatchSelector = Class.extend({ this.show_dialog = show_dialog; // frm, item, warehouse_details, has_batch, oldest let d = this.item; - if(d && d.has_batch_no && (!d.batch_no || this.show_dialog)) { + if (d && d.has_batch_no && (!d.batch_no || this.show_dialog)) { this.has_batch = 1; this.setup(); + // !(this.show_dialog == false) ensures that show_dialog is implictly true, even when undefined } else if(d && d.has_serial_no && !(this.show_dialog == false)) { this.has_batch = 0; this.setup(); @@ -66,14 +67,14 @@ erpnext.SerialNoBatchSelector = Class.extend({ { fieldname: 'qty', fieldtype:'Float', - read_only: me.has_batch ? 1 : 0, + read_only: me.has_batch, label: __(me.has_batch ? 'Total Qty' : 'Qty'), default: 0 }, { fieldname: 'auto_fetch_button', fieldtype:'Button', - hidden: me.has_batch ? 1 : 0, + hidden: me.has_batch, label: __('Fetch based on FIFO'), click: () => { let qty = this.dialog.fields_dict.qty.get_value(); @@ -89,7 +90,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ numbers.then((data) => { let auto_fetched_serial_numbers = data.message; let records_length = auto_fetched_serial_numbers.length; - if(records_length < qty) { + if (records_length < qty) { frappe.msgprint(`Fetched only ${records_length} serial numbers.`); } let serial_no_list_field = this.dialog.fields_dict.serial_no; @@ -100,7 +101,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ } ]; - if(this.has_batch) { + if (this.has_batch) { title = __("Select Batch Numbers"); fields = fields.concat(this.get_batch_fields()); } else { From 38238b0cfdb0e5306bf13a101cfe54d74311e20b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 17 Apr 2019 15:56:00 +0530 Subject: [PATCH 30/44] fixed test cases --- .../doctype/production_plan/production_plan.py | 3 +++ erpnext/selling/doctype/sales_order/sales_order.js | 4 ++-- erpnext/selling/doctype/sales_order/sales_order.py | 7 ++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 490555ce47..18ca9ccac8 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -609,6 +609,9 @@ def get_items_for_material_requests(doc, ignore_existing_ordered_qty=None): so_item_details = frappe._dict() for data in po_items: planned_qty = data.get('required_qty') or data.get('planned_qty') + ignore_existing_ordered_qty = data.get('ignore_existing_ordered_qty') or ignore_existing_ordered_qty + warehouse = data.get("warehouse") or warehouse + item_details = {} if data.get("bom") or data.get("bom_no"): if data.get('required_qty'): diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index bc1f9587e2..e27ca018a9 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -365,6 +365,8 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( fields: [ {fieldtype:'Read Only', fieldname:'item_code', label: __('Item Code'), in_list_view:1}, + {fieldtype:'Link', fieldname:'warehouse', options: 'Warehouse', + label: __('For Warehouse'), in_list_view:1}, {fieldtype:'Link', fieldname:'bom', options: 'BOM', reqd: 1, label: __('BOM'), in_list_view:1, get_query: function(doc) { return {filters: {item: doc.item_code}}; @@ -372,8 +374,6 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( }, {fieldtype:'Float', fieldname:'required_qty', reqd: 1, label: __('Qty'), in_list_view:1}, - {fieldtype:'Link', fieldname:'for_warehouse', options: 'Warehouse', - label: __('For Warehouse')} ], data: r.message, get_data: function() { diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 7eab352fc0..ae2cd94ac1 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -936,7 +936,12 @@ def make_raw_material_request(items, company, sales_order, project=None): item["ignore_existing_ordered_qty"] = items.get('ignore_existing_ordered_qty') item["include_raw_materials_from_sales_order"] = items.get('include_raw_materials_from_sales_order') - raw_materials = get_items_for_material_requests(items, sales_order, company) + items.update({ + 'company': company, + 'sales_order': sales_order + }) + + raw_materials = get_items_for_material_requests(items) if not raw_materials: frappe.msgprint(_("Material Request not created, as quantity for Raw Materials already available.")) return From 36bb3f55f2a90c6748775d78c0a0e1dd0ff6ea9d Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 18 Apr 2019 14:47:44 +0530 Subject: [PATCH 31/44] fix: Added invoice discounting dashboard --- .../invoice_discounting_dashboard.py | 20 +++++++++++++++++++ erpnext/config/accounting.py | 5 +++++ 2 files changed, 25 insertions(+) create mode 100644 erpnext/accounts/doctype/invoice_discounting/invoice_discounting_dashboard.py diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting_dashboard.py b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting_dashboard.py new file mode 100644 index 0000000000..6523cd3cdb --- /dev/null +++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting_dashboard.py @@ -0,0 +1,20 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'reference_name', + 'internal_links': { + 'Sales Invoice': ['invoices', 'sales_invoice'] + }, + 'transactions': [ + { + 'label': _('Reference'), + 'items': ['Sales Invoice'] + }, + { + 'label': _('Payment'), + 'items': ['Payment Entry', 'Journal Entry'] + } + ] + } \ No newline at end of file diff --git a/erpnext/config/accounting.py b/erpnext/config/accounting.py index afe35f8078..1b8bf2717b 100644 --- a/erpnext/config/accounting.py +++ b/erpnext/config/accounting.py @@ -232,6 +232,11 @@ def get_data(): "label": _("Bank Account"), "name": "Bank Account", }, + { + "type": "doctype", + "label": _("Invoice Discounting"), + "name": "Invoice Discounting", + }, { "type": "doctype", "label": _("Bank Statement Transaction Entry List"), From 8170585faf3204b815d2a7770892509aaaac6052 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 18 Apr 2019 15:13:18 +0530 Subject: [PATCH 32/44] fix: Don't fetch already discounted invoice --- .../doctype/invoice_discounting/invoice_discounting.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py index 64aa4e4af6..d1ede67ed4 100644 --- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py +++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py @@ -190,9 +190,11 @@ def get_invoices(filters): customer, posting_date, outstanding_amount - from `tabSales Invoice` + from `tabSales Invoice` si where docstatus = 1 and outstanding_amount > 0 %s + and not exists(select di.name from `tabDiscounted Invoice` di + where di.docstatus=1 and di.sales_invoice=si.name) """ % where_condition, filters, as_dict=1) \ No newline at end of file From c99c712913add6c8fb1864c2e63cfbe3792855bd Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 18 Apr 2019 15:52:50 +0530 Subject: [PATCH 33/44] fix: Validate and update invoice discounting status on JE submit/cancel --- .../doctype/journal_entry/journal_entry.py | 57 ++++++++++++------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 28869d86d9..08d707385c 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe, erpnext, json -from frappe.utils import cstr, flt, fmt_money, formatdate, getdate, nowdate, cint +from frappe.utils import cstr, flt, fmt_money, formatdate, getdate, nowdate, cint, get_link_to_form from frappe import msgprint, _, scrub from erpnext.controllers.accounts_controller import AccountsController from erpnext.accounts.utils import get_balance_on, get_account_currency @@ -53,6 +53,20 @@ class JournalEntry(AccountsController): self.update_inter_company_jv() self.update_invoice_discounting() + def on_cancel(self): + from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries + from erpnext.hr.doctype.salary_slip.salary_slip import unlink_ref_doc_from_salary_slip + unlink_ref_doc_from_payment_entries(self) + unlink_ref_doc_from_salary_slip(self.name) + self.make_gl_entries(1) + self.update_advance_paid() + self.update_expense_claim() + self.update_loan() + self.unlink_advance_entry_reference() + self.unlink_asset_reference() + self.unlink_inter_company_jv() + self.unlink_asset_adjustment_entry() + self.update_invoice_discounting() def get_title(self): return self.pay_to_recd_from or self.accounts[0].account @@ -83,31 +97,32 @@ class JournalEntry(AccountsController): "inter_company_journal_entry_reference", self.name) def update_invoice_discounting(self): - invoice_discounting_list = [d.reference_name for d in self.accounts if d.reference_type=="Invoice Discounting"] + def _validate_invoice_discounting_status(inv_disc, id_status, expected_status, row_id): + id_link = get_link_to_form("Invoice Discounting", inv_disc) + if id_status != expected_status: + frappe.throw(_("Row #{0}: Status must be {1} for Invoice Discounting {2}").format(d.idx, expected_status, id_link)) + + invoice_discounting_list = list(set([d.reference_name for d in self.accounts if d.reference_type=="Invoice Discounting"])) for inv_disc in invoice_discounting_list: - short_term_loan_account = frappe.db.get_value("Invoice Discounting", inv_disc, "short_term_loan") + short_term_loan_account, id_status = frappe.db.get_value("Invoice Discounting", inv_disc, ["short_term_loan", "status"]) for d in self.accounts: if d.account == short_term_loan_account and d.reference_name == inv_disc: - if d.credit > 0: - status = "Disbursed" - elif d.debit > 0: - status = "Settled" + if self.docstatus == 1: + if d.credit > 0: + _validate_invoice_discounting_status(inv_disc, id_status, "Sanctioned", d.idx) + status = "Disbursed" + elif d.debit > 0: + _validate_invoice_discounting_status(inv_disc, id_status, "Disbursed", d.idx) + status = "Settled" + else: + if d.credit > 0: + _validate_invoice_discounting_status(inv_disc, id_status, "Disbursed", d.idx) + status = "Sanctioned" + elif d.debit > 0: + _validate_invoice_discounting_status(inv_disc, id_status, "Settled", d.idx) + status = "Disbursed" frappe.db.set_value("Invoice Discounting", inv_disc, "status", status) - def on_cancel(self): - from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries - from erpnext.hr.doctype.salary_slip.salary_slip import unlink_ref_doc_from_salary_slip - unlink_ref_doc_from_payment_entries(self) - unlink_ref_doc_from_salary_slip(self.name) - self.make_gl_entries(1) - self.update_advance_paid() - self.update_expense_claim() - self.update_loan() - self.unlink_advance_entry_reference() - self.unlink_asset_reference() - self.unlink_inter_company_jv() - self.unlink_asset_adjustment_entry() - def unlink_advance_entry_reference(self): for d in self.get("accounts"): if d.is_advance == "Yes" and d.reference_type in ("Sales Invoice", "Purchase Invoice"): From 376db4f6b79e91f6c0a7934b58663f127304d4fa Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 18 Apr 2019 22:01:33 +0530 Subject: [PATCH 34/44] fix: credit amount in account's currency not be consider if debit amount is present in the general ledger --- erpnext/accounts/report/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/utils.py b/erpnext/accounts/report/utils.py index 8a39744738..8500aea415 100644 --- a/erpnext/accounts/report/utils.py +++ b/erpnext/accounts/report/utils.py @@ -112,13 +112,15 @@ def convert_to_presentation_currency(gl_entries, currency_info): if entry.get('debit'): entry['debit'] = converted_value - else: + + if entry.get('credit'): entry['credit'] = converted_value elif account_currency == presentation_currency: if entry.get('debit'): entry['debit'] = debit_in_account_currency - else: + + if entry.get('credit'): entry['credit'] = credit_in_account_currency converted_gl_list.append(entry) From 94f43ca2e2b99722de684431b84f12dcd485c38e Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 18 Apr 2019 22:39:42 +0530 Subject: [PATCH 35/44] fix: task name was not able to search by name in global search --- erpnext/projects/doctype/task/task.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index 2602aef626..707db0812a 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -1392,7 +1392,7 @@ "istable": 0, "max_attachments": 5, "menu_index": 0, - "modified": "2019-02-19 12:22:02.147606", + "modified": "2019-04-18 12:22:02.147606", "modified_by": "Administrator", "module": "Projects", "name": "Task", @@ -1422,11 +1422,11 @@ "read_only": 0, "read_only_onload": 0, "search_fields": "subject", - "show_name_in_global_search": 0, + "show_name_in_global_search": 1, "sort_order": "DESC", "timeline_field": "project", "title_field": "subject", "track_changes": 0, "track_seen": 1, "track_views": 0 -} \ No newline at end of file +} From a55d32236a9bbd2c64d8ac2432fd603b1201e7ee Mon Sep 17 00:00:00 2001 From: scmmishra Date: Fri, 19 Apr 2019 12:22:30 +0530 Subject: [PATCH 36/44] fix: fixed trial details --- erpnext/templates/pages/demo.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/templates/pages/demo.html b/erpnext/templates/pages/demo.html index 8eec800d68..a4b5e0122c 100644 --- a/erpnext/templates/pages/demo.html +++ b/erpnext/templates/pages/demo.html @@ -60,7 +60,7 @@ $(document).ready(function() {
-

Start a free 30-day trial +

Start a free 14-day trial