From f956a2cf72ac3aabae35db517da2bb0402b40650 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 15 Oct 2020 01:19:11 +0530 Subject: [PATCH] fix: extra material received against send to warehouse entry --- .../stock/doctype/stock_entry/stock_entry.py | 91 ++++++------------- .../doctype/stock_entry/test_stock_entry.py | 27 +++++- 2 files changed, 54 insertions(+), 64 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 961f5f45325..c3be6eb2871 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe, erpnext import frappe.defaults from frappe import _ -from frappe.utils import cstr, cint, flt, comma_or, getdate, nowdate, formatdate, format_time +from frappe.utils import cstr, cint, flt, comma_or, getdate, nowdate, formatdate, format_time, get_link_to_form from erpnext.stock.utils import get_incoming_rate from erpnext.stock.stock_ledger import get_previous_sle, NegativeStockError, get_valuation_rate from erpnext.stock.get_item_details import get_bin_details, get_default_cost_center, get_conversion_factor, get_reserved_qty_for_so @@ -27,6 +27,7 @@ class IncorrectValuationRateError(frappe.ValidationError): pass class DuplicateEntryForWorkOrderError(frappe.ValidationError): pass class OperationsNotCompleteError(frappe.ValidationError): pass class MaxSampleAlreadyRetainedError(frappe.ValidationError): pass +class ExtraMaterialReceived(frappe.ValidationError): pass from erpnext.controllers.stock_controller import StockController @@ -35,6 +36,11 @@ form_grid_templates = { } class StockEntry(StockController): + def __init__(self, *args, **kwargs): + """To initialize the status updater.""" + super(StockEntry, self).__init__(*args, **kwargs) + self.status_updater = [] + def get_feed(self): return self.stock_entry_type @@ -51,7 +57,6 @@ class StockEntry(StockController): self.validate_purpose() self.validate_item() self.validate_customer_provided_item() - self.validate_qty() self.set_transfer_qty() self.validate_uom_is_integer("uom", "qty") self.validate_uom_is_integer("stock_uom", "transfer_qty") @@ -122,6 +127,22 @@ class StockEntry(StockController): self.from_bom = 1 self.bom_no = data.bom_no + def limits_crossed_error(self, args, item, qty_or_amount): + """To override the method limits_crossed_error which is defined in the status_updater.""" + """Raise the exception for extra material transfer against the send to warehouse.""" + + send_to_ste = frappe.bold(get_link_to_form("Stock Entry", self.outgoing_stock_entry)) + message = _("For more details please check the send to warehouse document {0}.").format(send_to_ste) + + frappe.throw(_('For the item {0}, the received quantity {1} is more than the sent quantity {2}. {3}{4}') + .format( + frappe.bold(item.get('item_code')), + frappe.bold((item[args["target_field"]])), + frappe.bold(item[args["target_ref_field"]]), + '
', + message + ), ExtraMaterialReceived, title = _('Extra Materials Transferred')) + def validate_work_order_status(self): pro_doc = frappe.get_doc("Work Order", self.work_order) if pro_doc.status == 'Completed': @@ -205,33 +226,6 @@ class StockEntry(StockController): frappe.throw(_("Row #{0}: Please specify Serial No for Item {1}").format(item.idx, item.item_code), frappe.MandatoryError) - def validate_qty(self): - manufacture_purpose = ["Manufacture", "Material Consumption for Manufacture"] - - if self.purpose in manufacture_purpose and self.work_order: - if not frappe.get_value('Work Order', self.work_order, 'skip_transfer'): - item_code = [] - for item in self.items: - if cstr(item.t_warehouse) == '': - req_items = frappe.get_all('Work Order Item', - filters={'parent': self.work_order, 'item_code': item.item_code}, fields=["item_code"]) - - transferred_materials = frappe.db.sql(""" - select - sum(qty) as qty - from `tabStock Entry` se,`tabStock Entry Detail` sed - where - se.name = sed.parent and se.docstatus=1 and - (se.purpose='Material Transfer for Manufacture' or se.purpose='Manufacture') - and sed.item_code=%s and se.work_order= %s and ifnull(sed.t_warehouse, '') != '' - """, (item.item_code, self.work_order), as_dict=1) - - stock_qty = flt(item.qty) - trans_qty = flt(transferred_materials[0].qty) - if req_items: - if stock_qty > trans_qty: - item_code.append(item.item_code) - def validate_fg_completed_qty(self): if self.purpose == "Manufacture" and self.work_order: production_item = frappe.get_value('Work Order', self.work_order, 'production_item') @@ -1296,37 +1290,7 @@ class StockEntry(StockController): def update_transferred_qty(self): if self.purpose == 'Receive at Warehouse': - stock_entries = {} - stock_entries_child_list = [] - for d in self.items: - if not (d.against_stock_entry and d.ste_detail): - continue - - stock_entries_child_list.append(d.ste_detail) - transferred_qty = frappe.get_all("Stock Entry Detail", fields = ["sum(qty) as qty"], - filters = { 'against_stock_entry': d.against_stock_entry, - 'ste_detail': d.ste_detail,'docstatus': 1}) - - stock_entries[(d.against_stock_entry, d.ste_detail)] = (transferred_qty[0].qty - if transferred_qty and transferred_qty[0] else 0.0) or 0.0 - - if not stock_entries: return None - - cond = '' - for data, transferred_qty in stock_entries.items(): - cond += """ WHEN (parent = %s and name = %s) THEN %s - """ %(frappe.db.escape(data[0]), frappe.db.escape(data[1]), transferred_qty) - - if cond and stock_entries_child_list: - frappe.db.sql(""" UPDATE `tabStock Entry Detail` - SET - transferred_qty = CASE {cond} END - WHERE - name in ({ste_details}) """.format(cond=cond, - ste_details = ','.join(['%s'] * len(stock_entries_child_list))), - tuple(stock_entries_child_list)) - - args = { + self.status_updater.append({ 'source_dt': 'Stock Entry Detail', 'target_field': 'transferred_qty', 'target_ref_field': 'qty', @@ -1335,10 +1299,11 @@ class StockEntry(StockController): 'target_parent_dt': 'Stock Entry', 'target_parent_field': 'per_transferred', 'source_field': 'qty', - 'percent_join_field': 'against_stock_entry' - } + 'percent_join_field': 'against_stock_entry', + 'no_allowance': 1 + }) - self._update_percent_field_in_targets(args, update_modified=True) + self.update_prevdoc_status() def update_quality_inspection(self): if self.inspection_required: diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 84f535912d4..e9778f6c8ca 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -14,7 +14,8 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import from erpnext.stock.doctype.item.test_item import set_item_variant_settings, make_item_variant, create_item from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.accounts.doctype.account.test_account import get_inventory_account -from erpnext.stock.doctype.stock_entry.stock_entry import move_sample_to_retention_warehouse, make_stock_in_entry +from erpnext.stock.doctype.stock_entry.stock_entry import (move_sample_to_retention_warehouse, + make_stock_in_entry, ExtraMaterialReceived) from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import OpeningEntryAccountError from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from six import iteritems @@ -871,6 +872,30 @@ class TestStockEntry(unittest.TestCase): doc = frappe.get_doc('Stock Entry', outward_entry.name) self.assertEqual(doc.per_transferred, 100) + def test_raise_extra_transfer_materials(self): + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + warehouse = "_Test Warehouse FG 1 - _TC" + + if not frappe.db.exists('Warehouse', warehouse): + create_warehouse("_Test Warehouse FG 1") + + outward_entry = make_stock_entry(item_code="_Test Item", + purpose="Send to Warehouse", + source="_Test Warehouse - _TC", + target="_Test Warehouse 1 - _TC", qty=50, basic_rate=100) + + inward_entry1 = make_stock_in_entry(outward_entry.name) + inward_entry1.items[0].t_warehouse = warehouse + inward_entry1.items[0].qty = 25 + inward_entry1.submit() + + inward_entry2 = make_stock_in_entry(outward_entry.name) + inward_entry2.items[0].t_warehouse = warehouse + inward_entry2.items[0].qty = 35 + + self.assertRaises(ExtraMaterialReceived, inward_entry2.submit) + print(inward_entry2.name) + def test_gle_for_opening_stock_entry(self): mr = make_stock_entry(item_code="_Test Item", target="Stores - TCP1", company="_Test Company with perpetual inventory",qty=50, basic_rate=100, expense_account="Stock Adjustment - TCP1", is_opening="Yes", do_not_save=True)