2015-03-03 14:55:30 +05:30
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
2013-08-05 14:59:54 +05:30
# License: GNU General Public License v3. See license.txt
2013-03-19 12:01:24 +05:30
2021-04-14 14:12:03 +05:30
import json
2020-12-07 21:35:49 +05:30
from collections import defaultdict
2021-04-14 14:12:03 +05:30
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 10:38:58 +05:30
from erpnext . accounts . general_ledger import make_gl_entries , make_reverse_gl_entries , process_gl_map
2021-06-22 15:23:04 +05:30
from erpnext . accounts . utils import get_fiscal_year
2015-08-24 14:32:38 +05:30
from erpnext . controllers . accounts_controller import AccountsController
2017-06-15 11:09:27 +05:30
from erpnext . stock import get_warehouse_account_map
2021-04-14 14:12:03 +05:30
from erpnext . stock . stock_ledger import get_valuation_rate
2013-03-19 12:01:24 +05:30
2018-12-24 14:54:42 +05:30
class QualityInspectionRequiredError ( frappe . ValidationError ) : pass
class QualityInspectionRejectedError ( frappe . ValidationError ) : pass
2018-12-28 16:53:00 +05:30
class QualityInspectionNotSubmittedError ( frappe . ValidationError ) : pass
2018-12-24 14:54:42 +05:30
2013-03-19 12:01:24 +05:30
class StockController ( AccountsController ) :
2016-11-16 17:21:59 +05:30
def validate ( self ) :
super ( StockController , self ) . validate ( )
2020-06-12 12:30:59 +05:30
if not self . get ( ' is_return ' ) :
2020-06-11 16:39:03 +05:30
self . validate_inspection ( )
2019-12-30 13:26:47 +05:30
self . validate_serialized_batch ( )
2021-07-22 13:23:54 +05:30
self . clean_serial_nos ( )
2020-04-03 15:46:48 +05:30
self . validate_customer_provided_item ( )
2021-02-11 20:19:30 +05:30
self . set_rate_of_stock_uom ( )
2021-01-28 13:09:56 +05:30
self . validate_internal_transfer ( )
2020-12-07 21:35:49 +05:30
self . validate_putaway_capacity ( )
2017-01-16 17:23:20 +05:30
2020-12-21 14:45:50 +05:30
def make_gl_entries ( self , gl_entries = None , from_repost = False ) :
2014-03-28 13:55:00 +05:30
if self . docstatus == 2 :
2020-04-30 10:38:58 +05:30
make_reverse_gl_entries ( voucher_type = self . doctype , voucher_no = self . name )
2014-04-07 18:51:58 +05:30
2017-06-19 12:54:59 +05:30
if cint ( erpnext . is_perpetual_inventory_enabled ( self . company ) ) :
2019-03-08 11:13:35 +05:30
warehouse_account = get_warehouse_account_map ( self . company )
2014-04-07 18:51:58 +05:30
2014-03-28 13:55:00 +05:30
if self . docstatus == 1 :
2016-12-30 16:21:35 +05:30
if not gl_entries :
gl_entries = self . get_gl_entries ( warehouse_account )
2020-12-21 14:45:50 +05:30
make_gl_entries ( gl_entries , from_repost = from_repost )
2013-10-22 23:51:41 +05:30
2018-06-12 13:54:40 +05:30
elif self . doctype in [ ' Purchase Receipt ' , ' Purchase Invoice ' ] and self . docstatus == 1 :
2018-05-16 18:16:08 +05:30
gl_entries = [ ]
gl_entries = self . get_asset_gl_entry ( gl_entries )
2020-12-21 14:45:50 +05:30
make_gl_entries ( gl_entries , from_repost = from_repost )
2014-04-07 18:51:58 +05:30
2019-12-30 13:26:47 +05:30
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 :
2021-07-23 16:40:45 +05:30
serial_nos = frappe . get_all ( " Serial No " ,
fields = [ " batch_no " , " name " , " warehouse " ] ,
filters = {
" name " : ( " in " , get_serial_nos ( d . serial_no ) )
}
)
for row in serial_nos :
if row . warehouse and row . batch_no != d . batch_no :
2019-12-30 13:26:47 +05:30
frappe . throw ( _ ( " Row # {0} : Serial No {1} does not belong to Batch {2} " )
2021-07-23 16:40:45 +05:30
. format ( d . idx , row . name , d . batch_no ) )
2019-12-30 13:26:47 +05:30
2020-10-20 11:59:06 +05:30
if flt ( d . qty ) > 0.0 and d . get ( " batch_no " ) and self . get ( " posting_date " ) and self . docstatus < 2 :
2020-04-28 13:01:43 +05:30
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 " ) ) ) )
2021-07-22 13:23:54 +05:30
def clean_serial_nos ( self ) :
for row in self . get ( " items " ) :
if hasattr ( row , " serial_no " ) and row . serial_no :
# replace commas by linefeed and remove all spaces in string
row . serial_no = row . serial_no . replace ( " , " , " \n " ) . replace ( " " , " " )
2013-11-14 18:40:08 +05:30
def get_gl_entries ( self , warehouse_account = None , default_expense_account = None ,
2013-09-17 15:15:16 +05:30
default_cost_center = None ) :
2014-10-06 11:53:52 +05:30
2013-09-17 15:15:16 +05:30
if not warehouse_account :
2019-03-08 11:13:35 +05:30
warehouse_account = get_warehouse_account_map ( self . company )
2014-04-07 18:51:58 +05:30
2014-04-17 11:37:46 +05:30
sle_map = self . get_stock_ledger_details ( )
voucher_details = self . get_voucher_details ( default_expense_account , default_cost_center , sle_map )
2014-04-07 18:51:58 +05:30
2013-08-28 18:53:11 +05:30
gl_list = [ ]
2013-09-17 10:21:20 +05:30
warehouse_with_no_account = [ ]
2021-02-22 22:27:22 +05:30
precision = self . get_debit_field_precision ( )
2016-12-15 13:46:03 +05:30
for item_row in voucher_details :
2021-01-28 13:09:56 +05:30
2016-12-15 13:46:03 +05:30
sle_list = sle_map . get ( item_row . name )
2013-08-28 18:53:11 +05:30
if sle_list :
for sle in sle_list :
if warehouse_account . get ( sle . warehouse ) :
2020-12-11 21:30:39 +05:30
# from warehouse account
2017-01-16 17:23:20 +05:30
2016-12-15 13:46:03 +05:30
self . check_expense_account ( item_row )
2017-01-16 17:23:20 +05:30
2017-04-14 18:24:04 +08:00
# If the item does not have the allow zero valuation rate flag set
2017-02-06 17:13:39 +05:30
# and ( valuation rate not mentioned in an incoming entry
2017-03-31 12:44:29 +05:30
# or incoming entry not found while delivering the item),
2017-02-06 17:13:39 +05:30
# try to pick valuation rate from previous sle or Item master and update in SLE
# Otherwise, throw an exception
2017-02-07 01:23:26 +05:30
if not sle . stock_value_difference and self . doctype != " Stock Reconciliation " \
2017-04-14 18:24:04 +08:00
and not item_row . get ( " allow_zero_valuation_rate " ) :
2017-02-07 01:23:26 +05:30
2017-02-06 17:13:39 +05:30
sle = self . update_stock_ledger_entries ( sle )
2015-10-15 12:28:20 +05:30
2020-12-11 21:30:39 +05:30
# 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 18:53:11 +05:30
gl_list . append ( self . get_gl_dict ( {
2017-06-15 11:09:27 +05:30
" account " : warehouse_account [ sle . warehouse ] [ " account " ] ,
2020-12-11 21:30:39 +05:30
" against " : expense_account ,
2016-12-15 13:46:03 +05:30
" cost_center " : item_row . cost_center ,
2020-06-19 12:12:08 +05:30
" project " : item_row . project or self . get ( ' project ' ) ,
2014-04-07 12:02:57 +05:30
" remarks " : self . get ( " remarks " ) or " Accounting Entry for Stock " ,
2020-01-15 16:35:31 +05:30
" debit " : flt ( sle . stock_value_difference , precision ) ,
2019-05-22 18:03:09 +05:30
" is_opening " : item_row . get ( " is_opening " ) or self . get ( " is_opening " ) or " No " ,
2019-05-19 00:02:01 +05:30
} , warehouse_account [ sle . warehouse ] [ " account_currency " ] , item = item_row ) )
2013-08-28 18:53:11 +05:30
gl_list . append ( self . get_gl_dict ( {
2020-12-11 21:30:39 +05:30
" account " : expense_account ,
2017-06-15 11:09:27 +05:30
" against " : warehouse_account [ sle . warehouse ] [ " account " ] ,
2016-12-15 13:46:03 +05:30
" cost_center " : item_row . cost_center ,
2014-04-07 12:02:57 +05:30
" remarks " : self . get ( " remarks " ) or " Accounting Entry for Stock " ,
2020-01-15 16:35:31 +05:30
" credit " : flt ( sle . stock_value_difference , precision ) ,
2019-05-16 17:28:39 +05:30
" project " : item_row . get ( " project " ) or self . get ( " project " ) ,
2019-05-22 18:03:09 +05:30
" is_opening " : item_row . get ( " is_opening " ) or self . get ( " is_opening " ) or " No "
2019-05-19 00:02:01 +05:30
} , item = item_row ) )
2013-09-17 10:21:20 +05:30
elif sle . warehouse not in warehouse_with_no_account :
warehouse_with_no_account . append ( sle . warehouse )
2014-04-07 18:51:58 +05:30
if warehouse_with_no_account :
2016-10-24 18:17:57 +05:30
for wh in warehouse_with_no_account :
if frappe . db . get_value ( " Warehouse " , wh , " company " ) :
2020-12-21 14:45:50 +05:30
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 12:28:20 +05:30
2021-02-22 22:27:22 +05:30
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 17:23:20 +05:30
2017-01-25 18:47:53 +05:30
def update_stock_ledger_entries ( self , sle ) :
2017-03-31 12:44:29 +05:30
sle . valuation_rate = get_valuation_rate ( sle . item_code , sle . warehouse ,
2017-06-16 15:21:36 +05:30
self . doctype , self . name , currency = self . company_currency , company = self . company )
2017-02-06 17:13:39 +05:30
2017-01-25 19:20:35 +05:30
sle . stock_value = flt ( sle . qty_after_transaction ) * flt ( sle . valuation_rate )
2017-02-06 17:13:39 +05:30
sle . stock_value_difference = flt ( sle . actual_qty ) * flt ( sle . valuation_rate )
2017-03-31 12:44:29 +05:30
2017-01-28 13:18:49 +05:30
if sle . name :
2017-02-06 17:13:39 +05:30
frappe . db . sql ( """
2017-03-31 12:44:29 +05:30
update
` tabStock Ledger Entry `
set
2017-02-06 17:13:39 +05:30
stock_value = % ( stock_value ) s ,
2017-03-31 12:44:29 +05:30
valuation_rate = % ( valuation_rate ) s ,
stock_value_difference = % ( stock_value_difference ) s
where
2017-02-06 17:13:39 +05:30
name = % ( name ) s """ , (sle))
2017-03-31 12:44:29 +05:30
2017-02-06 17:13:39 +05:30
return sle
2017-03-31 12:44:29 +05:30
2014-04-17 11:37:46 +05:30
def get_voucher_details ( self , default_expense_account , default_cost_center , sle_map ) :
if self . doctype == " Stock Reconciliation " :
2019-05-16 17:28:39 +05:30
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 10:34:31 +05:30
for voucher_detail_no in sle_map :
2019-05-16 17:28:39 +05:30
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 11:37:46 +05:30
else :
2014-12-26 13:15:21 +05:30
details = self . get ( " items " )
2014-04-17 11:37:46 +05:30
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 18:51:58 +05:30
2014-09-26 14:22:18 +05:30
def get_items_and_warehouses ( self ) :
2014-03-27 17:18:29 +05:30
items , warehouses = [ ] , [ ]
2014-04-07 18:51:58 +05:30
2014-12-26 13:15:21 +05:30
if hasattr ( self , " items " ) :
item_doclist = self . get ( " items " )
2014-03-28 13:55:00 +05:30
elif self . doctype == " Stock Reconciliation " :
2014-03-27 17:18:29 +05:30
item_doclist = [ ]
2014-03-28 13:55:00 +05:30
data = json . loads ( self . reconciliation_json )
2014-03-27 17:18:29 +05:30
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 18:51:58 +05:30
2014-03-27 17:18:29 +05:30
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 18:51:58 +05:30
2014-04-07 12:02:57 +05:30
if d . get ( " warehouse " ) and d . warehouse not in warehouses :
2014-03-27 17:18:29 +05:30
warehouses . append ( d . warehouse )
2014-04-07 18:51:58 +05:30
2014-04-07 12:02:57 +05:30
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 17:18:29 +05:30
2014-09-26 14:22:18 +05:30
return items , warehouses
2014-04-07 18:51:58 +05:30
2013-08-28 18:53:11 +05:30
def get_stock_ledger_details ( self ) :
stock_ledger = { }
2017-02-06 17:13:39 +05:30
stock_ledger_entries = frappe . db . sql ( """
2017-03-31 12:44:29 +05:30
select
2017-02-07 01:23:26 +05:30
name , warehouse , stock_value_difference , valuation_rate ,
2017-03-31 12:44:29 +05:30
voucher_detail_no , item_code , posting_date , posting_time ,
2017-02-07 01:23:26 +05:30
actual_qty , qty_after_transaction
2017-02-06 17:13:39 +05:30
from
` tabStock Ledger Entry `
where
voucher_type = % s and voucher_no = % s
""" , (self.doctype, self.name), as_dict=True)
2017-02-07 01:23:26 +05:30
2017-02-06 17:13:39 +05:30
for sle in stock_ledger_entries :
2021-01-28 13:09:56 +05:30
stock_ledger . setdefault ( sle . voucher_detail_no , [ ] ) . append ( sle )
2013-08-28 18:53:11 +05:30
return stock_ledger
2014-04-07 18:51:58 +05:30
2017-04-21 12:40:19 +05:30
def make_batches ( self , warehouse_field ) :
2017-04-20 15:21:01 +05:30
''' Create batches if required. Called before submit '''
for d in self . items :
2017-04-21 12:40:19 +05:30
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 15:21:01 +05:30
2013-08-26 16:53:30 +05:30
def check_expense_account ( self , item ) :
2014-04-16 19:20:11 +05:30
if not item . get ( " expense_account " ) :
2020-11-18 17:57:35 +05:30
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 18:51:58 +05:30
2014-06-19 19:25:19 +05:30
else :
2021-02-22 22:27:22 +05:30
is_expense_account = frappe . get_cached_value ( " Account " ,
2014-06-25 19:12:24 +05:30
item . get ( " expense_account " ) , " report_type " ) == " Profit and Loss "
2016-02-08 23:44:55 +07:00
if self . doctype not in ( " Purchase Receipt " , " Purchase Invoice " , " Stock Reconciliation " , " Stock Entry " ) and not is_expense_account :
2014-06-25 19:12:24 +05:30
frappe . throw ( _ ( " Expense / Difference account ( {0} ) must be a ' Profit or Loss ' account " )
. format ( item . get ( " expense_account " ) ) )
2014-06-19 19:25:19 +05:30
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 18:51:58 +05:30
2020-01-23 17:36:52 +05:30
def delete_auto_created_batches ( self ) :
for d in self . items :
if not d . batch_no : continue
2020-11-18 17:57:35 +05:30
serial_nos = [ sr . name for sr in frappe . get_all ( " Serial No " ,
{ ' batch_no ' : d . batch_no , ' status ' : ' Inactive ' } ) ]
2020-03-02 15:02:58 +05:30
if serial_nos :
frappe . db . set_value ( " Serial No " , { ' name ' : [ ' in ' , serial_nos ] } , " batch_no " , None )
2020-01-23 17:36:52 +05:30
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 12:42:42 +05:30
2014-04-07 18:51:58 +05:30
def get_sl_entries ( self , d , args ) :
2014-11-03 15:08:21 +05:30
sl_dict = frappe . _dict ( {
2014-05-02 17:36:13 +05:30
" item_code " : d . get ( " item_code " , None ) ,
2014-04-07 12:02:57 +05:30
" warehouse " : d . get ( " warehouse " , None ) ,
2014-03-28 13:55:00 +05:30
" posting_date " : self . posting_date ,
" posting_time " : self . posting_time ,
2016-03-04 14:33:49 +05:30
' fiscal_year ' : get_fiscal_year ( self . posting_date , company = self . company ) [ 0 ] ,
2014-03-28 13:55:00 +05:30
" voucher_type " : self . doctype ,
" voucher_no " : self . name ,
2013-08-02 11:42:11 +05:30
" voucher_detail_no " : d . name ,
2014-04-07 12:02:57 +05:30
" actual_qty " : ( self . docstatus == 1 and 1 or - 1 ) * flt ( d . get ( " stock_qty " ) ) ,
2015-09-21 09:18:43 +05:30
" stock_uom " : frappe . db . get_value ( " Item " , args . get ( " item_code " ) or d . get ( " item_code " ) , " stock_uom " ) ,
2013-08-02 11:42:11 +05:30
" incoming_rate " : 0 ,
2014-03-28 13:55:00 +05:30
" company " : self . company ,
2014-05-02 17:36:13 +05:30
" batch_no " : cstr ( d . get ( " batch_no " ) ) . strip ( ) ,
" serial_no " : d . get ( " serial_no " ) ,
2018-03-21 17:52:41 +05:30
" project " : d . get ( " project " ) or self . get ( ' project ' ) ,
2020-04-30 10:38:58 +05:30
" is_cancelled " : 1 if self . docstatus == 2 else 0
2014-11-03 15:08:21 +05:30
} )
2014-04-07 18:51:58 +05:30
2013-08-02 11:42:11 +05:30
sl_dict . update ( args )
return sl_dict
2014-04-07 18:51:58 +05:30
2020-04-30 10:38:58 +05:30
def make_sl_entries ( self , sl_entries , allow_negative_stock = False ,
2015-03-27 15:38:31 +05:30
via_landed_cost_voucher = False ) :
2013-12-12 19:12:19 +05:30
from erpnext . stock . stock_ledger import make_sl_entries
2020-04-30 10:38:58 +05:30
make_sl_entries ( sl_entries , allow_negative_stock , via_landed_cost_voucher )
2014-04-07 18:51:58 +05:30
2020-04-30 10:38:58 +05:30
def make_gl_entries_on_cancel ( self ) :
2014-04-07 18:51:58 +05:30
if frappe . db . sql ( """ select name from `tabGL Entry` where voucher_type= %s
2014-03-28 13:55:00 +05:30
and voucher_no = % s """ , (self.doctype, self.name)):
2020-04-30 10:38:58 +05:30
self . make_gl_entries ( )
2014-04-07 18:51:58 +05:30
2014-06-25 13:31:02 +05:30
def get_serialized_items ( self ) :
serialized_items = [ ]
2021-06-11 16:00:48 +05:30
item_codes = list ( set ( d . item_code for d in self . get ( " items " ) ) )
2014-06-25 13:31:02 +05:30
if item_codes :
serialized_items = frappe . db . sql_list ( """ select name from `tabItem`
2015-07-24 15:16:25 +05:30
where has_serial_no = 1 and name in ( { } ) """ .format( " , " .join([ " %s " ]*len(item_codes))),
2014-06-25 13:31:02 +05:30
tuple ( item_codes ) )
return serialized_items
2015-08-03 16:13:33 +05:30
2015-11-18 17:03:33 +05:30
def validate_warehouse ( self ) :
2021-04-14 14:12:03 +05:30
from erpnext . stock . utils import validate_disabled_warehouse , validate_warehouse_company
2015-11-18 17:03:33 +05:30
2021-06-11 16:00:48 +05:30
warehouses = list ( set ( d . warehouse for d in
self . get ( " items " ) if getattr ( d , " warehouse " , None ) ) )
2015-11-18 17:03:33 +05:30
2020-02-18 12:28:41 +05:30
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 17:03:33 +05:30
for w in warehouses :
2021-02-11 11:46:48 +05:30
validate_disabled_warehouse ( w )
2015-11-18 17:03:33 +05:30
validate_warehouse_company ( w , self . company )
2017-01-16 17:23:20 +05:30
2016-01-06 16:32:06 +05:30
def update_billing_percentage ( self , update_modified = True ) :
2020-11-02 15:07:48 +05:30
target_ref_field = " amount "
if self . doctype == " Delivery Note " :
target_ref_field = " amount - (returned_qty * rate) "
2015-12-30 19:08:11 +05:30
self . _update_percent_field ( {
" target_dt " : self . doctype + " Item " ,
" target_parent_dt " : self . doctype ,
" target_parent_field " : " per_billed " ,
2020-11-02 15:07:48 +05:30
" target_ref_field " : target_ref_field ,
2015-12-30 19:08:11 +05:30
" target_field " : " billed_amt " ,
" name " : self . name ,
2016-01-06 16:32:06 +05:30
} , update_modified )
2014-06-25 13:31:02 +05:30
2016-11-16 17:21:59 +05:30
def validate_inspection ( self ) :
2021-06-21 16:18:35 +05:30
""" 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 11:03:48 +05:30
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 17:21:59 +05:30
return
2017-01-16 17:23:20 +05:30
2021-06-21 16:18:35 +05:30
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 16:51:12 +05:30
msg = f " Row # { row . idx } : Quality Inspection is required for Item { frappe . bold ( row . item_code ) } "
2021-06-21 16:18:35 +05:30
if self . docstatus == 1 :
2021-06-21 16:51:12 +05:30
frappe . throw ( _ ( msg ) , title = _ ( " Inspection Required " ) , exc = QualityInspectionRequiredError )
2021-06-21 16:18:35 +05:30
else :
2021-06-21 16:51:12 +05:30
frappe . msgprint ( _ ( msg ) , title = _ ( " Inspection Required " ) , indicator = " blue " )
2021-06-21 16:18:35 +05:30
def validate_qi_submission ( self , row ) :
""" Check if QI is submitted on row level, during submission """
2021-07-10 18:24:24 +05:30
action = frappe . db . get_single_value ( " Stock Settings " , " action_if_quality_inspection_is_not_submitted " )
2021-06-21 16:18:35 +05:30
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 16:51:12 +05:30
msg = f " Row # { row . idx } : Quality Inspection { link } is not submitted for the item: { row . item_code } "
2021-06-21 16:18:35 +05:30
if action == " Stop " :
2021-06-21 16:51:12 +05:30
frappe . throw ( _ ( msg ) , title = _ ( " Inspection Submission " ) , exc = QualityInspectionNotSubmittedError )
2021-06-21 16:18:35 +05:30
else :
2021-06-22 11:20:17 +05:30
frappe . msgprint ( _ ( msg ) , alert = True , indicator = " orange " )
2021-06-21 16:18:35 +05:30
def validate_qi_rejection ( self , row ) :
""" Check if QI is rejected on row level, during submission """
2021-07-10 18:24:24 +05:30
action = frappe . db . get_single_value ( " Stock Settings " , " action_if_quality_inspection_is_rejected " )
2021-06-21 16:18:35 +05:30
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 16:51:12 +05:30
msg = f " Row # { row . idx } : Quality Inspection { link } was rejected for item { row . item_code } "
2021-06-21 16:18:35 +05:30
if action == " Stop " :
2021-06-21 16:51:12 +05:30
frappe . throw ( _ ( msg ) , title = _ ( " Inspection Rejected " ) , exc = QualityInspectionRejectedError )
2021-06-21 16:18:35 +05:30
else :
2021-06-21 16:51:12 +05:30
frappe . msgprint ( _ ( msg ) , alert = True , indicator = " orange " )
2016-11-10 19:15:11 +05:30
2018-06-14 15:54:34 +05:30
def update_blanket_order ( self ) :
2018-06-14 17:09:55 +05:30
blanket_orders = list ( set ( [ d . blanket_order for d in self . items if d . blanket_order ] ) )
2018-06-14 15:54:34 +05:30
for blanket_order in blanket_orders :
frappe . get_doc ( " Blanket Order " , blanket_order ) . update_ordered_qty ( )
2018-05-28 20:07:08 +05:30
2020-04-03 15:46:48 +05:30
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 18:51:58 +05:30
2021-02-11 20:19:30 +05:30
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 14:11:50 +05:30
d . stock_uom_rate = d . rate / ( d . conversion_factor or 1 )
2021-02-11 20:19:30 +05:30
2021-01-28 13:09:56 +05:30
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 21:35:49 +05:30
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 23:47:24 +05:30
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 21:35:49 +05:30
2021-01-18 23:47:24 +05:30
if self . doctype == " Purchase Invoice " and self . get ( " update_stock " ) == 0 :
valid_doctype = False
if valid_doctype :
2020-12-07 21:35:49 +05:30
rule_map = defaultdict ( dict )
for item in self . get ( " items " ) :
2021-01-18 23:47:24 +05:30
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 21:35:49 +05:30
for rule , values in rule_map . items ( ) :
if flt ( values [ " qty_put " ] ) > flt ( values [ " capacity " ] ) :
2021-01-18 23:47:24 +05:30
message = self . prepare_over_receipt_message ( rule , values )
2020-12-07 21:35:49 +05:30
frappe . throw ( msg = message , title = _ ( " Over Receipt " ) )
2021-01-18 23:47:24 +05:30
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 16:50:02 +05:30
message + = _ ( " Please adjust the qty or edit {0} to proceed. " ) . format ( rule_link )
2021-01-18 23:47:24 +05:30
return message
2020-12-07 21:35:49 +05:30
2020-12-21 14:45:50 +05:30
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 16:10:20 +05:30
if future_sle_exists ( args ) :
2020-12-21 14:45:50 +05:30
create_repost_item_valuation_entry ( args )
2021-02-22 22:27:22 +05:30
2021-04-14 14:12:03 +05:30
@frappe.whitelist ( )
def make_quality_inspections ( doctype , docname , items ) :
2021-06-02 14:55:31 +05:30
if isinstance ( items , str ) :
items = json . loads ( items )
2021-04-14 14:12:03 +05:30
2021-06-02 14:55:31 +05:30
inspections = [ ]
2021-04-14 14:12:03 +05:30
for item in items :
2021-05-26 14:42:15 +05:30
if flt ( item . get ( " sample_size " ) ) > flt ( item . get ( " qty " ) ) :
2021-04-14 14:12:03 +05:30
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 14:42:15 +05:30
" sample_size " : flt ( item . get ( " sample_size " ) ) ,
2021-04-14 14:12:03 +05:30
" 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 14:42:15 +05:30
inspections . append ( quality_inspection . name )
2021-04-14 14:12:03 +05:30
2021-05-26 14:42:15 +05:30
return inspections
2021-04-14 14:12:03 +05:30
2020-12-25 18:12:35 +05:30
def is_reposting_pending ( ) :
return frappe . db . exists ( " Repost Item Valuation " ,
{ ' docstatus ' : 1 , ' status ' : [ ' in ' , [ ' Queued ' , ' In Progress ' ] ] } )
2021-06-15 10:21:44 +05:30
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 14:45:50 +05:30
2021-06-15 10:21:44 +05:30
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 14:45:50 +05:30
filters = { " voucher_type " : args . voucher_type , " voucher_no " : args . voucher_no } ,
fields = [ " item_code " , " warehouse " ] ,
order_by = " creation asc " )
2021-06-15 10:21:44 +05:30
def get_conditions_to_validate_future_sle ( sl_entries ) :
2021-03-27 16:10:20 +05:30
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 12:44:04 +05:30
f """ warehouse = { frappe . db . escape ( warehouse ) }
and item_code in ( { ' , ' . join ( frappe . db . escape ( item ) for item in items ) } ) """ )
2020-12-21 14:45:50 +05:30
2021-06-15 10:21:44 +05:30
return or_conditions
2020-12-21 14:45:50 +05:30
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 16:10:20 +05:30
repost_entry . submit ( )