# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt import json from typing import Dict import frappe from frappe import _ from frappe.utils import cint, cstr, flt, getdate from erpnext.stock.doctype.item.item import get_last_purchase_details, validate_end_of_life def update_last_purchase_rate(doc, is_submit) -> None: """updates last_purchase_rate in item table for each item""" this_purchase_date = getdate(doc.get("posting_date") or doc.get("transaction_date")) for d in doc.get("items"): # get last purchase details last_purchase_details = get_last_purchase_details(d.item_code, doc.name) # compare last purchase date and this transaction's date last_purchase_rate = None if last_purchase_details and ( doc.get("docstatus") == 2 or last_purchase_details.purchase_date > this_purchase_date ): last_purchase_rate = last_purchase_details["base_net_rate"] elif is_submit == 1: # even if this transaction is the latest one, it should be submitted # for it to be considered for latest purchase rate if flt(d.conversion_factor): last_purchase_rate = flt(d.base_net_rate) / flt(d.conversion_factor) # Check if item code is present # Conversion factor should not be mandatory for non itemized items elif d.item_code: frappe.throw(_("UOM Conversion factor is required in row {0}").format(d.idx)) # update last purchsae rate frappe.db.set_value("Item", d.item_code, "last_purchase_rate", flt(last_purchase_rate)) def validate_for_items(doc) -> None: items = [] for d in doc.get("items"): if not d.qty: if doc.doctype == "Purchase Receipt" and d.rejected_qty: continue frappe.throw(_("Please enter quantity for Item {0}").format(d.item_code)) set_stock_levels(row=d) # update with latest quantities item = validate_item_and_get_basic_data(row=d) validate_stock_item_warehouse(row=d, item=item) validate_end_of_life(d.item_code, item.end_of_life, item.disabled) items.append(cstr(d.item_code)) if ( items and len(items) != len(set(items)) and not cint(frappe.db.get_single_value("Buying Settings", "allow_multiple_items") or 0) ): frappe.throw(_("Same item cannot be entered multiple times.")) def set_stock_levels(row) -> None: projected_qty = frappe.db.get_value( "Bin", { "item_code": row.item_code, "warehouse": row.warehouse, }, "projected_qty", ) qty_data = { "projected_qty": flt(projected_qty), "ordered_qty": 0, "received_qty": 0, } if row.doctype in ("Purchase Receipt Item", "Purchase Invoice Item"): qty_data.pop("received_qty") for field in qty_data: if row.meta.get_field(field): row.set(field, qty_data[field]) def validate_item_and_get_basic_data(row) -> Dict: item = frappe.db.get_values( "Item", filters={"name": row.item_code}, fieldname=["is_stock_item", "is_sub_contracted_item", "end_of_life", "disabled"], as_dict=1, ) if not item: frappe.throw(_("Row #{0}: Item {1} does not exist").format(row.idx, frappe.bold(row.item_code))) return item[0] def validate_stock_item_warehouse(row, item) -> None: if ( item.is_stock_item == 1 and row.qty and not row.warehouse and not row.get("delivered_by_supplier") ): frappe.throw( _("Row #{1}: Warehouse is mandatory for stock Item {0}").format( frappe.bold(row.item_code), row.idx ) ) def check_on_hold_or_closed_status(doctype, docname) -> None: status = frappe.db.get_value(doctype, docname, "status") if status in ("Closed", "On Hold"): frappe.throw( _("{0} {1} status is {2}").format(doctype, docname, status), frappe.InvalidStatusError ) @frappe.whitelist() def get_linked_material_requests(items): items = json.loads(items) mr_list = [] for item in items: material_request = frappe.db.sql( """SELECT distinct mr.name AS mr_name, (mr_item.qty - mr_item.ordered_qty) AS qty, mr_item.item_code AS item_code, mr_item.name AS mr_item FROM `tabMaterial Request` mr, `tabMaterial Request Item` mr_item WHERE mr.name = mr_item.parent AND mr_item.item_code = %(item)s AND mr.material_request_type = 'Purchase' AND mr.per_ordered < 99.99 AND mr.docstatus = 1 AND mr.status != 'Stopped' ORDER BY mr_item.item_code ASC""", {"item": item}, as_dict=1, ) if material_request: mr_list.append(material_request) return mr_list