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-06-03 16:45:38 +05:30
2021-09-02 16:44:59 +05:30
2014-02-14 15:47:51 +05:30
import frappe
2016-07-07 14:02:26 +05:30
from frappe import _
2014-04-21 15:06:56 +05:30
from frappe . model . document import Document
2021-08-17 13:17:09 +05:30
from frappe . utils import comma_or , flt , getdate , now , nowdate
2021-09-02 16:44:59 +05:30
2013-06-03 16:45:38 +05:30
2019-07-15 18:02:58 +05:30
class OverAllowanceError ( frappe . ValidationError ) :
pass
2022-03-28 18:52:46 +05:30
2019-07-15 18:02:58 +05:30
2015-05-18 11:18:55 +05:30
def validate_status ( status , options ) :
if status not in options :
frappe . throw ( _ ( " Status must be one of {0} " ) . format ( comma_or ( options ) ) )
2022-03-28 18:52:46 +05:30
2013-10-03 17:26:33 +05:30
status_map = {
" Lead " : [
2016-12-15 11:51:54 +05:30
[ " Lost Quotation " , " has_lost_quotation " ] ,
2013-10-03 17:26:33 +05:30
[ " Opportunity " , " has_opportunity " ] ,
2016-06-09 14:19:51 -04:00
[ " Quotation " , " has_quotation " ] ,
[ " Converted " , " has_customer " ] ,
2013-10-03 17:26:33 +05:30
] ,
" Opportunity " : [
2016-03-29 11:13:07 +05:30
[ " Lost " , " eval:self.status== ' Lost ' " ] ,
2016-06-09 14:19:51 -04:00
[ " Lost " , " has_lost_quotation " ] ,
2017-05-30 15:34:20 +05:30
[ " Quotation " , " has_active_quotation " ] ,
[ " Converted " , " has_ordered_quotation " ] ,
2016-03-29 11:13:07 +05:30
[ " Closed " , " eval:self.status== ' Closed ' " ] ,
2013-10-03 17:26:33 +05:30
] ,
" Quotation " : [
2015-04-13 16:56:03 +05:30
[ " Draft " , None ] ,
2019-06-26 11:05:51 +05:30
[ " Open " , " eval:self.docstatus==1 " ] ,
2014-03-28 13:55:00 +05:30
[ " Lost " , " eval:self.status== ' Lost ' " ] ,
2022-06-11 21:55:59 +05:30
[ " Partially Ordered " , " is_partially_ordered " ] ,
[ " Ordered " , " is_fully_ordered " ] ,
2014-03-28 13:55:00 +05:30
[ " Cancelled " , " eval:self.docstatus==2 " ] ,
2013-10-03 17:26:33 +05:30
] ,
" Sales Order " : [
[ " Draft " , None ] ,
2015-10-02 12:42:48 +05:30
[
" To Deliver and Bill " ,
" eval:self.per_delivered < 100 and self.per_billed < 100 and self.docstatus == 1 " ,
] ,
2019-10-12 19:33:11 +05:30
[
" To Bill " ,
" eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed < 100 and self.docstatus == 1 " ,
] ,
[
" To Deliver " ,
" eval:self.per_delivered < 100 and self.per_billed == 100 and self.docstatus == 1 and not self.skip_delivery_note " ,
] ,
[
" Completed " ,
" eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed == 100 and self.docstatus == 1 " ,
] ,
2015-10-02 12:42:48 +05:30
[ " Cancelled " , " eval:self.docstatus==2 " ] ,
2015-10-19 14:17:52 +05:30
[ " Closed " , " eval:self.status== ' Closed ' " ] ,
2019-03-01 16:23:27 +05:30
[ " On Hold " , " eval:self.status== ' On Hold ' " ] ,
2015-10-02 12:42:48 +05:30
] ,
" Purchase Order " : [
[ " Draft " , None ] ,
[
" To Receive and Bill " ,
" eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1 " ,
] ,
2019-09-05 15:11:43 +05:30
[ " To Bill " , " eval:self.per_received >= 100 and self.per_billed < 100 and self.docstatus == 1 " ] ,
2015-10-02 12:42:48 +05:30
[
" To Receive " ,
" eval:self.per_received < 100 and self.per_billed == 100 and self.docstatus == 1 " ,
] ,
2019-09-05 15:11:43 +05:30
[
" Completed " ,
" eval:self.per_received >= 100 and self.per_billed == 100 and self.docstatus == 1 " ,
] ,
2015-10-23 10:40:32 +05:30
[ " Delivered " , " eval:self.status== ' Delivered ' " ] ,
2014-03-28 13:55:00 +05:30
[ " Cancelled " , " eval:self.docstatus==2 " ] ,
2019-03-11 16:40:27 +05:30
[ " On Hold " , " eval:self.status== ' On Hold ' " ] ,
2015-10-19 15:16:17 +05:30
[ " Closed " , " eval:self.status== ' Closed ' " ] ,
2013-10-03 17:26:33 +05:30
] ,
2015-05-18 11:18:55 +05:30
" Delivery Note " : [
[ " Draft " , None ] ,
2015-12-28 13:03:55 +05:30
[ " To Bill " , " eval:self.per_billed < 100 and self.docstatus == 1 " ] ,
2020-07-31 20:01:06 +05:30
[ " Return Issued " , " eval:self.per_returned == 100 and self.docstatus == 1 " ] ,
2015-12-28 13:03:55 +05:30
[ " Completed " , " eval:self.per_billed == 100 and self.docstatus == 1 " ] ,
2015-05-18 11:18:55 +05:30
[ " Cancelled " , " eval:self.docstatus==2 " ] ,
2015-11-02 12:57:08 +05:30
[ " Closed " , " eval:self.status== ' Closed ' " ] ,
2015-05-18 11:18:55 +05:30
] ,
" Purchase Receipt " : [
[ " Draft " , None ] ,
2015-12-30 19:08:11 +05:30
[ " To Bill " , " eval:self.per_billed < 100 and self.docstatus == 1 " ] ,
2020-07-31 15:54:05 +05:30
[ " Return Issued " , " eval:self.per_returned == 100 and self.docstatus == 1 " ] ,
2015-12-30 19:08:11 +05:30
[ " Completed " , " eval:self.per_billed == 100 and self.docstatus == 1 " ] ,
2015-05-18 11:18:55 +05:30
[ " Cancelled " , " eval:self.docstatus==2 " ] ,
2015-11-02 12:57:08 +05:30
[ " Closed " , " eval:self.status== ' Closed ' " ] ,
2017-06-07 07:32:07 +01:00
] ,
" Material Request " : [
[ " Draft " , None ] ,
[ " Stopped " , " eval:self.status == ' Stopped ' " ] ,
[ " Cancelled " , " eval:self.docstatus == 2 " ] ,
[ " Pending " , " eval:self.status != ' Stopped ' and self.per_ordered == 0 and self.docstatus == 1 " ] ,
[
" Ordered " ,
" eval:self.status != ' Stopped ' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == ' Purchase ' " ,
] ,
[
" Transferred " ,
" eval:self.status != ' Stopped ' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == ' Material Transfer ' " ,
] ,
2019-04-22 12:25:14 +05:30
[
" Issued " ,
" eval:self.status != ' Stopped ' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == ' Material Issue ' " ,
] ,
2019-04-29 21:42:48 +05:30
[
" Received " ,
" eval:self.status != ' Stopped ' and self.per_received == 100 and self.docstatus == 1 and self.material_request_type == ' Purchase ' " ,
] ,
2019-07-22 11:35:16 +05:30
[
" Partially Received " ,
" eval:self.status != ' Stopped ' and self.per_received > 0 and self.per_received < 100 and self.docstatus == 1 and self.material_request_type == ' Purchase ' " ,
] ,
2021-05-25 19:54:07 +05:30
[
" Partially Ordered " ,
" eval:self.status != ' Stopped ' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1 " ,
] ,
2019-07-22 11:35:16 +05:30
[
" Manufactured " ,
" eval:self.status != ' Stopped ' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == ' Manufacture ' " ,
2022-03-28 18:52:46 +05:30
] ,
2019-07-03 10:34:31 +05:30
] ,
" Bank Transaction " : [
[ " Unreconciled " , " eval:self.docstatus == 1 and self.unallocated_amount>0 " ] ,
2021-08-25 16:25:51 +05:30
[ " Reconciled " , " eval:self.docstatus == 1 and self.unallocated_amount<=0 " ] ,
[ " Cancelled " , " eval:self.docstatus == 2 " ] ,
2020-07-23 18:51:26 +05:30
] ,
" POS Opening Entry " : [
[ " Draft " , None ] ,
[ " Open " , " eval:self.docstatus == 1 and not self.pos_closing_entry " ] ,
[ " Closed " , " eval:self.docstatus == 1 and self.pos_closing_entry " ] ,
[ " Cancelled " , " eval:self.docstatus == 2 " ] ,
2021-01-28 18:42:43 +05:30
] ,
" POS Closing Entry " : [
[ " Draft " , None ] ,
[ " Submitted " , " eval:self.docstatus == 1 " ] ,
[ " Queued " , " eval:self.status == ' Queued ' " ] ,
2021-05-06 17:02:47 +05:30
[ " Failed " , " eval:self.status == ' Failed ' " ] ,
2021-01-28 18:42:43 +05:30
[ " Cancelled " , " eval:self.docstatus == 2 " ] ,
2021-05-10 14:02:58 +05:30
] ,
" Transaction Deletion Record " : [
[ " Draft " , None ] ,
[ " Completed " , " eval:self.docstatus == 1 " ] ,
2015-05-18 11:18:55 +05:30
] ,
2013-10-03 17:26:33 +05:30
}
2022-03-28 18:52:46 +05:30
2014-04-21 15:06:56 +05:30
class StatusUpdater ( Document ) :
2013-06-03 16:45:38 +05:30
"""
Updates the status of the calling records
Delivery Note : Update Delivered Qty , Update Percent and Validate over delivery
Sales Invoice : Update Billed Amt , Update Percent and Validate over billing
Installation Note : Update Installed Qty , Update Percent Qty and Validate over installation
"""
def update_prevdoc_status ( self ) :
self . update_qty ( )
self . validate_qty ( )
2014-04-09 19:20:01 +05:30
2015-12-08 14:02:53 +05:30
def set_status ( self , update = False , status = None , update_modified = True ) :
2014-08-19 16:20:04 +05:30
if self . is_new ( ) :
2017-01-13 12:51:19 +05:30
if self . get ( " amended_from " ) :
self . status = " Draft "
2013-10-03 17:26:33 +05:30
return
2016-01-06 16:32:06 +05:30
2014-03-28 13:55:00 +05:30
if self . doctype in status_map :
2014-08-21 16:34:38 +05:30
_status = self . status
2015-11-04 15:20:50 +05:30
if status and update :
self . db_set ( " status " , status )
2014-03-28 13:55:00 +05:30
sl = status_map [ self . doctype ] [ : ]
2013-10-03 18:12:36 +05:30
sl . reverse ( )
for s in sl :
2013-10-03 17:26:33 +05:30
if not s [ 1 ] :
2014-03-28 13:55:00 +05:30
self . status = s [ 0 ]
2013-10-03 17:26:33 +05:30
break
2013-10-03 18:12:36 +05:30
elif s [ 1 ] . startswith ( " eval: " ) :
2018-09-06 18:27:06 +05:30
if frappe . safe_eval (
s [ 1 ] [ 5 : ] ,
None ,
{
" self " : self . as_dict ( ) ,
" getdate " : getdate ,
2017-05-16 12:43:00 +05:30
" nowdate " : nowdate ,
" get_value " : frappe . db . get_value ,
} ,
) :
2014-03-28 13:55:00 +05:30
self . status = s [ 0 ]
2013-10-03 17:26:33 +05:30
break
elif getattr ( self , s [ 1 ] ) ( ) :
2014-03-28 13:55:00 +05:30
self . status = s [ 0 ]
2013-10-03 17:26:33 +05:30
break
2014-04-09 19:20:01 +05:30
2017-06-07 07:32:07 +01:00
if self . status != _status and self . status not in (
" Cancelled " ,
" Partially Ordered " ,
" Ordered " ,
" Issued " ,
" Transferred " ,
) :
2015-01-07 15:41:32 +05:30
self . add_comment ( " Label " , _ ( self . status ) )
2016-01-06 16:32:06 +05:30
2013-10-03 17:26:33 +05:30
if update :
2016-07-20 12:08:47 +05:30
self . db_set ( " status " , self . status , update_modified = update_modified )
2014-04-09 19:20:01 +05:30
2013-06-03 16:45:38 +05:30
def validate_qty ( self ) :
2014-12-19 16:20:32 +05:30
""" Validates qty at row level """
2019-07-15 18:02:58 +05:30
self . item_allowance = { }
self . global_qty_allowance = None
self . global_amount_allowance = None
2014-04-09 19:20:01 +05:30
2013-06-03 16:45:38 +05:30
for args in self . status_updater :
2015-08-25 12:49:40 +05:30
if " target_ref_field " not in args :
# if target_ref_field is not specified, the programmer does not want to validate qty / amount
continue
2013-06-03 16:45:38 +05:30
# get unique transactions to update
2014-04-02 18:09:34 +05:30
for d in self . get_all_children ( ) :
2018-03-12 11:20:30 +05:30
if hasattr ( d , " qty " ) and d . qty < 0 and not self . get ( " is_return " ) :
frappe . throw ( _ ( " For an item {0} , quantity must be positive number " ) . format ( d . item_code ) )
2018-06-11 12:02:14 +05:30
if hasattr ( d , " qty " ) and d . qty > 0 and self . get ( " is_return " ) :
frappe . throw ( _ ( " For an item {0} , quantity must be negative number " ) . format ( d . item_code ) )
2014-03-28 13:55:00 +05:30
if d . doctype == args [ " source_dt " ] and d . get ( args [ " join_field " ] ) :
2014-03-31 23:37:40 +05:30
args [ " name " ] = d . get ( args [ " join_field " ] )
2013-06-03 16:45:38 +05:30
# get all qty where qty > target_field
2014-04-09 19:20:01 +05:30
item = frappe . db . sql (
""" select item_code, ` {target_ref_field} `,
` { target_field } ` , parenttype , parent from ` tab { target_dt } `
where ` { target_ref_field } ` < ` { target_field } `
and name = % s and docstatus = 1 """ .format(
* * args
) ,
2014-03-03 15:51:13 +05:30
args [ " name " ] ,
as_dict = 1 ,
)
2013-06-03 16:45:38 +05:30
if item :
item = item [ 0 ]
item [ " idx " ] = d . idx
item [ " target_ref_field " ] = args [ " target_ref_field " ] . replace ( " _ " , " " )
2016-07-07 14:02:26 +05:30
# if not item[args['target_ref_field']]:
# msgprint(_("Note: System will not check over-delivery and over-booking for Item {0} as quantity or amount is 0").format(item.item_code))
2019-07-15 18:02:58 +05:30
if args . get ( " no_allowance " ) :
2014-07-09 17:36:38 +05:30
item [ " reduce_by " ] = item [ args [ " target_field " ] ] - item [ args [ " target_ref_field " ] ]
2013-06-03 16:45:38 +05:30
if item [ " reduce_by " ] > 0.01 :
2020-01-15 19:22:35 +05:30
self . limits_crossed_error ( args , item , " qty " )
2014-04-09 19:20:01 +05:30
2016-08-26 11:39:23 +05:30
elif item [ args [ " target_ref_field " ] ] :
2019-07-15 18:02:58 +05:30
self . check_overflow_with_allowance ( item , args )
2014-04-09 19:20:01 +05:30
2019-07-15 18:02:58 +05:30
def check_overflow_with_allowance ( self , item , args ) :
2013-06-03 16:45:38 +05:30
"""
2019-07-15 18:02:58 +05:30
Checks if there is overflow condering a relaxation allowance
2013-06-03 16:45:38 +05:30
"""
2019-07-15 18:02:58 +05:30
qty_or_amount = " qty " if " qty " in args [ " target_ref_field " ] else " amount "
# check if overflow is within allowance
2022-03-28 18:52:46 +05:30
(
2019-07-15 18:02:58 +05:30
allowance ,
self . item_allowance ,
self . global_qty_allowance ,
self . global_amount_allowance ,
) = get_allowance_for (
item [ " item_code " ] ,
self . item_allowance ,
self . global_qty_allowance ,
self . global_amount_allowance ,
qty_or_amount ,
)
2021-04-19 13:25:15 +05:30
role_allowed_to_over_deliver_receive = frappe . db . get_single_value (
" Stock Settings " , " role_allowed_to_over_deliver_receive "
)
role_allowed_to_over_bill = frappe . db . get_single_value (
" Accounts Settings " , " role_allowed_to_over_bill "
)
role = (
role_allowed_to_over_deliver_receive if qty_or_amount == " qty " else role_allowed_to_over_bill
2022-03-28 18:52:46 +05:30
)
2021-04-19 13:25:15 +05:30
2014-04-09 19:20:01 +05:30
overflow_percent = (
( item [ args [ " target_field " ] ] - item [ args [ " target_ref_field " ] ] ) / item [ args [ " target_ref_field " ] ]
2021-04-19 13:25:15 +05:30
) * 100
2014-07-19 17:00:15 +05:30
2021-10-27 10:30:05 +05:30
if overflow_percent - allowance > 0.01 :
2019-07-15 18:02:58 +05:30
item [ " max_allowed " ] = flt ( item [ args [ " target_ref_field " ] ] * ( 100 + allowance ) / 100 )
2013-06-03 16:45:38 +05:30
item [ " reduce_by " ] = item [ args [ " target_field " ] ] - item [ " max_allowed " ]
2014-04-09 19:20:01 +05:30
2021-10-27 10:30:05 +05:30
if role not in frappe . get_roles ( ) :
self . limits_crossed_error ( args , item , qty_or_amount )
else :
self . warn_about_bypassing_with_role ( item , qty_or_amount , role )
2016-07-07 14:02:26 +05:30
2019-07-15 18:02:58 +05:30
def limits_crossed_error ( self , args , item , qty_or_amount ) :
2016-07-07 14:02:26 +05:30
""" Raise exception for limits crossed """
2022-09-06 16:22:00 +05:30
if (
self . doctype in [ " Sales Invoice " , " Delivery Note " ]
and qty_or_amount == " amount "
and self . is_internal_customer
) :
return
elif (
self . doctype in [ " Purchase Invoice " , " Purchase Receipt " ]
and qty_or_amount == " amount "
and self . is_internal_supplier
) :
return
2019-07-15 18:02:58 +05:30
if qty_or_amount == " qty " :
action_msg = _ (
' To allow over receipt / delivery, update " Over Receipt/Delivery Allowance " in Stock Settings or the Item. '
)
else :
action_msg = _ (
' To allow over billing, update " Over Billing Allowance " in Accounts Settings or the Item. '
)
2022-03-28 18:52:46 +05:30
2016-07-07 14:02:26 +05:30
frappe . throw (
_ (
" This document is over limit by {0} {1} for item {4} . Are you making another {3} against the same {2} ? "
) . format (
frappe . bold ( _ ( item [ " target_ref_field " ] . title ( ) ) ) ,
frappe . bold ( item [ " reduce_by " ] ) ,
frappe . bold ( _ ( args . get ( " target_dt " ) ) ) ,
frappe . bold ( _ ( self . doctype ) ) ,
frappe . bold ( item . get ( " item_code " ) ) ,
2019-07-15 18:02:58 +05:30
)
+ " <br><br> "
+ action_msg ,
OverAllowanceError ,
title = _ ( " Limit Crossed " ) ,
)
2013-06-03 16:45:38 +05:30
2021-10-27 10:30:05 +05:30
def warn_about_bypassing_with_role ( self , item , qty_or_amount , role ) :
2022-12-13 18:59:20 +01:00
if qty_or_amount == " qty " :
msg = _ ( " Over Receipt/Delivery of {0} {1} ignored for item {2} because you have {3} role. " )
else :
msg = _ ( " Overbilling of {0} {1} ignored for item {2} because you have {3} role. " )
frappe . msgprint (
msg . format (
_ ( item [ " target_ref_field " ] . title ( ) ) ,
frappe . bold ( item [ " reduce_by " ] ) ,
frappe . bold ( item . get ( " item_code " ) ) ,
role ,
) ,
indicator = " orange " ,
alert = True ,
2021-10-27 10:30:05 +05:30
)
2016-01-06 16:32:06 +05:30
def update_qty ( self , update_modified = True ) :
2015-08-25 12:49:40 +05:30
""" Updates qty or amount at row level
2016-01-06 16:32:06 +05:30
: param update_modified : If true , updates ` modified ` and ` modified_by ` for target parent doc
2013-06-03 16:45:38 +05:30
"""
for args in self . status_updater :
# condition to include current record (if submit or no if cancel)
2014-03-28 13:55:00 +05:30
if self . docstatus == 1 :
2022-06-17 06:31:27 -05:00
args [ " cond " ] = " or parent= ' %s ' " % self . name . replace ( ' " ' , ' " ' )
2013-06-03 16:45:38 +05:30
else :
2022-06-17 06:31:27 -05:00
args [ " cond " ] = " and parent!= ' %s ' " % self . name . replace ( ' " ' , ' " ' )
2014-04-09 19:20:01 +05:30
2016-01-06 16:32:06 +05:30
self . _update_children ( args , update_modified )
2015-08-25 12:49:40 +05:30
2020-07-31 15:54:05 +05:30
if " percent_join_field " in args or " percent_join_field_parent " in args :
2016-01-06 16:32:06 +05:30
self . _update_percent_field_in_targets ( args , update_modified )
2015-08-25 12:49:40 +05:30
2016-01-06 16:32:06 +05:30
def _update_children ( self , args , update_modified ) :
2015-08-25 12:49:40 +05:30
""" Update quantities or amount in child table """
for d in self . get_all_children ( ) :
if d . doctype != args [ " source_dt " ] :
continue
2016-01-06 16:32:06 +05:30
self . _update_modified ( args , update_modified )
2015-08-25 12:49:40 +05:30
# updates qty in the child table
args [ " detail_id " ] = d . get ( args [ " join_field " ] )
2022-03-28 18:52:46 +05:30
2015-08-25 12:49:40 +05:30
args [ " second_source_condition " ] = " "
if (
args . get ( " second_source_dt " )
and args . get ( " second_source_field " )
and args . get ( " second_join_field " )
) :
if not args . get ( " second_source_extra_cond " ) :
args [ " second_source_extra_cond " ] = " "
2020-11-18 20:17:52 +05:30
args [ " second_source_condition " ] = frappe . db . sql (
""" select ifnull((select sum( %(second_source_field)s )
2015-08-25 12:49:40 +05:30
from ` tab % ( second_source_dt ) s `
2022-06-17 06:31:27 -05:00
where ` % ( second_join_field ) s ` = ' %(detail_id)s '
2020-11-18 20:17:52 +05:30
and ( ` tab % ( second_source_dt ) s ` . docstatus = 1 )
% ( second_source_extra_cond ) s ) , 0 ) """
% args
) [ 0 ] [ 0 ]
2015-08-25 12:49:40 +05:30
if args [ " detail_id " ] :
if not args . get ( " extra_cond " ) :
args [ " extra_cond " ] = " "
2018-09-06 18:27:06 +05:30
2020-11-18 20:17:52 +05:30
args [ " source_dt_value " ] = (
frappe . db . sql (
"""
2016-01-06 16:32:06 +05:30
( select ifnull ( sum ( % ( source_field ) s ) , 0 )
2022-06-17 06:31:27 -05:00
from ` tab % ( source_dt ) s ` where ` % ( join_field ) s ` = ' %(detail_id)s '
2016-01-06 16:32:06 +05:30
and ( docstatus = 1 % ( cond ) s ) % ( extra_cond ) s )
2020-11-18 20:17:52 +05:30
"""
% args
) [ 0 ] [ 0 ]
or 0.0
2022-03-28 18:52:46 +05:30
)
2020-11-18 20:17:52 +05:30
if args [ " second_source_condition " ] :
args [ " source_dt_value " ] + = flt ( args [ " second_source_condition " ] )
frappe . db . sql (
""" update `tab %(target_dt)s `
set % ( target_field ) s = % ( source_dt_value ) s % ( update_modified ) s
2015-08-25 12:49:40 +05:30
where name = ' %(detail_id)s ' """
% args
)
2016-01-06 16:32:06 +05:30
def _update_percent_field_in_targets ( self , args , update_modified = True ) :
2015-08-25 12:49:40 +05:30
""" Update percent field in parent transaction """
2020-07-31 15:54:05 +05:30
if args . get ( " percent_join_field_parent " ) :
# if reference to target doc where % is to be updated, is
# in source doc's parent form, consider percent_join_field_parent
args [ " name " ] = self . get ( args [ " percent_join_field_parent " ] )
self . _update_percent_field ( args , update_modified )
else :
2021-06-11 18:40:22 +05:30
distinct_transactions = set (
d . get ( args [ " percent_join_field " ] ) for d in self . get_all_children ( args [ " source_dt " ] )
2022-03-28 18:52:46 +05:30
)
2015-08-25 12:49:40 +05:30
2020-07-31 15:54:05 +05:30
for name in distinct_transactions :
if name :
args [ " name " ] = name
self . _update_percent_field ( args , update_modified )
2015-08-25 12:49:40 +05:30
2016-01-06 16:32:06 +05:30
def _update_percent_field ( self , args , update_modified = True ) :
2015-12-30 19:08:11 +05:30
""" Update percent field in parent transaction """
2016-01-06 16:32:06 +05:30
self . _update_modified ( args , update_modified )
2018-09-06 18:27:06 +05:30
2015-12-30 19:08:11 +05:30
if args . get ( " target_parent_field " ) :
frappe . db . sql (
""" update `tab %(target_parent_dt)s `
set % ( target_parent_field ) s = round (
ifnull ( ( select
2022-06-17 10:47:48 -05:00
ifnull ( sum ( case when abs ( % ( target_ref_field ) s ) > abs ( % ( target_field ) s ) then abs ( % ( target_field ) s ) else abs ( % ( target_ref_field ) s ) end ) , 0 )
2016-09-12 14:54:46 +08:00
/ sum ( abs ( % ( target_ref_field ) s ) ) * 100
2022-06-17 06:31:27 -05:00
from ` tab % ( target_dt ) s ` where parent = ' %(name)s ' having sum ( abs ( % ( target_ref_field ) s ) ) > 0 ) , 0 ) , 6 )
2016-01-06 16:32:06 +05:30
% ( update_modified ) s
2015-12-30 19:08:11 +05:30
where name = ' %(name)s ' """
% args
)
2016-11-16 11:14:33 +05:30
# update field
if args . get ( " status_field " ) :
frappe . db . sql (
""" update `tab %(target_parent_dt)s `
2022-06-17 10:47:48 -05:00
set % ( status_field ) s = ( case when % ( target_parent_field ) s < 0.001 then ' Not %(keyword)s '
else case when % ( target_parent_field ) s > = 99.999999 then ' Fully %(keyword)s '
else ' Partly %(keyword)s ' end end )
2016-11-16 11:14:33 +05:30
where name = ' %(name)s ' """
% args
)
if update_modified :
target = frappe . get_doc ( args [ " target_parent_dt " ] , args [ " name " ] )
target . set_status ( update = True )
target . notify_update ( )
2014-04-09 19:20:01 +05:30
2016-01-06 16:32:06 +05:30
def _update_modified ( self , args , update_modified ) :
2021-08-17 13:17:09 +05:30
if not update_modified :
args [ " update_modified " ] = " "
return
args [ " update_modified " ] = " , modified = {0} , modified_by = {1} " . format (
frappe . db . escape ( now ( ) ) , frappe . db . escape ( frappe . session . user )
)
2016-01-06 16:32:06 +05:30
2014-01-15 17:36:18 +05:30
def update_billing_status_for_zero_amount_refdoc ( self , ref_dt ) :
2019-05-29 14:19:35 +05:30
ref_fieldname = frappe . scrub ( ref_dt )
ref_docs = [
item . get ( ref_fieldname ) for item in ( self . get ( " items " ) or [ ] ) if item . get ( ref_fieldname )
]
if not ref_docs :
return
zero_amount_refdocs = frappe . db . sql_list (
"""
SELECT
name
from
` tab { ref_dt } `
where
docstatus = 1
and base_net_total = 0
and name in % ( ref_docs ) s
""" .format(
ref_dt = ref_dt
) ,
{ " ref_docs " : ref_docs } ,
)
if zero_amount_refdocs :
self . update_billing_status ( zero_amount_refdocs , ref_dt , ref_fieldname )
2014-04-09 19:20:01 +05:30
2015-11-04 15:20:50 +05:30
def update_billing_status ( self , zero_amount_refdoc , ref_dt , ref_fieldname ) :
2014-01-15 17:36:18 +05:30
for ref_dn in zero_amount_refdoc :
2015-11-25 15:51:01 +05:30
ref_doc_qty = flt (
frappe . db . sql (
""" select ifnull(sum(qty), 0) from `tab %s Item`
2014-01-15 17:36:18 +05:30
where parent = % s """
% ( ref_dt , " %s " ) ,
( ref_dn ) ,
) [ 0 ] [ 0 ]
)
2022-03-28 18:52:46 +05:30
2015-11-25 15:51:01 +05:30
billed_qty = flt (
frappe . db . sql (
""" select ifnull(sum(qty), 0)
2014-04-09 19:20:01 +05:30
from ` tab % s Item ` where % s = % s and docstatus = 1 """
2014-03-28 13:55:00 +05:30
% ( self . doctype , ref_fieldname , " %s " ) ,
( ref_dn ) ,
) [ 0 ] [ 0 ]
)
2014-04-09 19:20:01 +05:30
2018-09-06 18:27:06 +05:30
per_billed = ( min ( ref_doc_qty , billed_qty ) / ref_doc_qty ) * 100
2016-07-20 12:08:47 +05:30
ref_doc = frappe . get_doc ( ref_dt , ref_dn )
ref_doc . db_set ( " per_billed " , per_billed )
2022-02-06 11:35:23 +05:30
# set billling status
if hasattr ( ref_doc , " billing_status " ) :
if ref_doc . per_billed < 0.001 :
ref_doc . db_set ( " billing_status " , " Not Billed " )
elif ref_doc . per_billed > 99.999999 :
ref_doc . db_set ( " billing_status " , " Fully Billed " )
else :
ref_doc . db_set ( " billing_status " , " Partly Billed " )
2016-10-05 23:02:15 +05:30
ref_doc . set_status ( update = True )
2014-04-09 19:20:01 +05:30
2022-03-28 18:52:46 +05:30
2021-04-19 10:33:39 +05:30
def get_allowance_for (
item_code ,
item_allowance = None ,
global_qty_allowance = None ,
global_amount_allowance = None ,
qty_or_amount = " qty " ,
) :
2014-01-03 17:43:19 +05:30
"""
2019-07-15 18:02:58 +05:30
Returns the allowance for the item , if not set , returns global allowance
2014-01-03 17:43:19 +05:30
"""
2021-04-19 10:33:39 +05:30
if item_allowance is None :
item_allowance = { }
2019-07-15 18:02:58 +05:30
if qty_or_amount == " qty " :
if item_allowance . get ( item_code , frappe . _dict ( ) ) . get ( " qty " ) :
return (
item_allowance [ item_code ] . qty ,
item_allowance ,
global_qty_allowance ,
global_amount_allowance ,
2022-03-28 18:52:46 +05:30
)
2019-07-15 18:02:58 +05:30
else :
if item_allowance . get ( item_code , frappe . _dict ( ) ) . get ( " amount " ) :
return (
item_allowance [ item_code ] . amount ,
item_allowance ,
global_qty_allowance ,
global_amount_allowance ,
2022-03-28 18:52:46 +05:30
)
2019-07-15 18:02:58 +05:30
qty_allowance , over_billing_allowance = frappe . db . get_value (
" Item " , item_code , [ " over_delivery_receipt_allowance " , " over_billing_allowance " ]
)
if qty_or_amount == " qty " and not qty_allowance :
if global_qty_allowance == None :
global_qty_allowance = flt (
frappe . db . get_single_value ( " Stock Settings " , " over_delivery_receipt_allowance " )
2022-03-28 18:52:46 +05:30
)
2019-07-15 18:02:58 +05:30
qty_allowance = global_qty_allowance
elif qty_or_amount == " amount " and not over_billing_allowance :
if global_amount_allowance == None :
global_amount_allowance = flt (
frappe . db . get_single_value ( " Accounts Settings " , " over_billing_allowance " )
2022-03-28 18:52:46 +05:30
)
2019-07-15 18:02:58 +05:30
over_billing_allowance = global_amount_allowance
if qty_or_amount == " qty " :
allowance = qty_allowance
item_allowance . setdefault ( item_code , frappe . _dict ( ) ) . setdefault ( " qty " , qty_allowance )
else :
allowance = over_billing_allowance
item_allowance . setdefault ( item_code , frappe . _dict ( ) ) . setdefault ( " amount " , over_billing_allowance )
return allowance , item_allowance , global_qty_allowance , global_amount_allowance