diff --git a/.eslintrc b/.eslintrc index 276d6ff372..12fefa0968 100644 --- a/.eslintrc +++ b/.eslintrc @@ -154,7 +154,6 @@ "before": true, "beforeEach": true, "onScan": true, - "html2canvas": true, "extend_cscript": true, "localforage": true } diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 42eb018078..45bddfc096 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -642,13 +642,6 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): gle_filters={"account": "Stock In Hand - TCP1"}, ) - # assert loss booked in COGS - self.assertGLEs( - return_pi, - [{"credit": 0, "debit": 200}], - gle_filters={"account": "Cost of Goods Sold - TCP1"}, - ) - def test_return_with_lcv(self): from erpnext.controllers.sales_and_purchase_return import make_return_doc from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import ( @@ -1671,6 +1664,21 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): self.assertTrue(return_pi.docstatus == 1) + def test_gl_entries_for_standalone_debit_note(self): + make_purchase_invoice(qty=5, rate=500, update_stock=True) + + returned_inv = make_purchase_invoice(qty=-5, rate=5, update_stock=True, is_return=True) + + # override the rate with valuation rate + sle = frappe.get_all( + "Stock Ledger Entry", + fields=["stock_value_difference", "actual_qty"], + filters={"voucher_no": returned_inv.name}, + )[0] + + rate = flt(sle.stock_value_difference) / flt(sle.actual_qty) + self.assertAlmostEqual(returned_inv.items[0].rate, rate) + def check_gl_entries(doc, voucher_no, expected_gle, posting_date): gl_entries = frappe.db.sql( diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index e3a159ba58..b3212b5a7b 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1001,10 +1001,16 @@ class SalesInvoice(SellingController): def check_prev_docstatus(self): for d in self.get("items"): - if d.sales_order and frappe.db.get_value("Sales Order", d.sales_order, "docstatus") != 1: + if ( + d.sales_order + and frappe.db.get_value("Sales Order", d.sales_order, "docstatus", cache=True) != 1 + ): frappe.throw(_("Sales Order {0} is not submitted").format(d.sales_order)) - if d.delivery_note and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus") != 1: + if ( + d.delivery_note + and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus", cache=True) != 1 + ): throw(_("Delivery Note {0} is not submitted").format(d.delivery_note)) def make_gl_entries(self, gl_entries=None, from_repost=False): diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index c64b917e18..bfef57e494 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -513,18 +513,22 @@ def get_gl_entries_on_asset_disposal( }, item=asset, ), - asset.get_gl_dict( - { - "account": accumulated_depr_account, - "debit_in_account_currency": accumulated_depr_amount, - "debit": accumulated_depr_amount, - "cost_center": depreciation_cost_center, - "posting_date": date, - }, - item=asset, - ), ] + if accumulated_depr_amount: + gl_entries.append( + asset.get_gl_dict( + { + "account": accumulated_depr_account, + "debit_in_account_currency": accumulated_depr_amount, + "debit": accumulated_depr_amount, + "cost_center": depreciation_cost_center, + "posting_date": date, + }, + item=asset, + ), + ) + profit_amount = flt(selling_amount) - flt(value_after_depreciation) if profit_amount: get_profit_gl_entries( diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index ad6a49a029..fec494a84c 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -26,6 +26,8 @@ class BuyingController(SubcontractingController): self.flags.ignore_permlevel_for_fields = ["buying_price_list", "price_list_currency"] def validate(self): + self.set_rate_for_standalone_debit_note() + super(BuyingController, self).validate() if getattr(self, "supplier", None) and not self.supplier_name: self.supplier_name = frappe.db.get_value("Supplier", self.supplier, "supplier_name") @@ -100,6 +102,30 @@ class BuyingController(SubcontractingController): do_not_submit=True, ) + def set_rate_for_standalone_debit_note(self): + if self.get("is_return") and self.get("update_stock") and not self.return_against: + for row in self.items: + + # override the rate with valuation rate + row.rate = get_incoming_rate( + { + "item_code": row.item_code, + "warehouse": row.warehouse, + "posting_date": self.get("posting_date"), + "posting_time": self.get("posting_time"), + "qty": row.qty, + "serial_and_batch_bundle": row.get("serial_and_batch_bundle"), + "company": self.company, + "voucher_type": self.doctype, + "voucher_no": self.name, + }, + raise_error_if_no_rate=False, + ) + + row.discount_percentage = 0.0 + row.discount_amount = 0.0 + row.margin_rate_or_amount = 0.0 + def set_missing_values(self, for_validate=False): super(BuyingController, self).set_missing_values(for_validate) @@ -472,7 +498,7 @@ class BuyingController(SubcontractingController): continue if d.warehouse: - pr_qty = flt(d.qty) * flt(d.conversion_factor) + pr_qty = flt(flt(d.qty) * flt(d.conversion_factor), d.precision("stock_qty")) if pr_qty: @@ -548,7 +574,7 @@ class BuyingController(SubcontractingController): d, { "warehouse": d.rejected_warehouse, - "actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor), + "actual_qty": flt(flt(d.rejected_qty) * flt(d.conversion_factor), d.precision("stock_qty")), "incoming_rate": 0.0, "serial_and_batch_bundle": d.rejected_serial_and_batch_bundle, }, diff --git a/erpnext/manufacturing/report/process_loss_report/process_loss_report.py b/erpnext/manufacturing/report/process_loss_report/process_loss_report.py index ce8f4f35a3..c3dd9cf9b1 100644 --- a/erpnext/manufacturing/report/process_loss_report/process_loss_report.py +++ b/erpnext/manufacturing/report/process_loss_report/process_loss_report.py @@ -33,10 +33,9 @@ def get_data(filters: Filters) -> Data: wo.name, wo.status, wo.production_item, - wo.qty, wo.produced_qty, wo.process_loss_qty, - (wo.produced_qty - wo.process_loss_qty).as_("actual_produced_qty"), + wo.qty.as_("qty_to_manufacture"), Sum(se.total_incoming_value).as_("total_fg_value"), Sum(se.total_outgoing_value).as_("total_rm_value"), ) @@ -44,6 +43,7 @@ def get_data(filters: Filters) -> Data: (wo.process_loss_qty > 0) & (wo.company == filters.company) & (se.docstatus == 1) + & (se.purpose == "Manufacture") & (se.posting_date.between(filters.from_date, filters.to_date)) ) .groupby(se.work_order) @@ -79,20 +79,30 @@ def get_columns() -> Columns: "width": "100", }, {"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": "100"}, + { + "label": _("Qty To Manufacture"), + "fieldname": "qty_to_manufacture", + "fieldtype": "Float", + "width": "150", + }, { "label": _("Manufactured Qty"), "fieldname": "produced_qty", "fieldtype": "Float", "width": "150", }, - {"label": _("Loss Qty"), "fieldname": "process_loss_qty", "fieldtype": "Float", "width": "150"}, { - "label": _("Actual Manufactured Qty"), - "fieldname": "actual_produced_qty", + "label": _("Process Loss Qty"), + "fieldname": "process_loss_qty", + "fieldtype": "Float", + "width": "150", + }, + { + "label": _("Process Loss Value"), + "fieldname": "total_pl_value", "fieldtype": "Float", "width": "150", }, - {"label": _("Loss Value"), "fieldname": "total_pl_value", "fieldtype": "Float", "width": "150"}, {"label": _("FG Value"), "fieldname": "total_fg_value", "fieldtype": "Float", "width": "150"}, { "label": _("Raw Material Value"), @@ -105,5 +115,5 @@ def get_columns() -> Columns: def update_data_with_total_pl_value(data: Data) -> None: for row in data: - value_per_unit_fg = row["total_fg_value"] / row["actual_produced_qty"] + value_per_unit_fg = row["total_fg_value"] / row["qty_to_manufacture"] row["total_pl_value"] = row["process_loss_qty"] * value_per_unit_fg diff --git a/erpnext/public/build.json b/erpnext/public/build.json index 3d38aca418..1bed541831 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -2,8 +2,7 @@ "css/erpnext.css": [ "public/less/erpnext.less", "public/scss/call_popup.scss", - "public/scss/point-of-sale.scss", - "public/scss/hierarchy_chart.scss" + "public/scss/point-of-sale.scss" ], "js/erpnext-web.min.js": [ "public/js/website_utils.js", @@ -37,7 +36,6 @@ "public/js/utils/dimension_tree_filter.js", "public/js/telephony.js", "public/js/templates/call_link.html", - "public/js/templates/node_card.html", "public/js/bulk_transaction_processing.js" ], "js/item-dashboard.min.js": [ @@ -62,10 +60,6 @@ "public/js/bank_reconciliation_tool/number_card.js", "public/js/bank_reconciliation_tool/dialog_manager.js" ], - "js/hierarchy-chart.min.js": [ - "public/js/hierarchy_chart/hierarchy_chart_desktop.js", - "public/js/hierarchy_chart/hierarchy_chart_mobile.js" - ], "js/e-commerce.min.js": [ "e_commerce/product_ui/views.js", "e_commerce/product_ui/grid.js", diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 953a8936b0..933556774b 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -2340,14 +2340,11 @@ erpnext.show_serial_batch_selector = function (frm, item_row, callback, on_close frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() { if (in_list(["Sales Invoice", "Delivery Note"], frm.doc.doctype)) { - item_row.outward = frm.doc.is_return ? 0 : 1; + item_row.type_of_transaction = frm.doc.is_return ? "Inward" : "Outward"; } else { - item_row.outward = frm.doc.is_return ? 1 : 0; + item_row.type_of_transaction = frm.doc.is_return ? "Outward" : "Inward"; } - item_row.type_of_transaction = (item_row.outward === 1 - ? "Outward":"Inward"); - new erpnext.SerialBatchPackageSelector(frm, item_row, (r) => { if (r) { let update_values = { diff --git a/erpnext/public/js/hierarchy-chart.bundle.js b/erpnext/public/js/hierarchy-chart.bundle.js deleted file mode 100644 index 02703139dd..0000000000 --- a/erpnext/public/js/hierarchy-chart.bundle.js +++ /dev/null @@ -1,3 +0,0 @@ -import "./hierarchy_chart/hierarchy_chart_desktop.js"; -import "./hierarchy_chart/hierarchy_chart_mobile.js"; -import "./templates/node_card.html"; diff --git a/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js b/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js deleted file mode 100644 index a585aa614f..0000000000 --- a/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js +++ /dev/null @@ -1,608 +0,0 @@ -import html2canvas from 'html2canvas'; -erpnext.HierarchyChart = class { - /* Options: - - doctype - - wrapper: wrapper for the hierarchy view - - method: - - to get the data for each node - - this method should return id, name, title, image, and connections for each node - */ - constructor(doctype, wrapper, method) { - this.page = wrapper.page; - this.method = method; - this.doctype = doctype; - - this.setup_page_style(); - this.page.main.addClass('frappe-card'); - - this.nodes = {}; - this.setup_node_class(); - } - - setup_page_style() { - this.page.main.css({ - 'min-height': '300px', - 'max-height': '600px', - 'overflow': 'auto', - 'position': 'relative' - }); - } - - setup_node_class() { - let me = this; - this.Node = class { - constructor({ - id, parent, parent_id, image, name, title, expandable, connections, is_root // eslint-disable-line - }) { - // to setup values passed via constructor - $.extend(this, arguments[0]); - - this.expanded = 0; - - me.nodes[this.id] = this; - me.make_node_element(this); - - if (!me.all_nodes_expanded) { - me.setup_node_click_action(this); - } - - me.setup_edit_node_action(this); - } - }; - } - - make_node_element(node) { - let node_card = frappe.render_template('node_card', { - id: node.id, - name: node.name, - title: node.title, - image: node.image, - parent: node.parent_id, - connections: node.connections, - is_mobile: false - }); - - node.parent.append(node_card); - node.$link = $(`[id="${node.id}"]`); - } - - show() { - this.setup_actions(); - if ($(`[data-fieldname="company"]`).length) return; - let me = this; - - let company = this.page.add_field({ - fieldtype: 'Link', - options: 'Company', - fieldname: 'company', - placeholder: __('Select Company'), - default: frappe.defaults.get_default('company'), - only_select: true, - reqd: 1, - change: () => { - me.company = undefined; - $('#hierarchy-chart-wrapper').remove(); - - if (company.get_value()) { - me.company = company.get_value(); - - // svg for connectors - me.make_svg_markers(); - me.setup_hierarchy(); - me.render_root_nodes(); - me.all_nodes_expanded = false; - } else { - frappe.throw(__('Please select a company first.')); - } - } - }); - - company.refresh(); - $(`[data-fieldname="company"]`).trigger('change'); - $(`[data-fieldname="company"] .link-field`).css('z-index', 2); - } - - setup_actions() { - let me = this; - this.page.clear_inner_toolbar(); - this.page.add_inner_button(__('Export'), function() { - me.export_chart(); - }); - - this.page.add_inner_button(__('Expand All'), function() { - me.load_children(me.root_node, true); - me.all_nodes_expanded = true; - - me.page.remove_inner_button(__('Expand All')); - me.page.add_inner_button(__('Collapse All'), function() { - me.setup_hierarchy(); - me.render_root_nodes(); - me.all_nodes_expanded = false; - - me.page.remove_inner_button(__('Collapse All')); - me.setup_actions(); - }); - }); - } - - export_chart() { - frappe.dom.freeze(__('Exporting...')); - this.page.main.css({ - 'min-height': '', - 'max-height': '', - 'overflow': 'visible', - 'position': 'fixed', - 'left': '0', - 'top': '0' - }); - - $('.node-card').addClass('exported'); - - html2canvas(document.querySelector('#hierarchy-chart-wrapper'), { - scrollY: -window.scrollY, - scrollX: 0 - }).then(function(canvas) { - // Export the canvas to its data URI representation - let dataURL = canvas.toDataURL('image/png'); - - // download the image - let a = document.createElement('a'); - a.href = dataURL; - a.download = 'hierarchy_chart'; - a.click(); - }).finally(() => { - frappe.dom.unfreeze(); - }); - - this.setup_page_style(); - $('.node-card').removeClass('exported'); - } - - setup_hierarchy() { - if (this.$hierarchy) - this.$hierarchy.remove(); - - $(`#connectors`).empty(); - - // setup hierarchy - this.$hierarchy = $( - ``); - - this.page.main - .find('#hierarchy-chart') - .empty() - .append(this.$hierarchy); - - this.nodes = {}; - } - - make_svg_markers() { - $('#hierarchy-chart-wrapper').remove(); - - this.page.main.append(` -
- - - - - - - - - - - - - - - - - - - -
-
-
`); - } - - render_root_nodes(expanded_view=false) { - let me = this; - - return frappe.call({ - method: me.method, - args: { - company: me.company - } - }).then(r => { - if (r.message.length) { - let expand_node = undefined; - let node = undefined; - - $.each(r.message, (_i, data) => { - if ($(`[id="${data.id}"]`).length) - return; - - node = new me.Node({ - id: data.id, - parent: $('
  • ').appendTo(me.$hierarchy.find('.node-children')), - parent_id: undefined, - image: data.image, - name: data.name, - title: data.title, - expandable: true, - connections: data.connections, - is_root: true - }); - - if (!expand_node && data.connections) - expand_node = node; - }); - - me.root_node = expand_node; - if (!expanded_view) { - me.expand_node(expand_node); - } - } - }); - } - - expand_node(node) { - const is_sibling = this.selected_node && this.selected_node.parent_id === node.parent_id; - this.set_selected_node(node); - this.show_active_path(node); - this.collapse_previous_level_nodes(node); - - // since the previous node collapses, all connections to that node need to be rebuilt - // if a sibling node is clicked, connections don't need to be rebuilt - if (!is_sibling) { - // rebuild outgoing connections - this.refresh_connectors(node.parent_id); - - // rebuild incoming connections - let grandparent = $(`[id="${node.parent_id}"]`).attr('data-parent'); - this.refresh_connectors(grandparent); - } - - if (node.expandable && !node.expanded) { - return this.load_children(node); - } - } - - collapse_node() { - if (this.selected_node.expandable) { - this.selected_node.$children.hide(); - $(`path[data-parent="${this.selected_node.id}"]`).hide(); - this.selected_node.expanded = false; - } - } - - show_active_path(node) { - // mark node parent on active path - $(`[id="${node.parent_id}"]`).addClass('active-path'); - } - - load_children(node, deep=false) { - if (!deep) { - frappe.run_serially([ - () => this.get_child_nodes(node.id), - (child_nodes) => this.render_child_nodes(node, child_nodes) - ]); - } else { - frappe.run_serially([ - () => frappe.dom.freeze(), - () => this.setup_hierarchy(), - () => this.render_root_nodes(true), - () => this.get_all_nodes(), - (data_list) => this.render_children_of_all_nodes(data_list), - () => frappe.dom.unfreeze() - ]); - } - } - - get_child_nodes(node_id) { - let me = this; - return new Promise(resolve => { - frappe.call({ - method: me.method, - args: { - parent: node_id, - company: me.company - } - }).then(r => resolve(r.message)); - }); - } - - render_child_nodes(node, child_nodes) { - const last_level = this.$hierarchy.find('.level:last').index(); - const current_level = $(`[id="${node.id}"]`).parent().parent().parent().index(); - - if (last_level === current_level) { - this.$hierarchy.append(` -
  • - `); - } - - if (!node.$children) { - node.$children = $('') - .hide() - .appendTo(this.$hierarchy.find('.level:last')); - - node.$children.empty(); - - if (child_nodes) { - $.each(child_nodes, (_i, data) => { - if (!$(`[id="${data.id}"]`).length) { - this.add_node(node, data); - setTimeout(() => { - this.add_connector(node.id, data.id); - }, 250); - } - }); - } - } - - node.$children.show(); - $(`path[data-parent="${node.id}"]`).show(); - node.expanded = true; - } - - get_all_nodes() { - let me = this; - return new Promise(resolve => { - frappe.call({ - method: 'erpnext.utilities.hierarchy_chart.get_all_nodes', - args: { - method: me.method, - company: me.company - }, - callback: (r) => { - resolve(r.message); - } - }); - }); - } - - render_children_of_all_nodes(data_list) { - let entry = undefined; - let node = undefined; - - while (data_list.length) { - // to avoid overlapping connectors - entry = data_list.shift(); - node = this.nodes[entry.parent]; - if (node) { - this.render_child_nodes_for_expanded_view(node, entry.data); - } else if (data_list.length) { - data_list.push(entry); - } - } - } - - render_child_nodes_for_expanded_view(node, child_nodes) { - node.$children = $(''); - - const last_level = this.$hierarchy.find('.level:last').index(); - const node_level = $(`[id="${node.id}"]`).parent().parent().parent().index(); - - if (last_level === node_level) { - this.$hierarchy.append(` -
  • - `); - node.$children.appendTo(this.$hierarchy.find('.level:last')); - } else { - node.$children.appendTo(this.$hierarchy.find('.level:eq(' + (node_level + 1) + ')')); - } - - node.$children.hide().empty(); - - if (child_nodes) { - $.each(child_nodes, (_i, data) => { - this.add_node(node, data); - setTimeout(() => { - this.add_connector(node.id, data.id); - }, 250); - }); - } - - node.$children.show(); - $(`path[data-parent="${node.id}"]`).show(); - node.expanded = true; - } - - add_node(node, data) { - return new this.Node({ - id: data.id, - parent: $('
  • ').appendTo(node.$children), - parent_id: node.id, - image: data.image, - name: data.name, - title: data.title, - expandable: data.expandable, - connections: data.connections, - children: undefined - }); - } - - add_connector(parent_id, child_id) { - // using pure javascript for better performance - const parent_node = document.getElementById(`${parent_id}`); - const child_node = document.getElementById(`${child_id}`); - - let path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); - - // we need to connect right side of the parent to the left side of the child node - const pos_parent_right = { - x: parent_node.offsetLeft + parent_node.offsetWidth, - y: parent_node.offsetTop + parent_node.offsetHeight / 2 - }; - const pos_child_left = { - x: child_node.offsetLeft - 5, - y: child_node.offsetTop + child_node.offsetHeight / 2 - }; - - const connector = this.get_connector(pos_parent_right, pos_child_left); - - path.setAttribute('d', connector); - this.set_path_attributes(path, parent_id, child_id); - - document.getElementById('connectors').appendChild(path); - } - - get_connector(pos_parent_right, pos_child_left) { - if (pos_parent_right.y === pos_child_left.y) { - // don't add arcs if it's a straight line - return "M" + - (pos_parent_right.x) + "," + (pos_parent_right.y) + " " + - "L"+ - (pos_child_left.x) + "," + (pos_child_left.y); - } else { - let arc_1 = ""; - let arc_2 = ""; - let offset = 0; - - if (pos_parent_right.y > pos_child_left.y) { - // if child is above parent on Y axis 1st arc is anticlocwise - // second arc is clockwise - arc_1 = "a10,10 1 0 0 10,-10 "; - arc_2 = "a10,10 0 0 1 10,-10 "; - offset = 10; - } else { - // if child is below parent on Y axis 1st arc is clockwise - // second arc is anticlockwise - arc_1 = "a10,10 0 0 1 10,10 "; - arc_2 = "a10,10 1 0 0 10,10 "; - offset = -10; - } - - return "M" + (pos_parent_right.x) + "," + (pos_parent_right.y) + " " + - "L" + - (pos_parent_right.x + 40) + "," + (pos_parent_right.y) + " " + - arc_1 + - "L" + - (pos_parent_right.x + 50) + "," + (pos_child_left.y + offset) + " " + - arc_2 + - "L"+ - (pos_child_left.x) + "," + (pos_child_left.y); - } - } - - set_path_attributes(path, parent_id, child_id) { - path.setAttribute("data-parent", parent_id); - path.setAttribute("data-child", child_id); - const parent = $(`[id="${parent_id}"]`); - - if (parent.hasClass('active')) { - path.setAttribute("class", "active-connector"); - path.setAttribute("marker-start", "url(#arrowstart-active)"); - path.setAttribute("marker-end", "url(#arrowhead-active)"); - } else { - path.setAttribute("class", "collapsed-connector"); - path.setAttribute("marker-start", "url(#arrowstart-collapsed)"); - path.setAttribute("marker-end", "url(#arrowhead-collapsed)"); - } - } - - set_selected_node(node) { - // remove active class from the current node - if (this.selected_node) - this.selected_node.$link.removeClass('active'); - - // add active class to the newly selected node - this.selected_node = node; - node.$link.addClass('active'); - } - - collapse_previous_level_nodes(node) { - let node_parent = $(`[id="${node.parent_id}"]`); - let previous_level_nodes = node_parent.parent().parent().children('li'); - let node_card = undefined; - - previous_level_nodes.each(function() { - node_card = $(this).find('.node-card'); - - if (!node_card.hasClass('active-path')) { - node_card.addClass('collapsed'); - } - }); - } - - refresh_connectors(node_parent) { - if (!node_parent) return; - - $(`path[data-parent="${node_parent}"]`).remove(); - - frappe.run_serially([ - () => this.get_child_nodes(node_parent), - (child_nodes) => { - if (child_nodes) { - $.each(child_nodes, (_i, data) => { - this.add_connector(node_parent, data.id); - }); - } - } - ]); - } - - setup_node_click_action(node) { - let me = this; - let node_element = $(`[id="${node.id}"]`); - - node_element.click(function() { - const is_sibling = me.selected_node.parent_id === node.parent_id; - - if (is_sibling) { - me.collapse_node(); - } else if (node_element.is(':visible') - && (node_element.hasClass('collapsed') || node_element.hasClass('active-path'))) { - me.remove_levels_after_node(node); - me.remove_orphaned_connectors(); - } - - me.expand_node(node); - }); - } - - setup_edit_node_action(node) { - let node_element = $(`[id="${node.id}"]`); - let me = this; - - node_element.find('.btn-edit-node').click(function() { - frappe.set_route('Form', me.doctype, node.id); - }); - } - - remove_levels_after_node(node) { - let level = $(`[id="${node.id}"]`).parent().parent().parent().index(); - - level = $('.hierarchy > li:eq('+ level + ')'); - level.nextAll('li').remove(); - - let nodes = level.find('.node-card'); - let node_object = undefined; - - $.each(nodes, (_i, element) => { - node_object = this.nodes[element.id]; - node_object.expanded = 0; - node_object.$children = undefined; - }); - - nodes.removeClass('collapsed active-path'); - } - - remove_orphaned_connectors() { - let paths = $('#connectors > path'); - $.each(paths, (_i, path) => { - const parent = $(path).data('parent'); - const child = $(path).data('child'); - - if ($(`[id="${parent}"]`).length && $(`[id="${child}"]`).length) - return; - - $(path).remove(); - }); - } -}; diff --git a/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js b/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js deleted file mode 100644 index 52236e7df9..0000000000 --- a/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js +++ /dev/null @@ -1,550 +0,0 @@ -erpnext.HierarchyChartMobile = class { - /* Options: - - doctype - - wrapper: wrapper for the hierarchy view - - method: - - to get the data for each node - - this method should return id, name, title, image, and connections for each node - */ - constructor(doctype, wrapper, method) { - this.page = wrapper.page; - this.method = method; - this.doctype = doctype; - - this.page.main.css({ - 'min-height': '300px', - 'max-height': '600px', - 'overflow': 'auto', - 'position': 'relative' - }); - this.page.main.addClass('frappe-card'); - - this.nodes = {}; - this.setup_node_class(); - } - - setup_node_class() { - let me = this; - this.Node = class { - constructor({ - id, parent, parent_id, image, name, title, expandable, connections, is_root // eslint-disable-line - }) { - // to setup values passed via constructor - $.extend(this, arguments[0]); - - this.expanded = 0; - - me.nodes[this.id] = this; - me.make_node_element(this); - me.setup_node_click_action(this); - me.setup_edit_node_action(this); - } - }; - } - - make_node_element(node) { - let node_card = frappe.render_template('node_card', { - id: node.id, - name: node.name, - title: node.title, - image: node.image, - parent: node.parent_id, - connections: node.connections, - is_mobile: true - }); - - node.parent.append(node_card); - node.$link = $(`[id="${node.id}"]`); - node.$link.addClass('mobile-node'); - } - - show() { - let me = this; - if ($(`[data-fieldname="company"]`).length) return; - - let company = this.page.add_field({ - fieldtype: 'Link', - options: 'Company', - fieldname: 'company', - placeholder: __('Select Company'), - default: frappe.defaults.get_default('company'), - only_select: true, - reqd: 1, - change: () => { - me.company = undefined; - - if (company.get_value() && me.company != company.get_value()) { - me.company = company.get_value(); - - // svg for connectors - me.make_svg_markers(); - - if (me.$sibling_group) - me.$sibling_group.remove(); - - // setup sibling group wrapper - me.$sibling_group = $(`
    `); - me.page.main.append(me.$sibling_group); - - me.setup_hierarchy(); - me.render_root_nodes(); - } - } - }); - - company.refresh(); - $(`[data-fieldname="company"]`).trigger('change'); - } - - make_svg_markers() { - $('#arrows').remove(); - - this.page.main.prepend(` - - - - - - - - - - - - - - - - - - - `); - } - - setup_hierarchy() { - $(`#connectors`).empty(); - if (this.$hierarchy) - this.$hierarchy.remove(); - - if (this.$sibling_group) - this.$sibling_group.empty(); - - this.$hierarchy = $( - ``); - - this.page.main.append(this.$hierarchy); - } - - render_root_nodes() { - let me = this; - - frappe.call({ - method: me.method, - args: { - company: me.company - }, - }).then(r => { - if (r.message.length) { - let root_level = me.$hierarchy.find('.root-level'); - root_level.empty(); - - $.each(r.message, (_i, data) => { - return new me.Node({ - id: data.id, - parent: root_level, - parent_id: undefined, - image: data.image, - name: data.name, - title: data.title, - expandable: true, - connections: data.connections, - is_root: true - }); - }); - } - }); - } - - expand_node(node) { - const is_same_node = (this.selected_node && this.selected_node.id === node.id); - this.set_selected_node(node); - this.show_active_path(node); - - if (this.$sibling_group) { - const sibling_parent = this.$sibling_group.find('.node-group').attr('data-parent'); - if (node.parent_id !== undefined && node.parent_id != sibling_parent) - this.$sibling_group.empty(); - } - - if (!is_same_node) { - // since the previous/parent node collapses, all connections to that node need to be rebuilt - // rebuild outgoing connections of parent - this.refresh_connectors(node.parent_id, node.id); - - // rebuild incoming connections of parent - let grandparent = $(`[id="${node.parent_id}"]`).attr('data-parent'); - this.refresh_connectors(grandparent, node.parent_id); - } - - if (node.expandable && !node.expanded) { - return this.load_children(node); - } - } - - collapse_node() { - let node = this.selected_node; - if (node.expandable && node.$children) { - node.$children.hide(); - node.expanded = 0; - - // add a collapsed level to show the collapsed parent - // and a button beside it to move to that level - let node_parent = node.$link.parent(); - node_parent.prepend( - `
    ` - ); - - node_parent - .find('.collapsed-level') - .append(node.$link); - - frappe.run_serially([ - () => this.get_child_nodes(node.parent_id, node.id), - (child_nodes) => this.get_node_group(child_nodes, node.parent_id), - (node_group) => node_parent.find('.collapsed-level').append(node_group), - () => this.setup_node_group_action() - ]); - } - } - - show_active_path(node) { - // mark node parent on active path - $(`[id="${node.parent_id}"]`).addClass('active-path'); - } - - load_children(node) { - frappe.run_serially([ - () => this.get_child_nodes(node.id), - (child_nodes) => this.render_child_nodes(node, child_nodes) - ]); - } - - get_child_nodes(node_id, exclude_node=null) { - let me = this; - return new Promise(resolve => { - frappe.call({ - method: me.method, - args: { - parent: node_id, - company: me.company, - exclude_node: exclude_node - } - }).then(r => resolve(r.message)); - }); - } - - render_child_nodes(node, child_nodes) { - if (!node.$children) { - node.$children = $('') - .hide() - .appendTo(node.$link.parent()); - - node.$children.empty(); - - if (child_nodes) { - $.each(child_nodes, (_i, data) => { - this.add_node(node, data); - $(`[id="${data.id}"]`).addClass('active-child'); - - setTimeout(() => { - this.add_connector(node.id, data.id); - }, 250); - }); - } - } - - node.$children.show(); - node.expanded = 1; - } - - add_node(node, data) { - var $li = $('
  • '); - - return new this.Node({ - id: data.id, - parent: $li.appendTo(node.$children), - parent_id: node.id, - image: data.image, - name: data.name, - title: data.title, - expandable: data.expandable, - connections: data.connections, - children: undefined - }); - } - - add_connector(parent_id, child_id) { - const parent_node = document.getElementById(`${parent_id}`); - const child_node = document.getElementById(`${child_id}`); - - const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); - - let connector = undefined; - - if ($(`[id="${parent_id}"]`).hasClass('active')) { - connector = this.get_connector_for_active_node(parent_node, child_node); - } else if ($(`[id="${parent_id}"]`).hasClass('active-path')) { - connector = this.get_connector_for_collapsed_node(parent_node, child_node); - } - - path.setAttribute('d', connector); - this.set_path_attributes(path, parent_id, child_id); - - document.getElementById('connectors').appendChild(path); - } - - get_connector_for_active_node(parent_node, child_node) { - // we need to connect the bottom left of the parent to the left side of the child node - let pos_parent_bottom = { - x: parent_node.offsetLeft + 20, - y: parent_node.offsetTop + parent_node.offsetHeight - }; - let pos_child_left = { - x: child_node.offsetLeft - 5, - y: child_node.offsetTop + child_node.offsetHeight / 2 - }; - - let connector = - "M" + - (pos_parent_bottom.x) + "," + (pos_parent_bottom.y) + " " + - "L" + - (pos_parent_bottom.x) + "," + (pos_child_left.y - 10) + " " + - "a10,10 1 0 0 10,10 " + - "L" + - (pos_child_left.x) + "," + (pos_child_left.y); - - return connector; - } - - get_connector_for_collapsed_node(parent_node, child_node) { - // we need to connect the bottom left of the parent to the top left of the child node - let pos_parent_bottom = { - x: parent_node.offsetLeft + 20, - y: parent_node.offsetTop + parent_node.offsetHeight - }; - let pos_child_top = { - x: child_node.offsetLeft + 20, - y: child_node.offsetTop - }; - - let connector = - "M" + - (pos_parent_bottom.x) + "," + (pos_parent_bottom.y) + " " + - "L" + - (pos_child_top.x) + "," + (pos_child_top.y); - - return connector; - } - - set_path_attributes(path, parent_id, child_id) { - path.setAttribute("data-parent", parent_id); - path.setAttribute("data-child", child_id); - const parent = $(`[id="${parent_id}"]`); - - if (parent.hasClass('active')) { - path.setAttribute("class", "active-connector"); - path.setAttribute("marker-start", "url(#arrowstart-active)"); - path.setAttribute("marker-end", "url(#arrowhead-active)"); - } else if (parent.hasClass('active-path')) { - path.setAttribute("class", "collapsed-connector"); - } - } - - set_selected_node(node) { - // remove .active class from the current node - if (this.selected_node) - this.selected_node.$link.removeClass('active'); - - // add active class to the newly selected node - this.selected_node = node; - node.$link.addClass('active'); - } - - setup_node_click_action(node) { - let me = this; - let node_element = $(`[id="${node.id}"]`); - - node_element.click(function() { - let el = undefined; - - if (node.is_root) { - el = $(this).detach(); - me.$hierarchy.empty(); - $(`#connectors`).empty(); - me.add_node_to_hierarchy(el, node); - } else if (node_element.is(':visible') && node_element.hasClass('active-path')) { - me.remove_levels_after_node(node); - me.remove_orphaned_connectors(); - } else { - el = $(this).detach(); - me.add_node_to_hierarchy(el, node); - me.collapse_node(); - } - - me.expand_node(node); - }); - } - - setup_edit_node_action(node) { - let node_element = $(`[id="${node.id}"]`); - let me = this; - - node_element.find('.btn-edit-node').click(function() { - frappe.set_route('Form', me.doctype, node.id); - }); - } - - setup_node_group_action() { - let me = this; - - $('.node-group').on('click', function() { - let parent = $(this).attr('data-parent'); - if (parent === 'undefined') { - me.setup_hierarchy(); - me.render_root_nodes(); - } else { - me.expand_sibling_group_node(parent); - } - }); - } - - add_node_to_hierarchy(node_element, node) { - this.$hierarchy.append(`
  • `); - node_element.removeClass('active-child active-path'); - this.$hierarchy.find('.level:last').append(node_element); - - let node_object = this.nodes[node.id]; - node_object.expanded = 0; - node_object.$children = undefined; - this.nodes[node.id] = node_object; - } - - get_node_group(nodes, parent, collapsed=true) { - let limit = 2; - const display_nodes = nodes.slice(0, limit); - const extra_nodes = nodes.slice(limit); - - let html = display_nodes.map(node => - this.get_avatar(node) - ).join(''); - - if (extra_nodes.length === 1) { - let node = extra_nodes[0]; - html += this.get_avatar(node); - } else if (extra_nodes.length > 1) { - html = ` - ${html} - -
    - +${extra_nodes.length} -
    -
    - `; - } - - if (html) { - const $node_group = - $(`
    -
    - ${html} -
    -
    `); - - if (collapsed) - $node_group.addClass('collapsed'); - - return $node_group; - } - - return null; - } - - get_avatar(node) { - return ` - - `; - } - - expand_sibling_group_node(parent) { - let node_object = this.nodes[parent]; - let node = node_object.$link; - - node.removeClass('active-child active-path'); - node_object.expanded = 0; - node_object.$children = undefined; - this.nodes[node.id] = node_object; - - // show parent's siblings and expand parent node - frappe.run_serially([ - () => this.get_child_nodes(node_object.parent_id, node_object.id), - (child_nodes) => this.get_node_group(child_nodes, node_object.parent_id, false), - (node_group) => { - if (node_group) - this.$sibling_group.empty().append(node_group); - }, - () => this.setup_node_group_action(), - () => this.reattach_and_expand_node(node, node_object) - ]); - } - - reattach_and_expand_node(node, node_object) { - var el = node.detach(); - - this.$hierarchy.empty().append(` -
  • - `); - this.$hierarchy.find('.level').append(el); - $(`#connectors`).empty(); - this.expand_node(node_object); - } - - remove_levels_after_node(node) { - let level = $(`[id="${node.id}"]`).parent().parent().index(); - - level = $('.hierarchy-mobile > li:eq('+ level + ')'); - level.nextAll('li').remove(); - - let node_object = this.nodes[node.id]; - let current_node = level.find(`[id="${node.id}"]`).detach(); - - current_node.removeClass('active-child active-path'); - - node_object.expanded = 0; - node_object.$children = undefined; - - level.empty().append(current_node); - } - - remove_orphaned_connectors() { - let paths = $('#connectors > path'); - $.each(paths, (_i, path) => { - const parent = $(path).data('parent'); - const child = $(path).data('child'); - - if ($(`[id="${parent}"]`).length && $(`[id="${child}"]`).length) - return; - - $(path).remove(); - }); - } - - refresh_connectors(node_parent, node_id) { - if (!node_parent) return; - - $(`path[data-parent="${node_parent}"]`).remove(); - this.add_connector(node_parent, node_id); - } -}; diff --git a/erpnext/public/js/templates/node_card.html b/erpnext/public/js/templates/node_card.html deleted file mode 100644 index 4cb6ee03c0..0000000000 --- a/erpnext/public/js/templates/node_card.html +++ /dev/null @@ -1,33 +0,0 @@ -
    -
    -
    - - - -
    -
    -
    - {{ name }} -
    - {{ frappe.utils.icon("edit", "xs") }} - {{ __("Edit") }} -
    -
    -
    -
    {{ title }}
    - - {% if is_mobile %} -
    - · {{ connections }} -
    - {% else %} - {% if connections == 1 %} -
    · {{ connections }} Connection
    - {% else %} -
    · {{ connections }} Connections
    - {% endif %} - {% endif %} -
    -
    -
    -
    diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 58aa8d7da2..a859a671b0 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -350,6 +350,38 @@ $.extend(erpnext.utils, { } }, + + pick_serial_and_batch_bundle(frm, cdt, cdn, type_of_transaction, warehouse_field) { + let item_row = frappe.get_doc(cdt, cdn); + item_row.type_of_transaction = type_of_transaction; + + frappe.db.get_value("Item", item_row.item_code, ["has_batch_no", "has_serial_no"]) + .then((r) => { + item_row.has_batch_no = r.message.has_batch_no; + item_row.has_serial_no = r.message.has_serial_no; + + frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() { + new erpnext.SerialBatchPackageSelector(frm, item_row, (r) => { + if (r) { + let update_values = { + "serial_and_batch_bundle": r.name, + "qty": Math.abs(r.total_qty) + } + + if (!warehouse_field) { + warehouse_field = "warehouse"; + } + + if (r.warehouse) { + update_values[warehouse_field] = r.warehouse; + } + + frappe.model.set_value(item_row.doctype, item_row.name, update_values); + } + }); + }); + }); + } }); erpnext.utils.select_alternate_items = function(opts) { diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index f9eec2a411..27a7968033 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -26,7 +26,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { title: this.item?.title || primary_label, fields: this.get_dialog_fields(), primary_action_label: primary_label, - primary_action: () => this.update_ledgers(), + primary_action: () => this.update_bundle_entries(), secondary_action_label: __('Edit Full Form'), secondary_action: () => this.edit_full_form(), }); @@ -36,7 +36,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { } get_serial_no_filters() { - let warehouse = this.item?.outward ? + let warehouse = this.item?.type_of_transaction === "Outward" ? (this.item.warehouse || this.item.s_warehouse) : ""; return { @@ -121,7 +121,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { }); } - if (this.item?.outward) { + if (this.item?.type_of_transaction === "Outward") { fields = [...this.get_filter_fields(), ...fields]; } else { fields = [...fields, ...this.get_attach_field()]; @@ -267,7 +267,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { label: __('Batch No'), in_list_view: 1, get_query: () => { - if (!this.item.outward) { + if (this.item.type_of_transaction !== "Outward") { return { filters: { 'item': this.item.item_code, @@ -356,7 +356,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { this.dialog.fields_dict.entries.grid.refresh(); } - update_ledgers() { + update_bundle_entries() { let entries = this.dialog.get_values().entries; let warehouse = this.dialog.get_value('warehouse'); @@ -390,7 +390,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { _new.warehouse = this.get_warehouse(); _new.has_serial_no = this.item.has_serial_no; _new.has_batch_no = this.item.has_batch_no; - _new.type_of_transaction = this.get_type_of_transaction(); + _new.type_of_transaction = this.item.type_of_transaction; _new.company = this.frm.doc.company; _new.voucher_type = this.frm.doc.doctype; bundle_id = _new.name; @@ -401,15 +401,11 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { } get_warehouse() { - return (this.item?.outward ? + return (this.item?.type_of_transaction === "Outward" ? (this.item.warehouse || this.item.s_warehouse) : (this.item.warehouse || this.item.t_warehouse)); } - get_type_of_transaction() { - return (this.item?.outward ? 'Outward' : 'Inward'); - } - render_data() { if (!this.frm.is_new() && this.bundle) { frappe.call({ diff --git a/erpnext/public/scss/erpnext.bundle.scss b/erpnext/public/scss/erpnext.bundle.scss index b68ddf52b2..d3313c7cee 100644 --- a/erpnext/public/scss/erpnext.bundle.scss +++ b/erpnext/public/scss/erpnext.bundle.scss @@ -1,4 +1,3 @@ @import "./erpnext"; @import "./call_popup"; @import "./point-of-sale"; -@import "./hierarchy_chart"; diff --git a/erpnext/public/scss/hierarchy_chart.scss b/erpnext/public/scss/hierarchy_chart.scss deleted file mode 100644 index 57d5e8414a..0000000000 --- a/erpnext/public/scss/hierarchy_chart.scss +++ /dev/null @@ -1,313 +0,0 @@ -.node-card { - background: white; - stroke: 1px solid var(--gray-200); - box-shadow: var(--shadow-base); - border-radius: 0.5rem; - padding: 0.75rem; - margin-left: 3rem; - width: 18rem; - overflow: hidden; - - .btn-edit-node { - display: none; - } - - .edit-chart-node { - display: none; - } - - .node-edit-icon { - display: none; - } -} - -.node-card.exported { - box-shadow: none -} - -.node-image { - width: 3.0rem; - height: 3.0rem; -} - -.node-name { - font-size: 1rem; - line-height: 1.72; -} - -.node-title { - font-size: 0.75rem; - line-height: 1.35; -} - -.node-info { - width: 12.7rem; -} - -.node-connections { - font-size: 0.75rem; - line-height: 1.35; -} - -.node-card.active { - background: var(--blue-50); - border: 1px solid var(--blue-500); - box-shadow: var(--shadow-md); - border-radius: 0.5rem; - padding: 0.75rem; - width: 18rem; - - .btn-edit-node { - display: flex; - background: var(--blue-100); - color: var(--blue-500); - padding: .25rem .5rem; - font-size: .75rem; - justify-content: center; - box-shadow: var(--shadow-sm); - margin-left: auto; - } - - .edit-chart-node { - display: block; - margin-right: 0.25rem; - } - - .node-edit-icon { - display: block; - } - - .node-edit-icon > .icon{ - stroke: var(--blue-500); - } - - .node-name { - align-items: center; - justify-content: space-between; - margin-bottom: 2px; - width: 12.2rem; - } -} - -.node-card.active-path { - background: var(--blue-100); - border: 1px solid var(--blue-300); - box-shadow: var(--shadow-sm); - border-radius: 0.5rem; - padding: 0.75rem; - width: 15rem; - height: 3.0rem; - - .btn-edit-node { - display: none !important; - } - - .edit-chart-node { - display: none; - } - - .node-edit-icon { - display: none; - } - - .node-info { - display: none; - } - - .node-title { - display: none; - } - - .node-connections { - display: none; - } - - .node-name { - font-size: 0.85rem; - line-height: 1.35; - } - - .node-image { - width: 1.5rem; - height: 1.5rem; - } - - .node-meta { - align-items: baseline; - } -} - -.node-card.collapsed { - background: white; - stroke: 1px solid var(--gray-200); - box-shadow: var(--shadow-sm); - border-radius: 0.5rem; - padding: 0.75rem; - width: 15rem; - height: 3.0rem; - - .btn-edit-node { - display: none !important; - } - - .edit-chart-node { - display: none; - } - - .node-edit-icon { - display: none; - } - - .node-info { - display: none; - } - - .node-title { - display: none; - } - - .node-connections { - display: none; - } - - .node-name { - font-size: 0.85rem; - line-height: 1.35; - } - - .node-image { - width: 1.5rem; - height: 1.5rem; - } - - .node-meta { - align-items: baseline; - } -} - -// horizontal hierarchy tree view -#hierarchy-chart-wrapper { - padding-top: 30px; - - #arrows { - margin-top: -80px; - } -} - -.hierarchy { - display: flex; -} - -.hierarchy li { - list-style-type: none; -} - -.child-node { - margin: 0px 0px 16px 0px; -} - -.hierarchy, .hierarchy-mobile { - .level { - margin-right: 8px; - align-items: flex-start; - flex-direction: column; - } -} - -#arrows { - position: absolute; - overflow: visible; -} - -.active-connector { - stroke: var(--blue-500); -} - -.collapsed-connector { - stroke: var(--blue-300); -} - -// mobile - -.hierarchy-mobile { - display: flex; - flex-direction: column; - align-items: center; - padding-top: 10px; - padding-left: 0px; -} - -.hierarchy-mobile li { - list-style-type: none; - display: flex; - flex-direction: column; - align-items: flex-end; -} - -.mobile-node { - margin-left: 0; -} - -.mobile-node.active-path { - width: 12.25rem; -} - -.active-child { - width: 15.5rem; -} - -.mobile-node .node-connections { - max-width: 80px; -} - -.hierarchy-mobile .node-children { - margin-top: 16px; -} - -.root-level .node-card { - margin: 0 0 16px; -} - -// node group - -.collapsed-level { - margin-bottom: 16px; - width: 18rem; -} - -.node-group { - background: white; - border: 1px solid var(--gray-300); - box-shadow: var(--shadow-sm); - border-radius: 0.5rem; - padding: 0.75rem; - width: 18rem; - height: 3rem; - overflow: hidden; - align-items: center; -} - -.node-group .avatar-group { - margin-left: 0px; -} - -.node-group .avatar-extra-count { - background-color: var(--blue-100); - color: var(--blue-500); -} - -.node-group .avatar-frame { - width: 1.5rem; - height: 1.5rem; -} - -.node-group.collapsed { - width: 5rem; - margin-left: 12px; -} - -.sibling-group { - display: flex; - flex-direction: column; - align-items: center; -} diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index e6b2b3b5d5..b6e567c7cc 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -379,7 +379,6 @@ erpnext.PointOfSale.ItemDetails = class { frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", () => { let frm = this.events.get_frm(); let item_row = this.item_row; - item_row.outward = 1; item_row.type_of_transaction = "Outward"; new erpnext.SerialBatchPackageSelector(frm, item_row, (r) => { diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 98ad8a7cdb..87c0fae42a 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -317,7 +317,6 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran item.has_serial_no = r.message.has_serial_no; item.has_batch_no = r.message.has_batch_no; item.type_of_transaction = item.qty > 0 ? "Outward":"Inward"; - item.outward = item.qty > 0 ? 1 : 0; item.title = item.has_serial_no ? __("Select Serial No") : __("Select Batch No"); diff --git a/erpnext/stock/deprecated_serial_batch.py b/erpnext/stock/deprecated_serial_batch.py index 023773142d..2f1270e958 100644 --- a/erpnext/stock/deprecated_serial_batch.py +++ b/erpnext/stock/deprecated_serial_batch.py @@ -125,6 +125,9 @@ class DeprecatedBatchNoValuation: if batch_no not in self.non_batchwise_valuation_batches: continue + if not self.non_batchwise_balance_qty: + continue + self.batch_avg_rate[batch_no] = ( self.non_batchwise_balance_value / self.non_batchwise_balance_qty ) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index 6ee8f205e0..6a9e241444 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -1223,7 +1223,8 @@ "hidden": 1, "label": "Pick List", "options": "Pick List", - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "default": "0", @@ -1399,7 +1400,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2023-06-03 16:13:25.011487", + "modified": "2023-06-16 14:58:55.066602", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", @@ -1469,4 +1470,4 @@ "title_field": "title", "track_changes": 1, "track_seen": 1 -} +} \ No newline at end of file diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 4970bf7292..922f76cff2 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -40,7 +40,7 @@ class PickList(Document): for location in self.get("locations"): if ( location.sales_order - and frappe.db.get_value("Sales Order", location.sales_order, "per_picked") == 100 + and frappe.db.get_value("Sales Order", location.sales_order, "per_picked", cache=True) == 100 ): frappe.throw( _("Row #{}: item {} has been picked already.").format(location.idx, location.item_code) @@ -384,6 +384,7 @@ class PickList(Document): (pi_item.item_code.isin([x.item_code for x in items])) & ((pi_item.picked_qty > 0) | (pi_item.stock_qty > 0)) & (pi.status != "Completed") + & (pi.status != "Cancelled") & (pi_item.docstatus != 2) ) .groupby( @@ -497,7 +498,7 @@ def get_items_with_location_and_quantity(item_doc, item_location_map, docstatus) ) qty = stock_qty / (item_doc.conversion_factor or 1) - uom_must_be_whole_number = frappe.db.get_value("UOM", item_doc.uom, "must_be_whole_number") + uom_must_be_whole_number = frappe.get_cached_value("UOM", item_doc.uom, "must_be_whole_number") if uom_must_be_whole_number: qty = floor(qty) stock_qty = qty * item_doc.conversion_factor diff --git a/erpnext/stock/doctype/pick_list_item/pick_list_item.json b/erpnext/stock/doctype/pick_list_item/pick_list_item.json index e6653a804a..2b519f5878 100644 --- a/erpnext/stock/doctype/pick_list_item/pick_list_item.json +++ b/erpnext/stock/doctype/pick_list_item/pick_list_item.json @@ -153,7 +153,8 @@ "fieldtype": "Data", "hidden": 1, "label": "Sales Order Item", - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "serial_no_and_batch_section", @@ -208,7 +209,7 @@ ], "istable": 1, "links": [], - "modified": "2023-03-12 13:50:22.258100", + "modified": "2023-06-16 14:05:51.719959", "modified_by": "Administrator", "module": "Stock", "name": "Pick List Item", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index f7fb633d7e..3d497ac2eb 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -1114,7 +1114,7 @@ erpnext.stock.select_batch_and_serial_no = (frm, item) => { if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) { item.has_serial_no = r.message.has_serial_no; item.has_batch_no = r.message.has_batch_no; - item.outward = item.s_warehouse ? 1 : 0; + item.type_of_transaction = item.s_warehouse ? "Outward" : "Inward"; frappe.require(path, function() { new erpnext.SerialBatchPackageSelector( diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index 9bf679b895..fe42b1f135 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -577,7 +577,8 @@ "fieldtype": "Link", "label": "Pick List", "options": "Pick List", - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "print_settings_col_break", @@ -677,7 +678,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-06-09 15:46:28.418339", + "modified": "2023-06-16 14:59:10.917235", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry", @@ -738,7 +739,6 @@ "read": 1, "report": 1, "role": "Stock Manager", - "set_user_permissions": 1, "share": 1, "submit": 1, "write": 1 diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index 56cc21cb2c..6afbf01e1e 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -286,6 +286,10 @@ frappe.ui.form.on("Stock Reconciliation Item", { } }, + add_serial_batch_bundle(frm, cdt, cdn) { + erpnext.utils.pick_serial_and_batch_bundle(frm, cdt, cdn, "Inward"); + } + }); erpnext.stock.StockReconciliation = class StockReconciliation extends erpnext.stock.StockController { diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 4004c0012f..6ea27edc45 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -193,7 +193,13 @@ class StockReconciliation(StockController): def _changed(item): if item.current_serial_and_batch_bundle: - self.calculate_difference_amount(item, frappe._dict({})) + bundle_data = frappe.get_all( + "Serial and Batch Bundle", + filters={"name": item.current_serial_and_batch_bundle}, + fields=["total_qty as qty", "avg_rate as rate"], + )[0] + + self.calculate_difference_amount(item, bundle_data) return True item_dict = get_stock_balance_for( @@ -446,16 +452,17 @@ class StockReconciliation(StockController): sl_entries.append(args) - args = self.get_sle_for_items(row) - args.update( - { - "actual_qty": row.qty, - "incoming_rate": row.valuation_rate, - "serial_and_batch_bundle": row.serial_and_batch_bundle, - } - ) + if row.qty != 0: + args = self.get_sle_for_items(row) + args.update( + { + "actual_qty": row.qty, + "incoming_rate": row.valuation_rate, + "serial_and_batch_bundle": row.serial_and_batch_bundle, + } + ) - sl_entries.append(args) + sl_entries.append(args) def update_valuation_rate_for_serial_no(self): for d in self.items: diff --git a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json index 8738f4ae2b..62d6e4c8a2 100644 --- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json +++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json @@ -103,7 +103,8 @@ { "fieldname": "serial_no", "fieldtype": "Long Text", - "label": "Serial No" + "label": "Serial No", + "read_only": 1 }, { "fieldname": "column_break_11", @@ -213,7 +214,7 @@ ], "istable": 1, "links": [], - "modified": "2023-05-27 17:35:31.026852", + "modified": "2023-06-15 11:45:55.808942", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reconciliation Item", diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index a75c3b0ffb..2c18f99acd 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -5,7 +5,7 @@ import frappe from frappe import _, bold from frappe.model.naming import make_autoname from frappe.query_builder.functions import CombineDatetime, Sum -from frappe.utils import cint, flt, now, nowtime, today +from frappe.utils import cint, flt, get_link_to_form, now, nowtime, today from erpnext.stock.deprecated_serial_batch import ( DeprecatedBatchNoValuation, @@ -79,9 +79,24 @@ class SerialBatchBundle: self.set_serial_and_batch_bundle(sn_doc) def validate_actual_qty(self, sn_doc): + link = get_link_to_form("Serial and Batch Bundle", sn_doc.name) + + condition = { + "Inward": self.sle.actual_qty > 0, + "Outward": self.sle.actual_qty < 0, + }.get(sn_doc.type_of_transaction) + + if not condition: + correct_type = "Inward" + if sn_doc.type_of_transaction == "Inward": + correct_type = "Outward" + + msg = f"The type of transaction of Serial and Batch Bundle {link} is {bold(sn_doc.type_of_transaction)} but as per the Actual Qty {self.sle.actual_qty} for the item {bold(self.sle.item_code)} in the {self.sle.voucher_type} {self.sle.voucher_no} the type of transaction should be {bold(correct_type)}" + frappe.throw(_(msg), title=_("Incorrect Type of Transaction")) + precision = sn_doc.precision("total_qty") if flt(sn_doc.total_qty, precision) != flt(self.sle.actual_qty, precision): - msg = f"Total qty {flt(sn_doc.total_qty, precision)} of Serial and Batch Bundle {sn_doc.name} is not equal to Actual Qty {flt(self.sle.actual_qty, precision)} in the {self.sle.voucher_type} {self.sle.voucher_no}" + msg = f"Total qty {flt(sn_doc.total_qty, precision)} of Serial and Batch Bundle {link} is not equal to Actual Qty {flt(self.sle.actual_qty, precision)} in the {self.sle.voucher_type} {self.sle.voucher_no}" frappe.throw(_(msg)) def validate_item(self): diff --git a/erpnext/utilities/hierarchy_chart.py b/erpnext/utilities/hierarchy_chart.py deleted file mode 100644 index 4bf4353cdf..0000000000 --- a/erpnext/utilities/hierarchy_chart.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - - -import frappe -from frappe import _ - - -@frappe.whitelist() -def get_all_nodes(method, company): - """Recursively gets all data from nodes""" - method = frappe.get_attr(method) - - if method not in frappe.whitelisted: - frappe.throw(_("Not Permitted"), frappe.PermissionError) - - root_nodes = method(company=company) - result = [] - nodes_to_expand = [] - - for root in root_nodes: - data = method(root.id, company) - result.append(dict(parent=root.id, parent_name=root.name, data=data)) - nodes_to_expand.extend( - [{"id": d.get("id"), "name": d.get("name")} for d in data if d.get("expandable")] - ) - - while nodes_to_expand: - parent = nodes_to_expand.pop(0) - data = method(parent.get("id"), company) - result.append(dict(parent=parent.get("id"), parent_name=parent.get("name"), data=data)) - for d in data: - if d.get("expandable"): - nodes_to_expand.append({"id": d.get("id"), "name": d.get("name")}) - - return result diff --git a/package.json b/package.json index 6c11e9dddc..4e686f7ca7 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ }, "devDependencies": {}, "dependencies": { - "html2canvas": "^1.1.4", "onscan.js": "^1.5.2" } } diff --git a/yarn.lock b/yarn.lock index 8e5d1bd1c1..fa1b1d673b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,25 +2,6 @@ # yarn lockfile v1 -base64-arraybuffer@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz#4b944fac0191aa5907afe2d8c999ccc57ce80f45" - integrity sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ== - -css-line-break@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-1.1.1.tgz#d5e9bdd297840099eb0503c7310fd34927a026ef" - integrity sha512-1feNVaM4Fyzdj4mKPIQNL2n70MmuYzAXZ1aytlROFX1JsOo070OsugwGjj7nl6jnDJWHDM8zRZswkmeYVWZJQA== - dependencies: - base64-arraybuffer "^0.2.0" - -html2canvas@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/html2canvas/-/html2canvas-1.1.4.tgz#53ae91cd26e9e9e623c56533cccb2e3f57c8124c" - integrity sha512-uHgQDwrXsRmFdnlOVFvHin9R7mdjjZvoBoXxicPR+NnucngkaLa5zIDW9fzMkiip0jSffyTyWedE8iVogYOeWg== - dependencies: - css-line-break "1.1.1" - onscan.js@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/onscan.js/-/onscan.js-1.5.2.tgz#14ed636e5f4c3f0a78bacbf9a505dad3140ee341"