2015-03-03 09:25:30 +00:00
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
2013-08-05 09:29:54 +00:00
# License: GNU General Public License v3. See license.txt
2013-03-19 06:31:24 +00:00
2021-04-14 08:42:03 +00:00
import json
2020-12-07 16:05:49 +00:00
from collections import defaultdict
2021-04-14 08:42:03 +00:00
import frappe
import frappe . defaults
from frappe import _
from frappe . utils import cint , cstr , flt , get_link_to_form , getdate
import erpnext
2020-04-30 05:08:58 +00:00
from erpnext . accounts . general_ledger import make_gl_entries , make_reverse_gl_entries , process_gl_map
2021-04-14 08:42:03 +00:00
from erpnext . accounts . utils import check_if_stock_and_account_balance_synced , get_fiscal_year
2015-08-24 09:02:38 +00:00
from erpnext . controllers . accounts_controller import AccountsController
2017-06-15 05:39:27 +00:00
from erpnext . stock import get_warehouse_account_map
2021-04-14 08:42:03 +00:00
from erpnext . stock . stock_ledger import get_valuation_rate
2013-03-19 06:31:24 +00:00
2018-12-24 09:24:42 +00:00
class QualityInspectionRequiredError ( frappe . ValidationError ) : pass
class QualityInspectionRejectedError ( frappe . ValidationError ) : pass
2018-12-28 11:23:00 +00:00
class QualityInspectionNotSubmittedError ( frappe . ValidationError ) : pass
2018-12-24 09:24:42 +00:00
2013-03-19 06:31:24 +00:00
class StockController ( AccountsController ) :
2016-11-16 11:51:59 +00:00
def validate ( self ) :
super ( StockController , self ) . validate ( )
2020-06-12 07:00:59 +00:00
if not self . get ( ' is_return ' ) :
2020-06-11 11:09:03 +00:00
self . validate_inspection ( )
2019-12-30 07:56:47 +00:00
self . validate_serialized_batch ( )
2020-04-03 10:16:48 +00:00
self . validate_customer_provided_item ( )
2021-02-11 14:49:30 +00:00
self . set_rate_of_stock_uom ( )
2021-01-28 07:39:56 +00:00
self . validate_internal_transfer ( )
2020-12-07 16:05:49 +00:00
self . validate_putaway_capacity ( )
2017-01-16 11:53:20 +00:00
2020-12-21 09:15:50 +00:00
def make_gl_entries ( self , gl_entries = None , from_repost = False ) :
2014-03-28 08:25:00 +00:00
if self . docstatus == 2 :
2020-04-30 05:08:58 +00:00
make_reverse_gl_entries ( voucher_type = self . doctype , voucher_no = self . name )
2014-04-07 13:21:58 +00:00
2017-06-19 07:24:59 +00:00
if cint ( erpnext . is_perpetual_inventory_enabled ( self . company ) ) :
2019-03-08 05:43:35 +00:00
warehouse_account = get_warehouse_account_map ( self . company )
2014-04-07 13:21:58 +00:00
2014-03-28 08:25:00 +00:00
if self . docstatus == 1 :
2016-12-30 10:51:35 +00:00
if not gl_entries :
gl_entries = self . get_gl_entries ( warehouse_account )
2020-12-21 09:15:50 +00:00
make_gl_entries ( gl_entries , from_repost = from_repost )
2013-10-22 18:21:41 +00:00
2018-06-12 08:24:40 +00:00
elif self . doctype in [ ' Purchase Receipt ' , ' Purchase Invoice ' ] and self . docstatus == 1 :
2018-05-16 12:46:08 +00:00
gl_entries = [ ]
gl_entries = self . get_asset_gl_entry ( gl_entries )
2020-12-21 09:15:50 +00:00
make_gl_entries ( gl_entries , from_repost = from_repost )
2014-04-07 13:21:58 +00:00
2019-12-30 07:56:47 +00:00
def validate_serialized_batch ( self ) :
from erpnext . stock . doctype . serial_no . serial_no import get_serial_nos
for d in self . get ( " items " ) :
if hasattr ( d , ' serial_no ' ) and hasattr ( d , ' batch_no ' ) and d . serial_no and d . batch_no :
serial_nos = get_serial_nos ( d . serial_no )
for serial_no_data in frappe . get_all ( " Serial No " ,
filters = { " name " : ( " in " , serial_nos ) } , fields = [ " batch_no " , " name " ] ) :
if serial_no_data . batch_no != d . batch_no :
frappe . throw ( _ ( " Row # {0} : Serial No {1} does not belong to Batch {2} " )
. format ( d . idx , serial_no_data . name , d . batch_no ) )
2020-10-20 06:29:06 +00:00
if flt ( d . qty ) > 0.0 and d . get ( " batch_no " ) and self . get ( " posting_date " ) and self . docstatus < 2 :
2020-04-28 07:31:43 +00:00
expiry_date = frappe . get_cached_value ( " Batch " , d . get ( " batch_no " ) , " expiry_date " )
if expiry_date and getdate ( expiry_date ) < getdate ( self . posting_date ) :
frappe . throw ( _ ( " Row # {0} : The batch {1} has already expired. " )
. format ( d . idx , get_link_to_form ( " Batch " , d . get ( " batch_no " ) ) ) )
2013-11-14 13:10:08 +00:00
def get_gl_entries ( self , warehouse_account = None , default_expense_account = None ,
2013-09-17 09:45:16 +00:00
default_cost_center = None ) :
2014-10-06 06:23:52 +00:00
2013-09-17 09:45:16 +00:00
if not warehouse_account :
2019-03-08 05:43:35 +00:00
warehouse_account = get_warehouse_account_map ( self . company )
2014-04-07 13:21:58 +00:00
2014-04-17 06:07:46 +00:00
sle_map = self . get_stock_ledger_details ( )
voucher_details = self . get_voucher_details ( default_expense_account , default_cost_center , sle_map )
2014-04-07 13:21:58 +00:00
2013-08-28 13:23:11 +00:00
gl_list = [ ]
2013-09-17 04:51:20 +00:00
warehouse_with_no_account = [ ]
2021-02-22 16:57:22 +00:00
precision = self . get_debit_field_precision ( )
2016-12-15 08:16:03 +00:00
for item_row in voucher_details :
2021-01-28 07:39:56 +00:00
2016-12-15 08:16:03 +00:00
sle_list = sle_map . get ( item_row . name )
2013-08-28 13:23:11 +00:00
if sle_list :
for sle in sle_list :
if warehouse_account . get ( sle . warehouse ) :
2020-12-11 16:00:39 +00:00
# from warehouse account
2017-01-16 11:53:20 +00:00
2016-12-15 08:16:03 +00:00
self . check_expense_account ( item_row )
2017-01-16 11:53:20 +00:00
2017-04-14 10:24:04 +00:00
# If the item does not have the allow zero valuation rate flag set
2017-02-06 11:43:39 +00:00
# and ( valuation rate not mentioned in an incoming entry
2017-03-31 07:14:29 +00:00
# or incoming entry not found while delivering the item),
2017-02-06 11:43:39 +00:00
# try to pick valuation rate from previous sle or Item master and update in SLE
# Otherwise, throw an exception
2017-02-06 19:53:26 +00:00
if not sle . stock_value_difference and self . doctype != " Stock Reconciliation " \
2017-04-14 10:24:04 +00:00
and not item_row . get ( " allow_zero_valuation_rate " ) :
2017-02-06 19:53:26 +00:00
2017-02-06 11:43:39 +00:00
sle = self . update_stock_ledger_entries ( sle )
2015-10-15 06:58:20 +00:00
2020-12-11 16:00:39 +00:00
# expense account/ target_warehouse / source_warehouse
if item_row . get ( ' target_warehouse ' ) :
warehouse = item_row . get ( ' target_warehouse ' )
expense_account = warehouse_account [ warehouse ] [ " account " ]
else :
expense_account = item_row . expense_account
2013-08-28 13:23:11 +00:00
gl_list . append ( self . get_gl_dict ( {
2017-06-15 05:39:27 +00:00
" account " : warehouse_account [ sle . warehouse ] [ " account " ] ,
2020-12-11 16:00:39 +00:00
" against " : expense_account ,
2016-12-15 08:16:03 +00:00
" cost_center " : item_row . cost_center ,
2020-06-19 06:42:08 +00:00
" project " : item_row . project or self . get ( ' project ' ) ,
2014-04-07 06:32:57 +00:00
" remarks " : self . get ( " remarks " ) or " Accounting Entry for Stock " ,
2020-01-15 11:05:31 +00:00
" debit " : flt ( sle . stock_value_difference , precision ) ,
2019-05-22 12:33:09 +00:00
" is_opening " : item_row . get ( " is_opening " ) or self . get ( " is_opening " ) or " No " ,
2019-05-18 18:32:01 +00:00
} , warehouse_account [ sle . warehouse ] [ " account_currency " ] , item = item_row ) )
2013-08-28 13:23:11 +00:00
gl_list . append ( self . get_gl_dict ( {
2020-12-11 16:00:39 +00:00
" account " : expense_account ,
2017-06-15 05:39:27 +00:00
" against " : warehouse_account [ sle . warehouse ] [ " account " ] ,
2016-12-15 08:16:03 +00:00
" cost_center " : item_row . cost_center ,
2014-04-07 06:32:57 +00:00
" remarks " : self . get ( " remarks " ) or " Accounting Entry for Stock " ,
2020-01-15 11:05:31 +00:00
" credit " : flt ( sle . stock_value_difference , precision ) ,
2019-05-16 11:58:39 +00:00
" project " : item_row . get ( " project " ) or self . get ( " project " ) ,
2019-05-22 12:33:09 +00:00
" is_opening " : item_row . get ( " is_opening " ) or self . get ( " is_opening " ) or " No "
2019-05-18 18:32:01 +00:00
} , item = item_row ) )
2013-09-17 04:51:20 +00:00
elif sle . warehouse not in warehouse_with_no_account :
warehouse_with_no_account . append ( sle . warehouse )
2014-04-07 13:21:58 +00:00
if warehouse_with_no_account :
2016-10-24 12:47:57 +00:00
for wh in warehouse_with_no_account :
if frappe . db . get_value ( " Warehouse " , wh , " company " ) :
2020-12-21 09:15:50 +00:00
frappe . throw ( _ ( " Warehouse {0} is not linked to any account, please mention the account in the warehouse record or set default inventory account in company {1} . " ) . format ( wh , self . company ) )
2015-10-15 06:58:20 +00:00
2021-02-22 16:57:22 +00:00
return process_gl_map ( gl_list , precision = precision )
def get_debit_field_precision ( self ) :
if not frappe . flags . debit_field_precision :
frappe . flags . debit_field_precision = frappe . get_precision ( " GL Entry " , " debit_in_account_currency " )
return frappe . flags . debit_field_precision
2017-01-16 11:53:20 +00:00
2017-01-25 13:17:53 +00:00
def update_stock_ledger_entries ( self , sle ) :
2017-03-31 07:14:29 +00:00
sle . valuation_rate = get_valuation_rate ( sle . item_code , sle . warehouse ,
2017-06-16 09:51:36 +00:00
self . doctype , self . name , currency = self . company_currency , company = self . company )
2017-02-06 11:43:39 +00:00
2017-01-25 13:50:35 +00:00
sle . stock_value = flt ( sle . qty_after_transaction ) * flt ( sle . valuation_rate )
2017-02-06 11:43:39 +00:00
sle . stock_value_difference = flt ( sle . actual_qty ) * flt ( sle . valuation_rate )
2017-03-31 07:14:29 +00:00
2017-01-28 07:48:49 +00:00
if sle . name :
2017-02-06 11:43:39 +00:00
frappe . db . sql ( """
2017-03-31 07:14:29 +00:00
update
` tabStock Ledger Entry `
set
2017-02-06 11:43:39 +00:00
stock_value = % ( stock_value ) s ,
2017-03-31 07:14:29 +00:00
valuation_rate = % ( valuation_rate ) s ,
stock_value_difference = % ( stock_value_difference ) s
where
2017-02-06 11:43:39 +00:00
name = % ( name ) s """ , (sle))
2017-03-31 07:14:29 +00:00
2017-02-06 11:43:39 +00:00
return sle
2017-03-31 07:14:29 +00:00
2014-04-17 06:07:46 +00:00
def get_voucher_details ( self , default_expense_account , default_cost_center , sle_map ) :
if self . doctype == " Stock Reconciliation " :
2019-05-16 11:58:39 +00:00
reconciliation_purpose = frappe . db . get_value ( self . doctype , self . name , " purpose " )
is_opening = " Yes " if reconciliation_purpose == " Opening Stock " else " No "
details = [ ]
2019-07-03 05:04:31 +00:00
for voucher_detail_no in sle_map :
2019-05-16 11:58:39 +00:00
details . append ( frappe . _dict ( {
" name " : voucher_detail_no ,
" expense_account " : default_expense_account ,
" cost_center " : default_cost_center ,
" is_opening " : is_opening
} ) )
return details
2014-04-17 06:07:46 +00:00
else :
2014-12-26 07:45:21 +00:00
details = self . get ( " items " )
2014-04-17 06:07:46 +00:00
if default_expense_account or default_cost_center :
for d in details :
if default_expense_account and not d . get ( " expense_account " ) :
d . expense_account = default_expense_account
if default_cost_center and not d . get ( " cost_center " ) :
d . cost_center = default_cost_center
return details
2014-04-07 13:21:58 +00:00
2014-09-26 08:52:18 +00:00
def get_items_and_warehouses ( self ) :
2014-03-27 11:48:29 +00:00
items , warehouses = [ ] , [ ]
2014-04-07 13:21:58 +00:00
2014-12-26 07:45:21 +00:00
if hasattr ( self , " items " ) :
item_doclist = self . get ( " items " )
2014-03-28 08:25:00 +00:00
elif self . doctype == " Stock Reconciliation " :
2014-03-27 11:48:29 +00:00
item_doclist = [ ]
2014-03-28 08:25:00 +00:00
data = json . loads ( self . reconciliation_json )
2014-03-27 11:48:29 +00:00
for row in data [ data . index ( self . head_row ) + 1 : ] :
d = frappe . _dict ( zip ( [ " item_code " , " warehouse " , " qty " , " valuation_rate " ] , row ) )
item_doclist . append ( d )
2014-04-07 13:21:58 +00:00
2014-03-27 11:48:29 +00:00
if item_doclist :
for d in item_doclist :
if d . item_code and d . item_code not in items :
items . append ( d . item_code )
2014-04-07 13:21:58 +00:00
2014-04-07 06:32:57 +00:00
if d . get ( " warehouse " ) and d . warehouse not in warehouses :
2014-03-27 11:48:29 +00:00
warehouses . append ( d . warehouse )
2014-04-07 13:21:58 +00:00
2014-04-07 06:32:57 +00:00
if self . doctype == " Stock Entry " :
if d . get ( " s_warehouse " ) and d . s_warehouse not in warehouses :
warehouses . append ( d . s_warehouse )
if d . get ( " t_warehouse " ) and d . t_warehouse not in warehouses :
warehouses . append ( d . t_warehouse )
2014-03-27 11:48:29 +00:00
2014-09-26 08:52:18 +00:00
return items , warehouses
2014-04-07 13:21:58 +00:00
2013-08-28 13:23:11 +00:00
def get_stock_ledger_details ( self ) :
stock_ledger = { }
2017-02-06 11:43:39 +00:00
stock_ledger_entries = frappe . db . sql ( """
2017-03-31 07:14:29 +00:00
select
2017-02-06 19:53:26 +00:00
name , warehouse , stock_value_difference , valuation_rate ,
2017-03-31 07:14:29 +00:00
voucher_detail_no , item_code , posting_date , posting_time ,
2017-02-06 19:53:26 +00:00
actual_qty , qty_after_transaction
2017-02-06 11:43:39 +00:00
from
` tabStock Ledger Entry `
where
voucher_type = % s and voucher_no = % s
""" , (self.doctype, self.name), as_dict=True)
2017-02-06 19:53:26 +00:00
2017-02-06 11:43:39 +00:00
for sle in stock_ledger_entries :
2021-01-28 07:39:56 +00:00
stock_ledger . setdefault ( sle . voucher_detail_no , [ ] ) . append ( sle )
2013-08-28 13:23:11 +00:00
return stock_ledger
2014-04-07 13:21:58 +00:00
2017-04-21 07:10:19 +00:00
def make_batches ( self , warehouse_field ) :
2017-04-20 09:51:01 +00:00
''' Create batches if required. Called before submit '''
for d in self . items :
2017-04-21 07:10:19 +00:00
if d . get ( warehouse_field ) and not d . batch_no :
has_batch_no , create_new_batch = frappe . db . get_value ( ' Item ' , d . item_code , [ ' has_batch_no ' , ' create_new_batch ' ] )
if has_batch_no and create_new_batch :
d . batch_no = frappe . get_doc ( dict (
doctype = ' Batch ' ,
item = d . item_code ,
supplier = getattr ( self , ' supplier ' , None ) ,
reference_doctype = self . doctype ,
reference_name = self . name ) ) . insert ( ) . name
2017-04-20 09:51:01 +00:00
2013-08-26 11:23:30 +00:00
def check_expense_account ( self , item ) :
2014-04-16 13:50:11 +00:00
if not item . get ( " expense_account " ) :
2020-11-18 12:27:35 +00:00
msg = _ ( " Please set an Expense Account in the Items table " )
frappe . throw ( _ ( " Row # {0} : Expense Account not set for the Item {1} . {2} " )
. format ( item . idx , frappe . bold ( item . item_code ) , msg ) , title = _ ( " Expense Account Missing " ) )
2014-04-07 13:21:58 +00:00
2014-06-19 13:55:19 +00:00
else :
2021-02-22 16:57:22 +00:00
is_expense_account = frappe . get_cached_value ( " Account " ,
2014-06-25 13:42:24 +00:00
item . get ( " expense_account " ) , " report_type " ) == " Profit and Loss "
2016-02-08 16:44:55 +00:00
if self . doctype not in ( " Purchase Receipt " , " Purchase Invoice " , " Stock Reconciliation " , " Stock Entry " ) and not is_expense_account :
2014-06-25 13:42:24 +00:00
frappe . throw ( _ ( " Expense / Difference account ( {0} ) must be a ' Profit or Loss ' account " )
. format ( item . get ( " expense_account " ) ) )
2014-06-19 13:55:19 +00:00
if is_expense_account and not item . get ( " cost_center " ) :
frappe . throw ( _ ( " {0} {1} : Cost Center is mandatory for Item {2} " ) . format (
_ ( self . doctype ) , self . name , item . get ( " item_code " ) ) )
2014-04-07 13:21:58 +00:00
2020-01-23 12:06:52 +00:00
def delete_auto_created_batches ( self ) :
for d in self . items :
if not d . batch_no : continue
2020-11-18 12:27:35 +00:00
serial_nos = [ sr . name for sr in frappe . get_all ( " Serial No " ,
{ ' batch_no ' : d . batch_no , ' status ' : ' Inactive ' } ) ]
2020-03-02 09:32:58 +00:00
if serial_nos :
frappe . db . set_value ( " Serial No " , { ' name ' : [ ' in ' , serial_nos ] } , " batch_no " , None )
2020-01-23 12:06:52 +00:00
d . batch_no = None
d . db_set ( " batch_no " , None )
for data in frappe . get_all ( " Batch " ,
{ ' reference_name ' : self . name , ' reference_doctype ' : self . doctype } ) :
frappe . delete_doc ( " Batch " , data . name )
2020-01-23 07:12:42 +00:00
2014-04-07 13:21:58 +00:00
def get_sl_entries ( self , d , args ) :
2014-11-03 09:38:21 +00:00
sl_dict = frappe . _dict ( {
2014-05-02 12:06:13 +00:00
" item_code " : d . get ( " item_code " , None ) ,
2014-04-07 06:32:57 +00:00
" warehouse " : d . get ( " warehouse " , None ) ,
2014-03-28 08:25:00 +00:00
" posting_date " : self . posting_date ,
" posting_time " : self . posting_time ,
2016-03-04 09:03:49 +00:00
' fiscal_year ' : get_fiscal_year ( self . posting_date , company = self . company ) [ 0 ] ,
2014-03-28 08:25:00 +00:00
" voucher_type " : self . doctype ,
" voucher_no " : self . name ,
2013-08-02 06:12:11 +00:00
" voucher_detail_no " : d . name ,
2014-04-07 06:32:57 +00:00
" actual_qty " : ( self . docstatus == 1 and 1 or - 1 ) * flt ( d . get ( " stock_qty " ) ) ,
2015-09-21 03:48:43 +00:00
" stock_uom " : frappe . db . get_value ( " Item " , args . get ( " item_code " ) or d . get ( " item_code " ) , " stock_uom " ) ,
2013-08-02 06:12:11 +00:00
" incoming_rate " : 0 ,
2014-03-28 08:25:00 +00:00
" company " : self . company ,
2014-05-02 12:06:13 +00:00
" batch_no " : cstr ( d . get ( " batch_no " ) ) . strip ( ) ,
" serial_no " : d . get ( " serial_no " ) ,
2018-03-21 12:22:41 +00:00
" project " : d . get ( " project " ) or self . get ( ' project ' ) ,
2020-04-30 05:08:58 +00:00
" is_cancelled " : 1 if self . docstatus == 2 else 0
2014-11-03 09:38:21 +00:00
} )
2014-04-07 13:21:58 +00:00
2013-08-02 06:12:11 +00:00
sl_dict . update ( args )
return sl_dict
2014-04-07 13:21:58 +00:00
2020-04-30 05:08:58 +00:00
def make_sl_entries ( self , sl_entries , allow_negative_stock = False ,
2015-03-27 10:08:31 +00:00
via_landed_cost_voucher = False ) :
2013-12-12 13:42:19 +00:00
from erpnext . stock . stock_ledger import make_sl_entries
2020-04-30 05:08:58 +00:00
make_sl_entries ( sl_entries , allow_negative_stock , via_landed_cost_voucher )
2014-04-07 13:21:58 +00:00
2020-04-30 05:08:58 +00:00
def make_gl_entries_on_cancel ( self ) :
2014-04-07 13:21:58 +00:00
if frappe . db . sql ( """ select name from `tabGL Entry` where voucher_type= %s
2014-03-28 08:25:00 +00:00
and voucher_no = % s """ , (self.doctype, self.name)):
2020-04-30 05:08:58 +00:00
self . make_gl_entries ( )
2014-04-07 13:21:58 +00:00
2014-06-25 08:01:02 +00:00
def get_serialized_items ( self ) :
serialized_items = [ ]
2021-06-11 10:30:48 +00:00
item_codes = list ( set ( d . item_code for d in self . get ( " items " ) ) )
2014-06-25 08:01:02 +00:00
if item_codes :
serialized_items = frappe . db . sql_list ( """ select name from `tabItem`
2015-07-24 09:46:25 +00:00
where has_serial_no = 1 and name in ( { } ) """ .format( " , " .join([ " %s " ]*len(item_codes))),
2014-06-25 08:01:02 +00:00
tuple ( item_codes ) )
return serialized_items
2015-08-03 10:43:33 +00:00
2015-11-18 11:33:33 +00:00
def validate_warehouse ( self ) :
2021-04-14 08:42:03 +00:00
from erpnext . stock . utils import validate_disabled_warehouse , validate_warehouse_company
2015-11-18 11:33:33 +00:00
2021-06-11 10:30:48 +00:00
warehouses = list ( set ( d . warehouse for d in
self . get ( " items " ) if getattr ( d , " warehouse " , None ) ) )
2015-11-18 11:33:33 +00:00
2020-02-18 06:58:41 +00:00
target_warehouses = list ( set ( [ d . target_warehouse for d in
self . get ( " items " ) if getattr ( d , " target_warehouse " , None ) ] ) )
warehouses . extend ( target_warehouses )
from_warehouse = list ( set ( [ d . from_warehouse for d in
self . get ( " items " ) if getattr ( d , " from_warehouse " , None ) ] ) )
warehouses . extend ( from_warehouse )
2015-11-18 11:33:33 +00:00
for w in warehouses :
2021-02-11 06:16:48 +00:00
validate_disabled_warehouse ( w )
2015-11-18 11:33:33 +00:00
validate_warehouse_company ( w , self . company )
2017-01-16 11:53:20 +00:00
2016-01-06 11:02:06 +00:00
def update_billing_percentage ( self , update_modified = True ) :
2020-11-02 09:37:48 +00:00
target_ref_field = " amount "
if self . doctype == " Delivery Note " :
target_ref_field = " amount - (returned_qty * rate) "
2015-12-30 13:38:11 +00:00
self . _update_percent_field ( {
" target_dt " : self . doctype + " Item " ,
" target_parent_dt " : self . doctype ,
" target_parent_field " : " per_billed " ,
2020-11-02 09:37:48 +00:00
" target_ref_field " : target_ref_field ,
2015-12-30 13:38:11 +00:00
" target_field " : " billed_amt " ,
" name " : self . name ,
2016-01-06 11:02:06 +00:00
} , update_modified )
2014-06-25 08:01:02 +00:00
2016-11-16 11:51:59 +00:00
def validate_inspection ( self ) :
2021-06-21 10:48:35 +00:00
""" Checks if quality inspection is set/ is valid for Items that require inspection. """
inspection_fieldname_map = {
" Purchase Receipt " : " inspection_required_before_purchase " ,
" Purchase Invoice " : " inspection_required_before_purchase " ,
" Sales Invoice " : " inspection_required_before_delivery " ,
" Delivery Note " : " inspection_required_before_delivery "
}
inspection_required_fieldname = inspection_fieldname_map . get ( self . doctype )
# return if inspection is not required on document level
2018-02-22 05:33:48 +00:00
if ( ( not inspection_required_fieldname and self . doctype != " Stock Entry " ) or
( self . doctype == " Stock Entry " and not self . inspection_required ) or
( self . doctype in [ " Sales Invoice " , " Purchase Invoice " ] and not self . update_stock ) ) :
2016-11-16 11:51:59 +00:00
return
2017-01-16 11:53:20 +00:00
2021-06-21 10:48:35 +00:00
for row in self . get ( ' items ' ) :
qi_required = False
if ( inspection_required_fieldname and frappe . db . get_value ( " Item " , row . item_code , inspection_required_fieldname ) ) :
qi_required = True
elif self . doctype == " Stock Entry " and row . t_warehouse :
qi_required = True # inward stock needs inspection
if qi_required : # validate row only if inspection is required on item level
self . validate_qi_presence ( row )
if self . docstatus == 1 :
self . validate_qi_submission ( row )
self . validate_qi_rejection ( row )
def validate_qi_presence ( self , row ) :
""" Check if QI is present on row level. Warn on save and stop on submit if missing. """
if not row . quality_inspection :
2021-06-21 11:21:12 +00:00
msg = f " Row # { row . idx } : Quality Inspection is required for Item { frappe . bold ( row . item_code ) } "
2021-06-21 10:48:35 +00:00
if self . docstatus == 1 :
2021-06-21 11:21:12 +00:00
frappe . throw ( _ ( msg ) , title = _ ( " Inspection Required " ) , exc = QualityInspectionRequiredError )
2021-06-21 10:48:35 +00:00
else :
2021-06-21 11:21:12 +00:00
frappe . msgprint ( _ ( msg ) , title = _ ( " Inspection Required " ) , indicator = " blue " )
2021-06-21 10:48:35 +00:00
def validate_qi_submission ( self , row ) :
""" Check if QI is submitted on row level, during submission """
action = frappe . get_doc ( ' Stock Settings ' ) . action_if_quality_inspection_is_not_submitted or " Stop "
qa_docstatus = frappe . db . get_value ( " Quality Inspection " , row . quality_inspection , " docstatus " )
if not qa_docstatus == 1 :
link = frappe . utils . get_link_to_form ( ' Quality Inspection ' , row . quality_inspection )
2021-06-21 11:21:12 +00:00
msg = f " Row # { row . idx } : Quality Inspection { link } is not submitted for the item: { row . item_code } "
2021-06-21 10:48:35 +00:00
if action == " Stop " :
2021-06-21 11:21:12 +00:00
frappe . throw ( _ ( msg ) , title = _ ( " Inspection Submission " ) , exc = QualityInspectionNotSubmittedError )
2021-06-21 10:48:35 +00:00
else :
2021-06-21 11:21:12 +00:00
frappe . msgprint ( _ ( msg ) , alert = True )
2021-06-21 10:48:35 +00:00
def validate_qi_rejection ( self , row ) :
""" Check if QI is rejected on row level, during submission """
action = frappe . get_doc ( ' Stock Settings ' ) . action_if_quality_inspection_is_rejected or " Stop "
qa_status = frappe . db . get_value ( " Quality Inspection " , row . quality_inspection , " status " )
if qa_status == " Rejected " :
link = frappe . utils . get_link_to_form ( ' Quality Inspection ' , row . quality_inspection )
2021-06-21 11:21:12 +00:00
msg = f " Row # { row . idx } : Quality Inspection { link } was rejected for item { row . item_code } "
2021-06-21 10:48:35 +00:00
if action == " Stop " :
2021-06-21 11:21:12 +00:00
frappe . throw ( _ ( msg ) , title = _ ( " Inspection Rejected " ) , exc = QualityInspectionRejectedError )
2021-06-21 10:48:35 +00:00
else :
2021-06-21 11:21:12 +00:00
frappe . msgprint ( _ ( msg ) , alert = True , indicator = " orange " )
2016-11-10 13:45:11 +00:00
2018-06-14 10:24:34 +00:00
def update_blanket_order ( self ) :
2018-06-14 11:39:55 +00:00
blanket_orders = list ( set ( [ d . blanket_order for d in self . items if d . blanket_order ] ) )
2018-06-14 10:24:34 +00:00
for blanket_order in blanket_orders :
frappe . get_doc ( " Blanket Order " , blanket_order ) . update_ordered_qty ( )
2018-05-28 14:37:08 +00:00
2020-04-03 10:16:48 +00:00
def validate_customer_provided_item ( self ) :
for d in self . get ( ' items ' ) :
# Customer Provided parts will have zero valuation rate
if frappe . db . get_value ( ' Item ' , d . item_code , ' is_customer_provided_item ' ) :
d . allow_zero_valuation_rate = 1
2014-04-07 13:21:58 +00:00
2021-02-11 14:49:30 +00:00
def set_rate_of_stock_uom ( self ) :
if self . doctype in [ " Purchase Receipt " , " Purchase Invoice " , " Purchase Order " , " Sales Invoice " , " Sales Order " , " Delivery Note " , " Quotation " ] :
for d in self . get ( " items " ) :
2021-03-26 08:41:50 +00:00
d . stock_uom_rate = d . rate / ( d . conversion_factor or 1 )
2021-02-11 14:49:30 +00:00
2021-01-28 07:39:56 +00:00
def validate_internal_transfer ( self ) :
if self . doctype in ( ' Sales Invoice ' , ' Delivery Note ' , ' Purchase Invoice ' , ' Purchase Receipt ' ) \
and self . is_internal_transfer ( ) :
self . validate_in_transit_warehouses ( )
self . validate_multi_currency ( )
self . validate_packed_items ( )
def validate_in_transit_warehouses ( self ) :
if ( self . doctype == ' Sales Invoice ' and self . get ( ' update_stock ' ) ) or self . doctype == ' Delivery Note ' :
for item in self . get ( ' items ' ) :
if not item . target_warehouse :
frappe . throw ( _ ( " Row {0} : Target Warehouse is mandatory for internal transfers " ) . format ( item . idx ) )
if ( self . doctype == ' Purchase Invoice ' and self . get ( ' update_stock ' ) ) or self . doctype == ' Purchase Receipt ' :
for item in self . get ( ' items ' ) :
if not item . from_warehouse :
frappe . throw ( _ ( " Row {0} : From Warehouse is mandatory for internal transfers " ) . format ( item . idx ) )
def validate_multi_currency ( self ) :
if self . currency != self . company_currency :
frappe . throw ( _ ( " Internal transfers can only be done in company ' s default currency " ) )
def validate_packed_items ( self ) :
if self . doctype in ( ' Sales Invoice ' , ' Delivery Note Item ' ) and self . get ( ' packed_items ' ) :
frappe . throw ( _ ( " Packed Items cannot be transferred internally " ) )
2020-12-07 16:05:49 +00:00
def validate_putaway_capacity ( self ) :
# if over receipt is attempted while 'apply putaway rule' is disabled
# and if rule was applied on the transaction, validate it.
2021-01-18 18:17:24 +00:00
from erpnext . stock . doctype . putaway_rule . putaway_rule import get_available_putaway_capacity
valid_doctype = self . doctype in ( " Purchase Receipt " , " Stock Entry " , " Purchase Invoice " ,
" Stock Reconciliation " )
2020-12-07 16:05:49 +00:00
2021-01-18 18:17:24 +00:00
if self . doctype == " Purchase Invoice " and self . get ( " update_stock " ) == 0 :
valid_doctype = False
if valid_doctype :
2020-12-07 16:05:49 +00:00
rule_map = defaultdict ( dict )
for item in self . get ( " items " ) :
2021-01-18 18:17:24 +00:00
warehouse_field = " t_warehouse " if self . doctype == " Stock Entry " else " warehouse "
rule = frappe . db . get_value ( " Putaway Rule " ,
{
" item_code " : item . get ( " item_code " ) ,
" warehouse " : item . get ( warehouse_field )
} ,
[ " name " , " disable " ] , as_dict = True )
if rule :
if rule . get ( " disabled " ) : continue # dont validate for disabled rule
if self . doctype == " Stock Reconciliation " :
stock_qty = flt ( item . qty )
else :
stock_qty = flt ( item . transfer_qty ) if self . doctype == " Stock Entry " else flt ( item . stock_qty )
rule_name = rule . get ( " name " )
if not rule_map [ rule_name ] :
rule_map [ rule_name ] [ " warehouse " ] = item . get ( warehouse_field )
rule_map [ rule_name ] [ " item " ] = item . get ( " item_code " )
rule_map [ rule_name ] [ " qty_put " ] = 0
rule_map [ rule_name ] [ " capacity " ] = get_available_putaway_capacity ( rule_name )
rule_map [ rule_name ] [ " qty_put " ] + = flt ( stock_qty )
2020-12-07 16:05:49 +00:00
for rule , values in rule_map . items ( ) :
if flt ( values [ " qty_put " ] ) > flt ( values [ " capacity " ] ) :
2021-01-18 18:17:24 +00:00
message = self . prepare_over_receipt_message ( rule , values )
2020-12-07 16:05:49 +00:00
frappe . throw ( msg = message , title = _ ( " Over Receipt " ) )
2021-01-18 18:17:24 +00:00
def prepare_over_receipt_message ( self , rule , values ) :
message = _ ( " {0} qty of Item {1} is being received into Warehouse {2} with capacity {3} . " ) \
. format (
frappe . bold ( values [ " qty_put " ] ) , frappe . bold ( values [ " item " ] ) ,
frappe . bold ( values [ " warehouse " ] ) , frappe . bold ( values [ " capacity " ] )
)
message + = " <br><br> "
rule_link = frappe . utils . get_link_to_form ( " Putaway Rule " , rule )
2021-04-17 11:20:02 +00:00
message + = _ ( " Please adjust the qty or edit {0} to proceed. " ) . format ( rule_link )
2021-01-18 18:17:24 +00:00
return message
2020-12-07 16:05:49 +00:00
2020-12-21 09:15:50 +00:00
def repost_future_sle_and_gle ( self ) :
args = frappe . _dict ( {
" posting_date " : self . posting_date ,
" posting_time " : self . posting_time ,
" voucher_type " : self . doctype ,
" voucher_no " : self . name ,
" company " : self . company
} )
2021-03-27 10:40:20 +00:00
if future_sle_exists ( args ) :
2020-12-21 09:15:50 +00:00
create_repost_item_valuation_entry ( args )
2020-12-25 12:42:35 +00:00
elif not is_reposting_pending ( ) :
check_if_stock_and_account_balance_synced ( self . posting_date ,
self . company , self . doctype , self . name )
2021-02-22 16:57:22 +00:00
2021-04-14 08:42:03 +00:00
@frappe.whitelist ( )
def make_quality_inspections ( doctype , docname , items ) :
2021-06-02 09:25:31 +00:00
if isinstance ( items , str ) :
items = json . loads ( items )
2021-04-14 08:42:03 +00:00
2021-06-02 09:25:31 +00:00
inspections = [ ]
2021-04-14 08:42:03 +00:00
for item in items :
2021-05-26 09:12:15 +00:00
if flt ( item . get ( " sample_size " ) ) > flt ( item . get ( " qty " ) ) :
2021-04-14 08:42:03 +00:00
frappe . throw ( _ ( " {item_name} ' s Sample Size ( {sample_size} ) cannot be greater than the Accepted Quantity ( {accepted_quantity} ) " ) . format (
item_name = item . get ( " item_name " ) ,
sample_size = item . get ( " sample_size " ) ,
accepted_quantity = item . get ( " qty " )
) )
quality_inspection = frappe . get_doc ( {
" doctype " : " Quality Inspection " ,
" inspection_type " : " Incoming " ,
" inspected_by " : frappe . session . user ,
" reference_type " : doctype ,
" reference_name " : docname ,
" item_code " : item . get ( " item_code " ) ,
" description " : item . get ( " description " ) ,
2021-05-26 09:12:15 +00:00
" sample_size " : flt ( item . get ( " sample_size " ) ) ,
2021-04-14 08:42:03 +00:00
" item_serial_no " : item . get ( " serial_no " ) . split ( " \n " ) [ 0 ] if item . get ( " serial_no " ) else None ,
" batch_no " : item . get ( " batch_no " )
} ) . insert ( )
quality_inspection . save ( )
2021-05-26 09:12:15 +00:00
inspections . append ( quality_inspection . name )
2021-04-14 08:42:03 +00:00
2021-05-26 09:12:15 +00:00
return inspections
2021-04-14 08:42:03 +00:00
2020-12-25 12:42:35 +00:00
def is_reposting_pending ( ) :
return frappe . db . exists ( " Repost Item Valuation " ,
{ ' docstatus ' : 1 , ' status ' : [ ' in ' , [ ' Queued ' , ' In Progress ' ] ] } )
2021-06-15 04:51:44 +00:00
def future_sle_exists ( args , sl_entries = None ) :
key = ( args . voucher_type , args . voucher_no )
if validate_future_sle_not_exists ( args , key , sl_entries ) :
return False
elif get_cached_data ( args , key ) :
return True
if not sl_entries :
sl_entries = get_sle_entries_against_voucher ( args )
if not sl_entries :
return
or_conditions = get_conditions_to_validate_future_sle ( sl_entries )
data = frappe . db . sql ( """
select item_code , warehouse , count ( name ) as total_row
from ` tabStock Ledger Entry `
where
( { } )
and timestamp ( posting_date , posting_time )
> = timestamp ( % ( posting_date ) s , % ( posting_time ) s )
and voucher_no != % ( voucher_no ) s
and is_cancelled = 0
GROUP BY
item_code , warehouse
""" .format( " or " .join(or_conditions)), args, as_dict=1)
for d in data :
frappe . local . future_sle [ key ] [ ( d . item_code , d . warehouse ) ] = d . total_row
return len ( data )
2020-12-21 09:15:50 +00:00
2021-06-15 04:51:44 +00:00
def validate_future_sle_not_exists ( args , key , sl_entries = None ) :
item_key = ' '
if args . get ( ' item_code ' ) :
item_key = ( args . get ( ' item_code ' ) , args . get ( ' warehouse ' ) )
if not sl_entries and hasattr ( frappe . local , ' future_sle ' ) :
if ( not frappe . local . future_sle . get ( key ) or
( item_key and item_key not in frappe . local . future_sle . get ( key ) ) ) :
return True
def get_cached_data ( args , key ) :
if not hasattr ( frappe . local , ' future_sle ' ) :
frappe . local . future_sle = { }
if key not in frappe . local . future_sle :
frappe . local . future_sle [ key ] = frappe . _dict ( { } )
if args . get ( ' item_code ' ) :
item_key = ( args . get ( ' item_code ' ) , args . get ( ' warehouse ' ) )
count = frappe . local . future_sle [ key ] . get ( item_key )
return True if ( count or count == 0 ) else False
else :
return frappe . local . future_sle [ key ]
def get_sle_entries_against_voucher ( args ) :
return frappe . get_all ( " Stock Ledger Entry " ,
2020-12-21 09:15:50 +00:00
filters = { " voucher_type " : args . voucher_type , " voucher_no " : args . voucher_no } ,
fields = [ " item_code " , " warehouse " ] ,
order_by = " creation asc " )
2021-06-15 04:51:44 +00:00
def get_conditions_to_validate_future_sle ( sl_entries ) :
2021-03-27 10:40:20 +00:00
warehouse_items_map = { }
for entry in sl_entries :
if entry . warehouse not in warehouse_items_map :
warehouse_items_map [ entry . warehouse ] = set ( )
warehouse_items_map [ entry . warehouse ] . add ( entry . item_code )
or_conditions = [ ]
for warehouse , items in warehouse_items_map . items ( ) :
or_conditions . append (
2021-06-15 07:14:04 +00:00
f """ warehouse = { frappe . db . escape ( warehouse ) }
and item_code in ( { ' , ' . join ( frappe . db . escape ( item ) for item in items ) } ) """ )
2020-12-21 09:15:50 +00:00
2021-06-15 04:51:44 +00:00
return or_conditions
2020-12-21 09:15:50 +00:00
def create_repost_item_valuation_entry ( args ) :
args = frappe . _dict ( args )
repost_entry = frappe . new_doc ( " Repost Item Valuation " )
repost_entry . based_on = args . based_on
if not args . based_on :
repost_entry . based_on = ' Transaction ' if args . voucher_no else " Item and Warehouse "
repost_entry . voucher_type = args . voucher_type
repost_entry . voucher_no = args . voucher_no
repost_entry . item_code = args . item_code
repost_entry . warehouse = args . warehouse
repost_entry . posting_date = args . posting_date
repost_entry . posting_time = args . posting_time
repost_entry . company = args . company
repost_entry . allow_zero_rate = args . allow_zero_rate
repost_entry . flags . ignore_links = True
repost_entry . save ( )
2021-03-27 10:40:20 +00:00
repost_entry . submit ( )