blob: ce5d5dcb57568e83524f0a385c784e1a83401c03 [file] [log] [blame]
Anand Doshi885e0742015-03-03 14:55:30 +05301# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
Rushabh Mehtae67d1fb2013-08-05 14:59:54 +05302# License: GNU General Public License v3. See license.txt
Nabin Hait2df4d542013-01-29 11:34:39 +05303
Chillar Anand915b3432021-09-02 16:44:59 +05304
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08005import json
Chillar Anand915b3432021-09-02 16:44:59 +05306
7import frappe
Rushabh Mehta793ba6b2014-02-14 15:47:51 +05308from frappe import _, throw
Chillar Anand915b3432021-09-02 16:44:59 +05309from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied
Pruthvi Patel6c96ed42021-10-30 16:44:15 +053010from frappe.query_builder.functions import Sum
Chillar Anand915b3432021-09-02 16:44:59 +053011from frappe.utils import (
12 add_days,
13 add_months,
14 cint,
15 flt,
16 fmt_money,
17 formatdate,
18 get_last_day,
19 get_link_to_form,
20 getdate,
21 nowdate,
GangaManojd24cfff2021-10-28 20:06:48 +053022 today,
Chillar Anand915b3432021-09-02 16:44:59 +053023)
Chillar Anand915b3432021-09-02 16:44:59 +053024
25import erpnext
26from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
27 get_accounting_dimensions,
28)
29from erpnext.accounts.doctype.pricing_rule.utils import (
30 apply_pricing_rule_for_free_items,
31 apply_pricing_rule_on_transaction,
32 get_applied_pricing_rules,
33)
34from erpnext.accounts.party import (
35 get_party_account,
36 get_party_account_currency,
37 validate_party_frozen_disabled,
38)
39from erpnext.accounts.utils import get_account_currency, get_fiscal_years, validate_fiscal_year
40from erpnext.buying.utils import update_last_purchase_rate
41from erpnext.controllers.print_settings import (
42 set_print_templates_for_item_table,
43 set_print_templates_for_taxes,
44)
45from erpnext.controllers.sales_and_purchase_return import validate_return
46from erpnext.exceptions import InvalidCurrency
47from erpnext.setup.utils import get_exchange_rate
Marica3dee5272020-06-23 10:33:47 +053048from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
Chillar Anand915b3432021-09-02 16:44:59 +053049from erpnext.stock.get_item_details import (
50 _get_item_tax_template,
51 get_conversion_factor,
52 get_item_details,
53 get_item_tax_map,
54 get_item_warehouse,
55)
56from erpnext.utilities.transaction_base import TransactionBase
57
Nabin Hait1d218422015-07-17 15:19:02 +053058
marination53b1a9a2020-11-03 15:45:25 +053059class AccountMissingError(frappe.ValidationError): pass
60
Rohit Waghchaurebde159a2021-03-22 23:36:48 +053061force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate",
62 "pricing_rules", "weight_per_unit", "weight_uom", "total_weight")
Doridel Cahanap6f06cc22018-05-17 16:28:58 +080063
Nabin Haitbf495c92013-01-30 12:49:08 +053064class AccountsController(TransactionBase):
Nabin Hait7eba1a32017-10-02 15:59:27 +053065 def __init__(self, *args, **kwargs):
66 super(AccountsController, self).__init__(*args, **kwargs)
Anand Doshi979326b2015-09-11 16:22:37 +053067
prssanna71e5b602020-10-29 14:19:34 +053068 def get_print_settings(self):
69 print_setting_fields = []
70 items_field = self.meta.get_field('items')
71
72 if items_field and items_field.fieldtype == 'Table':
73 print_setting_fields += ['compact_item_print', 'print_uom_after_quantity']
74
75 taxes_field = self.meta.get_field('taxes')
76 if taxes_field and taxes_field.fieldtype == 'Table':
77 print_setting_fields += ['print_taxes_with_zero_amount']
78
79 return print_setting_fields
80
Nabin Hait4ffd7f32015-08-27 12:28:36 +053081 @property
82 def company_currency(self):
83 if not hasattr(self, "__company_currency"):
Rushabh Mehtacc8b2b22017-03-31 12:44:29 +053084 self.__company_currency = erpnext.get_company_currency(self.company)
Anand Doshi979326b2015-09-11 16:22:37 +053085
Nabin Hait4ffd7f32015-08-27 12:28:36 +053086 return self.__company_currency
Anand Doshi979326b2015-09-11 16:22:37 +053087
Rohit Waghchaure7d529892016-10-06 14:35:04 +053088 def onload(self):
Nabin Hait34c551d2019-07-03 10:34:31 +053089 self.set_onload("make_payment_via_journal_entry",
90 frappe.db.get_single_value('Accounts Settings', 'make_payment_via_journal_entry'))
Nabin Hait0551f7b2017-11-21 19:58:16 +053091
tundee52bb822017-09-25 09:02:23 +010092 if self.is_new():
Nabin Hait0551f7b2017-11-21 19:58:16 +053093 relevant_docs = ("Quotation", "Purchase Order", "Sales Order",
Doridel Cahanap6f06cc22018-05-17 16:28:58 +080094 "Purchase Invoice", "Sales Invoice")
tundee52bb822017-09-25 09:02:23 +010095 if self.doctype in relevant_docs:
96 self.set_payment_schedule()
Rohit Waghchaure7d529892016-10-06 14:35:04 +053097
tundebabzyad08d4c2018-05-16 07:01:41 +010098 def ensure_supplier_is_not_blocked(self):
99 is_supplier_payment = self.doctype == 'Payment Entry' and self.party_type == 'Supplier'
100 is_buying_invoice = self.doctype in ['Purchase Invoice', 'Purchase Order']
101 supplier = None
102 supplier_name = None
103
104 if is_buying_invoice or is_supplier_payment:
105 supplier_name = self.supplier if is_buying_invoice else self.party
106 supplier = frappe.get_doc('Supplier', supplier_name)
107
108 if supplier and supplier_name and supplier.on_hold:
109 if (is_buying_invoice and supplier.hold_type in ['All', 'Invoices']) or \
110 (is_supplier_payment and supplier.hold_type in ['All', 'Payments']):
111 if not supplier.release_date or getdate(nowdate()) <= supplier.release_date:
112 frappe.msgprint(
Suraj Shetty48e9bc32020-01-29 15:06:18 +0530113 _('{0} is blocked so this transaction cannot proceed').format(supplier_name), raise_exception=1)
tundebabzyad08d4c2018-05-16 07:01:41 +0100114
Saurabh6f753182013-03-20 12:55:28 +0530115 def validate(self):
Deepesh Gargd301d262019-07-31 15:58:19 +0530116 if not self.get('is_return'):
117 self.validate_qty_is_not_zero()
118
nabinhait23cce732014-07-03 12:25:06 +0530119 if self.get("_action") and self._action != "update_after_submit":
Nabin Hait704a4532014-06-05 16:55:31 +0530120 self.set_missing_values(for_validate=True)
tunde62af5c52017-09-22 15:16:38 +0100121
tundebabzyad08d4c2018-05-16 07:01:41 +0100122 self.ensure_supplier_is_not_blocked()
123
Nabin Haitcfecd2b2013-07-11 17:49:18 +0530124 self.validate_date_with_fiscal_year()
Deepesh Garg4c8d15b2021-04-26 15:24:34 +0530125 self.validate_party_accounts()
126
Deepesh Gargb4be2922021-01-28 13:09:56 +0530127 self.validate_inter_company_reference()
128
129 self.set_incoming_rate()
Rushabh Mehtab16b9cd2015-08-03 16:13:33 +0530130
Anand Doshi3543f302013-05-24 19:25:01 +0530131 if self.meta.get_field("currency"):
Anand Doshi923d41d2013-05-28 17:23:36 +0530132 self.calculate_taxes_and_totals()
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530133
Nabin Hait1d218422015-07-17 15:19:02 +0530134 if not self.meta.get_field("is_return") or not self.is_return:
135 self.validate_value("base_grand_total", ">=", 0)
Rushabh Mehtab16b9cd2015-08-03 16:13:33 +0530136
Nabin Hait3cf67a42015-07-24 13:26:36 +0530137 validate_return(self)
Anand Doshi3543f302013-05-24 19:25:01 +0530138 self.set_total_in_words()
Anand Doshid2946502014-04-08 20:10:03 +0530139
tunde62af5c52017-09-22 15:16:38 +0100140 self.validate_all_documents_schedule()
Anand Doshid2946502014-04-08 20:10:03 +0530141
ankitjavalkarwork6c29d872014-10-06 13:20:53 +0530142 if self.meta.get_field("taxes_and_charges"):
143 self.validate_enabled_taxes_and_charges()
Nabin Haita2426fc2018-01-15 17:45:46 +0530144 self.validate_tax_account_company()
Rushabh Mehtab16b9cd2015-08-03 16:13:33 +0530145
Neil Trini Lasrado3fbbb712015-07-17 12:10:12 +0530146 self.validate_party()
Nabin Hait895029d2015-08-20 14:55:39 +0530147 self.validate_currency()
Rushabh Mehta2cb7a9f2016-06-15 16:45:03 +0530148
Nabin Hait041a5c22018-08-01 18:07:39 +0530149 if self.doctype in ['Purchase Invoice', 'Sales Invoice']:
Nabin Hait4a994d42019-04-25 17:47:26 +0530150 pos_check_field = "is_pos" if self.doctype=="Sales Invoice" else "is_paid"
151 if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
Nabin Hait041a5c22018-08-01 18:07:39 +0530152 self.set_advances()
153
Saqiba20999c2021-07-12 14:33:23 +0530154 self.set_advance_gain_or_loss()
155
Nabin Hait041a5c22018-08-01 18:07:39 +0530156 if self.is_return:
157 self.validate_qty()
Nabin Haitacdd5082019-12-04 15:30:01 +0530158 else:
159 self.validate_deferred_start_and_end_date()
rohitwaghchaure70489252018-06-11 12:02:14 +0530160
Deepesh Gargf17ea2c2020-12-11 21:30:39 +0530161 self.set_inter_company_account()
162
Deepesh Garg7f06c8c2021-11-26 10:27:57 +0530163 if self.doctype == 'Purchase Invoice':
164 self.calculate_paid_amount()
165 # apply tax withholding only if checked and applicable
166 self.set_tax_withholding()
167
Gauravf1e28e02019-02-13 16:46:24 +0530168 validate_regional(self)
Deepesh Garg7c300852020-12-26 20:14:51 +0530169
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530170 if self.doctype != 'Material Request':
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530171 apply_pricing_rule_on_transaction(self)
Gauravf1e28e02019-02-13 16:46:24 +0530172
Saqib8e556772021-01-28 12:26:45 +0530173 def on_trash(self):
174 # delete sl and gl entries on deletion of transaction
175 if frappe.db.get_single_value('Accounts Settings', 'delete_linked_ledger_entries'):
176 frappe.db.sql("delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name))
177 frappe.db.sql("delete from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name))
Nabin Hait0551f7b2017-11-21 19:58:16 +0530178
Nabin Haitacdd5082019-12-04 15:30:01 +0530179 def validate_deferred_start_and_end_date(self):
180 for d in self.items:
181 if d.get("enable_deferred_revenue") or d.get("enable_deferred_expense"):
182 if not (d.service_start_date and d.service_end_date):
183 frappe.throw(_("Row #{0}: Service Start and End Date is required for deferred accounting").format(d.idx))
184 elif getdate(d.service_start_date) > getdate(d.service_end_date):
185 frappe.throw(_("Row #{0}: Service Start Date cannot be greater than Service End Date").format(d.idx))
186 elif getdate(self.posting_date) > getdate(d.service_end_date):
187 frappe.throw(_("Row #{0}: Service End Date cannot be before Invoice Posting Date").format(d.idx))
Deepesh Garg7ad149f2021-12-23 14:39:20 +0530188 elif getdate(self.posting_date) > getdate(d.service_start_date):
189 frappe.throw(_("Row #{0}: Service Start Date cannot be before Invoice Posting Date").format(d.idx))
Nabin Haitacdd5082019-12-04 15:30:01 +0530190
tunde62af5c52017-09-22 15:16:38 +0100191 def validate_invoice_documents_schedule(self):
Nabin Hait0551f7b2017-11-21 19:58:16 +0530192 self.validate_payment_schedule_dates()
193 self.set_due_date()
Nabin Hait0551f7b2017-11-21 19:58:16 +0530194 self.set_payment_schedule()
195 self.validate_payment_schedule_amount()
Deepesh Gargbd709f82021-08-23 19:05:52 +0530196 if not self.get('ignore_default_payment_terms_template'):
197 self.validate_due_date()
tunde62af5c52017-09-22 15:16:38 +0100198 self.validate_advance_entries()
199
200 def validate_non_invoice_documents_schedule(self):
Nabin Hait0551f7b2017-11-21 19:58:16 +0530201 self.set_payment_schedule()
Nabin Hait2f9f4f92017-12-20 12:24:59 +0530202 self.validate_payment_schedule_dates()
Nabin Hait0551f7b2017-11-21 19:58:16 +0530203 self.validate_payment_schedule_amount()
tunde62af5c52017-09-22 15:16:38 +0100204
205 def validate_all_documents_schedule(self):
206 if self.doctype in ("Sales Invoice", "Purchase Invoice") and not self.is_return:
207 self.validate_invoice_documents_schedule()
208 elif self.doctype in ("Quotation", "Purchase Order", "Sales Order"):
209 self.validate_non_invoice_documents_schedule()
210
prssanna71e5b602020-10-29 14:19:34 +0530211 def before_print(self, settings=None):
Zarrarb9f54ca2018-05-28 17:41:09 +0530212 if self.doctype in ['Purchase Order', 'Sales Order', 'Sales Invoice', 'Purchase Invoice',
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800213 'Supplier Quotation', 'Purchase Receipt', 'Delivery Note', 'Quotation']:
KanchanChauhan49a50fe2016-11-18 13:57:53 +0530214 if self.get("group_same_items"):
215 self.group_similar_items()
216
Zarrar5be6d192018-11-08 12:16:26 +0530217 df = self.meta.get_field("discount_amount")
218 if self.get("discount_amount") and hasattr(self, "taxes") and not len(self.taxes):
219 df.set("print_hide", 0)
220 self.discount_amount = -self.discount_amount
221 else:
222 df.set("print_hide", 1)
223
prssanna71e5b602020-10-29 14:19:34 +0530224 set_print_templates_for_item_table(self, settings)
225 set_print_templates_for_taxes(self, settings)
226
Mangesh-Khairnar7bcb24e2019-09-11 10:49:33 +0530227 def calculate_paid_amount(self):
Saurabh43520f92016-03-21 18:32:48 +0530228 if hasattr(self, "is_pos") or hasattr(self, "is_paid"):
229 is_paid = self.get("is_pos") or self.get("is_paid")
Mangesh-Khairnar7bcb24e2019-09-11 10:49:33 +0530230
231 if is_paid:
232 if not self.cash_bank_account:
233 # show message that the amount is not paid
234 frappe.throw(_("Note: Payment Entry will not be created since 'Cash or Bank Account' was not specified"))
Marica23d7b092019-09-25 17:17:36 +0530235
Mangesh-Khairnar7bcb24e2019-09-11 10:49:33 +0530236 if cint(self.is_return) and self.grand_total > self.paid_amount:
237 self.paid_amount = flt(flt(self.grand_total), self.precision("paid_amount"))
238
239 elif not flt(self.paid_amount) and flt(self.outstanding_amount) > 0:
240 self.paid_amount = flt(flt(self.outstanding_amount), self.precision("paid_amount"))
241
242 self.base_paid_amount = flt(self.paid_amount * self.conversion_rate,
243 self.precision("base_paid_amount"))
Saurabh43520f92016-03-21 18:32:48 +0530244
Anand Doshiabc10032013-06-14 17:44:03 +0530245 def set_missing_values(self, for_validate=False):
Rohit Waghchaure5d97d892016-05-12 12:58:29 +0530246 if frappe.flags.in_test:
tunde62af5c52017-09-22 15:16:38 +0100247 for fieldname in ["posting_date", "transaction_date"]:
Rohit Waghchaure5d97d892016-05-12 12:58:29 +0530248 if self.meta.get_field(fieldname) and not self.get(fieldname):
249 self.set(fieldname, today())
250 break
Anand Doshid2946502014-04-08 20:10:03 +0530251
Nabin Hait3237c752015-02-17 11:11:11 +0530252 def calculate_taxes_and_totals(self):
Nabin Haitfe81da22015-02-18 12:23:18 +0530253 from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals
254 calculate_taxes_and_totals(self)
Nabin Hait3237c752015-02-17 11:11:11 +0530255
Raffael Meyere10ab162021-11-30 13:24:18 +0100256 if self.doctype in (
257 'Sales Order',
258 'Delivery Note',
259 'Sales Invoice',
260 'POS Invoice',
261 ):
Nabin Hait3237c752015-02-17 11:11:11 +0530262 self.calculate_commission()
263 self.calculate_contribution()
264
Nabin Haitcfecd2b2013-07-11 17:49:18 +0530265 def validate_date_with_fiscal_year(self):
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800266 if self.meta.get_field("fiscal_year"):
Rohan Bansal1e3a3b22021-05-26 15:18:10 +0530267 date_field = None
Nabin Haitcfecd2b2013-07-11 17:49:18 +0530268 if self.meta.get_field("posting_date"):
269 date_field = "posting_date"
270 elif self.meta.get_field("transaction_date"):
271 date_field = "transaction_date"
Anand Doshid2946502014-04-08 20:10:03 +0530272
Rushabh Mehtaf2227d02014-03-31 23:37:40 +0530273 if date_field and self.get(date_field):
Rushabh Mehta66958302017-01-16 16:57:53 +0530274 validate_fiscal_year(self.get(date_field), self.fiscal_year, self.company,
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800275 self.meta.get_label(date_field), self)
Anand Doshid2946502014-04-08 20:10:03 +0530276
Deepesh Garg4c8d15b2021-04-26 15:24:34 +0530277 def validate_party_accounts(self):
278 if self.doctype not in ('Sales Invoice', 'Purchase Invoice'):
279 return
280
281 if self.doctype == 'Sales Invoice':
282 party_account_field = 'debit_to'
283 item_field = 'income_account'
284 else:
285 party_account_field = 'credit_to'
286 item_field = 'expense_account'
287
288 for item in self.get('items'):
289 if item.get(item_field) == self.get(party_account_field):
290 frappe.throw(_("Row {0}: {1} {2} cannot be same as {3} (Party Account) {4}").format(item.idx,
291 frappe.bold(frappe.unscrub(item_field)), item.get(item_field),
292 frappe.bold(frappe.unscrub(party_account_field)), self.get(party_account_field)))
293
Deepesh Gargb4be2922021-01-28 13:09:56 +0530294 def validate_inter_company_reference(self):
295 if self.doctype not in ('Purchase Invoice', 'Purchase Receipt', 'Purchase Order'):
296 return
297
298 if self.is_internal_transfer():
299 if not (self.get('inter_company_reference') or self.get('inter_company_invoice_reference')
300 or self.get('inter_company_order_reference')):
Deepesh Garg4c8d15b2021-04-26 15:24:34 +0530301 msg = _("Internal Sale or Delivery Reference missing.")
Deepesh Gargb4be2922021-01-28 13:09:56 +0530302 msg += _("Please create purchase from internal sale or delivery document itself")
303 frappe.throw(msg, title=_("Internal Sales Reference Missing"))
304
Nabin Haite9daefe2014-08-27 16:46:33 +0530305 def validate_due_date(self):
rohitwaghchaureea943c62018-07-06 12:28:58 +0530306 if self.get('is_pos'): return
307
Nabin Haite9daefe2014-08-27 16:46:33 +0530308 from erpnext.accounts.party import validate_due_date
309 if self.doctype == "Sales Invoice":
Nabin Hait04d244a2015-07-22 17:47:49 +0530310 if not self.due_date:
311 frappe.throw(_("Due Date is mandatory"))
Rushabh Mehtab16b9cd2015-08-03 16:13:33 +0530312
rohitwaghchaure3ffe8962018-07-13 17:40:48 +0530313 validate_due_date(self.posting_date, self.due_date,
314 "Customer", self.customer, self.company, self.payment_terms_template)
Nabin Haite9daefe2014-08-27 16:46:33 +0530315 elif self.doctype == "Purchase Invoice":
Rohit Waghchaured1a85a32018-11-26 18:42:29 +0530316 validate_due_date(self.bill_date or self.posting_date, self.due_date,
Saurabhd7897f12018-07-18 17:08:16 +0530317 "Supplier", self.supplier, self.company, self.bill_date, self.payment_terms_template)
Nabin Haite9daefe2014-08-27 16:46:33 +0530318
Nabin Hait096d3632013-10-17 17:01:14 +0530319 def set_price_list_currency(self, buying_or_selling):
Nabin Hait1cc55fb2016-12-08 14:09:23 +0530320 if self.meta.get_field("posting_date"):
Nabin Hait288a18e2016-12-08 15:36:23 +0530321 transaction_date = self.posting_date
Nabin Hait1cc55fb2016-12-08 14:09:23 +0530322 else:
Nabin Hait288a18e2016-12-08 15:36:23 +0530323 transaction_date = self.transaction_date
Rushabh Mehta66958302017-01-16 16:57:53 +0530324
Rushabh Mehta4a404e92013-08-09 18:11:35 +0530325 if self.meta.get_field("currency"):
Nabin Hait7a75e102013-09-17 10:21:20 +0530326 # price list part
Shreya3f778522018-05-15 16:59:20 +0530327 if buying_or_selling.lower() == "selling":
328 fieldname = "selling_price_list"
329 args = "for_selling"
330 else:
331 fieldname = "buying_price_list"
332 args = "for_buying"
333
Anand Doshif78d1ae2014-03-28 13:55:00 +0530334 if self.meta.get_field(fieldname) and self.get(fieldname):
335 self.price_list_currency = frappe.db.get_value("Price List",
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800336 self.get(fieldname), "currency")
Anand Doshid2946502014-04-08 20:10:03 +0530337
Nabin Hait6e439a52015-08-28 19:24:22 +0530338 if self.price_list_currency == self.company_currency:
Anand Doshif78d1ae2014-03-28 13:55:00 +0530339 self.plc_conversion_rate = 1.0
Nabin Hait11f41952013-09-24 14:36:55 +0530340
Anand Doshif78d1ae2014-03-28 13:55:00 +0530341 elif not self.plc_conversion_rate:
Rushabh Mehta66958302017-01-16 16:57:53 +0530342 self.plc_conversion_rate = get_exchange_rate(self.price_list_currency,
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800343 self.company_currency, transaction_date, args)
Anand Doshid2946502014-04-08 20:10:03 +0530344
Nabin Hait7a75e102013-09-17 10:21:20 +0530345 # currency
Anand Doshif78d1ae2014-03-28 13:55:00 +0530346 if not self.currency:
347 self.currency = self.price_list_currency
348 self.conversion_rate = self.plc_conversion_rate
Nabin Hait6e439a52015-08-28 19:24:22 +0530349 elif self.currency == self.company_currency:
Anand Doshif78d1ae2014-03-28 13:55:00 +0530350 self.conversion_rate = 1.0
351 elif not self.conversion_rate:
Anand Doshidffec8f2014-07-01 17:45:15 +0530352 self.conversion_rate = get_exchange_rate(self.currency,
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800353 self.company_currency, transaction_date, args)
Nabin Hait7a75e102013-09-17 10:21:20 +0530354
Nabin Haitcccc45e2016-10-05 17:15:43 +0530355 def set_missing_item_details(self, for_validate=False):
Anand Doshi3543f302013-05-24 19:25:01 +0530356 """set missing item values"""
Nabin Haitf37d43d2017-07-18 12:14:42 +0530357 from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
Rushabh Mehtab46069d2016-01-15 16:59:26 +0530358
Nabin Haitdd38a262014-12-26 13:15:21 +0530359 if hasattr(self, "items"):
Nabin Hait444f9562014-06-20 15:59:49 +0530360 parent_dict = {}
Pratik Vyasf7daab72014-04-10 17:53:30 +0530361 for fieldname in self.meta.get_valid_columns():
362 parent_dict[fieldname] = self.get(fieldname)
363
mbauskara52472c2016-03-05 15:10:25 +0530364 if self.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]:
365 document_type = "{} Item".format(self.doctype)
mbauskarc97becb2016-01-18 16:28:21 +0530366 parent_dict.update({"document_type": document_type})
367
Nabin Hait34c551d2019-07-03 10:34:31 +0530368 # party_name field used for customer in quotation
369 if self.doctype == "Quotation" and self.quotation_to == "Customer" and parent_dict.get("party_name"):
370 parent_dict.update({"customer": parent_dict.get("party_name")})
371
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530372 self.pricing_rules = []
Nabin Haitdd38a262014-12-26 13:15:21 +0530373 for item in self.get("items"):
Rushabh Mehtaf14b8092014-04-03 14:30:42 +0530374 if item.get("item_code"):
Nabin Hait444f9562014-06-20 15:59:49 +0530375 args = parent_dict.copy()
376 args.update(item.as_dict())
Rushabh Mehtab46069d2016-01-15 16:59:26 +0530377
Nabin Hait34d28222016-01-19 15:45:49 +0530378 args["doctype"] = self.doctype
379 args["name"] = self.name
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530380 args["child_docname"] = item.name
Saqibac9e6ff2021-01-28 17:58:55 +0530381 args["ignore_pricing_rule"] = self.ignore_pricing_rule if hasattr(self, 'ignore_pricing_rule') else 0
Rushabh Mehtab46069d2016-01-15 16:59:26 +0530382
Nabin Haite2f054c2015-03-09 14:54:37 +0530383 if not args.get("transaction_date"):
384 args["transaction_date"] = args.get("posting_date")
Anand Doshi2b2b6392015-03-20 14:18:09 +0530385
386 if self.get("is_subcontracted"):
387 args["is_subcontracted"] = self.is_subcontracted
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530388
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530389 ret = get_item_details(args, self, for_validate=True, overwrite_warehouse=False)
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530390
Rushabh Mehtaf14b8092014-04-03 14:30:42 +0530391 for fieldname, value in ret.items():
Anand Doshi602e8252015-11-16 19:05:46 +0530392 if item.meta.get_field(fieldname) and value is not None:
393 if (item.get(fieldname) is None or fieldname in force_item_fields):
Rushabh Mehtaf14b8092014-04-03 14:30:42 +0530394 item.set(fieldname, value)
Anand Doshid2946502014-04-08 20:10:03 +0530395
Rohit Waghchauredc981dc2017-04-03 14:17:08 +0530396 elif fieldname in ['cost_center', 'conversion_factor'] and not item.get(fieldname):
Anand Doshi602e8252015-11-16 19:05:46 +0530397 item.set(fieldname, value)
398
Rohit Waghchauredc981dc2017-04-03 14:17:08 +0530399 elif fieldname == "serial_no":
Alchez9155e402018-07-09 16:57:42 +0530400 # Ensure that serial numbers are matched against Stock UOM
401 item_conversion_factor = item.get("conversion_factor") or 1.0
402 item_qty = abs(item.get("qty")) * item_conversion_factor
Alchez8f2a0f32018-07-06 14:36:52 +0530403
404 if item_qty != len(get_serial_nos(item.get('serial_no'))):
Rohit Waghchauredc981dc2017-04-03 14:17:08 +0530405 item.set(fieldname, value)
Nabin Haita74468b2014-12-30 18:33:52 +0530406
Rohit Waghchauref6f503a2018-12-19 15:06:44 +0530407 if self.doctype in ["Purchase Invoice", "Sales Invoice"] and item.meta.get_field('is_fixed_asset'):
408 item.set('is_fixed_asset', ret.get('is_fixed_asset', 0))
409
Deepesh Garga60c3082021-05-11 16:38:33 +0530410 # Double check for cost center
411 # Items add via promotional scheme may not have cost center set
412 if hasattr(item, 'cost_center') and not item.get('cost_center'):
413 item.set('cost_center', self.get('cost_center') or erpnext.get_default_cost_center(self.company))
414
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530415 if ret.get("pricing_rules"):
416 self.apply_pricing_rule_on_items(item, ret)
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530417 self.set_pricing_rule_details(item, ret)
Rushabh Mehtab46069d2016-01-15 16:59:26 +0530418
Nabin Hait14aa9c52016-04-18 15:54:01 +0530419 if self.doctype == "Purchase Invoice":
Nabin Haitcccc45e2016-10-05 17:15:43 +0530420 self.set_expense_account(for_validate)
Nabin Haita3dd72a2014-05-28 12:49:20 +0530421
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530422 def apply_pricing_rule_on_items(self, item, pricing_rule_args):
423 if not pricing_rule_args.get("validate_applied_rule", 0):
424 # if user changed the discount percentage then set user's discount percentage ?
425 if pricing_rule_args.get("price_or_product_discount") == 'Price':
426 item.set("pricing_rules", pricing_rule_args.get("pricing_rules"))
427 item.set("discount_percentage", pricing_rule_args.get("discount_percentage"))
428 item.set("discount_amount", pricing_rule_args.get("discount_amount"))
429 if pricing_rule_args.get("pricing_rule_for") == "Rate":
430 item.set("price_list_rate", pricing_rule_args.get("price_list_rate"))
431
432 if item.get("price_list_rate"):
433 item.rate = flt(item.price_list_rate *
434 (1.0 - (flt(item.discount_percentage) / 100.0)), item.precision("rate"))
435
436 if item.get('discount_amount'):
437 item.rate = item.price_list_rate - item.discount_amount
438
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530439 if item.get("apply_discount_on_discounted_rate") and pricing_rule_args.get("rate"):
440 item.rate = pricing_rule_args.get("rate")
441
Rohit Waghchaure21fe97e2019-12-13 16:18:38 +0530442 elif pricing_rule_args.get('free_item_data'):
443 apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data'))
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530444
445 elif pricing_rule_args.get("validate_applied_rule"):
Rushabh Mehtaff5d1962020-08-25 11:59:57 +0530446 for pricing_rule in get_applied_pricing_rules(item.get('pricing_rules')):
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530447 pricing_rule_doc = frappe.get_cached_doc("Pricing Rule", pricing_rule)
448 for field in ['discount_percentage', 'discount_amount', 'rate']:
449 if item.get(field) < pricing_rule_doc.get(field):
450 title = get_link_to_form("Pricing Rule", pricing_rule)
451
452 frappe.msgprint(_("Row {0}: user has not applied the rule {1} on the item {2}")
453 .format(item.idx, frappe.bold(title), frappe.bold(item.item_code)))
454
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530455 def set_pricing_rule_details(self, item_row, args):
456 pricing_rules = get_applied_pricing_rules(args.get("pricing_rules"))
457 if not pricing_rules: return
458
459 for pricing_rule in pricing_rules:
460 self.append("pricing_rules", {
461 "pricing_rule": pricing_rule,
462 "item_code": item_row.item_code,
463 "child_docname": item_row.name,
464 "rule_applied": True
465 })
466
Nabin Haitffc7f3f2015-05-12 15:07:02 +0530467 def set_taxes(self):
468 if not self.meta.get_field("taxes"):
Anand Doshi3543f302013-05-24 19:25:01 +0530469 return
Anand Doshid2946502014-04-08 20:10:03 +0530470
Nabin Haitffc7f3f2015-05-12 15:07:02 +0530471 tax_master_doctype = self.meta.get_field("taxes_and_charges").options
Anand Doshid2946502014-04-08 20:10:03 +0530472
rohitwaghchaure57914f12018-04-24 19:19:47 +0530473 if (self.is_new() or self.is_pos_profile_changed()) and not self.get("taxes"):
rohitwaghchaure505661b2017-12-11 14:52:28 +0530474 if self.company and not self.get("taxes_and_charges"):
Anand Doshi3543f302013-05-24 19:25:01 +0530475 # get the default tax master
rohitwaghchaure505661b2017-12-11 14:52:28 +0530476 self.taxes_and_charges = frappe.db.get_value(tax_master_doctype,
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800477 {"is_default": 1, 'company': self.company})
Anand Doshid2946502014-04-08 20:10:03 +0530478
Nabin Haitffc7f3f2015-05-12 15:07:02 +0530479 self.append_taxes_from_master(tax_master_doctype)
Anand Doshid2946502014-04-08 20:10:03 +0530480
rohitwaghchaure57914f12018-04-24 19:19:47 +0530481 def is_pos_profile_changed(self):
482 if (self.doctype == 'Sales Invoice' and self.is_pos and
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800483 self.pos_profile != frappe.db.get_value('Sales Invoice', self.name, 'pos_profile')):
rohitwaghchaure57914f12018-04-24 19:19:47 +0530484 return True
485
Nabin Haitffc7f3f2015-05-12 15:07:02 +0530486 def append_taxes_from_master(self, tax_master_doctype=None):
487 if self.get("taxes_and_charges"):
Anand Doshi99100a42013-07-04 17:13:53 +0530488 if not tax_master_doctype:
Nabin Haitffc7f3f2015-05-12 15:07:02 +0530489 tax_master_doctype = self.meta.get_field("taxes_and_charges").options
Anand Doshid2946502014-04-08 20:10:03 +0530490
Nabin Haitffc7f3f2015-05-12 15:07:02 +0530491 self.extend("taxes", get_taxes_and_charges(tax_master_doctype, self.get("taxes_and_charges")))
Akhilesh Darjee4cdb7992014-01-30 13:56:57 +0530492
Anand Doshiac32bad2014-04-18 01:30:14 +0530493 def set_other_charges(self):
Nabin Haite7d15362014-12-25 16:01:55 +0530494 self.set("taxes", [])
Nabin Haitffc7f3f2015-05-12 15:07:02 +0530495 self.set_taxes()
Anand Doshid2946502014-04-08 20:10:03 +0530496
ankitjavalkarwork6c29d872014-10-06 13:20:53 +0530497 def validate_enabled_taxes_and_charges(self):
498 taxes_and_charges_doctype = self.meta.get_options("taxes_and_charges")
499 if frappe.db.get_value(taxes_and_charges_doctype, self.taxes_and_charges, "disabled"):
500 frappe.throw(_("{0} '{1}' is disabled").format(taxes_and_charges_doctype, self.taxes_and_charges))
501
Nabin Haita2426fc2018-01-15 17:45:46 +0530502 def validate_tax_account_company(self):
503 for d in self.get("taxes"):
504 if d.account_head:
505 tax_account_company = frappe.db.get_value("Account", d.account_head, "company")
506 if tax_account_company != self.company:
507 frappe.throw(_("Row #{0}: Account {1} does not belong to company {2}")
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800508 .format(d.idx, d.account_head, self.company))
Nabin Haita2426fc2018-01-15 17:45:46 +0530509
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530510 def get_gl_dict(self, args, account_currency=None, item=None):
Nabin Hait2df4d542013-01-29 11:34:39 +0530511 """this method populates the common properties of a gl entry record"""
Rushabh Mehta2cb7a9f2016-06-15 16:45:03 +0530512
Rohit Waghchaureaa7b4342018-05-11 01:56:05 +0530513 posting_date = args.get('posting_date') or self.get('posting_date')
514 fiscal_years = get_fiscal_years(posting_date, company=self.company)
Nabin Hait2ed0b592016-03-29 13:14:17 +0530515 if len(fiscal_years) > 1:
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800516 frappe.throw(_("Multiple fiscal years exist for the date {0}. Please set company in Fiscal Year").format(
517 formatdate(posting_date)))
Nabin Hait2ed0b592016-03-29 13:14:17 +0530518 else:
519 fiscal_year = fiscal_years[0][0]
Rushabh Mehta2cb7a9f2016-06-15 16:45:03 +0530520
Rushabh Mehta793ba6b2014-02-14 15:47:51 +0530521 gl_dict = frappe._dict({
Anand Doshid2946502014-04-08 20:10:03 +0530522 'company': self.company,
Rohit Waghchaureaa7b4342018-05-11 01:56:05 +0530523 'posting_date': posting_date,
Nabin Hait2ed0b592016-03-29 13:14:17 +0530524 'fiscal_year': fiscal_year,
Anand Doshif78d1ae2014-03-28 13:55:00 +0530525 'voucher_type': self.doctype,
526 'voucher_no': self.name,
Nabin Hait5cd04a62019-05-27 11:44:06 +0530527 'remarks': self.get("remarks") or self.get("remark"),
Nabin Hait2df4d542013-01-29 11:34:39 +0530528 'debit': 0,
529 'credit': 0,
Nabin Hait46bcbaf2015-08-19 13:49:10 +0530530 'debit_in_account_currency': 0,
531 'credit_in_account_currency': 0,
Anand Doshif78d1ae2014-03-28 13:55:00 +0530532 'is_opening': self.get("is_opening") or "No",
Nabin Hait32251022014-08-29 11:18:32 +0530533 'party_type': None,
Nabin Hait591a5ab2016-05-26 17:41:39 +0530534 'party': None,
Deepesh Garg5ba3b282021-11-25 15:42:30 +0530535 'project': self.get("project"),
536 'post_net_value': args.get('post_net_value')
Nabin Hait2e296fa2013-08-28 18:53:11 +0530537 })
deepeshgarg007d83cf652019-05-12 18:34:23 +0530538
539 accounting_dimensions = get_accounting_dimensions()
540 dimension_dict = frappe._dict()
541
542 for dimension in accounting_dimensions:
543 dimension_dict[dimension] = self.get(dimension)
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530544 if item and item.get(dimension):
545 dimension_dict[dimension] = item.get(dimension)
deepeshgarg007d83cf652019-05-12 18:34:23 +0530546
547 gl_dict.update(dimension_dict)
Nabin Hait2df4d542013-01-29 11:34:39 +0530548 gl_dict.update(args)
Anand Doshi979326b2015-09-11 16:22:37 +0530549
Nabin Hait895029d2015-08-20 14:55:39 +0530550 if not account_currency:
Anand Doshicd0989e2015-09-28 13:31:17 +0530551 account_currency = get_account_currency(gl_dict.account)
Anand Doshi979326b2015-09-11 16:22:37 +0530552
Rushabh Mehtabc4e2cd2017-10-17 12:30:34 +0530553 if gl_dict.account and self.doctype not in ["Journal Entry",
Deepesh Gargbfc17e42020-12-25 18:34:39 +0530554 "Period Closing Voucher", "Payment Entry", "Purchase Receipt", "Purchase Invoice", "Stock Entry"]:
Anand Doshibe6cfdd2015-09-17 21:07:04 +0530555 self.validate_account_currency(gl_dict.account, account_currency)
Deepesh Garg7c300852020-12-26 20:14:51 +0530556
557 if gl_dict.account and self.doctype not in ["Journal Entry", "Period Closing Voucher", "Payment Entry"]:
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800558 set_balance_in_account_currency(gl_dict, account_currency, self.get("conversion_rate"),
559 self.company_currency)
Anand Doshi979326b2015-09-11 16:22:37 +0530560
Nabin Haitc561a492015-08-19 19:22:34 +0530561 return gl_dict
Anand Doshi979326b2015-09-11 16:22:37 +0530562
Anurag Mishrae657fe82018-11-26 15:19:17 +0530563 def validate_qty_is_not_zero(self):
Maricac68a9402019-12-03 12:59:22 +0530564 if self.doctype != "Purchase Receipt":
565 for item in self.items:
566 if not item.qty:
567 frappe.throw(_("Item quantity can not be zero"))
Anurag Mishrae657fe82018-11-26 15:19:17 +0530568
Nabin Hait895029d2015-08-20 14:55:39 +0530569 def validate_account_currency(self, account, account_currency=None):
Nabin Hait6e439a52015-08-28 19:24:22 +0530570 valid_currency = [self.company_currency]
571 if self.get("currency") and self.currency != self.company_currency:
572 valid_currency.append(self.currency)
573
Nabin Hait895029d2015-08-20 14:55:39 +0530574 if account_currency not in valid_currency:
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530575 frappe.throw(_("Account {0} is invalid. Account Currency must be {1}")
Suraj Shettyda6806e2020-04-08 09:32:41 +0530576 .format(account, (' ' + _("or") + ' ').join(valid_currency)))
Anand Doshi979326b2015-09-11 16:22:37 +0530577
Anand Doshi613cb6a2013-02-06 17:33:46 +0530578 def clear_unallocated_advances(self, childtype, parentfield):
Rushabh Mehtaf191f852014-04-02 18:09:34 +0530579 self.set(parentfield, self.get(parentfield, {"allocated_amount": ["not in", [0, None, ""]]}))
580
Anand Doshid2946502014-04-08 20:10:03 +0530581 frappe.db.sql("""delete from `tab%s` where parentfield=%s and parent = %s
Anand Doshi602e8252015-11-16 19:05:46 +0530582 and allocated_amount = 0""" % (childtype, '%s', '%s'), (parentfield, self.name))
Anand Doshid2946502014-04-08 20:10:03 +0530583
Walstan Baptistad6360752021-03-31 12:30:32 +0530584 @frappe.whitelist()
Rushabh Mehta30dc9a12017-11-17 14:31:09 +0530585 def apply_shipping_rule(self):
586 if self.shipping_rule:
587 shipping_rule = frappe.get_doc("Shipping Rule", self.shipping_rule)
588 shipping_rule.apply(self)
589 self.calculate_taxes_and_totals()
590
591 def get_shipping_address(self):
592 '''Returns Address object from shipping address fields if present'''
593
594 # shipping address fields can be `shipping_address_name` or `shipping_address`
595 # try getting value from both
596
597 for fieldname in ('shipping_address_name', 'shipping_address'):
598 shipping_field = self.meta.get_field(fieldname)
599 if shipping_field and shipping_field.fieldtype == 'Link':
600 if self.get(fieldname):
601 return frappe.get_doc('Address', self.get(fieldname))
602
603 return {}
604
Walstan Baptistad6360752021-03-31 12:30:32 +0530605 @frappe.whitelist()
Nabin Hait041a5c22018-08-01 18:07:39 +0530606 def set_advances(self):
607 """Returns list of advances against Account, Party, Reference"""
608
609 res = self.get_advance_entries()
610
611 self.set("advances", [])
612 advance_allocated = 0
613 for d in res:
614 if d.against_order:
615 allocated_amount = flt(d.amount)
616 else:
Deepesh Garg26210162020-08-11 16:06:13 +0530617 if self.get('party_account_currency') == self.company_currency:
618 amount = self.get('base_rounded_total') or self.base_grand_total
619 else:
620 amount = self.get('rounded_total') or self.grand_total
621
Rohit Waghchaure6cc2f522018-11-27 12:07:03 +0530622 allocated_amount = min(amount - advance_allocated, d.amount)
Nabin Hait041a5c22018-08-01 18:07:39 +0530623 advance_allocated += flt(allocated_amount)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530624
Saqiba20999c2021-07-12 14:33:23 +0530625 advance_row = {
Nabin Hait041a5c22018-08-01 18:07:39 +0530626 "doctype": self.doctype + " Advance",
627 "reference_type": d.reference_type,
628 "reference_name": d.reference_name,
629 "reference_row": d.reference_row,
630 "remarks": d.remarks,
631 "advance_amount": flt(d.amount),
Saqiba20999c2021-07-12 14:33:23 +0530632 "allocated_amount": allocated_amount,
633 "ref_exchange_rate": flt(d.exchange_rate) # exchange_rate of advance entry
634 }
635
636 self.append("advances", advance_row)
Nabin Hait041a5c22018-08-01 18:07:39 +0530637
Nabin Hait28a05282016-06-27 17:41:39 +0530638 def get_advance_entries(self, include_unallocated=True):
639 if self.doctype == "Sales Invoice":
640 party_account = self.debit_to
641 party_type = "Customer"
642 party = self.customer
643 amount_field = "credit_in_account_currency"
644 order_field = "sales_order"
645 order_doctype = "Sales Order"
646 else:
647 party_account = self.credit_to
648 party_type = "Supplier"
649 party = self.supplier
650 amount_field = "debit_in_account_currency"
651 order_field = "purchase_order"
652 order_doctype = "Purchase Order"
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530653
Ankush Menat98917802021-06-11 18:40:22 +0530654 order_list = list(set(d.get(order_field)
655 for d in self.get("items") if d.get(order_field)))
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530656
657 journal_entries = get_advance_journal_entries(party_type, party, party_account,
Nabin Hait868766d2019-07-15 18:02:58 +0530658 amount_field, order_doctype, order_list, include_unallocated)
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530659
660 payment_entries = get_advance_payment_entries(party_type, party, party_account,
Nabin Hait868766d2019-07-15 18:02:58 +0530661 order_doctype, order_list, include_unallocated)
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530662
Nabin Hait28a05282016-06-27 17:41:39 +0530663 res = journal_entries + payment_entries
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530664
Nabin Hait28a05282016-06-27 17:41:39 +0530665 return res
Nabin Hait48f5fa62014-09-18 15:03:54 +0530666
rohitwaghchaurebf4c1142018-01-08 15:20:15 +0530667 def is_inclusive_tax(self):
Nabin Hait868766d2019-07-15 18:02:58 +0530668 is_inclusive = cint(frappe.db.get_single_value("Accounts Settings", "show_inclusive_tax_in_print"))
rohitwaghchaurebf4c1142018-01-08 15:20:15 +0530669
670 if is_inclusive:
671 is_inclusive = 0
672 if self.get("taxes", filters={"included_in_print_rate": 1}):
673 is_inclusive = 1
674
675 return is_inclusive
676
Nabin Hait28a05282016-06-27 17:41:39 +0530677 def validate_advance_entries(self):
Nabin Hait05aefbb2016-06-28 19:42:19 +0530678 order_field = "sales_order" if self.doctype == "Sales Invoice" else "purchase_order"
Ankush Menat98917802021-06-11 18:40:22 +0530679 order_list = list(set(d.get(order_field)
680 for d in self.get("items") if d.get(order_field)))
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530681
Nabin Hait05aefbb2016-06-28 19:42:19 +0530682 if not order_list: return
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530683
Nabin Hait28a05282016-06-27 17:41:39 +0530684 advance_entries = self.get_advance_entries(include_unallocated=False)
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530685
Nabin Hait28a05282016-06-27 17:41:39 +0530686 if advance_entries:
687 advance_entries_against_si = [d.reference_name for d in self.get("advances")]
688 for d in advance_entries:
689 if not advance_entries_against_si or d.reference_name not in advance_entries_against_si:
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800690 frappe.msgprint(_(
691 "Payment Entry {0} is linked against Order {1}, check if it should be pulled as advance in this invoice.")
Nabin Hait868766d2019-07-15 18:02:58 +0530692 .format(d.reference_name, d.against_order))
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530693
Saqiba20999c2021-07-12 14:33:23 +0530694 def set_advance_gain_or_loss(self):
Saqib Ansari64efe8b2021-09-26 15:46:13 +0530695 if self.get('conversion_rate') == 1 or not self.get("advances"):
696 return
697
698 is_purchase_invoice = self.doctype == 'Purchase Invoice'
699 party_account = self.credit_to if is_purchase_invoice else self.debit_to
700 if get_account_currency(party_account) != self.currency:
Saqiba20999c2021-07-12 14:33:23 +0530701 return
702
703 for d in self.get("advances"):
704 advance_exchange_rate = d.ref_exchange_rate
Saqib Ansari64efe8b2021-09-26 15:46:13 +0530705 if (d.allocated_amount and self.conversion_rate != advance_exchange_rate):
Saqiba20999c2021-07-12 14:33:23 +0530706
707 base_allocated_amount_in_ref_rate = advance_exchange_rate * d.allocated_amount
708 base_allocated_amount_in_inv_rate = self.conversion_rate * d.allocated_amount
709 difference = base_allocated_amount_in_ref_rate - base_allocated_amount_in_inv_rate
710
711 d.exchange_gain_loss = difference
712
713 def make_exchange_gain_loss_gl_entries(self, gl_entries):
714 if self.get('doctype') in ['Purchase Invoice', 'Sales Invoice']:
715 for d in self.get("advances"):
716 if d.exchange_gain_loss:
Saqibd4ae1fe2021-07-30 11:21:49 +0530717 is_purchase_invoice = self.get('doctype') == 'Purchase Invoice'
718 party = self.supplier if is_purchase_invoice else self.customer
719 party_account = self.credit_to if is_purchase_invoice else self.debit_to
720 party_type = "Supplier" if is_purchase_invoice else "Customer"
Saqiba20999c2021-07-12 14:33:23 +0530721
722 gain_loss_account = frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account')
Saqibd4ae1fe2021-07-30 11:21:49 +0530723 if not gain_loss_account:
Saqib Ansari78ad50e2021-09-26 16:00:28 +0530724 frappe.throw(_("Please set default Exchange Gain/Loss Account in Company {}")
Saqibd4ae1fe2021-07-30 11:21:49 +0530725 .format(self.get('company')))
Saqiba20999c2021-07-12 14:33:23 +0530726 account_currency = get_account_currency(gain_loss_account)
727 if account_currency != self.company_currency:
Saqibd4ae1fe2021-07-30 11:21:49 +0530728 frappe.throw(_("Currency for {0} must be {1}").format(gain_loss_account, self.company_currency))
Saqiba20999c2021-07-12 14:33:23 +0530729
730 # for purchase
731 dr_or_cr = 'debit' if d.exchange_gain_loss > 0 else 'credit'
Saqibd4ae1fe2021-07-30 11:21:49 +0530732 if not is_purchase_invoice:
733 # just reverse for sales?
734 dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit'
Saqiba20999c2021-07-12 14:33:23 +0530735
736 gl_entries.append(
737 self.get_gl_dict({
738 "account": gain_loss_account,
739 "account_currency": account_currency,
740 "against": party,
741 dr_or_cr + "_in_account_currency": abs(d.exchange_gain_loss),
742 dr_or_cr: abs(d.exchange_gain_loss),
Saqib Ansari78ad50e2021-09-26 16:00:28 +0530743 "cost_center": self.cost_center or erpnext.get_default_cost_center(self.company),
Saqiba20999c2021-07-12 14:33:23 +0530744 "project": self.project
745 }, item=d)
746 )
747
748 dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit'
749
750 gl_entries.append(
751 self.get_gl_dict({
752 "account": party_account,
753 "party_type": party_type,
754 "party": party,
755 "against": gain_loss_account,
756 dr_or_cr + "_in_account_currency": flt(abs(d.exchange_gain_loss) / self.conversion_rate),
757 dr_or_cr: abs(d.exchange_gain_loss),
758 "cost_center": self.cost_center,
759 "project": self.project
760 }, self.party_account_currency, item=self)
761 )
762
Nabin Hait28a05282016-06-27 17:41:39 +0530763 def update_against_document_in_jv(self):
764 """
765 Links invoice and advance voucher:
766 1. cancel advance voucher
767 2. split into multiple rows if partially adjusted, assign against voucher
768 3. submit advance voucher
769 """
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530770
Nabin Hait28a05282016-06-27 17:41:39 +0530771 if self.doctype == "Sales Invoice":
Nabin Haitff2f3ef2016-07-04 11:41:14 +0530772 party_type = "Customer"
Nabin Hait28a05282016-06-27 17:41:39 +0530773 party = self.customer
774 party_account = self.debit_to
775 dr_or_cr = "credit_in_account_currency"
776 else:
Nabin Haitff2f3ef2016-07-04 11:41:14 +0530777 party_type = "Supplier"
Nabin Hait28a05282016-06-27 17:41:39 +0530778 party = self.supplier
779 party_account = self.credit_to
780 dr_or_cr = "debit_in_account_currency"
Nabin Hait48f5fa62014-09-18 15:03:54 +0530781
Nabin Hait28a05282016-06-27 17:41:39 +0530782 lst = []
783 for d in self.get('advances'):
784 if flt(d.allocated_amount) > 0:
785 args = frappe._dict({
786 'voucher_type': d.reference_type,
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800787 'voucher_no': d.reference_name,
788 'voucher_detail_no': d.reference_row,
789 'against_voucher_type': self.doctype,
790 'against_voucher': self.name,
791 'account': party_account,
Nabin Haitff2f3ef2016-07-04 11:41:14 +0530792 'party_type': party_type,
Nabin Hait28a05282016-06-27 17:41:39 +0530793 'party': party,
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800794 'is_advance': 'Yes',
795 'dr_or_cr': dr_or_cr,
796 'unadjusted_amount': flt(d.advance_amount),
797 'allocated_amount': flt(d.allocated_amount),
Deepesh Gargf54d5962021-03-31 14:06:02 +0530798 'precision': d.precision('advance_amount'),
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530799 'exchange_rate': (self.conversion_rate
Nabin Hait868766d2019-07-15 18:02:58 +0530800 if self.party_account_currency != self.company_currency else 1),
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530801 'grand_total': (self.base_grand_total
Nabin Hait868766d2019-07-15 18:02:58 +0530802 if self.party_account_currency == self.company_currency else self.grand_total),
Saqiba20999c2021-07-12 14:33:23 +0530803 'outstanding_amount': self.outstanding_amount,
804 'difference_account': frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account'),
805 'exchange_gain_loss': flt(d.get('exchange_gain_loss'))
Nabin Hait28a05282016-06-27 17:41:39 +0530806 })
807 lst.append(args)
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530808
Nabin Hait28a05282016-06-27 17:41:39 +0530809 if lst:
810 from erpnext.accounts.utils import reconcile_against_document
811 reconcile_against_document(lst)
Anand Doshid2946502014-04-08 20:10:03 +0530812
Rohit Waghchaure5fa9a7a2019-04-01 00:40:38 +0530813 def on_cancel(self):
814 from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
815
816 if self.doctype in ["Sales Invoice", "Purchase Invoice"]:
817 if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
818 unlink_ref_doc_from_payment_entries(self)
819
820 elif self.doctype in ["Sales Order", "Purchase Order"]:
Rohit Waghchaure9835c892019-04-01 01:02:06 +0530821 if frappe.db.get_single_value('Accounts Settings', 'unlink_advance_payment_on_cancelation_of_order'):
Rohit Waghchaure5fa9a7a2019-04-01 00:40:38 +0530822 unlink_ref_doc_from_payment_entries(self)
823
GangaManoj8396f242021-09-20 19:01:46 +0530824 if self.doctype == "Sales Order":
825 self.unlink_ref_doc_from_po()
826
827 def unlink_ref_doc_from_po(self):
GangaManoj8396f242021-09-20 19:01:46 +0530828 so_items = []
829 for item in self.items:
830 so_items.append(item.name)
831
Deepesh Garg051aaa72021-10-29 11:35:34 +0530832 linked_po = list(set(frappe.get_all(
GangaManoj8396f242021-09-20 19:01:46 +0530833 'Purchase Order Item',
834 filters = {
835 'sales_order': self.name,
836 'sales_order_item': ['in', so_items],
837 'docstatus': ['<', 2]
838 },
839 pluck='parent'
Deepesh Garg051aaa72021-10-29 11:35:34 +0530840 )))
GangaManoj8396f242021-09-20 19:01:46 +0530841
GangaManoj8396f242021-09-20 19:01:46 +0530842 if linked_po:
GangaManoje77534f2021-09-20 19:01:46 +0530843 frappe.db.set_value(
844 'Purchase Order Item', {
845 'sales_order': self.name,
846 'sales_order_item': ['in', so_items],
847 'docstatus': ['<', 2]
848 },{
849 'sales_order': None,
850 'sales_order_item': None
851 }
852 )
GangaManoj8396f242021-09-20 19:01:46 +0530853
854 frappe.msgprint(_("Purchase Orders {0} are un-linked").format("\n".join(linked_po)))
855
Deepesh Gargd18dde72020-11-29 21:40:04 +0530856 def get_tax_map(self):
857 tax_map = {}
858 for tax in self.get('taxes'):
859 tax_map.setdefault(tax.account_head, 0.0)
860 tax_map[tax.account_head] += tax.tax_amount
861
862 return tax_map
863
Deepesh Garg92f7a5a2021-07-21 15:25:40 +0530864 def get_amount_and_base_amount(self, item, enable_discount_accounting):
865 amount = item.net_amount
866 base_amount = item.base_net_amount
867
868 if enable_discount_accounting and self.get('discount_amount') and self.get('additional_discount_account'):
869 amount = item.amount
870 base_amount = item.base_amount
871
872 return amount, base_amount
873
874 def get_tax_amounts(self, tax, enable_discount_accounting):
875 amount = tax.tax_amount_after_discount_amount
876 base_amount = tax.base_tax_amount_after_discount_amount
877
878 if enable_discount_accounting and self.get('discount_amount') and self.get('additional_discount_account') \
879 and self.get('apply_discount_on') == 'Grand Total':
880 amount = tax.tax_amount
881 base_amount = tax.base_tax_amount
882
883 return amount, base_amount
884
GangaManoj8f7b0a12021-07-13 03:01:02 +0530885 def make_discount_gl_entries(self, gl_entries):
886 enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
887
888 if enable_discount_accounting:
GangaManoj4105e272021-07-20 03:46:02 +0530889 if self.doctype == "Purchase Invoice":
890 dr_or_cr = "credit"
891 rev_dr_cr = "debit"
892 supplier_or_customer = self.supplier
Ankush Menat4551d7d2021-08-19 13:41:10 +0530893
GangaManoj4105e272021-07-20 03:46:02 +0530894 else:
895 dr_or_cr = "debit"
896 rev_dr_cr = "credit"
897 supplier_or_customer = self.customer
898
GangaManoj8f7b0a12021-07-13 03:01:02 +0530899 for item in self.get("items"):
900 if item.get('discount_amount') and item.get('discount_account'):
Deepesh Gargad7bb312021-07-28 11:38:44 +0530901 discount_amount = item.discount_amount * item.qty
GangaManoj8f7b0a12021-07-13 03:01:02 +0530902 if self.doctype == "Purchase Invoice":
GangaManoj8f7b0a12021-07-13 03:01:02 +0530903 income_or_expense_account = (item.expense_account
Ankush Menat4551d7d2021-08-19 13:41:10 +0530904 if (not item.enable_deferred_expense or self.is_return)
GangaManoj8f7b0a12021-07-13 03:01:02 +0530905 else item.deferred_expense_account)
906 else:
GangaManoj8f7b0a12021-07-13 03:01:02 +0530907 income_or_expense_account = (item.income_account
Ankush Menat4551d7d2021-08-19 13:41:10 +0530908 if (not item.enable_deferred_revenue or self.is_return)
GangaManoj8f7b0a12021-07-13 03:01:02 +0530909 else item.deferred_revenue_account)
910
911 account_currency = get_account_currency(item.discount_account)
912 gl_entries.append(
913 self.get_gl_dict({
914 "account": item.discount_account,
915 "against": supplier_or_customer,
Deepesh Gargad7bb312021-07-28 11:38:44 +0530916 dr_or_cr: flt(discount_amount, item.precision('discount_amount')),
Ankush Menat4551d7d2021-08-19 13:41:10 +0530917 dr_or_cr + "_in_account_currency": flt(discount_amount * self.get('conversion_rate'),
Deepesh Gargad7bb312021-07-28 11:38:44 +0530918 item.precision('discount_amount')),
Ganga Manoj980798c2021-07-19 23:43:36 +0530919 "cost_center": item.cost_center,
Ganga Manoj63b7ecd2021-07-19 23:44:21 +0530920 "project": item.project
Ganga Manoj0ea29342021-07-19 23:44:55 +0530921 }, account_currency, item=item)
GangaManoj8f7b0a12021-07-13 03:01:02 +0530922 )
923
924 account_currency = get_account_currency(income_or_expense_account)
925 gl_entries.append(
926 self.get_gl_dict({
927 "account": income_or_expense_account,
928 "against": supplier_or_customer,
Deepesh Gargad7bb312021-07-28 11:38:44 +0530929 rev_dr_cr: flt(discount_amount, item.precision('discount_amount')),
Ankush Menat4551d7d2021-08-19 13:41:10 +0530930 rev_dr_cr + "_in_account_currency": flt(discount_amount * self.get('conversion_rate'),
Deepesh Gargad7bb312021-07-28 11:38:44 +0530931 item.precision('discount_amount')),
GangaManoj8f7b0a12021-07-13 03:01:02 +0530932 "cost_center": item.cost_center,
933 "project": item.project or self.project
934 }, account_currency, item=item)
935 )
GangaManoj857501c2021-07-15 22:03:46 +0530936
GangaManoj4105e272021-07-20 03:46:02 +0530937 if self.get('discount_amount') and self.get('additional_discount_account'):
GangaManoj857501c2021-07-15 22:03:46 +0530938 gl_entries.append(
939 self.get_gl_dict({
940 "account": self.additional_discount_account,
GangaManoj4105e272021-07-20 03:46:02 +0530941 "against": supplier_or_customer,
942 dr_or_cr: self.discount_amount,
943 "cost_center": self.cost_center
944 }, item=self)
Ankush Menat4551d7d2021-08-19 13:41:10 +0530945 )
946
Deepesh Gargc9da1fc2021-05-19 18:38:35 +0530947
Nabin Hait19d945a2013-07-29 18:35:39 +0530948 def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield):
Nabin Hait868766d2019-07-15 18:02:58 +0530949 from erpnext.controllers.status_updater import get_allowance_for
Saqib9c913c92021-11-26 12:00:13 +0530950
Nabin Hait868766d2019-07-15 18:02:58 +0530951 item_allowance = {}
952 global_qty_allowance, global_amount_allowance = None, None
Anand Doshid2946502014-04-08 20:10:03 +0530953
Ankush Menat648b2d72021-09-20 15:27:12 +0530954 role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
955 user_roles = frappe.get_roles()
956
Ankush Menat43bf82b2021-09-20 16:31:20 +0530957 total_overbilled_amt = 0.0
958
Nabin Hait4b8185d2014-12-25 18:19:39 +0530959 for item in self.get("items"):
Ankush Menat5e4fbba2021-09-20 16:36:27 +0530960 if not item.get(item_ref_dn):
961 continue
Anand Doshid2946502014-04-08 20:10:03 +0530962
Ankush Menat5e4fbba2021-09-20 16:36:27 +0530963 ref_amt = flt(frappe.db.get_value(ref_dt + " Item",
964 item.get(item_ref_dn), based_on), self.precision(based_on, item))
965 if not ref_amt:
966 frappe.msgprint(
Ankush Menat21a955d2021-09-20 16:58:23 +0530967 _("System will not check overbilling since amount for Item {0} in {1} is zero")
968 .format(item.item_code, ref_dt), title=_("Warning"), indicator="orange")
Ankush Menat5e4fbba2021-09-20 16:36:27 +0530969 continue
Anand Doshid2946502014-04-08 20:10:03 +0530970
Saqib9c913c92021-11-26 12:00:13 +0530971 already_billed = self.get_billed_amount_for_item(item, item_ref_dn, based_on)
Anand Doshid2946502014-04-08 20:10:03 +0530972
Ankush Menat5e4fbba2021-09-20 16:36:27 +0530973 total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)),
974 self.precision(based_on, item))
Anand Doshid2946502014-04-08 20:10:03 +0530975
Ankush Menat5e4fbba2021-09-20 16:36:27 +0530976 allowance, item_allowance, global_qty_allowance, global_amount_allowance = \
977 get_allowance_for(item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount")
rohitwaghchaure285344e2019-10-11 11:02:11 +0530978
Ankush Menat5e4fbba2021-09-20 16:36:27 +0530979 max_allowed_amt = flt(ref_amt * (100 + allowance) / 100)
Deepesh Gargcb718fc2021-04-19 13:25:15 +0530980
Ankush Menat5e4fbba2021-09-20 16:36:27 +0530981 if total_billed_amt < 0 and max_allowed_amt < 0:
982 # while making debit note against purchase return entry(purchase receipt) getting overbill error
983 total_billed_amt = abs(total_billed_amt)
984 max_allowed_amt = abs(max_allowed_amt)
Afshan53fefd72021-06-24 10:09:02 +0530985
Ankush Menat5e4fbba2021-09-20 16:36:27 +0530986 overbill_amt = total_billed_amt - max_allowed_amt
987 total_overbilled_amt += overbill_amt
988
989 if overbill_amt > 0.01 and role_allowed_to_over_bill not in user_roles:
990 if self.doctype != "Purchase Invoice":
991 self.throw_overbill_exception(item, max_allowed_amt)
992 elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")):
993 self.throw_overbill_exception(item, max_allowed_amt)
994
995 if role_allowed_to_over_bill in user_roles and total_overbilled_amt > 0.1:
Ankush Menat21a955d2021-09-20 16:58:23 +0530996 frappe.msgprint(_("Overbilling of {} ignored because you have {} role.")
Ankush Menat6ec047c2021-10-27 10:30:05 +0530997 .format(total_overbilled_amt, role_allowed_to_over_bill), indicator="orange", alert=True)
Afshan53fefd72021-06-24 10:09:02 +0530998
Saqib9c913c92021-11-26 12:00:13 +0530999 def get_billed_amount_for_item(self, item, item_ref_dn, based_on):
1000 '''
1001 Returns Sum of Amount of
1002 Sales/Purchase Invoice Items
1003 that are linked to `item_ref_dn` (`dn_detail` / `pr_detail`)
1004 that are submitted OR not submitted but are under current invoice
1005 '''
1006
1007 from frappe.query_builder import Criterion
1008 from frappe.query_builder.functions import Sum
1009
1010 item_doctype = frappe.qb.DocType(item.doctype)
1011 based_on_field = frappe.qb.Field(based_on)
1012 join_field = frappe.qb.Field(item_ref_dn)
1013
1014 result = (
1015 frappe.qb.from_(item_doctype)
1016 .select(Sum(based_on_field))
1017 .where(
1018 join_field == item.get(item_ref_dn)
1019 ).where(
1020 Criterion.any([ # select all items from other invoices OR current invoices
1021 Criterion.all([ # for selecting items from other invoices
1022 item_doctype.docstatus == 1,
1023 item_doctype.parent != self.name
1024 ]),
1025 Criterion.all([ # for selecting items from current invoice, that are linked to same reference
1026 item_doctype.docstatus == 0,
1027 item_doctype.parent == self.name,
1028 item_doctype.name != item.name
1029 ])
1030 ])
1031 )
1032 ).run()
1033
1034 return result[0][0] if result else 0
1035
Afshan53fefd72021-06-24 10:09:02 +05301036 def throw_overbill_exception(self, item, max_allowed_amt):
1037 frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
1038 .format(item.item_code, item.idx, max_allowed_amt))
Anand Doshid2946502014-04-08 20:10:03 +05301039
Rohit Waghchaure2a14f252021-07-30 12:36:35 +05301040 def get_company_default(self, fieldname, ignore_validation=False):
Rushabh Mehta1f847992013-12-12 19:12:19 +05301041 from erpnext.accounts.utils import get_company_default
Rohit Waghchaure2a14f252021-07-30 12:36:35 +05301042 return get_company_default(self.company, fieldname, ignore_validation=ignore_validation)
Anand Doshid2946502014-04-08 20:10:03 +05301043
Nabin Haita36adbd2013-08-02 14:50:12 +05301044 def get_stock_items(self):
1045 stock_items = []
Nabin Haitdd38a262014-12-26 13:15:21 +05301046 item_codes = list(set(item.item_code for item in self.get("items")))
Nabin Haita36adbd2013-08-02 14:50:12 +05301047 if item_codes:
Nabin Hait868766d2019-07-15 18:02:58 +05301048 stock_items = [r[0] for r in frappe.db.sql("""
1049 select name from `tabItem`
1050 where name in (%s) and is_stock_item=1
Ankush Menat8fe5feb2021-11-04 19:48:32 +05301051 """ % (", ".join(["%s"] * len(item_codes)),), item_codes)]
Anand Doshid2946502014-04-08 20:10:03 +05301052
Nabin Haita36adbd2013-08-02 14:50:12 +05301053 return stock_items
Anand Doshid2946502014-04-08 20:10:03 +05301054
Ankit Javalkar8e7ca412014-09-12 15:18:53 +05301055 def set_total_advance_paid(self):
1056 if self.doctype == "Sales Order":
Nabin Hait6e439a52015-08-28 19:24:22 +05301057 dr_or_cr = "credit_in_account_currency"
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +05301058 rev_dr_or_cr = "debit_in_account_currency"
Nabin Haitb2206d12016-01-27 15:43:12 +05301059 party = self.customer
Ankit Javalkar8e7ca412014-09-12 15:18:53 +05301060 else:
Nabin Hait6e439a52015-08-28 19:24:22 +05301061 dr_or_cr = "debit_in_account_currency"
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +05301062 rev_dr_or_cr = "credit_in_account_currency"
Nabin Haitb2206d12016-01-27 15:43:12 +05301063 party = self.supplier
Ankit Javalkar8e7ca412014-09-12 15:18:53 +05301064
Nabin Haitb2206d12016-01-27 15:43:12 +05301065 advance = frappe.db.sql("""
Ankit Javalkar8e7ca412014-09-12 15:18:53 +05301066 select
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +05301067 account_currency, sum({dr_or_cr}) - sum({rev_dr_cr}) as amount
Ankit Javalkar8e7ca412014-09-12 15:18:53 +05301068 from
Nabin Hait12e2a512016-06-26 01:37:21 +05301069 `tabGL Entry`
Ankit Javalkar8e7ca412014-09-12 15:18:53 +05301070 where
Nabin Hait12e2a512016-06-26 01:37:21 +05301071 against_voucher_type = %s and against_voucher = %s and party=%s
1072 and docstatus = 1
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +05301073 """.format(dr_or_cr=dr_or_cr, rev_dr_cr=rev_dr_or_cr), (self.doctype, self.name, party), as_dict=1) #nosec
Ankit Javalkar8e7ca412014-09-12 15:18:53 +05301074
Nabin Haitb2206d12016-01-27 15:43:12 +05301075 if advance:
Anand Doshi7c0a58a2016-01-29 12:16:24 +05301076 advance = advance[0]
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +05301077
Anand Doshi7c0a58a2016-01-29 12:16:24 +05301078 advance_paid = flt(advance.amount, self.precision("advance_paid"))
1079 formatted_advance_paid = fmt_money(advance_paid, precision=self.precision("advance_paid"),
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001080 currency=advance.account_currency)
Anand Doshi7c0a58a2016-01-29 12:16:24 +05301081
1082 frappe.db.set_value(self.doctype, self.name, "party_account_currency",
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001083 advance.account_currency)
Anand Doshi7c0a58a2016-01-29 12:16:24 +05301084
1085 if advance.account_currency == self.currency:
finbyz5efc7972019-01-05 11:12:11 +05301086 order_total = self.get("rounded_total") or self.grand_total
1087 precision = "rounded_total" if self.get("rounded_total") else "grand_total"
Nabin Haitb2206d12016-01-27 15:43:12 +05301088 else:
finbyz5efc7972019-01-05 11:12:11 +05301089 order_total = self.get("base_rounded_total") or self.base_grand_total
1090 precision = "base_rounded_total" if self.get("base_rounded_total") else "base_grand_total"
Sagar Voraf733a392019-01-07 14:24:01 +05301091
finbyz5efc7972019-01-05 11:12:11 +05301092 formatted_order_total = fmt_money(order_total, precision=self.precision(precision),
1093 currency=advance.account_currency)
Anand Doshi7c0a58a2016-01-29 12:16:24 +05301094
Nabin Hait9db1b222016-06-30 12:37:53 +05301095 if self.currency == self.company_currency and advance_paid > order_total:
Nabin Haitb2206d12016-01-27 15:43:12 +05301096 frappe.throw(_("Total advance ({0}) against Order {1} cannot be greater than the Grand Total ({2})")
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001097 .format(formatted_advance_paid, self.name, formatted_order_total))
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301098
Nabin Hait13093b42016-06-29 18:04:37 +05301099 frappe.db.set_value(self.doctype, self.name, "advance_paid", advance_paid)
Ankit Javalkar8e7ca412014-09-12 15:18:53 +05301100
ankitjavalkarworke60822b2014-08-26 14:29:06 +05301101 @property
1102 def company_abbr(self):
Nabin Hait6a5695f2018-08-09 11:30:21 +05301103 if not hasattr(self, "_abbr"):
1104 self._abbr = frappe.db.get_value('Company', self.company, "abbr")
ankitjavalkarworke60822b2014-08-26 14:29:06 +05301105
1106 return self._abbr
1107
marination4be5b5c2020-10-08 19:08:27 +05301108 def raise_missing_debit_credit_account_error(self, party_type, party):
marination53b1a9a2020-11-03 15:45:25 +05301109 """Raise an error if debit to/credit to account does not exist."""
marination4be5b5c2020-10-08 19:08:27 +05301110 db_or_cr = frappe.bold("Debit To") if self.doctype == "Sales Invoice" else frappe.bold("Credit To")
1111 rec_or_pay = "Receivable" if self.doctype == "Sales Invoice" else "Payable"
1112
1113 link_to_party = frappe.utils.get_link_to_form(party_type, party)
1114 link_to_company = frappe.utils.get_link_to_form("Company", self.company)
1115
1116 message = _("{0} Account not found against Customer {1}.").format(db_or_cr, frappe.bold(party) or '')
1117 message += "<br>" + _("Please set one of the following:") + "<br>"
1118 message += "<br><ul><li>" + _("'Account' in the Accounting section of Customer {0}").format(link_to_party) + "</li>"
1119 message += "<li>" + _("'Default {0} Account' in Company {1}").format(rec_or_pay, link_to_company) + "</li></ul>"
1120
marination53b1a9a2020-11-03 15:45:25 +05301121 frappe.throw(message, title=_("Account Missing"), exc=AccountMissingError)
marination4be5b5c2020-10-08 19:08:27 +05301122
Neil Trini Lasrado3fbbb712015-07-17 12:10:12 +05301123 def validate_party(self):
Nabin Hait4ffd7f32015-08-27 12:28:36 +05301124 party_type, party = self.get_party()
shreyaseba9ca42016-01-26 14:56:52 +05301125 validate_party_frozen_disabled(party_type, party)
Anand Doshi979326b2015-09-11 16:22:37 +05301126
Nabin Hait4ffd7f32015-08-27 12:28:36 +05301127 def get_party(self):
Neil Trini Lasrado79bf2332015-07-20 13:03:48 +05301128 party_type = None
shreyas29b565f2016-01-25 17:30:49 +05301129 if self.doctype in ("Opportunity", "Quotation", "Sales Order", "Delivery Note", "Sales Invoice"):
shreyaseba9ca42016-01-26 14:56:52 +05301130 party_type = 'Customer'
shreyas29b565f2016-01-25 17:30:49 +05301131
1132 elif self.doctype in ("Supplier Quotation", "Purchase Order", "Purchase Receipt", "Purchase Invoice"):
shreyaseba9ca42016-01-26 14:56:52 +05301133 party_type = 'Supplier'
shreyas29b565f2016-01-25 17:30:49 +05301134
1135 elif self.meta.get_field("customer"):
1136 party_type = "Customer"
Neil Trini Lasrado3fbbb712015-07-17 12:10:12 +05301137
Neil Trini Lasrado79bf2332015-07-20 13:03:48 +05301138 elif self.meta.get_field("supplier"):
shreyas29b565f2016-01-25 17:30:49 +05301139 party_type = "Supplier"
Anand Doshi979326b2015-09-11 16:22:37 +05301140
Nabin Hait4ffd7f32015-08-27 12:28:36 +05301141 party = self.get(party_type.lower()) if party_type else None
Anand Doshi979326b2015-09-11 16:22:37 +05301142
Nabin Hait4ffd7f32015-08-27 12:28:36 +05301143 return party_type, party
Anand Doshi979326b2015-09-11 16:22:37 +05301144
Nabin Hait4ffd7f32015-08-27 12:28:36 +05301145 def validate_currency(self):
Nabin Hait6e439a52015-08-28 19:24:22 +05301146 if self.get("currency"):
Nabin Hait4ffd7f32015-08-27 12:28:36 +05301147 party_type, party = self.get_party()
Nabin Hait6e439a52015-08-28 19:24:22 +05301148 if party_type and party:
Anand Doshib20baf82015-09-25 16:17:50 +05301149 party_account_currency = get_party_account_currency(party_type, party, self.company)
Anand Doshi979326b2015-09-11 16:22:37 +05301150
Anand Doshi7afaeb02015-10-01 18:55:25 +05301151 if (party_account_currency
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001152 and party_account_currency != self.company_currency
1153 and self.currency != party_account_currency):
Nabin Hait98096772015-09-03 10:28:08 +05301154 frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}")
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001155 .format(party_type, party, party_account_currency), InvalidCurrency)
Anand Doshi979326b2015-09-11 16:22:37 +05301156
shreyas29b565f2016-01-25 17:30:49 +05301157 # Note: not validating with gle account because we don't have the account
1158 # at quotation / sales order level and we shouldn't stop someone
1159 # from creating a sales invoice if sales order is already created
Rushabh Mehta2cb7a9f2016-06-15 16:45:03 +05301160
Nabin Hait297d74a2016-11-23 15:58:51 +05301161 def delink_advance_entries(self, linked_doc_name):
1162 total_allocated_amount = 0
1163 for adv in self.advances:
1164 consider_for_total_advance = True
1165 if adv.reference_name == linked_doc_name:
1166 frappe.db.sql("""delete from `tab{0} Advance`
1167 where name = %s""".format(self.doctype), adv.name)
1168 consider_for_total_advance = False
1169
1170 if consider_for_total_advance:
1171 total_allocated_amount += flt(adv.allocated_amount, adv.precision("allocated_amount"))
1172
Rushabh Mehta66958302017-01-16 16:57:53 +05301173 frappe.db.set_value(self.doctype, self.name, "total_advance",
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001174 total_allocated_amount, update_modified=False)
Rohit Waghchaure6087fe12016-04-09 14:31:09 +05301175
KanchanChauhan49a50fe2016-11-18 13:57:53 +05301176 def group_similar_items(self):
1177 group_item_qty = {}
1178 group_item_amount = {}
Shreya Shah785f1aa2018-10-11 10:14:25 +05301179 # to update serial number in print
1180 count = 0
KanchanChauhan49a50fe2016-11-18 13:57:53 +05301181
1182 for item in self.items:
1183 group_item_qty[item.item_code] = group_item_qty.get(item.item_code, 0) + item.qty
1184 group_item_amount[item.item_code] = group_item_amount.get(item.item_code, 0) + item.amount
1185
1186 duplicate_list = []
KanchanChauhan49a50fe2016-11-18 13:57:53 +05301187 for item in self.items:
1188 if item.item_code in group_item_qty:
Shreya Shah785f1aa2018-10-11 10:14:25 +05301189 count += 1
KanchanChauhan49a50fe2016-11-18 13:57:53 +05301190 item.qty = group_item_qty[item.item_code]
1191 item.amount = group_item_amount[item.item_code]
deepeshgarg007e8d21cd2019-06-24 17:46:44 +05301192
1193 if item.qty:
1194 item.rate = flt(flt(item.amount) / flt(item.qty), item.precision("rate"))
1195 else:
1196 item.rate = 0
1197
Shreya Shah785f1aa2018-10-11 10:14:25 +05301198 item.idx = count
KanchanChauhan49a50fe2016-11-18 13:57:53 +05301199 del group_item_qty[item.item_code]
1200 else:
1201 duplicate_list.append(item)
KanchanChauhan49a50fe2016-11-18 13:57:53 +05301202 for item in duplicate_list:
1203 self.remove(item)
1204
tunde32aa7c12017-09-07 06:52:15 +01001205 def set_payment_schedule(self):
Manas Solankia7f55892018-04-02 10:32:00 +05301206 if self.doctype == 'Sales Invoice' and self.is_pos:
1207 self.payment_terms_template = ''
1208 return
rohitwaghchauredb9fa782018-03-01 10:32:29 +05301209
Deepesh Garg26210162020-08-11 16:06:13 +05301210 party_account_currency = self.get('party_account_currency')
1211 if not party_account_currency:
1212 party_type, party = self.get_party()
1213
1214 if party_type and party:
1215 party_account_currency = get_party_account_currency(party_type, party, self.company)
1216
rohitwaghchaureda941af2018-01-17 16:23:04 +05301217 posting_date = self.get("bill_date") or self.get("posting_date") or self.get("transaction_date")
tundedf3a1752017-09-11 11:02:57 +01001218 date = self.get("due_date")
1219 due_date = date or posting_date
Deepesh Garg26210162020-08-11 16:06:13 +05301220
Saqib Ansarid552fe62021-04-23 14:46:52 +05301221 base_grand_total = self.get("base_rounded_total") or self.base_grand_total
1222 grand_total = self.get("rounded_total") or self.grand_total
Deepesh Garg26210162020-08-11 16:06:13 +05301223
Nabin Hait2f9f4f92017-12-20 12:24:59 +05301224 if self.doctype in ("Sales Invoice", "Purchase Invoice"):
Saqib Ansarid552fe62021-04-23 14:46:52 +05301225 base_grand_total = base_grand_total - flt(self.base_write_off_amount)
Nabin Hait2f9f4f92017-12-20 12:24:59 +05301226 grand_total = grand_total - flt(self.write_off_amount)
Deepesh Gargbcf56e62021-08-06 23:53:16 +05301227 po_or_so, doctype, fieldname = self.get_order_details()
1228 automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms'))
tunde96b8f222017-09-08 15:35:59 +01001229
Rohit Waghchaure4a267b92019-05-09 19:50:00 +05301230 if self.get("total_advance"):
Saqib Ansarid552fe62021-04-23 14:46:52 +05301231 if party_account_currency == self.company_currency:
1232 base_grand_total -= self.get("total_advance")
1233 grand_total = flt(base_grand_total / self.get("conversion_rate"), self.precision("grand_total"))
1234 else:
1235 grand_total -= self.get("total_advance")
1236 base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total"))
Rohit Waghchaure4a267b92019-05-09 19:50:00 +05301237
Nabin Hait0551f7b2017-11-21 19:58:16 +05301238 if not self.get("payment_schedule"):
Deepesh Gargbff3b092021-08-07 00:12:57 +05301239 if self.doctype in ["Sales Invoice", "Purchase Invoice"] and automatically_fetch_payment_terms \
1240 and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype):
Deepesh Gargbcf56e62021-08-06 23:53:16 +05301241 self.fetch_payment_terms_from_order(po_or_so, doctype)
1242 if self.get('payment_terms_template'):
1243 self.ignore_default_payment_terms_template = 1
1244 elif self.get("payment_terms_template"):
Saqib Ansarid552fe62021-04-23 14:46:52 +05301245 data = get_payment_terms(self.payment_terms_template, posting_date, grand_total, base_grand_total)
Nabin Hait0551f7b2017-11-21 19:58:16 +05301246 for item in data:
1247 self.append("payment_schedule", item)
GangaManoj4323f4b2021-07-22 05:57:42 +05301248 elif self.doctype not in ["Purchase Receipt"]:
Saqib Ansarida5b55c2021-04-28 14:55:33 +05301249 data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total, base_payment_amount=base_grand_total)
Nabin Hait0551f7b2017-11-21 19:58:16 +05301250 self.append("payment_schedule", data)
Deepesh Gargbcf56e62021-08-06 23:53:16 +05301251
1252 for d in self.get("payment_schedule"):
1253 if d.invoice_portion:
1254 d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
1255 d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('base_payment_amount'))
1256 d.outstanding = d.payment_amount
1257 elif not d.invoice_portion:
Frappe PR Bot81fb4932021-08-31 19:01:03 +05301258 d.base_payment_amount = flt(d.payment_amount * self.get("conversion_rate"), d.precision('base_payment_amount'))
Subin Tomfac88a32021-07-23 21:23:48 +05301259
tunde43870aa2017-08-18 11:59:30 +01001260
GangaManoj4323f4b2021-07-22 05:57:42 +05301261 def get_order_details(self):
1262 if self.doctype == "Sales Invoice":
1263 po_or_so = self.get('items')[0].get('sales_order')
1264 po_or_so_doctype = "Sales Order"
1265 po_or_so_doctype_name = "sales_order"
1266
tunde43870aa2017-08-18 11:59:30 +01001267 else:
GangaManoj4323f4b2021-07-22 05:57:42 +05301268 po_or_so = self.get('items')[0].get('purchase_order')
1269 po_or_so_doctype = "Purchase Order"
1270 po_or_so_doctype_name = "purchase_order"
Ankush Menat4551d7d2021-08-19 13:41:10 +05301271
GangaManoj4323f4b2021-07-22 05:57:42 +05301272 return po_or_so, po_or_so_doctype, po_or_so_doctype_name
1273
GangaManojc7c90242021-07-29 19:18:35 +05301274 def linked_order_has_payment_terms(self, po_or_so, fieldname, doctype):
GangaManoj4323f4b2021-07-22 05:57:42 +05301275 if po_or_so and self.all_items_have_same_po_or_so(po_or_so, fieldname):
GangaManojc7c90242021-07-29 19:18:35 +05301276 if self.linked_order_has_payment_terms_template(po_or_so, doctype):
GangaManoj4323f4b2021-07-22 05:57:42 +05301277 return True
1278 elif self.linked_order_has_payment_schedule(po_or_so):
1279 return True
Ankush Menat4551d7d2021-08-19 13:41:10 +05301280
GangaManoj4323f4b2021-07-22 05:57:42 +05301281 return False
1282
1283 def all_items_have_same_po_or_so(self, po_or_so, fieldname):
1284 for item in self.get('items'):
1285 if item.get(fieldname) != po_or_so:
1286 return False
Ankush Menat4551d7d2021-08-19 13:41:10 +05301287
GangaManoj4323f4b2021-07-22 05:57:42 +05301288 return True
1289
GangaManojc7c90242021-07-29 19:18:35 +05301290 def linked_order_has_payment_terms_template(self, po_or_so, doctype):
1291 return frappe.get_value(doctype, po_or_so, 'payment_terms_template')
GangaManoj4323f4b2021-07-22 05:57:42 +05301292
1293 def linked_order_has_payment_schedule(self, po_or_so):
1294 return frappe.get_all('Payment Schedule', filters={'parent': po_or_so})
1295
1296 def fetch_payment_terms_from_order(self, po_or_so, po_or_so_doctype):
1297 """
1298 Fetch Payment Terms from Purchase/Sales Order on creating a new Purchase/Sales Invoice.
1299 """
1300 po_or_so = frappe.get_cached_doc(po_or_so_doctype, po_or_so)
1301
1302 self.payment_schedule = []
1303 self.payment_terms_template = po_or_so.payment_terms_template
1304
1305 for schedule in po_or_so.payment_schedule:
1306 payment_schedule = {
1307 'payment_term': schedule.payment_term,
1308 'due_date': schedule.due_date,
1309 'invoice_portion': schedule.invoice_portion,
GangaManoj5b33e752021-08-05 21:50:09 +05301310 'mode_of_payment': schedule.mode_of_payment,
1311 'description': schedule.description
GangaManoj4323f4b2021-07-22 05:57:42 +05301312 }
GangaManoj5b33e752021-08-05 21:50:09 +05301313
1314 if schedule.discount_type == 'Percentage':
1315 payment_schedule['discount_type'] = schedule.discount_type
1316 payment_schedule['discount'] = schedule.discount
1317
GangaManoj4323f4b2021-07-22 05:57:42 +05301318 self.append("payment_schedule", payment_schedule)
tunde43870aa2017-08-18 11:59:30 +01001319
tundebe1b8712017-08-19 08:21:44 +01001320 def set_due_date(self):
Nabin Hait0551f7b2017-11-21 19:58:16 +05301321 due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date]
1322 if due_dates:
1323 self.due_date = max(due_dates)
tunde62af5c52017-09-22 15:16:38 +01001324
1325 def validate_payment_schedule_dates(self):
tunde77ecacc2017-09-22 23:12:55 +01001326 dates = []
tunde9bed2de2017-09-25 10:19:35 +01001327 li = []
tunde43870aa2017-08-18 11:59:30 +01001328
rohitwaghchauredb9fa782018-03-01 10:32:29 +05301329 if self.doctype == 'Sales Invoice' and self.is_pos: return
1330
tunde43870aa2017-08-18 11:59:30 +01001331 for d in self.get("payment_schedule"):
Nabin Hait2f9f4f92017-12-20 12:24:59 +05301332 if self.doctype == "Sales Order" and getdate(d.due_date) < getdate(self.transaction_date):
Michelle Alva7f2477b2020-04-09 17:23:23 +05301333 frappe.throw(_("Row {0}: Due Date in the Payment Terms table cannot be before Posting Date").format(d.idx))
tunde77ecacc2017-09-22 23:12:55 +01001334 elif d.due_date in dates:
mnatalia5f0e8d62018-05-22 06:38:50 +03001335 li.append(_("{0} in row {1}").format(d.due_date, d.idx))
tunde9bed2de2017-09-25 10:19:35 +01001336 dates.append(d.due_date)
1337
1338 if li:
1339 duplicates = '<br>' + '<br>'.join(li)
Faris Ansari2ab6e0e2018-09-19 13:13:59 +05301340 frappe.throw(_("Rows with duplicate due dates in other rows were found: {0}").format(duplicates))
tunde43870aa2017-08-18 11:59:30 +01001341
tunde62af5c52017-09-22 15:16:38 +01001342 def validate_payment_schedule_amount(self):
rohitwaghchauredb9fa782018-03-01 10:32:29 +05301343 if self.doctype == 'Sales Invoice' and self.is_pos: return
1344
Deepesh Garg26210162020-08-11 16:06:13 +05301345 party_account_currency = self.get('party_account_currency')
1346 if not party_account_currency:
1347 party_type, party = self.get_party()
1348
1349 if party_type and party:
1350 party_account_currency = get_party_account_currency(party_type, party, self.company)
1351
Nabin Hait0551f7b2017-11-21 19:58:16 +05301352 if self.get("payment_schedule"):
1353 total = 0
Saqib Ansarid552fe62021-04-23 14:46:52 +05301354 base_total = 0
Nabin Hait0551f7b2017-11-21 19:58:16 +05301355 for d in self.get("payment_schedule"):
Deepesh Garg9c170522021-10-25 20:06:24 +05301356 total += flt(d.payment_amount, d.precision("payment_amount"))
1357 base_total += flt(d.base_payment_amount, d.precision("base_payment_amount"))
tunde43870aa2017-08-18 11:59:30 +01001358
Saqib Ansarid552fe62021-04-23 14:46:52 +05301359 base_grand_total = self.get("base_rounded_total") or self.base_grand_total
1360 grand_total = self.get("rounded_total") or self.grand_total
Rohit Waghchaure4a267b92019-05-09 19:50:00 +05301361
Nabin Haite591c852017-12-21 11:46:30 +05301362 if self.doctype in ("Sales Invoice", "Purchase Invoice"):
Saqib Ansarid552fe62021-04-23 14:46:52 +05301363 base_grand_total = base_grand_total - flt(self.base_write_off_amount)
Nabin Haite591c852017-12-21 11:46:30 +05301364 grand_total = grand_total - flt(self.write_off_amount)
Saqib Ansarid552fe62021-04-23 14:46:52 +05301365
1366 if self.get("total_advance"):
1367 if party_account_currency == self.company_currency:
1368 base_grand_total -= self.get("total_advance")
1369 grand_total = flt(base_grand_total / self.get("conversion_rate"), self.precision("grand_total"))
1370 else:
1371 grand_total -= self.get("total_advance")
1372 base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total"))
Deepesh Garg9c170522021-10-25 20:06:24 +05301373
Deepesh Garg88648572021-11-12 12:39:30 +05301374 if flt(total, self.precision("grand_total")) - flt(grand_total, self.precision("grand_total")) > 0.1 or \
1375 flt(base_total, self.precision("base_grand_total")) - flt(base_grand_total, self.precision("base_grand_total")) > 0.1:
Nabin Hait0551f7b2017-11-21 19:58:16 +05301376 frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total"))
tunde43870aa2017-08-18 11:59:30 +01001377
Nabin Hait877e1bb2017-11-17 12:27:43 +05301378 def is_rounded_total_disabled(self):
1379 if self.meta.get_field("disable_rounded_total"):
1380 return self.disable_rounded_total
1381 else:
1382 return frappe.db.get_single_value("Global Defaults", "disable_rounded_total")
1383
Deepesh Gargf17ea2c2020-12-11 21:30:39 +05301384 def set_inter_company_account(self):
1385 """
1386 Set intercompany account for inter warehouse transactions
1387 This account will be used in case billing company and internal customer's
1388 representation company is same
1389 """
1390
1391 if self.is_internal_transfer() and not self.unrealized_profit_loss_account:
1392 unrealized_profit_loss_account = frappe.db.get_value('Company', self.company, 'unrealized_profit_loss_account')
1393
1394 if not unrealized_profit_loss_account:
1395 msg = _("Please select Unrealized Profit / Loss account or add default Unrealized Profit / Loss account account for company {0}").format(
1396 frappe.bold(self.company))
1397 frappe.throw(msg)
1398
1399 self.unrealized_profit_loss_account = unrealized_profit_loss_account
1400
1401 def is_internal_transfer(self):
1402 """
1403 It will an internal transfer if its an internal customer and representation
1404 company is same as billing company
1405 """
Deepesh Gargb4be2922021-01-28 13:09:56 +05301406 if self.doctype in ('Sales Invoice', 'Delivery Note', 'Sales Order'):
Deepesh Gargf17ea2c2020-12-11 21:30:39 +05301407 internal_party_field = 'is_internal_customer'
Deepesh Gargb4be2922021-01-28 13:09:56 +05301408 elif self.doctype in ('Purchase Invoice', 'Purchase Receipt', 'Purchase Order'):
Deepesh Gargf17ea2c2020-12-11 21:30:39 +05301409 internal_party_field = 'is_internal_supplier'
1410
1411 if self.get(internal_party_field) and (self.represents_company == self.company):
1412 return True
1413
1414 return False
1415
Saqib Ansari977b09b2021-08-19 17:57:30 +05301416 def process_common_party_accounting(self):
1417 is_invoice = self.doctype in ['Sales Invoice', 'Purchase Invoice']
1418 if not is_invoice:
1419 return
1420
1421 if frappe.db.get_single_value('Accounts Settings', 'enable_common_party_accounting'):
1422 party_link = self.get_common_party_link()
1423 if party_link and self.outstanding_amount:
1424 self.create_advance_and_reconcile(party_link)
1425
1426 def get_common_party_link(self):
1427 party_type, party = self.get_party()
Saqib Ansari72543682021-08-25 20:15:23 +05301428 return frappe.db.get_value(
1429 doctype='Party Link',
1430 filters={'secondary_role': party_type, 'secondary_party': party},
1431 fieldname=['primary_role', 'primary_party'],
1432 as_dict=True
1433 )
Saqib Ansari977b09b2021-08-19 17:57:30 +05301434
1435 def create_advance_and_reconcile(self, party_link):
1436 secondary_party_type, secondary_party = self.get_party()
1437 primary_party_type, primary_party = party_link.primary_role, party_link.primary_party
1438
1439 primary_account = get_party_account(primary_party_type, primary_party, self.company)
1440 secondary_account = get_party_account(secondary_party_type, secondary_party, self.company)
1441
1442 jv = frappe.new_doc('Journal Entry')
1443 jv.voucher_type = 'Journal Entry'
Saqib Ansari977b09b2021-08-19 17:57:30 +05301444 jv.posting_date = self.posting_date
1445 jv.company = self.company
1446 jv.remark = 'Adjustment for {} {}'.format(self.doctype, self.name)
1447
1448 reconcilation_entry = frappe._dict()
1449 advance_entry = frappe._dict()
Saqib Ansari977b09b2021-08-19 17:57:30 +05301450
1451 reconcilation_entry.account = secondary_account
1452 reconcilation_entry.party_type = secondary_party_type
1453 reconcilation_entry.party = secondary_party
1454 reconcilation_entry.reference_type = self.doctype
1455 reconcilation_entry.reference_name = self.name
Saqib Ansaric6c7a8b2021-08-25 20:17:04 +05301456 reconcilation_entry.cost_center = self.cost_center
Saqib Ansari977b09b2021-08-19 17:57:30 +05301457
1458 advance_entry.account = primary_account
1459 advance_entry.party_type = primary_party_type
1460 advance_entry.party = primary_party
Saqib Ansaric6c7a8b2021-08-25 20:17:04 +05301461 advance_entry.cost_center = self.cost_center
Saqib Ansari977b09b2021-08-19 17:57:30 +05301462 advance_entry.is_advance = 'Yes'
1463
1464 if self.doctype == 'Sales Invoice':
1465 reconcilation_entry.credit_in_account_currency = self.outstanding_amount
1466 advance_entry.debit_in_account_currency = self.outstanding_amount
1467 else:
1468 advance_entry.credit_in_account_currency = self.outstanding_amount
1469 reconcilation_entry.debit_in_account_currency = self.outstanding_amount
1470
1471 jv.append('accounts', reconcilation_entry)
1472 jv.append('accounts', advance_entry)
1473
1474 jv.save()
1475 jv.submit()
1476
Rushabh Mehta793ba6b2014-02-14 15:47:51 +05301477@frappe.whitelist()
Rushabh Mehtaf56d73c2013-10-10 16:35:09 +05301478def get_tax_rate(account_head):
Rushabh Mehta2cb7a9f2016-06-15 16:45:03 +05301479 return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True)
Rushabh Mehtaa33d4682015-06-01 17:15:42 +05301480
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001481
Nabin Haitffc7f3f2015-05-12 15:07:02 +05301482@frappe.whitelist()
Nabin Haita2426fc2018-01-15 17:45:46 +05301483def get_default_taxes_and_charges(master_doctype, tax_template=None, company=None):
rohitwaghchaure505661b2017-12-11 14:52:28 +05301484 if not company: return {}
1485
Nabin Haita2426fc2018-01-15 17:45:46 +05301486 if tax_template and company:
1487 tax_template_company = frappe.db.get_value(master_doctype, tax_template, "company")
1488 if tax_template_company == company:
1489 return
1490
1491 default_tax = frappe.db.get_value(master_doctype, {"is_default": 1, "company": company})
rohitwaghchaure505661b2017-12-11 14:52:28 +05301492
Rushabh Mehta98aa5812017-11-14 09:32:32 +05301493 return {
1494 'taxes_and_charges': default_tax,
1495 'taxes': get_taxes_and_charges(master_doctype, default_tax)
1496 }
Nabin Hait6b039142014-05-02 15:45:10 +05301497
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001498
Nabin Hait6b039142014-05-02 15:45:10 +05301499@frappe.whitelist()
Nabin Haitffc7f3f2015-05-12 15:07:02 +05301500def get_taxes_and_charges(master_doctype, master_name):
Nabin Hait1fe50122015-05-16 12:14:39 +05301501 if not master_name:
1502 return
Nabin Hait6b039142014-05-02 15:45:10 +05301503 from frappe.model import default_fields
1504 tax_master = frappe.get_doc(master_doctype, master_name)
1505
1506 taxes_and_charges = []
Nabin Haitffc7f3f2015-05-12 15:07:02 +05301507 for i, tax in enumerate(tax_master.get("taxes")):
Nabin Hait6b039142014-05-02 15:45:10 +05301508 tax = tax.as_dict()
1509
1510 for fieldname in default_fields:
1511 if fieldname in tax:
1512 del tax[fieldname]
1513
1514 taxes_and_charges.append(tax)
1515
1516 return taxes_and_charges
Rushabh Mehta66e08e32014-09-29 12:17:03 +05301517
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001518
Rushabh Mehta66e08e32014-09-29 12:17:03 +05301519def validate_conversion_rate(currency, conversion_rate, conversion_rate_label, company):
1520 """common validation for currency and price list currency"""
1521
Rushabh Mehta708e47a2018-08-08 16:37:31 +05301522 company_currency = frappe.get_cached_value('Company', company, "default_currency")
Rushabh Mehta66e08e32014-09-29 12:17:03 +05301523
1524 if not conversion_rate:
Saqib60212ff2020-10-26 11:17:04 +05301525 throw(
1526 _("{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}.")
1527 .format(conversion_rate_label, currency, company_currency)
1528 )
Nabin Hait613d0812015-02-23 11:58:15 +05301529
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001530
Nabin Hait613d0812015-02-23 11:58:15 +05301531def validate_taxes_and_charges(tax):
Deepesh Garg302855e2021-06-14 11:16:39 +05301532 if tax.charge_type in ['Actual', 'On Net Total', 'On Paid Amount'] and tax.row_id:
Nabin Hait613d0812015-02-23 11:58:15 +05301533 frappe.throw(_("Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'"))
Nabin Hait37b047d2015-02-23 16:01:33 +05301534 elif tax.charge_type in ['On Previous Row Amount', 'On Previous Row Total']:
Nabin Hait613d0812015-02-23 11:58:15 +05301535 if cint(tax.idx) == 1:
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001536 frappe.throw(
1537 _("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"))
Nabin Hait613d0812015-02-23 11:58:15 +05301538 elif not tax.row_id:
Suraj Shetty48e9bc32020-01-29 15:06:18 +05301539 frappe.throw(_("Please specify a valid Row ID for row {0} in table {1}").format(tax.idx, _(tax.doctype)))
Nabin Hait613d0812015-02-23 11:58:15 +05301540 elif tax.row_id and cint(tax.row_id) >= cint(tax.idx):
1541 frappe.throw(_("Cannot refer row number greater than or equal to current row number for this Charge type"))
1542
Rushabh Mehta2a21bc92015-02-25 15:08:42 +05301543 if tax.charge_type == "Actual":
Nabin Haitbc6c3602015-02-27 23:40:56 +05301544 tax.rate = None
Rushabh Mehta2a21bc92015-02-25 15:08:42 +05301545
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001546
Anuja Pawar0e337be2021-08-10 17:26:35 +05301547def validate_account_head(tax, doc):
1548 company = frappe.get_cached_value('Account',
1549 tax.account_head, 'company')
1550
1551 if company != doc.company:
1552 frappe.throw(_('Row {0}: Account {1} does not belong to Company {2}')
1553 .format(tax.idx, frappe.bold(tax.account_head), frappe.bold(doc.company)), title=_('Invalid Account'))
1554
1555
1556def validate_cost_center(tax, doc):
1557 if not tax.cost_center:
1558 return
1559
1560 company = frappe.get_cached_value('Cost Center',
1561 tax.cost_center, 'company')
1562
1563 if company != doc.company:
1564 frappe.throw(_('Row {0}: Cost Center {1} does not belong to Company {2}')
1565 .format(tax.idx, frappe.bold(tax.cost_center), frappe.bold(doc.company)), title=_('Invalid Cost Center'))
1566
1567
Nabin Hait613d0812015-02-23 11:58:15 +05301568def validate_inclusive_tax(tax, doc):
1569 def _on_previous_row_error(row_range):
Deepesh Garg302855e2021-06-14 11:16:39 +05301570 throw(_("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(tax.idx, row_range))
Nabin Hait613d0812015-02-23 11:58:15 +05301571
1572 if cint(getattr(tax, "included_in_print_rate", None)):
1573 if tax.charge_type == "Actual":
1574 # inclusive tax cannot be of type Actual
Deepesh Garg5ef9a622021-06-14 14:34:44 +05301575 throw(_("Charge of type 'Actual' in row {0} cannot be included in Item Rate or Paid Amount").format(tax.idx))
Nabin Hait613d0812015-02-23 11:58:15 +05301576 elif tax.charge_type == "On Previous Row Amount" and \
1577 not cint(doc.get("taxes")[cint(tax.row_id) - 1].included_in_print_rate):
1578 # referred row should also be inclusive
1579 _on_previous_row_error(tax.row_id)
1580 elif tax.charge_type == "On Previous Row Total" and \
1581 not all([cint(t.included_in_print_rate) for t in doc.get("taxes")[:cint(tax.row_id) - 1]]):
Deepesh Garg5ef9a622021-06-14 14:34:44 +05301582 # all rows about the referred tax should be inclusive
Nabin Hait613d0812015-02-23 11:58:15 +05301583 _on_previous_row_error("1 - %d" % (tax.row_id,))
Nabin Hait93b3a372015-02-24 17:08:34 +05301584 elif tax.get("category") == "Valuation":
Nabin Hait19ea7212020-08-11 20:34:57 +05301585 frappe.throw(_("Valuation type charges can not be marked as Inclusive"))
Revant Nandgaonkar5da941b2016-02-18 16:02:05 +05301586
Revant Nandgaonkar5da941b2016-02-18 16:02:05 +05301587
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001588def set_balance_in_account_currency(gl_dict, account_currency=None, conversion_rate=None, company_currency=None):
1589 if (not conversion_rate) and (account_currency != company_currency):
1590 frappe.throw(_("Account: {0} with currency: {1} can not be selected")
1591 .format(gl_dict.account, account_currency))
1592
1593 gl_dict["account_currency"] = company_currency if account_currency == company_currency \
Revant Nandgaonkar5da941b2016-02-18 16:02:05 +05301594 else account_currency
1595
1596 # set debit/credit in account currency if not provided
1597 if flt(gl_dict.debit) and not flt(gl_dict.debit_in_account_currency):
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001598 gl_dict.debit_in_account_currency = gl_dict.debit if account_currency == company_currency \
Revant Nandgaonkar5da941b2016-02-18 16:02:05 +05301599 else flt(gl_dict.debit / conversion_rate, 2)
1600
1601 if flt(gl_dict.credit) and not flt(gl_dict.credit_in_account_currency):
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001602 gl_dict.credit_in_account_currency = gl_dict.credit if account_currency == company_currency \
Revant Nandgaonkar5da941b2016-02-18 16:02:05 +05301603 else flt(gl_dict.credit / conversion_rate, 2)
Nabin Hait1991c7b2016-06-27 20:09:05 +05301604
1605
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301606def get_advance_journal_entries(party_type, party, party_account, amount_field,
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001607 order_doctype, order_list, include_unallocated=True):
1608 dr_or_cr = "credit_in_account_currency" if party_type == "Customer" else "debit_in_account_currency"
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301609
Nabin Hait1991c7b2016-06-27 20:09:05 +05301610 conditions = []
1611 if include_unallocated:
1612 conditions.append("ifnull(t2.reference_name, '')=''")
1613
1614 if order_list:
1615 order_condition = ', '.join(['%s'] * len(order_list))
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001616 conditions.append(" (t2.reference_type = '{0}' and ifnull(t2.reference_name, '') in ({1}))" \
1617 .format(order_doctype, order_condition))
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301618
Nabin Hait1991c7b2016-06-27 20:09:05 +05301619 reference_condition = " and (" + " or ".join(conditions) + ")" if conditions else ""
Rushabh Mehta66958302017-01-16 16:57:53 +05301620
Nabin Hait1991c7b2016-06-27 20:09:05 +05301621 journal_entries = frappe.db.sql("""
1622 select
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301623 "Journal Entry" as reference_type, t1.name as reference_name,
Nabin Hait1991c7b2016-06-27 20:09:05 +05301624 t1.remark as remarks, t2.{0} as amount, t2.name as reference_row,
1625 t2.reference_name as against_order
1626 from
1627 `tabJournal Entry` t1, `tabJournal Entry Account` t2
1628 where
1629 t1.name = t2.parent and t2.account = %s
1630 and t2.party_type = %s and t2.party = %s
1631 and t2.is_advance = 'Yes' and t1.docstatus = 1
Nabin Haitca627fb2016-09-05 16:16:53 +05301632 and {1} > 0 {2}
Nabin Hait1991c7b2016-06-27 20:09:05 +05301633 order by t1.posting_date""".format(amount_field, dr_or_cr, reference_condition),
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001634 [party_account, party_type, party] + order_list, as_dict=1)
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301635
Nabin Hait1991c7b2016-06-27 20:09:05 +05301636 return list(journal_entries)
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301637
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001638
Rohit Waghchauref7258162019-01-15 18:12:04 +05301639def get_advance_payment_entries(party_type, party, party_account, order_doctype,
Anuja Pawar3e404f12021-08-31 18:59:29 +05301640 order_list=None, include_unallocated=True, against_all_orders=False, limit=None, condition=None):
Nabin Hait1991c7b2016-06-27 20:09:05 +05301641 party_account_field = "paid_from" if party_type == "Customer" else "paid_to"
Deepesh Gargc7eadfc2020-07-22 17:59:37 +05301642 currency_field = "paid_from_account_currency" if party_type == "Customer" else "paid_to_account_currency"
Nabin Hait1991c7b2016-06-27 20:09:05 +05301643 payment_type = "Receive" if party_type == "Customer" else "Pay"
Saqiba20999c2021-07-12 14:33:23 +05301644 exchange_rate_field = "source_exchange_rate" if payment_type == "Receive" else "target_exchange_rate"
1645
Nabin Hait1991c7b2016-06-27 20:09:05 +05301646 payment_entries_against_order, unallocated_payment_entries = [], []
Nabin Hait34c551d2019-07-03 10:34:31 +05301647 limit_cond = "limit %s" % limit if limit else ""
Nabin Haitff2f3ef2016-07-04 11:41:14 +05301648
Nabin Hait1991c7b2016-06-27 20:09:05 +05301649 if order_list or against_all_orders:
1650 if order_list:
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001651 reference_condition = " and t2.reference_name in ({0})" \
Nabin Hait1991c7b2016-06-27 20:09:05 +05301652 .format(', '.join(['%s'] * len(order_list)))
1653 else:
1654 reference_condition = ""
1655 order_list = []
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301656
Nabin Hait1991c7b2016-06-27 20:09:05 +05301657 payment_entries_against_order = frappe.db.sql("""
1658 select
1659 "Payment Entry" as reference_type, t1.name as reference_name,
1660 t1.remarks, t2.allocated_amount as amount, t2.name as reference_row,
Deepesh Gargc7eadfc2020-07-22 17:59:37 +05301661 t2.reference_name as against_order, t1.posting_date,
Saqiba20999c2021-07-12 14:33:23 +05301662 t1.{0} as currency, t1.{4} as exchange_rate
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301663 from `tabPayment Entry` t1, `tabPayment Entry Reference` t2
Nabin Hait1991c7b2016-06-27 20:09:05 +05301664 where
Deepesh Gargc7eadfc2020-07-22 17:59:37 +05301665 t1.name = t2.parent and t1.{1} = %s and t1.payment_type = %s
Nabin Hait1991c7b2016-06-27 20:09:05 +05301666 and t1.party_type = %s and t1.party = %s and t1.docstatus = 1
Deepesh Gargc7eadfc2020-07-22 17:59:37 +05301667 and t2.reference_doctype = %s {2}
1668 order by t1.posting_date {3}
Saqiba20999c2021-07-12 14:33:23 +05301669 """.format(currency_field, party_account_field, reference_condition, limit_cond, exchange_rate_field),
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001670 [party_account, payment_type, party_type, party,
1671 order_doctype] + order_list, as_dict=1)
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301672
Nabin Hait1991c7b2016-06-27 20:09:05 +05301673 if include_unallocated:
1674 unallocated_payment_entries = frappe.db.sql("""
Anuja Pawar3e404f12021-08-31 18:59:29 +05301675 select "Payment Entry" as reference_type, name as reference_name, posting_date,
1676 remarks, unallocated_amount as amount, {2} as exchange_rate, {3} as currency
Nabin Hait1991c7b2016-06-27 20:09:05 +05301677 from `tabPayment Entry`
1678 where
1679 {0} = %s and party_type = %s and party = %s and payment_type = %s
Anuja Pawar3e404f12021-08-31 18:59:29 +05301680 and docstatus = 1 and unallocated_amount > 0 {condition}
Rohit Waghchauref7258162019-01-15 18:12:04 +05301681 order by posting_date {1}
Anuja Pawar3e404f12021-08-31 18:59:29 +05301682 """.format(party_account_field, limit_cond, exchange_rate_field, currency_field, condition=condition or ""),
Saqiba20999c2021-07-12 14:33:23 +05301683 (party_account, party_type, party, payment_type), as_dict=1)
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301684
Rohit Waghchaure2f1db572016-11-08 12:39:33 +05301685 return list(payment_entries_against_order) + list(unallocated_payment_entries)
1686
1687def update_invoice_status():
Sagar Vorac8b9a552021-09-22 12:11:35 +05301688 """Updates status as Overdue for applicable invoices. Runs daily."""
Sagar Vora8d9d0982021-10-20 19:15:35 +05301689 today = getdate()
Pruthvi Patel6c96ed42021-10-30 16:44:15 +05301690 payment_schedule = frappe.qb.DocType("Payment Schedule")
Sagar Vorac8b9a552021-09-22 12:11:35 +05301691 for doctype in ("Sales Invoice", "Purchase Invoice"):
Pruthvi Patel6c96ed42021-10-30 16:44:15 +05301692 invoice = frappe.qb.DocType(doctype)
1693
1694 consider_base_amount = invoice.party_account_currency != invoice.currency
1695 payment_amount = (
1696 frappe.qb.terms.Case()
1697 .when(consider_base_amount, payment_schedule.base_payment_amount)
1698 .else_(payment_schedule.payment_amount)
Sagar Vora8d9d0982021-10-20 19:15:35 +05301699 )
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001700
Pruthvi Patel6c96ed42021-10-30 16:44:15 +05301701 payable_amount = (
1702 frappe.qb.from_(payment_schedule)
1703 .select(Sum(payment_amount))
1704 .where(
1705 (payment_schedule.parent == invoice.name)
1706 & (payment_schedule.due_date < today)
1707 )
1708 )
1709
1710 total = (
1711 frappe.qb.terms.Case()
1712 .when(invoice.disable_rounded_total, invoice.grand_total)
1713 .else_(invoice.rounded_total)
1714 )
1715
1716 base_total = (
1717 frappe.qb.terms.Case()
1718 .when(invoice.disable_rounded_total, invoice.base_grand_total)
1719 .else_(invoice.base_rounded_total)
1720 )
1721
1722 total_amount = (
1723 frappe.qb.terms.Case()
1724 .when(consider_base_amount, base_total)
1725 .else_(total)
1726 )
1727
1728 is_overdue = total_amount - invoice.outstanding_amount < payable_amount
1729
1730 conditions = (
1731 (invoice.docstatus == 1)
1732 & (invoice.outstanding_amount > 0)
1733 & (
Pruthvi Patel0799f372021-11-12 12:56:29 +05301734 invoice.status.like("Unpaid%")
1735 | invoice.status.like("Partly Paid%")
Pruthvi Patel6c96ed42021-10-30 16:44:15 +05301736 )
1737 & (
Pruthvi Patel16a90d32021-12-20 13:24:19 +05301738 ((invoice.is_pos & invoice.due_date < today) | is_overdue)
Pruthvi Patel6c96ed42021-10-30 16:44:15 +05301739 if doctype == "Sales Invoice"
1740 else is_overdue
1741 )
1742 )
1743
Pruthvi Patel0799f372021-11-12 12:56:29 +05301744 status = (
1745 frappe.qb.terms.Case()
1746 .when(invoice.status.like("%Discounted"), "Overdue and Discounted")
1747 .else_("Overdue")
1748 )
1749
1750 frappe.qb.update(invoice).set("status", status).where(conditions).run()
Pruthvi Patel6c96ed42021-10-30 16:44:15 +05301751
1752
Nabin Hait92759692017-08-15 08:23:51 +05301753@frappe.whitelist()
Saqib Ansarid552fe62021-04-23 14:46:52 +05301754def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None):
Nabin Hait92759692017-08-15 08:23:51 +05301755 if not terms_template:
1756 return
1757
1758 terms_doc = frappe.get_doc("Payment Terms Template", terms_template)
1759
1760 schedule = []
tundefb144302017-08-19 15:01:40 +01001761 for d in terms_doc.get("terms"):
Saqib Ansarid552fe62021-04-23 14:46:52 +05301762 term_details = get_payment_term_details(d, posting_date, grand_total, base_grand_total, bill_date)
Nabin Hait92759692017-08-15 08:23:51 +05301763 schedule.append(term_details)
1764
1765 return schedule
1766
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001767
Nabin Hait92759692017-08-15 08:23:51 +05301768@frappe.whitelist()
Saqib Ansarid552fe62021-04-23 14:46:52 +05301769def get_payment_term_details(term, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None):
Nabin Hait92759692017-08-15 08:23:51 +05301770 term_details = frappe._dict()
Ankush Menat8fe5feb2021-11-04 19:48:32 +05301771 if isinstance(term, str):
Nabin Hait92759692017-08-15 08:23:51 +05301772 term = frappe.get_doc("Payment Term", term)
1773 else:
1774 term_details.payment_term = term.payment_term
1775 term_details.description = term.description
1776 term_details.invoice_portion = term.invoice_portion
Rohit Waghchaure52bf56d2017-11-27 11:43:52 +05301777 term_details.payment_amount = flt(term.invoice_portion) * flt(grand_total) / 100
Saqib Ansarid552fe62021-04-23 14:46:52 +05301778 term_details.base_payment_amount = flt(term.invoice_portion) * flt(base_grand_total) / 100
Saqib0586b7d2021-03-31 15:03:53 +05301779 term_details.discount_type = term.discount_type
1780 term_details.discount = term.discount
Saqib0586b7d2021-03-31 15:03:53 +05301781 term_details.outstanding = term_details.payment_amount
1782 term_details.mode_of_payment = term.mode_of_payment
1783
Shreya Shah3a9eec22018-02-16 13:05:21 +05301784 if bill_date:
1785 term_details.due_date = get_due_date(term, bill_date)
Saqib0586b7d2021-03-31 15:03:53 +05301786 term_details.discount_date = get_discount_date(term, bill_date)
Shreya Shah3a9eec22018-02-16 13:05:21 +05301787 elif posting_date:
1788 term_details.due_date = get_due_date(term, posting_date)
Saqib0586b7d2021-03-31 15:03:53 +05301789 term_details.discount_date = get_discount_date(term, posting_date)
Shreya Shah3a9eec22018-02-16 13:05:21 +05301790
1791 if getdate(term_details.due_date) < getdate(posting_date):
1792 term_details.due_date = posting_date
1793
Nabin Hait92759692017-08-15 08:23:51 +05301794 return term_details
1795
Shreya Shah3a9eec22018-02-16 13:05:21 +05301796def get_due_date(term, posting_date=None, bill_date=None):
Nabin Hait92759692017-08-15 08:23:51 +05301797 due_date = None
Shreya Shah3a9eec22018-02-16 13:05:21 +05301798 date = bill_date or posting_date
Nabin Hait92759692017-08-15 08:23:51 +05301799 if term.due_date_based_on == "Day(s) after invoice date":
Shreya Shah3a9eec22018-02-16 13:05:21 +05301800 due_date = add_days(date, term.credit_days)
Nabin Hait92759692017-08-15 08:23:51 +05301801 elif term.due_date_based_on == "Day(s) after the end of the invoice month":
Shreya Shah3a9eec22018-02-16 13:05:21 +05301802 due_date = add_days(get_last_day(date), term.credit_days)
Nabin Hait92759692017-08-15 08:23:51 +05301803 elif term.due_date_based_on == "Month(s) after the end of the invoice month":
Shreya Shah3a9eec22018-02-16 13:05:21 +05301804 due_date = add_months(get_last_day(date), term.credit_months)
tunde96b8f222017-09-08 15:35:59 +01001805 return due_date
tundebabzyad08d4c2018-05-16 07:01:41 +01001806
Saqib0586b7d2021-03-31 15:03:53 +05301807def get_discount_date(term, posting_date=None, bill_date=None):
1808 discount_validity = None
1809 date = bill_date or posting_date
1810 if term.discount_validity_based_on == "Day(s) after invoice date":
1811 discount_validity = add_days(date, term.discount_validity)
1812 elif term.discount_validity_based_on == "Day(s) after the end of the invoice month":
1813 discount_validity = add_days(get_last_day(date), term.discount_validity)
1814 elif term.discount_validity_based_on == "Month(s) after the end of the invoice month":
1815 discount_validity = add_months(get_last_day(date), term.discount_validity)
1816 return discount_validity
tundebabzyad08d4c2018-05-16 07:01:41 +01001817
1818def get_supplier_block_status(party_name):
1819 """
1820 Returns a dict containing the values of `on_hold`, `release_date` and `hold_type` of
1821 a `Supplier`
1822 """
1823 supplier = frappe.get_doc('Supplier', party_name)
1824 info = {
1825 'on_hold': supplier.on_hold,
1826 'release_date': supplier.release_date,
1827 'hold_type': supplier.hold_type
1828 }
1829 return info
1830
marination698d9832020-08-19 14:59:46 +05301831def set_child_tax_template_and_map(item, child_item, parent_doc):
1832 args = {
1833 'item_code': item.item_code,
1834 'posting_date': parent_doc.transaction_date,
1835 'tax_category': parent_doc.get('tax_category'),
1836 'company': parent_doc.get('company')
1837 }
1838
1839 child_item.item_tax_template = _get_item_tax_template(args, item.taxes)
1840 if child_item.get("item_tax_template"):
1841 child_item.item_tax_rate = get_item_tax_map(parent_doc.get('company'), child_item.item_tax_template, as_json=True)
1842
Afshanb3bbebd2021-08-09 14:39:32 +05301843def add_taxes_from_tax_template(child_item, parent_doc, db_insert=True):
Marica3b1be2b2020-10-22 17:04:31 +05301844 add_taxes_from_item_tax_template = frappe.db.get_single_value("Accounts Settings", "add_taxes_from_item_tax_template")
1845
1846 if child_item.get("item_tax_rate") and add_taxes_from_item_tax_template:
1847 tax_map = json.loads(child_item.get("item_tax_rate"))
1848 for tax_type in tax_map:
1849 tax_rate = flt(tax_map[tax_type])
1850 taxes = parent_doc.get('taxes') or []
1851 # add new row for tax head only if missing
1852 found = any(tax.account_head == tax_type for tax in taxes)
1853 if not found:
1854 tax_row = parent_doc.append("taxes", {})
1855 tax_row.update({
1856 "description" : str(tax_type).split(' - ')[0],
1857 "charge_type" : "On Net Total",
1858 "account_head" : tax_type,
1859 "rate" : tax_rate
1860 })
1861 if parent_doc.doctype == "Purchase Order":
1862 tax_row.update({
1863 "category" : "Total",
1864 "add_deduct_tax" : "Add"
1865 })
Afshanb3bbebd2021-08-09 14:39:32 +05301866 if db_insert:
1867 tax_row.db_insert()
Marica3b1be2b2020-10-22 17:04:31 +05301868
pateljannat637ddff2021-02-09 16:17:30 +05301869def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, trans_item):
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001870 """
pateljannat637ddff2021-02-09 16:17:30 +05301871 Returns a Sales/Purchase Order Item child item containing the default values
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001872 """
Saqib438e0432020-04-03 10:02:56 +05301873 p_doc = frappe.get_doc(parent_doctype, parent_doctype_name)
pateljannat637ddff2021-02-09 16:17:30 +05301874 child_item = frappe.new_doc(child_doctype, p_doc, child_docname)
Saqib Ansarif53299e2020-04-15 22:08:12 +05301875 item = frappe.get_doc("Item", trans_item.get('item_code'))
marination0673f552021-03-31 01:38:22 +05301876
pateljannat637ddff2021-02-09 16:17:30 +05301877 for field in ("item_code", "item_name", "description", "item_group"):
marination0673f552021-03-31 01:38:22 +05301878 child_item.update({field: item.get(field)})
1879
pateljannat637ddff2021-02-09 16:17:30 +05301880 date_fieldname = "delivery_date" if child_doctype == "Sales Order Item" else "schedule_date"
Jannat Patelbb0d8f82021-02-11 20:59:28 +05301881 child_item.update({date_fieldname: trans_item.get(date_fieldname) or p_doc.get(date_fieldname)})
marination0673f552021-03-31 01:38:22 +05301882 child_item.stock_uom = item.stock_uom
Saqib61314242020-09-15 11:14:31 +05301883 child_item.uom = trans_item.get("uom") or item.stock_uom
Andy Zhu1062d7e2020-10-06 10:51:42 +13001884 child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
Saqib61314242020-09-15 11:14:31 +05301885 conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor"))
1886 child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor
marination0673f552021-03-31 01:38:22 +05301887
pateljannat637ddff2021-02-09 16:17:30 +05301888 if child_doctype == "Purchase Order Item":
marination0673f552021-03-31 01:38:22 +05301889 # Initialized value will update in parent validation
1890 child_item.base_rate = 1
1891 child_item.base_amount = 1
pateljannat637ddff2021-02-09 16:17:30 +05301892 if child_doctype == "Sales Order Item":
1893 child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
1894 if not child_item.warehouse:
1895 frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.")
1896 .format(frappe.bold("default warehouse"), frappe.bold(item.item_code)))
marination0673f552021-03-31 01:38:22 +05301897
marination698d9832020-08-19 14:59:46 +05301898 set_child_tax_template_and_map(item, child_item, p_doc)
Marica3b1be2b2020-10-22 17:04:31 +05301899 add_taxes_from_tax_template(child_item, p_doc)
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001900 return child_item
1901
marination0673f552021-03-31 01:38:22 +05301902def validate_child_on_delete(row, parent):
1903 """Check if partially transacted item (row) is being deleted."""
1904 if parent.doctype == "Sales Order":
1905 if flt(row.delivered_qty):
1906 frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been delivered").format(row.idx, row.item_code))
1907 if flt(row.work_order_qty):
1908 frappe.throw(_("Row #{0}: Cannot delete item {1} which has work order assigned to it.").format(row.idx, row.item_code))
1909 if flt(row.ordered_qty):
1910 frappe.throw(_("Row #{0}: Cannot delete item {1} which is assigned to customer's purchase order.").format(row.idx, row.item_code))
1911
1912 if parent.doctype == "Purchase Order" and flt(row.received_qty):
1913 frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been received").format(row.idx, row.item_code))
1914
1915 if flt(row.billed_amt):
1916 frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been billed.").format(row.idx, row.item_code))
1917
1918def update_bin_on_delete(row, doctype):
1919 """Update bin for deleted item (row)."""
Chillar Anand915b3432021-09-02 16:44:59 +05301920 from erpnext.stock.stock_balance import (
1921 get_indented_qty,
1922 get_ordered_qty,
1923 get_reserved_qty,
1924 update_bin_qty,
1925 )
marination0673f552021-03-31 01:38:22 +05301926 qty_dict = {}
1927
1928 if doctype == "Sales Order":
1929 qty_dict["reserved_qty"] = get_reserved_qty(row.item_code, row.warehouse)
1930 else:
1931 if row.material_request_item:
1932 qty_dict["indented_qty"] = get_indented_qty(row.item_code, row.warehouse)
1933
1934 qty_dict["ordered_qty"] = get_ordered_qty(row.item_code, row.warehouse)
1935
1936 update_bin_qty(row.item_code, row.warehouse, qty_dict)
1937
Saqib6db92fb2020-09-14 19:54:17 +05301938def validate_and_delete_children(parent, data):
Saqib0ad74492019-12-24 16:42:30 +05301939 deleted_children = []
1940 updated_item_names = [d.get("docname") for d in data]
1941 for item in parent.items:
1942 if item.name not in updated_item_names:
1943 deleted_children.append(item)
1944
1945 for d in deleted_children:
marination0673f552021-03-31 01:38:22 +05301946 validate_child_on_delete(d, parent)
Saqib0ad74492019-12-24 16:42:30 +05301947 d.cancel()
1948 d.delete()
marination0673f552021-03-31 01:38:22 +05301949
1950 # need to update ordered qty in Material Request first
1951 # bin uses Material Request Items to recalculate & update
1952 parent.update_prevdoc_status()
1953
1954 for d in deleted_children:
1955 update_bin_on_delete(d, parent.doctype)
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001956
Rohan Bansala93b5142021-04-06 17:10:52 +05301957
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001958@frappe.whitelist()
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001959def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
Saqib6db92fb2020-09-14 19:54:17 +05301960 def check_doc_permissions(doc, perm_type='create'):
Saqib Ansari2c3c8aa2020-05-29 21:55:38 +05301961 try:
1962 doc.check_permission(perm_type)
Saqib6db92fb2020-09-14 19:54:17 +05301963 except frappe.PermissionError:
marination661bf642020-09-29 18:16:45 +05301964 actions = { 'create': 'add', 'write': 'update'}
Saqib6db92fb2020-09-14 19:54:17 +05301965
1966 frappe.throw(_("You do not have permissions to {} items in a {}.")
1967 .format(actions[perm_type], parent_doctype), title=_("Insufficient Permissions"))
marination665c27a2020-09-15 12:04:38 +05301968
Saqib6db92fb2020-09-14 19:54:17 +05301969 def validate_workflow_conditions(doc):
1970 workflow = get_workflow_name(doc.doctype)
1971 if not workflow:
1972 return
1973
1974 workflow_doc = frappe.get_doc("Workflow", workflow)
1975 current_state = doc.get(workflow_doc.workflow_state_field)
1976 roles = frappe.get_roles()
1977
1978 transitions = []
1979 for transition in workflow_doc.transitions:
1980 if transition.next_state == current_state and transition.allowed in roles:
1981 if not is_transition_condition_satisfied(transition, doc):
1982 continue
1983 transitions.append(transition.as_dict())
1984
1985 if not transitions:
Saqib18318932020-09-23 13:01:49 +05301986 frappe.throw(
1987 _("You are not allowed to update as per the conditions set in {} Workflow.").format(get_link_to_form("Workflow", workflow)),
1988 title=_("Insufficient Permissions")
1989 )
Saqib Ansari2c3c8aa2020-05-29 21:55:38 +05301990
Rohit Waghchaure8d5380a2020-06-18 14:06:31 +05301991 def get_new_child_item(item_row):
Rohit Waghchaureff70e612021-03-06 22:08:08 +05301992 child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item"
pateljannat637ddff2021-02-09 16:17:30 +05301993 return set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, item_row)
Saqib Ansari2c3c8aa2020-05-29 21:55:38 +05301994
1995 def validate_quantity(child_item, d):
1996 if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
1997 frappe.throw(_("Cannot set quantity less than delivered quantity"))
1998
1999 if parent_doctype == "Purchase Order" and flt(d.get("qty")) < flt(child_item.received_qty):
2000 frappe.throw(_("Cannot set quantity less than received quantity"))
2001
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08002002 data = json.loads(trans_items)
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08002003
rohitwaghchaure81c21752019-10-31 15:56:10 +05302004 sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation']
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05302005 parent = frappe.get_doc(parent_doctype, parent_doctype_name)
marination665c27a2020-09-15 12:04:38 +05302006
marination661bf642020-09-29 18:16:45 +05302007 check_doc_permissions(parent, 'write')
Saqib6db92fb2020-09-14 19:54:17 +05302008 validate_and_delete_children(parent, data)
Saqib0ad74492019-12-24 16:42:30 +05302009
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01002010 for d in data:
2011 new_child_flag = False
Ankush Menat6de7b8e2021-08-24 12:18:40 +05302012
2013 if not d.get("item_code"):
2014 # ignore empty rows
2015 continue
2016
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01002017 if not d.get("docname"):
2018 new_child_flag = True
Saqib6db92fb2020-09-14 19:54:17 +05302019 check_doc_permissions(parent, 'create')
Rohit Waghchaure8d5380a2020-06-18 14:06:31 +05302020 child_item = get_new_child_item(d)
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01002021 else:
Saqib6db92fb2020-09-14 19:54:17 +05302022 check_doc_permissions(parent, 'write')
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01002023 child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname"))
Saqib Ansari2c3c8aa2020-05-29 21:55:38 +05302024
Saqib Ansaric579b082020-05-29 22:21:50 +05302025 prev_rate, new_rate = flt(child_item.get("rate")), flt(d.get("rate"))
2026 prev_qty, new_qty = flt(child_item.get("qty")), flt(d.get("qty"))
2027 prev_con_fac, new_con_fac = flt(child_item.get("conversion_factor")), flt(d.get("conversion_factor"))
Saqib61314242020-09-15 11:14:31 +05302028 prev_uom, new_uom = child_item.get("uom"), d.get("uom")
Saqib Ansaric579b082020-05-29 22:21:50 +05302029
2030 if parent_doctype == 'Sales Order':
2031 prev_date, new_date = child_item.get("delivery_date"), d.get("delivery_date")
2032 elif parent_doctype == 'Purchase Order':
marinationb7e94cc2020-06-15 11:32:42 +05302033 prev_date, new_date = child_item.get("schedule_date"), d.get("schedule_date")
Saqib Ansaric579b082020-05-29 22:21:50 +05302034
2035 rate_unchanged = prev_rate == new_rate
marinationf9c4b202020-06-12 15:49:53 +05302036 qty_unchanged = prev_qty == new_qty
Saqib61314242020-09-15 11:14:31 +05302037 uom_unchanged = prev_uom == new_uom
Saqib Ansaric579b082020-05-29 22:21:50 +05302038 conversion_factor_unchanged = prev_con_fac == new_con_fac
Frappe PR Bot44434ff2021-08-18 18:33:06 +05302039 date_unchanged = prev_date == getdate(new_date) if prev_date and new_date else False # in case of delivery note etc
Saqib61314242020-09-15 11:14:31 +05302040 if rate_unchanged and qty_unchanged and conversion_factor_unchanged and uom_unchanged and date_unchanged:
Saqibdd374ff2020-02-27 12:51:31 +05302041 continue
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01002042
Saqib Ansari2c3c8aa2020-05-29 21:55:38 +05302043 validate_quantity(child_item, d)
deepeshgarg00777cde832018-12-27 15:56:51 +05302044
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01002045 child_item.qty = flt(d.get("qty"))
Saqib56fea7d2020-10-09 21:19:25 +05302046 rate_precision = child_item.precision("rate") or 2
2047 conv_fac_precision = child_item.precision("conversion_factor") or 2
2048 qty_precision = child_item.precision("qty") or 2
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08002049
Saqib56fea7d2020-10-09 21:19:25 +05302050 if flt(child_item.billed_amt, rate_precision) > flt(flt(d.get("rate"), rate_precision) * flt(d.get("qty"), qty_precision), rate_precision):
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08002051 frappe.throw(_("Row #{0}: Cannot set Rate if amount is greater than billed amount for Item {1}.")
2052 .format(child_item.idx, child_item.item_code))
2053 else:
Saqib56fea7d2020-10-09 21:19:25 +05302054 child_item.rate = flt(d.get("rate"), rate_precision)
marinationf9c4b202020-06-12 15:49:53 +05302055
Saqib Ansari2c3c8aa2020-05-29 21:55:38 +05302056 if d.get("conversion_factor"):
marinationf9c4b202020-06-12 15:49:53 +05302057 if child_item.stock_uom == child_item.uom:
2058 child_item.conversion_factor = 1
2059 else:
Saqib56fea7d2020-10-09 21:19:25 +05302060 child_item.conversion_factor = flt(d.get('conversion_factor'), conv_fac_precision)
marination665c27a2020-09-15 12:04:38 +05302061
Saqib61314242020-09-15 11:14:31 +05302062 if d.get("uom"):
2063 child_item.uom = d.get("uom")
2064 conversion_factor = flt(get_conversion_factor(child_item.item_code, child_item.uom).get("conversion_factor"))
Saqib56fea7d2020-10-09 21:19:25 +05302065 child_item.conversion_factor = flt(d.get('conversion_factor'), conv_fac_precision) or conversion_factor
Mangesh-Khairnara3d52002019-08-05 10:16:40 +05302066
Saqib Ansaric579b082020-05-29 22:21:50 +05302067 if d.get("delivery_date") and parent_doctype == 'Sales Order':
2068 child_item.delivery_date = d.get('delivery_date')
marinationf9c4b202020-06-12 15:49:53 +05302069
Saqib Ansaric579b082020-05-29 22:21:50 +05302070 if d.get("schedule_date") and parent_doctype == 'Purchase Order':
2071 child_item.schedule_date = d.get('schedule_date')
2072
Mangesh-Khairnara3d52002019-08-05 10:16:40 +05302073 if flt(child_item.price_list_rate):
Maricaf0674472019-10-11 10:47:09 +05302074 if flt(child_item.rate) > flt(child_item.price_list_rate):
2075 # if rate is greater than price_list_rate, set margin
2076 # or set discount
2077 child_item.discount_percentage = 0
rohitwaghchaure81c21752019-10-31 15:56:10 +05302078
2079 if parent_doctype in sales_doctypes:
2080 child_item.margin_type = "Amount"
2081 child_item.margin_rate_or_amount = flt(child_item.rate - child_item.price_list_rate,
2082 child_item.precision("margin_rate_or_amount"))
2083 child_item.rate_with_margin = child_item.rate
Maricaf0674472019-10-11 10:47:09 +05302084 else:
2085 child_item.discount_percentage = flt((1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0,
2086 child_item.precision("discount_percentage"))
2087 child_item.discount_amount = flt(
2088 child_item.price_list_rate) - flt(child_item.rate)
rohitwaghchaure81c21752019-10-31 15:56:10 +05302089
2090 if parent_doctype in sales_doctypes:
2091 child_item.margin_type = ""
2092 child_item.margin_rate_or_amount = 0
2093 child_item.rate_with_margin = 0
Mangesh-Khairnara3d52002019-08-05 10:16:40 +05302094
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08002095 child_item.flags.ignore_validate_update_after_submit = True
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01002096 if new_child_flag:
Saqib Ansarif53299e2020-04-15 22:08:12 +05302097 parent.load_from_db()
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05302098 child_item.idx = len(parent.items) + 1
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01002099 child_item.insert()
2100 else:
2101 child_item.save()
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08002102
Rushabh Mehtafafbb022018-12-24 22:09:15 +05302103 parent.reload()
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05302104 parent.flags.ignore_validate_update_after_submit = True
2105 parent.set_qty_as_per_stock_uom()
2106 parent.calculate_taxes_and_totals()
Ankush Menatdf6e2082021-02-11 11:04:39 +05302107 parent.set_total_in_words()
Nabin Hait49a46f02019-10-21 13:35:43 +05302108 if parent_doctype == "Sales Order":
Marica3dee5272020-06-23 10:33:47 +05302109 make_packing_list(parent)
Nabin Hait49a46f02019-10-21 13:35:43 +05302110 parent.set_gross_profit()
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05302111 frappe.get_doc('Authorization Control').validate_approving_authority(parent.doctype,
2112 parent.company, parent.base_grand_total)
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08002113
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05302114 parent.set_payment_schedule()
Nabin Haitb2da0822018-07-13 17:01:40 +05302115 if parent_doctype == 'Purchase Order':
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05302116 parent.validate_minimum_order_qty()
2117 parent.validate_budget()
2118 if parent.is_against_so():
2119 parent.update_status_updater()
Nabin Haitb2da0822018-07-13 17:01:40 +05302120 else:
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05302121 parent.check_credit_limit()
Ankush Menat733c9de2022-01-04 18:39:30 +05302122
2123 # reset index of child table
2124 for idx, row in enumerate(parent.get(child_docname), start=1):
2125 row.idx = idx
2126
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05302127 parent.save()
Nabin Haitb2da0822018-07-13 17:01:40 +05302128
2129 if parent_doctype == 'Purchase Order':
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05302130 update_last_purchase_rate(parent, is_submit = 1)
2131 parent.update_prevdoc_status()
2132 parent.update_requested_qty()
2133 parent.update_ordered_qty()
2134 parent.update_ordered_and_reserved_qty()
2135 parent.update_receiving_percentage()
2136 if parent.is_subcontracted == "Yes":
2137 parent.update_reserved_qty_for_subcontract()
Saqib61314242020-09-15 11:14:31 +05302138 parent.create_raw_materials_supplied("supplied_items")
2139 parent.save()
Nabin Haitb2da0822018-07-13 17:01:40 +05302140 else:
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05302141 parent.update_reserved_qty()
2142 parent.update_project()
2143 parent.update_prevdoc_status('submit')
2144 parent.update_delivery_status()
Nabin Haitb2da0822018-07-13 17:01:40 +05302145
Saqib6db92fb2020-09-14 19:54:17 +05302146 parent.reload()
2147 validate_workflow_conditions(parent)
2148
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05302149 parent.update_blanket_order()
2150 parent.update_billing_percentage()
2151 parent.set_status()
Gauravf1e28e02019-02-13 16:46:24 +05302152
2153@erpnext.allow_regional
2154def validate_regional(doc):
2155 pass