blob: 01d354df81f93c113929eafc53df6761ca9b3c76 [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
4from __future__ import unicode_literals
Rushabh Mehtacc8b2b22017-03-31 12:44:29 +05305import frappe, erpnext
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08006import json
Rushabh Mehta793ba6b2014-02-14 15:47:51 +05307from frappe import _, throw
rohitwaghchaurea85ddf22019-11-19 18:47:48 +05308from frappe.utils import (today, flt, cint, fmt_money, formatdate,
9 getdate, add_days, add_months, get_last_day, nowdate, get_link_to_form)
Saqib6db92fb2020-09-14 19:54:17 +053010from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied, WorkflowPermissionError
rohitwaghchaurea85ddf22019-11-19 18:47:48 +053011from erpnext.stock.get_item_details import get_conversion_factor, get_item_details
Rushabh Mehtacc8b2b22017-03-31 12:44:29 +053012from erpnext.setup.utils import get_exchange_rate
Nabin Hait2ed0b592016-03-29 13:14:17 +053013from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_account_currency
Nabin Hait436f5262014-02-10 14:47:54 +053014from erpnext.utilities.transaction_base import TransactionBase
Doridel Cahanap6f06cc22018-05-17 16:28:58 +080015from erpnext.buying.utils import update_last_purchase_rate
Nabin Hait3cf67a42015-07-24 13:26:36 +053016from erpnext.controllers.sales_and_purchase_return import validate_return
shreyas29b565f2016-01-25 17:30:49 +053017from erpnext.accounts.party import get_party_account_currency, validate_party_frozen_disabled
rohitwaghchaurea85ddf22019-11-19 18:47:48 +053018from erpnext.accounts.doctype.pricing_rule.utils import (apply_pricing_rule_on_transaction,
19 apply_pricing_rule_for_free_items, get_applied_pricing_rules)
shreyasf76853a2016-01-25 21:25:11 +053020from erpnext.exceptions import InvalidCurrency
Achilles Rasquinha90509982018-03-08 12:55:41 +053021from six import text_type
deepeshgarg007d83cf652019-05-12 18:34:23 +053022from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
marination698d9832020-08-19 14:59:46 +053023from erpnext.stock.get_item_details import get_item_warehouse, _get_item_tax_template, get_item_tax_map
Marica3dee5272020-06-23 10:33:47 +053024from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
prssanna71e5b602020-10-29 14:19:34 +053025from erpnext.controllers.print_settings import set_print_templates_for_item_table, set_print_templates_for_taxes
Nabin Hait1d218422015-07-17 15:19:02 +053026
marination53b1a9a2020-11-03 15:45:25 +053027class AccountMissingError(frappe.ValidationError): pass
28
Rohit Waghchaurebde159a2021-03-22 23:36:48 +053029force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate",
30 "pricing_rules", "weight_per_unit", "weight_uom", "total_weight")
Doridel Cahanap6f06cc22018-05-17 16:28:58 +080031
Nabin Haitbf495c92013-01-30 12:49:08 +053032class AccountsController(TransactionBase):
Nabin Hait7eba1a32017-10-02 15:59:27 +053033 def __init__(self, *args, **kwargs):
34 super(AccountsController, self).__init__(*args, **kwargs)
Anand Doshi979326b2015-09-11 16:22:37 +053035
prssanna71e5b602020-10-29 14:19:34 +053036 def get_print_settings(self):
37 print_setting_fields = []
38 items_field = self.meta.get_field('items')
39
40 if items_field and items_field.fieldtype == 'Table':
41 print_setting_fields += ['compact_item_print', 'print_uom_after_quantity']
42
43 taxes_field = self.meta.get_field('taxes')
44 if taxes_field and taxes_field.fieldtype == 'Table':
45 print_setting_fields += ['print_taxes_with_zero_amount']
46
47 return print_setting_fields
48
Nabin Hait4ffd7f32015-08-27 12:28:36 +053049 @property
50 def company_currency(self):
51 if not hasattr(self, "__company_currency"):
Rushabh Mehtacc8b2b22017-03-31 12:44:29 +053052 self.__company_currency = erpnext.get_company_currency(self.company)
Anand Doshi979326b2015-09-11 16:22:37 +053053
Nabin Hait4ffd7f32015-08-27 12:28:36 +053054 return self.__company_currency
Anand Doshi979326b2015-09-11 16:22:37 +053055
Rohit Waghchaure7d529892016-10-06 14:35:04 +053056 def onload(self):
Nabin Hait34c551d2019-07-03 10:34:31 +053057 self.set_onload("make_payment_via_journal_entry",
58 frappe.db.get_single_value('Accounts Settings', 'make_payment_via_journal_entry'))
Nabin Hait0551f7b2017-11-21 19:58:16 +053059
tundee52bb822017-09-25 09:02:23 +010060 if self.is_new():
Nabin Hait0551f7b2017-11-21 19:58:16 +053061 relevant_docs = ("Quotation", "Purchase Order", "Sales Order",
Doridel Cahanap6f06cc22018-05-17 16:28:58 +080062 "Purchase Invoice", "Sales Invoice")
tundee52bb822017-09-25 09:02:23 +010063 if self.doctype in relevant_docs:
64 self.set_payment_schedule()
Rohit Waghchaure7d529892016-10-06 14:35:04 +053065
tundebabzyad08d4c2018-05-16 07:01:41 +010066 def ensure_supplier_is_not_blocked(self):
67 is_supplier_payment = self.doctype == 'Payment Entry' and self.party_type == 'Supplier'
68 is_buying_invoice = self.doctype in ['Purchase Invoice', 'Purchase Order']
69 supplier = None
70 supplier_name = None
71
72 if is_buying_invoice or is_supplier_payment:
73 supplier_name = self.supplier if is_buying_invoice else self.party
74 supplier = frappe.get_doc('Supplier', supplier_name)
75
76 if supplier and supplier_name and supplier.on_hold:
77 if (is_buying_invoice and supplier.hold_type in ['All', 'Invoices']) or \
78 (is_supplier_payment and supplier.hold_type in ['All', 'Payments']):
79 if not supplier.release_date or getdate(nowdate()) <= supplier.release_date:
80 frappe.msgprint(
Suraj Shetty48e9bc32020-01-29 15:06:18 +053081 _('{0} is blocked so this transaction cannot proceed').format(supplier_name), raise_exception=1)
tundebabzyad08d4c2018-05-16 07:01:41 +010082
Saurabh6f753182013-03-20 12:55:28 +053083 def validate(self):
Deepesh Gargd301d262019-07-31 15:58:19 +053084 if not self.get('is_return'):
85 self.validate_qty_is_not_zero()
86
nabinhait23cce732014-07-03 12:25:06 +053087 if self.get("_action") and self._action != "update_after_submit":
Nabin Hait704a4532014-06-05 16:55:31 +053088 self.set_missing_values(for_validate=True)
tunde62af5c52017-09-22 15:16:38 +010089
tundebabzyad08d4c2018-05-16 07:01:41 +010090 self.ensure_supplier_is_not_blocked()
91
Nabin Haitcfecd2b2013-07-11 17:49:18 +053092 self.validate_date_with_fiscal_year()
Deepesh Garg4c8d15b2021-04-26 15:24:34 +053093 self.validate_party_accounts()
94
Deepesh Gargb4be2922021-01-28 13:09:56 +053095 self.validate_inter_company_reference()
96
97 self.set_incoming_rate()
Rushabh Mehtab16b9cd2015-08-03 16:13:33 +053098
Anand Doshi3543f302013-05-24 19:25:01 +053099 if self.meta.get_field("currency"):
Anand Doshi923d41d2013-05-28 17:23:36 +0530100 self.calculate_taxes_and_totals()
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530101
Nabin Hait1d218422015-07-17 15:19:02 +0530102 if not self.meta.get_field("is_return") or not self.is_return:
103 self.validate_value("base_grand_total", ">=", 0)
Rushabh Mehtab16b9cd2015-08-03 16:13:33 +0530104
Nabin Hait3cf67a42015-07-24 13:26:36 +0530105 validate_return(self)
Anand Doshi3543f302013-05-24 19:25:01 +0530106 self.set_total_in_words()
Anand Doshid2946502014-04-08 20:10:03 +0530107
tunde62af5c52017-09-22 15:16:38 +0100108 self.validate_all_documents_schedule()
Anand Doshid2946502014-04-08 20:10:03 +0530109
ankitjavalkarwork6c29d872014-10-06 13:20:53 +0530110 if self.meta.get_field("taxes_and_charges"):
111 self.validate_enabled_taxes_and_charges()
Nabin Haita2426fc2018-01-15 17:45:46 +0530112 self.validate_tax_account_company()
Rushabh Mehtab16b9cd2015-08-03 16:13:33 +0530113
Neil Trini Lasrado3fbbb712015-07-17 12:10:12 +0530114 self.validate_party()
Nabin Hait895029d2015-08-20 14:55:39 +0530115 self.validate_currency()
Rushabh Mehta2cb7a9f2016-06-15 16:45:03 +0530116
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530117 if self.doctype == 'Purchase Invoice':
Mangesh-Khairnar7bcb24e2019-09-11 10:49:33 +0530118 self.calculate_paid_amount()
Deepesh Garg911818a2021-06-09 22:55:10 +0530119 # apply tax withholding only if checked and applicable
120 self.set_tax_withholding()
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530121
Nabin Hait041a5c22018-08-01 18:07:39 +0530122 if self.doctype in ['Purchase Invoice', 'Sales Invoice']:
Nabin Hait4a994d42019-04-25 17:47:26 +0530123 pos_check_field = "is_pos" if self.doctype=="Sales Invoice" else "is_paid"
124 if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
Nabin Hait041a5c22018-08-01 18:07:39 +0530125 self.set_advances()
126
Saqiba20999c2021-07-12 14:33:23 +0530127 self.set_advance_gain_or_loss()
128
Nabin Hait041a5c22018-08-01 18:07:39 +0530129 if self.is_return:
130 self.validate_qty()
Nabin Haitacdd5082019-12-04 15:30:01 +0530131 else:
132 self.validate_deferred_start_and_end_date()
rohitwaghchaure70489252018-06-11 12:02:14 +0530133
Deepesh Gargf17ea2c2020-12-11 21:30:39 +0530134 self.set_inter_company_account()
135
Gauravf1e28e02019-02-13 16:46:24 +0530136 validate_regional(self)
Deepesh Garg7c300852020-12-26 20:14:51 +0530137
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530138 if self.doctype != 'Material Request':
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530139 apply_pricing_rule_on_transaction(self)
Gauravf1e28e02019-02-13 16:46:24 +0530140
Saqib8e556772021-01-28 12:26:45 +0530141 def on_trash(self):
142 # delete sl and gl entries on deletion of transaction
143 if frappe.db.get_single_value('Accounts Settings', 'delete_linked_ledger_entries'):
144 frappe.db.sql("delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name))
145 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 +0530146
Nabin Haitacdd5082019-12-04 15:30:01 +0530147 def validate_deferred_start_and_end_date(self):
148 for d in self.items:
149 if d.get("enable_deferred_revenue") or d.get("enable_deferred_expense"):
150 if not (d.service_start_date and d.service_end_date):
151 frappe.throw(_("Row #{0}: Service Start and End Date is required for deferred accounting").format(d.idx))
152 elif getdate(d.service_start_date) > getdate(d.service_end_date):
153 frappe.throw(_("Row #{0}: Service Start Date cannot be greater than Service End Date").format(d.idx))
154 elif getdate(self.posting_date) > getdate(d.service_end_date):
155 frappe.throw(_("Row #{0}: Service End Date cannot be before Invoice Posting Date").format(d.idx))
156
tunde62af5c52017-09-22 15:16:38 +0100157 def validate_invoice_documents_schedule(self):
Nabin Hait0551f7b2017-11-21 19:58:16 +0530158 self.validate_payment_schedule_dates()
159 self.set_due_date()
Nabin Hait0551f7b2017-11-21 19:58:16 +0530160 self.set_payment_schedule()
161 self.validate_payment_schedule_amount()
tunde62af5c52017-09-22 15:16:38 +0100162 self.validate_due_date()
163 self.validate_advance_entries()
164
165 def validate_non_invoice_documents_schedule(self):
Nabin Hait0551f7b2017-11-21 19:58:16 +0530166 self.set_payment_schedule()
Nabin Hait2f9f4f92017-12-20 12:24:59 +0530167 self.validate_payment_schedule_dates()
Nabin Hait0551f7b2017-11-21 19:58:16 +0530168 self.validate_payment_schedule_amount()
tunde62af5c52017-09-22 15:16:38 +0100169
170 def validate_all_documents_schedule(self):
171 if self.doctype in ("Sales Invoice", "Purchase Invoice") and not self.is_return:
172 self.validate_invoice_documents_schedule()
173 elif self.doctype in ("Quotation", "Purchase Order", "Sales Order"):
174 self.validate_non_invoice_documents_schedule()
175
prssanna71e5b602020-10-29 14:19:34 +0530176 def before_print(self, settings=None):
Zarrarb9f54ca2018-05-28 17:41:09 +0530177 if self.doctype in ['Purchase Order', 'Sales Order', 'Sales Invoice', 'Purchase Invoice',
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800178 'Supplier Quotation', 'Purchase Receipt', 'Delivery Note', 'Quotation']:
KanchanChauhan49a50fe2016-11-18 13:57:53 +0530179 if self.get("group_same_items"):
180 self.group_similar_items()
181
Zarrar5be6d192018-11-08 12:16:26 +0530182 df = self.meta.get_field("discount_amount")
183 if self.get("discount_amount") and hasattr(self, "taxes") and not len(self.taxes):
184 df.set("print_hide", 0)
185 self.discount_amount = -self.discount_amount
186 else:
187 df.set("print_hide", 1)
188
prssanna71e5b602020-10-29 14:19:34 +0530189 set_print_templates_for_item_table(self, settings)
190 set_print_templates_for_taxes(self, settings)
191
Mangesh-Khairnar7bcb24e2019-09-11 10:49:33 +0530192 def calculate_paid_amount(self):
Saurabh43520f92016-03-21 18:32:48 +0530193 if hasattr(self, "is_pos") or hasattr(self, "is_paid"):
194 is_paid = self.get("is_pos") or self.get("is_paid")
Mangesh-Khairnar7bcb24e2019-09-11 10:49:33 +0530195
196 if is_paid:
197 if not self.cash_bank_account:
198 # show message that the amount is not paid
199 frappe.throw(_("Note: Payment Entry will not be created since 'Cash or Bank Account' was not specified"))
Marica23d7b092019-09-25 17:17:36 +0530200
Mangesh-Khairnar7bcb24e2019-09-11 10:49:33 +0530201 if cint(self.is_return) and self.grand_total > self.paid_amount:
202 self.paid_amount = flt(flt(self.grand_total), self.precision("paid_amount"))
203
204 elif not flt(self.paid_amount) and flt(self.outstanding_amount) > 0:
205 self.paid_amount = flt(flt(self.outstanding_amount), self.precision("paid_amount"))
206
207 self.base_paid_amount = flt(self.paid_amount * self.conversion_rate,
208 self.precision("base_paid_amount"))
Saurabh43520f92016-03-21 18:32:48 +0530209
Anand Doshiabc10032013-06-14 17:44:03 +0530210 def set_missing_values(self, for_validate=False):
Rohit Waghchaure5d97d892016-05-12 12:58:29 +0530211 if frappe.flags.in_test:
tunde62af5c52017-09-22 15:16:38 +0100212 for fieldname in ["posting_date", "transaction_date"]:
Rohit Waghchaure5d97d892016-05-12 12:58:29 +0530213 if self.meta.get_field(fieldname) and not self.get(fieldname):
214 self.set(fieldname, today())
215 break
Anand Doshid2946502014-04-08 20:10:03 +0530216
Nabin Hait3237c752015-02-17 11:11:11 +0530217 def calculate_taxes_and_totals(self):
Nabin Haitfe81da22015-02-18 12:23:18 +0530218 from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals
219 calculate_taxes_and_totals(self)
Nabin Hait3237c752015-02-17 11:11:11 +0530220
221 if self.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]:
222 self.calculate_commission()
223 self.calculate_contribution()
224
Nabin Haitcfecd2b2013-07-11 17:49:18 +0530225 def validate_date_with_fiscal_year(self):
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800226 if self.meta.get_field("fiscal_year"):
Rohan Bansal1e3a3b22021-05-26 15:18:10 +0530227 date_field = None
Nabin Haitcfecd2b2013-07-11 17:49:18 +0530228 if self.meta.get_field("posting_date"):
229 date_field = "posting_date"
230 elif self.meta.get_field("transaction_date"):
231 date_field = "transaction_date"
Anand Doshid2946502014-04-08 20:10:03 +0530232
Rushabh Mehtaf2227d02014-03-31 23:37:40 +0530233 if date_field and self.get(date_field):
Rushabh Mehta66958302017-01-16 16:57:53 +0530234 validate_fiscal_year(self.get(date_field), self.fiscal_year, self.company,
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800235 self.meta.get_label(date_field), self)
Anand Doshid2946502014-04-08 20:10:03 +0530236
Deepesh Garg4c8d15b2021-04-26 15:24:34 +0530237 def validate_party_accounts(self):
238 if self.doctype not in ('Sales Invoice', 'Purchase Invoice'):
239 return
240
241 if self.doctype == 'Sales Invoice':
242 party_account_field = 'debit_to'
243 item_field = 'income_account'
244 else:
245 party_account_field = 'credit_to'
246 item_field = 'expense_account'
247
248 for item in self.get('items'):
249 if item.get(item_field) == self.get(party_account_field):
250 frappe.throw(_("Row {0}: {1} {2} cannot be same as {3} (Party Account) {4}").format(item.idx,
251 frappe.bold(frappe.unscrub(item_field)), item.get(item_field),
252 frappe.bold(frappe.unscrub(party_account_field)), self.get(party_account_field)))
253
Deepesh Gargb4be2922021-01-28 13:09:56 +0530254 def validate_inter_company_reference(self):
255 if self.doctype not in ('Purchase Invoice', 'Purchase Receipt', 'Purchase Order'):
256 return
257
258 if self.is_internal_transfer():
259 if not (self.get('inter_company_reference') or self.get('inter_company_invoice_reference')
260 or self.get('inter_company_order_reference')):
Deepesh Garg4c8d15b2021-04-26 15:24:34 +0530261 msg = _("Internal Sale or Delivery Reference missing.")
Deepesh Gargb4be2922021-01-28 13:09:56 +0530262 msg += _("Please create purchase from internal sale or delivery document itself")
263 frappe.throw(msg, title=_("Internal Sales Reference Missing"))
264
Nabin Haite9daefe2014-08-27 16:46:33 +0530265 def validate_due_date(self):
rohitwaghchaureea943c62018-07-06 12:28:58 +0530266 if self.get('is_pos'): return
267
Nabin Haite9daefe2014-08-27 16:46:33 +0530268 from erpnext.accounts.party import validate_due_date
269 if self.doctype == "Sales Invoice":
Nabin Hait04d244a2015-07-22 17:47:49 +0530270 if not self.due_date:
271 frappe.throw(_("Due Date is mandatory"))
Rushabh Mehtab16b9cd2015-08-03 16:13:33 +0530272
rohitwaghchaure3ffe8962018-07-13 17:40:48 +0530273 validate_due_date(self.posting_date, self.due_date,
274 "Customer", self.customer, self.company, self.payment_terms_template)
Nabin Haite9daefe2014-08-27 16:46:33 +0530275 elif self.doctype == "Purchase Invoice":
Rohit Waghchaured1a85a32018-11-26 18:42:29 +0530276 validate_due_date(self.bill_date or self.posting_date, self.due_date,
Saurabhd7897f12018-07-18 17:08:16 +0530277 "Supplier", self.supplier, self.company, self.bill_date, self.payment_terms_template)
Nabin Haite9daefe2014-08-27 16:46:33 +0530278
Nabin Hait096d3632013-10-17 17:01:14 +0530279 def set_price_list_currency(self, buying_or_selling):
Nabin Hait1cc55fb2016-12-08 14:09:23 +0530280 if self.meta.get_field("posting_date"):
Nabin Hait288a18e2016-12-08 15:36:23 +0530281 transaction_date = self.posting_date
Nabin Hait1cc55fb2016-12-08 14:09:23 +0530282 else:
Nabin Hait288a18e2016-12-08 15:36:23 +0530283 transaction_date = self.transaction_date
Rushabh Mehta66958302017-01-16 16:57:53 +0530284
Rushabh Mehta4a404e92013-08-09 18:11:35 +0530285 if self.meta.get_field("currency"):
Nabin Hait7a75e102013-09-17 10:21:20 +0530286 # price list part
Shreya3f778522018-05-15 16:59:20 +0530287 if buying_or_selling.lower() == "selling":
288 fieldname = "selling_price_list"
289 args = "for_selling"
290 else:
291 fieldname = "buying_price_list"
292 args = "for_buying"
293
Anand Doshif78d1ae2014-03-28 13:55:00 +0530294 if self.meta.get_field(fieldname) and self.get(fieldname):
295 self.price_list_currency = frappe.db.get_value("Price List",
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800296 self.get(fieldname), "currency")
Anand Doshid2946502014-04-08 20:10:03 +0530297
Nabin Hait6e439a52015-08-28 19:24:22 +0530298 if self.price_list_currency == self.company_currency:
Anand Doshif78d1ae2014-03-28 13:55:00 +0530299 self.plc_conversion_rate = 1.0
Nabin Hait11f41952013-09-24 14:36:55 +0530300
Anand Doshif78d1ae2014-03-28 13:55:00 +0530301 elif not self.plc_conversion_rate:
Rushabh Mehta66958302017-01-16 16:57:53 +0530302 self.plc_conversion_rate = get_exchange_rate(self.price_list_currency,
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800303 self.company_currency, transaction_date, args)
Anand Doshid2946502014-04-08 20:10:03 +0530304
Nabin Hait7a75e102013-09-17 10:21:20 +0530305 # currency
Anand Doshif78d1ae2014-03-28 13:55:00 +0530306 if not self.currency:
307 self.currency = self.price_list_currency
308 self.conversion_rate = self.plc_conversion_rate
Nabin Hait6e439a52015-08-28 19:24:22 +0530309 elif self.currency == self.company_currency:
Anand Doshif78d1ae2014-03-28 13:55:00 +0530310 self.conversion_rate = 1.0
311 elif not self.conversion_rate:
Anand Doshidffec8f2014-07-01 17:45:15 +0530312 self.conversion_rate = get_exchange_rate(self.currency,
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800313 self.company_currency, transaction_date, args)
Nabin Hait7a75e102013-09-17 10:21:20 +0530314
Nabin Haitcccc45e2016-10-05 17:15:43 +0530315 def set_missing_item_details(self, for_validate=False):
Anand Doshi3543f302013-05-24 19:25:01 +0530316 """set missing item values"""
Nabin Haitf37d43d2017-07-18 12:14:42 +0530317 from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
Rushabh Mehtab46069d2016-01-15 16:59:26 +0530318
Nabin Haitdd38a262014-12-26 13:15:21 +0530319 if hasattr(self, "items"):
Nabin Hait444f9562014-06-20 15:59:49 +0530320 parent_dict = {}
Pratik Vyasf7daab72014-04-10 17:53:30 +0530321 for fieldname in self.meta.get_valid_columns():
322 parent_dict[fieldname] = self.get(fieldname)
323
mbauskara52472c2016-03-05 15:10:25 +0530324 if self.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]:
325 document_type = "{} Item".format(self.doctype)
mbauskarc97becb2016-01-18 16:28:21 +0530326 parent_dict.update({"document_type": document_type})
327
Nabin Hait34c551d2019-07-03 10:34:31 +0530328 # party_name field used for customer in quotation
329 if self.doctype == "Quotation" and self.quotation_to == "Customer" and parent_dict.get("party_name"):
330 parent_dict.update({"customer": parent_dict.get("party_name")})
331
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530332 self.pricing_rules = []
Nabin Haitdd38a262014-12-26 13:15:21 +0530333 for item in self.get("items"):
Rushabh Mehtaf14b8092014-04-03 14:30:42 +0530334 if item.get("item_code"):
Nabin Hait444f9562014-06-20 15:59:49 +0530335 args = parent_dict.copy()
336 args.update(item.as_dict())
Rushabh Mehtab46069d2016-01-15 16:59:26 +0530337
Nabin Hait34d28222016-01-19 15:45:49 +0530338 args["doctype"] = self.doctype
339 args["name"] = self.name
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530340 args["child_docname"] = item.name
Saqibac9e6ff2021-01-28 17:58:55 +0530341 args["ignore_pricing_rule"] = self.ignore_pricing_rule if hasattr(self, 'ignore_pricing_rule') else 0
Rushabh Mehtab46069d2016-01-15 16:59:26 +0530342
Nabin Haite2f054c2015-03-09 14:54:37 +0530343 if not args.get("transaction_date"):
344 args["transaction_date"] = args.get("posting_date")
Anand Doshi2b2b6392015-03-20 14:18:09 +0530345
346 if self.get("is_subcontracted"):
347 args["is_subcontracted"] = self.is_subcontracted
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530348
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530349 ret = get_item_details(args, self, for_validate=True, overwrite_warehouse=False)
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530350
Rushabh Mehtaf14b8092014-04-03 14:30:42 +0530351 for fieldname, value in ret.items():
Anand Doshi602e8252015-11-16 19:05:46 +0530352 if item.meta.get_field(fieldname) and value is not None:
353 if (item.get(fieldname) is None or fieldname in force_item_fields):
Rushabh Mehtaf14b8092014-04-03 14:30:42 +0530354 item.set(fieldname, value)
Anand Doshid2946502014-04-08 20:10:03 +0530355
Rohit Waghchauredc981dc2017-04-03 14:17:08 +0530356 elif fieldname in ['cost_center', 'conversion_factor'] and not item.get(fieldname):
Anand Doshi602e8252015-11-16 19:05:46 +0530357 item.set(fieldname, value)
358
Rohit Waghchauredc981dc2017-04-03 14:17:08 +0530359 elif fieldname == "serial_no":
Alchez9155e402018-07-09 16:57:42 +0530360 # Ensure that serial numbers are matched against Stock UOM
361 item_conversion_factor = item.get("conversion_factor") or 1.0
362 item_qty = abs(item.get("qty")) * item_conversion_factor
Alchez8f2a0f32018-07-06 14:36:52 +0530363
364 if item_qty != len(get_serial_nos(item.get('serial_no'))):
Rohit Waghchauredc981dc2017-04-03 14:17:08 +0530365 item.set(fieldname, value)
Nabin Haita74468b2014-12-30 18:33:52 +0530366
Rohit Waghchauref6f503a2018-12-19 15:06:44 +0530367 if self.doctype in ["Purchase Invoice", "Sales Invoice"] and item.meta.get_field('is_fixed_asset'):
368 item.set('is_fixed_asset', ret.get('is_fixed_asset', 0))
369
Deepesh Garga60c3082021-05-11 16:38:33 +0530370 # Double check for cost center
371 # Items add via promotional scheme may not have cost center set
372 if hasattr(item, 'cost_center') and not item.get('cost_center'):
373 item.set('cost_center', self.get('cost_center') or erpnext.get_default_cost_center(self.company))
374
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530375 if ret.get("pricing_rules"):
376 self.apply_pricing_rule_on_items(item, ret)
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530377 self.set_pricing_rule_details(item, ret)
Rushabh Mehtab46069d2016-01-15 16:59:26 +0530378
Nabin Hait14aa9c52016-04-18 15:54:01 +0530379 if self.doctype == "Purchase Invoice":
Nabin Haitcccc45e2016-10-05 17:15:43 +0530380 self.set_expense_account(for_validate)
Nabin Haita3dd72a2014-05-28 12:49:20 +0530381
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530382 def apply_pricing_rule_on_items(self, item, pricing_rule_args):
383 if not pricing_rule_args.get("validate_applied_rule", 0):
384 # if user changed the discount percentage then set user's discount percentage ?
385 if pricing_rule_args.get("price_or_product_discount") == 'Price':
386 item.set("pricing_rules", pricing_rule_args.get("pricing_rules"))
387 item.set("discount_percentage", pricing_rule_args.get("discount_percentage"))
388 item.set("discount_amount", pricing_rule_args.get("discount_amount"))
389 if pricing_rule_args.get("pricing_rule_for") == "Rate":
390 item.set("price_list_rate", pricing_rule_args.get("price_list_rate"))
391
392 if item.get("price_list_rate"):
393 item.rate = flt(item.price_list_rate *
394 (1.0 - (flt(item.discount_percentage) / 100.0)), item.precision("rate"))
395
396 if item.get('discount_amount'):
397 item.rate = item.price_list_rate - item.discount_amount
398
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530399 if item.get("apply_discount_on_discounted_rate") and pricing_rule_args.get("rate"):
400 item.rate = pricing_rule_args.get("rate")
401
Rohit Waghchaure21fe97e2019-12-13 16:18:38 +0530402 elif pricing_rule_args.get('free_item_data'):
403 apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data'))
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530404
405 elif pricing_rule_args.get("validate_applied_rule"):
Rushabh Mehtaff5d1962020-08-25 11:59:57 +0530406 for pricing_rule in get_applied_pricing_rules(item.get('pricing_rules')):
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530407 pricing_rule_doc = frappe.get_cached_doc("Pricing Rule", pricing_rule)
408 for field in ['discount_percentage', 'discount_amount', 'rate']:
409 if item.get(field) < pricing_rule_doc.get(field):
410 title = get_link_to_form("Pricing Rule", pricing_rule)
411
412 frappe.msgprint(_("Row {0}: user has not applied the rule {1} on the item {2}")
413 .format(item.idx, frappe.bold(title), frappe.bold(item.item_code)))
414
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530415 def set_pricing_rule_details(self, item_row, args):
416 pricing_rules = get_applied_pricing_rules(args.get("pricing_rules"))
417 if not pricing_rules: return
418
419 for pricing_rule in pricing_rules:
420 self.append("pricing_rules", {
421 "pricing_rule": pricing_rule,
422 "item_code": item_row.item_code,
423 "child_docname": item_row.name,
424 "rule_applied": True
425 })
426
Nabin Haitffc7f3f2015-05-12 15:07:02 +0530427 def set_taxes(self):
428 if not self.meta.get_field("taxes"):
Anand Doshi3543f302013-05-24 19:25:01 +0530429 return
Anand Doshid2946502014-04-08 20:10:03 +0530430
Nabin Haitffc7f3f2015-05-12 15:07:02 +0530431 tax_master_doctype = self.meta.get_field("taxes_and_charges").options
Anand Doshid2946502014-04-08 20:10:03 +0530432
rohitwaghchaure57914f12018-04-24 19:19:47 +0530433 if (self.is_new() or self.is_pos_profile_changed()) and not self.get("taxes"):
rohitwaghchaure505661b2017-12-11 14:52:28 +0530434 if self.company and not self.get("taxes_and_charges"):
Anand Doshi3543f302013-05-24 19:25:01 +0530435 # get the default tax master
rohitwaghchaure505661b2017-12-11 14:52:28 +0530436 self.taxes_and_charges = frappe.db.get_value(tax_master_doctype,
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800437 {"is_default": 1, 'company': self.company})
Anand Doshid2946502014-04-08 20:10:03 +0530438
Nabin Haitffc7f3f2015-05-12 15:07:02 +0530439 self.append_taxes_from_master(tax_master_doctype)
Anand Doshid2946502014-04-08 20:10:03 +0530440
rohitwaghchaure57914f12018-04-24 19:19:47 +0530441 def is_pos_profile_changed(self):
442 if (self.doctype == 'Sales Invoice' and self.is_pos and
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800443 self.pos_profile != frappe.db.get_value('Sales Invoice', self.name, 'pos_profile')):
rohitwaghchaure57914f12018-04-24 19:19:47 +0530444 return True
445
Nabin Haitffc7f3f2015-05-12 15:07:02 +0530446 def append_taxes_from_master(self, tax_master_doctype=None):
447 if self.get("taxes_and_charges"):
Anand Doshi99100a42013-07-04 17:13:53 +0530448 if not tax_master_doctype:
Nabin Haitffc7f3f2015-05-12 15:07:02 +0530449 tax_master_doctype = self.meta.get_field("taxes_and_charges").options
Anand Doshid2946502014-04-08 20:10:03 +0530450
Nabin Haitffc7f3f2015-05-12 15:07:02 +0530451 self.extend("taxes", get_taxes_and_charges(tax_master_doctype, self.get("taxes_and_charges")))
Akhilesh Darjee4cdb7992014-01-30 13:56:57 +0530452
Anand Doshiac32bad2014-04-18 01:30:14 +0530453 def set_other_charges(self):
Nabin Haite7d15362014-12-25 16:01:55 +0530454 self.set("taxes", [])
Nabin Haitffc7f3f2015-05-12 15:07:02 +0530455 self.set_taxes()
Anand Doshid2946502014-04-08 20:10:03 +0530456
ankitjavalkarwork6c29d872014-10-06 13:20:53 +0530457 def validate_enabled_taxes_and_charges(self):
458 taxes_and_charges_doctype = self.meta.get_options("taxes_and_charges")
459 if frappe.db.get_value(taxes_and_charges_doctype, self.taxes_and_charges, "disabled"):
460 frappe.throw(_("{0} '{1}' is disabled").format(taxes_and_charges_doctype, self.taxes_and_charges))
461
Nabin Haita2426fc2018-01-15 17:45:46 +0530462 def validate_tax_account_company(self):
463 for d in self.get("taxes"):
464 if d.account_head:
465 tax_account_company = frappe.db.get_value("Account", d.account_head, "company")
466 if tax_account_company != self.company:
467 frappe.throw(_("Row #{0}: Account {1} does not belong to company {2}")
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800468 .format(d.idx, d.account_head, self.company))
Nabin Haita2426fc2018-01-15 17:45:46 +0530469
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530470 def get_gl_dict(self, args, account_currency=None, item=None):
Nabin Hait2df4d542013-01-29 11:34:39 +0530471 """this method populates the common properties of a gl entry record"""
Rushabh Mehta2cb7a9f2016-06-15 16:45:03 +0530472
Rohit Waghchaureaa7b4342018-05-11 01:56:05 +0530473 posting_date = args.get('posting_date') or self.get('posting_date')
474 fiscal_years = get_fiscal_years(posting_date, company=self.company)
Nabin Hait2ed0b592016-03-29 13:14:17 +0530475 if len(fiscal_years) > 1:
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800476 frappe.throw(_("Multiple fiscal years exist for the date {0}. Please set company in Fiscal Year").format(
477 formatdate(posting_date)))
Nabin Hait2ed0b592016-03-29 13:14:17 +0530478 else:
479 fiscal_year = fiscal_years[0][0]
Rushabh Mehta2cb7a9f2016-06-15 16:45:03 +0530480
Rushabh Mehta793ba6b2014-02-14 15:47:51 +0530481 gl_dict = frappe._dict({
Anand Doshid2946502014-04-08 20:10:03 +0530482 'company': self.company,
Rohit Waghchaureaa7b4342018-05-11 01:56:05 +0530483 'posting_date': posting_date,
Nabin Hait2ed0b592016-03-29 13:14:17 +0530484 'fiscal_year': fiscal_year,
Anand Doshif78d1ae2014-03-28 13:55:00 +0530485 'voucher_type': self.doctype,
486 'voucher_no': self.name,
Nabin Hait5cd04a62019-05-27 11:44:06 +0530487 'remarks': self.get("remarks") or self.get("remark"),
Nabin Hait2df4d542013-01-29 11:34:39 +0530488 'debit': 0,
489 'credit': 0,
Nabin Hait46bcbaf2015-08-19 13:49:10 +0530490 'debit_in_account_currency': 0,
491 'credit_in_account_currency': 0,
Anand Doshif78d1ae2014-03-28 13:55:00 +0530492 'is_opening': self.get("is_opening") or "No",
Nabin Hait32251022014-08-29 11:18:32 +0530493 'party_type': None,
Nabin Hait591a5ab2016-05-26 17:41:39 +0530494 'party': None,
495 'project': self.get("project")
Nabin Hait2e296fa2013-08-28 18:53:11 +0530496 })
deepeshgarg007d83cf652019-05-12 18:34:23 +0530497
498 accounting_dimensions = get_accounting_dimensions()
499 dimension_dict = frappe._dict()
500
501 for dimension in accounting_dimensions:
502 dimension_dict[dimension] = self.get(dimension)
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530503 if item and item.get(dimension):
504 dimension_dict[dimension] = item.get(dimension)
deepeshgarg007d83cf652019-05-12 18:34:23 +0530505
506 gl_dict.update(dimension_dict)
Nabin Hait2df4d542013-01-29 11:34:39 +0530507 gl_dict.update(args)
Anand Doshi979326b2015-09-11 16:22:37 +0530508
Nabin Hait895029d2015-08-20 14:55:39 +0530509 if not account_currency:
Anand Doshicd0989e2015-09-28 13:31:17 +0530510 account_currency = get_account_currency(gl_dict.account)
Anand Doshi979326b2015-09-11 16:22:37 +0530511
Rushabh Mehtabc4e2cd2017-10-17 12:30:34 +0530512 if gl_dict.account and self.doctype not in ["Journal Entry",
Deepesh Gargbfc17e42020-12-25 18:34:39 +0530513 "Period Closing Voucher", "Payment Entry", "Purchase Receipt", "Purchase Invoice", "Stock Entry"]:
Anand Doshibe6cfdd2015-09-17 21:07:04 +0530514 self.validate_account_currency(gl_dict.account, account_currency)
Deepesh Garg7c300852020-12-26 20:14:51 +0530515
516 if gl_dict.account and self.doctype not in ["Journal Entry", "Period Closing Voucher", "Payment Entry"]:
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800517 set_balance_in_account_currency(gl_dict, account_currency, self.get("conversion_rate"),
518 self.company_currency)
Anand Doshi979326b2015-09-11 16:22:37 +0530519
Nabin Haitc561a492015-08-19 19:22:34 +0530520 return gl_dict
Anand Doshi979326b2015-09-11 16:22:37 +0530521
Anurag Mishrae657fe82018-11-26 15:19:17 +0530522 def validate_qty_is_not_zero(self):
Maricac68a9402019-12-03 12:59:22 +0530523 if self.doctype != "Purchase Receipt":
524 for item in self.items:
525 if not item.qty:
526 frappe.throw(_("Item quantity can not be zero"))
Anurag Mishrae657fe82018-11-26 15:19:17 +0530527
Nabin Hait895029d2015-08-20 14:55:39 +0530528 def validate_account_currency(self, account, account_currency=None):
Nabin Hait6e439a52015-08-28 19:24:22 +0530529 valid_currency = [self.company_currency]
530 if self.get("currency") and self.currency != self.company_currency:
531 valid_currency.append(self.currency)
532
Nabin Hait895029d2015-08-20 14:55:39 +0530533 if account_currency not in valid_currency:
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530534 frappe.throw(_("Account {0} is invalid. Account Currency must be {1}")
Suraj Shettyda6806e2020-04-08 09:32:41 +0530535 .format(account, (' ' + _("or") + ' ').join(valid_currency)))
Anand Doshi979326b2015-09-11 16:22:37 +0530536
Anand Doshi613cb6a2013-02-06 17:33:46 +0530537 def clear_unallocated_advances(self, childtype, parentfield):
Rushabh Mehtaf191f852014-04-02 18:09:34 +0530538 self.set(parentfield, self.get(parentfield, {"allocated_amount": ["not in", [0, None, ""]]}))
539
Anand Doshid2946502014-04-08 20:10:03 +0530540 frappe.db.sql("""delete from `tab%s` where parentfield=%s and parent = %s
Anand Doshi602e8252015-11-16 19:05:46 +0530541 and allocated_amount = 0""" % (childtype, '%s', '%s'), (parentfield, self.name))
Anand Doshid2946502014-04-08 20:10:03 +0530542
Walstan Baptistad6360752021-03-31 12:30:32 +0530543 @frappe.whitelist()
Rushabh Mehta30dc9a12017-11-17 14:31:09 +0530544 def apply_shipping_rule(self):
545 if self.shipping_rule:
546 shipping_rule = frappe.get_doc("Shipping Rule", self.shipping_rule)
547 shipping_rule.apply(self)
548 self.calculate_taxes_and_totals()
549
550 def get_shipping_address(self):
551 '''Returns Address object from shipping address fields if present'''
552
553 # shipping address fields can be `shipping_address_name` or `shipping_address`
554 # try getting value from both
555
556 for fieldname in ('shipping_address_name', 'shipping_address'):
557 shipping_field = self.meta.get_field(fieldname)
558 if shipping_field and shipping_field.fieldtype == 'Link':
559 if self.get(fieldname):
560 return frappe.get_doc('Address', self.get(fieldname))
561
562 return {}
563
Walstan Baptistad6360752021-03-31 12:30:32 +0530564 @frappe.whitelist()
Nabin Hait041a5c22018-08-01 18:07:39 +0530565 def set_advances(self):
566 """Returns list of advances against Account, Party, Reference"""
567
568 res = self.get_advance_entries()
569
570 self.set("advances", [])
571 advance_allocated = 0
572 for d in res:
573 if d.against_order:
574 allocated_amount = flt(d.amount)
575 else:
Deepesh Garg26210162020-08-11 16:06:13 +0530576 if self.get('party_account_currency') == self.company_currency:
577 amount = self.get('base_rounded_total') or self.base_grand_total
578 else:
579 amount = self.get('rounded_total') or self.grand_total
580
Rohit Waghchaure6cc2f522018-11-27 12:07:03 +0530581 allocated_amount = min(amount - advance_allocated, d.amount)
Nabin Hait041a5c22018-08-01 18:07:39 +0530582 advance_allocated += flt(allocated_amount)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530583
Saqiba20999c2021-07-12 14:33:23 +0530584 advance_row = {
Nabin Hait041a5c22018-08-01 18:07:39 +0530585 "doctype": self.doctype + " Advance",
586 "reference_type": d.reference_type,
587 "reference_name": d.reference_name,
588 "reference_row": d.reference_row,
589 "remarks": d.remarks,
590 "advance_amount": flt(d.amount),
Saqiba20999c2021-07-12 14:33:23 +0530591 "allocated_amount": allocated_amount,
592 "ref_exchange_rate": flt(d.exchange_rate) # exchange_rate of advance entry
593 }
594
595 self.append("advances", advance_row)
Nabin Hait041a5c22018-08-01 18:07:39 +0530596
Nabin Hait28a05282016-06-27 17:41:39 +0530597 def get_advance_entries(self, include_unallocated=True):
598 if self.doctype == "Sales Invoice":
599 party_account = self.debit_to
600 party_type = "Customer"
601 party = self.customer
602 amount_field = "credit_in_account_currency"
603 order_field = "sales_order"
604 order_doctype = "Sales Order"
605 else:
606 party_account = self.credit_to
607 party_type = "Supplier"
608 party = self.supplier
609 amount_field = "debit_in_account_currency"
610 order_field = "purchase_order"
611 order_doctype = "Purchase Order"
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530612
Ankush Menat98917802021-06-11 18:40:22 +0530613 order_list = list(set(d.get(order_field)
614 for d in self.get("items") if d.get(order_field)))
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530615
616 journal_entries = get_advance_journal_entries(party_type, party, party_account,
Nabin Hait868766d2019-07-15 18:02:58 +0530617 amount_field, order_doctype, order_list, include_unallocated)
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530618
619 payment_entries = get_advance_payment_entries(party_type, party, party_account,
Nabin Hait868766d2019-07-15 18:02:58 +0530620 order_doctype, order_list, include_unallocated)
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530621
Nabin Hait28a05282016-06-27 17:41:39 +0530622 res = journal_entries + payment_entries
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530623
Nabin Hait28a05282016-06-27 17:41:39 +0530624 return res
Nabin Hait48f5fa62014-09-18 15:03:54 +0530625
rohitwaghchaurebf4c1142018-01-08 15:20:15 +0530626 def is_inclusive_tax(self):
Nabin Hait868766d2019-07-15 18:02:58 +0530627 is_inclusive = cint(frappe.db.get_single_value("Accounts Settings", "show_inclusive_tax_in_print"))
rohitwaghchaurebf4c1142018-01-08 15:20:15 +0530628
629 if is_inclusive:
630 is_inclusive = 0
631 if self.get("taxes", filters={"included_in_print_rate": 1}):
632 is_inclusive = 1
633
634 return is_inclusive
635
Nabin Hait28a05282016-06-27 17:41:39 +0530636 def validate_advance_entries(self):
Nabin Hait05aefbb2016-06-28 19:42:19 +0530637 order_field = "sales_order" if self.doctype == "Sales Invoice" else "purchase_order"
Ankush Menat98917802021-06-11 18:40:22 +0530638 order_list = list(set(d.get(order_field)
639 for d in self.get("items") if d.get(order_field)))
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530640
Nabin Hait05aefbb2016-06-28 19:42:19 +0530641 if not order_list: return
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530642
Nabin Hait28a05282016-06-27 17:41:39 +0530643 advance_entries = self.get_advance_entries(include_unallocated=False)
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530644
Nabin Hait28a05282016-06-27 17:41:39 +0530645 if advance_entries:
646 advance_entries_against_si = [d.reference_name for d in self.get("advances")]
647 for d in advance_entries:
648 if not advance_entries_against_si or d.reference_name not in advance_entries_against_si:
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800649 frappe.msgprint(_(
650 "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 +0530651 .format(d.reference_name, d.against_order))
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530652
Saqiba20999c2021-07-12 14:33:23 +0530653 def set_advance_gain_or_loss(self):
654 if not self.get("advances"):
655 return
656
657 for d in self.get("advances"):
658 advance_exchange_rate = d.ref_exchange_rate
659 if (d.allocated_amount and self.conversion_rate != 1
660 and self.conversion_rate != advance_exchange_rate):
661
662 base_allocated_amount_in_ref_rate = advance_exchange_rate * d.allocated_amount
663 base_allocated_amount_in_inv_rate = self.conversion_rate * d.allocated_amount
664 difference = base_allocated_amount_in_ref_rate - base_allocated_amount_in_inv_rate
665
666 d.exchange_gain_loss = difference
667
668 def make_exchange_gain_loss_gl_entries(self, gl_entries):
669 if self.get('doctype') in ['Purchase Invoice', 'Sales Invoice']:
670 for d in self.get("advances"):
671 if d.exchange_gain_loss:
Saqibd4ae1fe2021-07-30 11:21:49 +0530672 is_purchase_invoice = self.get('doctype') == 'Purchase Invoice'
673 party = self.supplier if is_purchase_invoice else self.customer
674 party_account = self.credit_to if is_purchase_invoice else self.debit_to
675 party_type = "Supplier" if is_purchase_invoice else "Customer"
Saqiba20999c2021-07-12 14:33:23 +0530676
677 gain_loss_account = frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account')
Saqibd4ae1fe2021-07-30 11:21:49 +0530678 if not gain_loss_account:
679 frappe.throw(_("Please set Default Exchange Gain/Loss Account in Company {}")
680 .format(self.get('company')))
Saqiba20999c2021-07-12 14:33:23 +0530681 account_currency = get_account_currency(gain_loss_account)
682 if account_currency != self.company_currency:
Saqibd4ae1fe2021-07-30 11:21:49 +0530683 frappe.throw(_("Currency for {0} must be {1}").format(gain_loss_account, self.company_currency))
Saqiba20999c2021-07-12 14:33:23 +0530684
685 # for purchase
686 dr_or_cr = 'debit' if d.exchange_gain_loss > 0 else 'credit'
Saqibd4ae1fe2021-07-30 11:21:49 +0530687 if not is_purchase_invoice:
688 # just reverse for sales?
689 dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit'
Saqiba20999c2021-07-12 14:33:23 +0530690
691 gl_entries.append(
692 self.get_gl_dict({
693 "account": gain_loss_account,
694 "account_currency": account_currency,
695 "against": party,
696 dr_or_cr + "_in_account_currency": abs(d.exchange_gain_loss),
697 dr_or_cr: abs(d.exchange_gain_loss),
698 "cost_center": self.cost_center,
699 "project": self.project
700 }, item=d)
701 )
702
703 dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit'
704
705 gl_entries.append(
706 self.get_gl_dict({
707 "account": party_account,
708 "party_type": party_type,
709 "party": party,
710 "against": gain_loss_account,
711 dr_or_cr + "_in_account_currency": flt(abs(d.exchange_gain_loss) / self.conversion_rate),
712 dr_or_cr: abs(d.exchange_gain_loss),
713 "cost_center": self.cost_center,
714 "project": self.project
715 }, self.party_account_currency, item=self)
716 )
717
Nabin Hait28a05282016-06-27 17:41:39 +0530718 def update_against_document_in_jv(self):
719 """
720 Links invoice and advance voucher:
721 1. cancel advance voucher
722 2. split into multiple rows if partially adjusted, assign against voucher
723 3. submit advance voucher
724 """
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530725
Nabin Hait28a05282016-06-27 17:41:39 +0530726 if self.doctype == "Sales Invoice":
Nabin Haitff2f3ef2016-07-04 11:41:14 +0530727 party_type = "Customer"
Nabin Hait28a05282016-06-27 17:41:39 +0530728 party = self.customer
729 party_account = self.debit_to
730 dr_or_cr = "credit_in_account_currency"
731 else:
Nabin Haitff2f3ef2016-07-04 11:41:14 +0530732 party_type = "Supplier"
Nabin Hait28a05282016-06-27 17:41:39 +0530733 party = self.supplier
734 party_account = self.credit_to
735 dr_or_cr = "debit_in_account_currency"
Nabin Hait48f5fa62014-09-18 15:03:54 +0530736
Nabin Hait28a05282016-06-27 17:41:39 +0530737 lst = []
738 for d in self.get('advances'):
739 if flt(d.allocated_amount) > 0:
740 args = frappe._dict({
741 'voucher_type': d.reference_type,
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800742 'voucher_no': d.reference_name,
743 'voucher_detail_no': d.reference_row,
744 'against_voucher_type': self.doctype,
745 'against_voucher': self.name,
746 'account': party_account,
Nabin Haitff2f3ef2016-07-04 11:41:14 +0530747 'party_type': party_type,
Nabin Hait28a05282016-06-27 17:41:39 +0530748 'party': party,
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800749 'is_advance': 'Yes',
750 'dr_or_cr': dr_or_cr,
751 'unadjusted_amount': flt(d.advance_amount),
752 'allocated_amount': flt(d.allocated_amount),
Deepesh Gargf54d5962021-03-31 14:06:02 +0530753 'precision': d.precision('advance_amount'),
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530754 'exchange_rate': (self.conversion_rate
Nabin Hait868766d2019-07-15 18:02:58 +0530755 if self.party_account_currency != self.company_currency else 1),
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530756 'grand_total': (self.base_grand_total
Nabin Hait868766d2019-07-15 18:02:58 +0530757 if self.party_account_currency == self.company_currency else self.grand_total),
Saqiba20999c2021-07-12 14:33:23 +0530758 'outstanding_amount': self.outstanding_amount,
759 'difference_account': frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account'),
760 'exchange_gain_loss': flt(d.get('exchange_gain_loss'))
Nabin Hait28a05282016-06-27 17:41:39 +0530761 })
762 lst.append(args)
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530763
Nabin Hait28a05282016-06-27 17:41:39 +0530764 if lst:
765 from erpnext.accounts.utils import reconcile_against_document
766 reconcile_against_document(lst)
Anand Doshid2946502014-04-08 20:10:03 +0530767
Rohit Waghchaure5fa9a7a2019-04-01 00:40:38 +0530768 def on_cancel(self):
769 from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
770
771 if self.doctype in ["Sales Invoice", "Purchase Invoice"]:
Deepesh Gargc9da1fc2021-05-19 18:38:35 +0530772 self.update_allocated_advance_taxes_on_cancel()
Rohit Waghchaure5fa9a7a2019-04-01 00:40:38 +0530773 if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
774 unlink_ref_doc_from_payment_entries(self)
775
776 elif self.doctype in ["Sales Order", "Purchase Order"]:
Rohit Waghchaure9835c892019-04-01 01:02:06 +0530777 if frappe.db.get_single_value('Accounts Settings', 'unlink_advance_payment_on_cancelation_of_order'):
Rohit Waghchaure5fa9a7a2019-04-01 00:40:38 +0530778 unlink_ref_doc_from_payment_entries(self)
779
Deepesh Gargd18dde72020-11-29 21:40:04 +0530780 def get_tax_map(self):
781 tax_map = {}
782 for tax in self.get('taxes'):
783 tax_map.setdefault(tax.account_head, 0.0)
784 tax_map[tax.account_head] += tax.tax_amount
785
786 return tax_map
787
Deepesh Gargc9da1fc2021-05-19 18:38:35 +0530788 def update_allocated_advance_taxes_on_cancel(self):
Deepesh Gargd18dde72020-11-29 21:40:04 +0530789 if self.get('advances'):
790 tax_accounts = [d.account_head for d in self.get('taxes')]
791 allocated_tax_map = frappe._dict(frappe.get_all('GL Entry', fields=['account', 'sum(credit - debit)'],
792 filters={'voucher_no': self.name, 'account': ('in', tax_accounts)},
793 group_by='account', as_list=1))
794
795 tax_map = self.get_tax_map()
796
797 for pe in self.get('advances'):
Deepesh Garga23aaf42021-05-17 20:58:50 +0530798 if pe.reference_type == 'Payment Entry':
799 pe = frappe.get_doc('Payment Entry', pe.reference_name)
800 for tax in pe.get('taxes'):
801 allocated_amount = tax_map.get(tax.account_head) - allocated_tax_map.get(tax.account_head)
802 if allocated_amount > tax.tax_amount:
803 allocated_amount = tax.tax_amount
Deepesh Gargd18dde72020-11-29 21:40:04 +0530804
Deepesh Garga23aaf42021-05-17 20:58:50 +0530805 if allocated_amount:
Deepesh Gargc9da1fc2021-05-19 18:38:35 +0530806 frappe.db.set_value('Advance Taxes and Charges', tax.name, 'allocated_amount',
807 tax.allocated_amount - allocated_amount)
Deepesh Garga23aaf42021-05-17 20:58:50 +0530808 tax_map[tax.account_head] -= allocated_amount
809 allocated_tax_map[tax.account_head] -= allocated_amount
Deepesh Gargd18dde72020-11-29 21:40:04 +0530810
Deepesh Garg92f7a5a2021-07-21 15:25:40 +0530811 def get_amount_and_base_amount(self, item, enable_discount_accounting):
812 amount = item.net_amount
813 base_amount = item.base_net_amount
814
815 if enable_discount_accounting and self.get('discount_amount') and self.get('additional_discount_account'):
816 amount = item.amount
817 base_amount = item.base_amount
818
819 return amount, base_amount
820
821 def get_tax_amounts(self, tax, enable_discount_accounting):
822 amount = tax.tax_amount_after_discount_amount
823 base_amount = tax.base_tax_amount_after_discount_amount
824
825 if enable_discount_accounting and self.get('discount_amount') and self.get('additional_discount_account') \
826 and self.get('apply_discount_on') == 'Grand Total':
827 amount = tax.tax_amount
828 base_amount = tax.base_tax_amount
829
830 return amount, base_amount
831
GangaManoj8f7b0a12021-07-13 03:01:02 +0530832 def make_discount_gl_entries(self, gl_entries):
833 enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
834
835 if enable_discount_accounting:
GangaManoj4105e272021-07-20 03:46:02 +0530836 if self.doctype == "Purchase Invoice":
837 dr_or_cr = "credit"
838 rev_dr_cr = "debit"
839 supplier_or_customer = self.supplier
Ankush Menat4551d7d2021-08-19 13:41:10 +0530840
GangaManoj4105e272021-07-20 03:46:02 +0530841 else:
842 dr_or_cr = "debit"
843 rev_dr_cr = "credit"
844 supplier_or_customer = self.customer
845
GangaManoj8f7b0a12021-07-13 03:01:02 +0530846 for item in self.get("items"):
847 if item.get('discount_amount') and item.get('discount_account'):
Deepesh Gargad7bb312021-07-28 11:38:44 +0530848 discount_amount = item.discount_amount * item.qty
GangaManoj8f7b0a12021-07-13 03:01:02 +0530849 if self.doctype == "Purchase Invoice":
GangaManoj8f7b0a12021-07-13 03:01:02 +0530850 income_or_expense_account = (item.expense_account
Ankush Menat4551d7d2021-08-19 13:41:10 +0530851 if (not item.enable_deferred_expense or self.is_return)
GangaManoj8f7b0a12021-07-13 03:01:02 +0530852 else item.deferred_expense_account)
853 else:
GangaManoj8f7b0a12021-07-13 03:01:02 +0530854 income_or_expense_account = (item.income_account
Ankush Menat4551d7d2021-08-19 13:41:10 +0530855 if (not item.enable_deferred_revenue or self.is_return)
GangaManoj8f7b0a12021-07-13 03:01:02 +0530856 else item.deferred_revenue_account)
857
858 account_currency = get_account_currency(item.discount_account)
859 gl_entries.append(
860 self.get_gl_dict({
861 "account": item.discount_account,
862 "against": supplier_or_customer,
Deepesh Gargad7bb312021-07-28 11:38:44 +0530863 dr_or_cr: flt(discount_amount, item.precision('discount_amount')),
Ankush Menat4551d7d2021-08-19 13:41:10 +0530864 dr_or_cr + "_in_account_currency": flt(discount_amount * self.get('conversion_rate'),
Deepesh Gargad7bb312021-07-28 11:38:44 +0530865 item.precision('discount_amount')),
Ganga Manoj980798c2021-07-19 23:43:36 +0530866 "cost_center": item.cost_center,
Ganga Manoj63b7ecd2021-07-19 23:44:21 +0530867 "project": item.project
Ganga Manoj0ea29342021-07-19 23:44:55 +0530868 }, account_currency, item=item)
GangaManoj8f7b0a12021-07-13 03:01:02 +0530869 )
870
871 account_currency = get_account_currency(income_or_expense_account)
872 gl_entries.append(
873 self.get_gl_dict({
874 "account": income_or_expense_account,
875 "against": supplier_or_customer,
Deepesh Gargad7bb312021-07-28 11:38:44 +0530876 rev_dr_cr: flt(discount_amount, item.precision('discount_amount')),
Ankush Menat4551d7d2021-08-19 13:41:10 +0530877 rev_dr_cr + "_in_account_currency": flt(discount_amount * self.get('conversion_rate'),
Deepesh Gargad7bb312021-07-28 11:38:44 +0530878 item.precision('discount_amount')),
GangaManoj8f7b0a12021-07-13 03:01:02 +0530879 "cost_center": item.cost_center,
880 "project": item.project or self.project
881 }, account_currency, item=item)
882 )
GangaManoj857501c2021-07-15 22:03:46 +0530883
GangaManoj4105e272021-07-20 03:46:02 +0530884 if self.get('discount_amount') and self.get('additional_discount_account'):
GangaManoj857501c2021-07-15 22:03:46 +0530885 gl_entries.append(
886 self.get_gl_dict({
887 "account": self.additional_discount_account,
GangaManoj4105e272021-07-20 03:46:02 +0530888 "against": supplier_or_customer,
889 dr_or_cr: self.discount_amount,
890 "cost_center": self.cost_center
891 }, item=self)
Ankush Menat4551d7d2021-08-19 13:41:10 +0530892 )
893
Deepesh Gargc9da1fc2021-05-19 18:38:35 +0530894 def allocate_advance_taxes(self, gl_entries):
895 tax_map = self.get_tax_map()
896 for pe in self.get("advances"):
Deepesh Garg911818a2021-06-09 22:55:10 +0530897 if pe.reference_type == "Payment Entry" and \
898 frappe.db.get_value('Payment Entry', pe.reference_name, 'advance_tax_account'):
Deepesh Gargc9da1fc2021-05-19 18:38:35 +0530899 pe = frappe.get_doc("Payment Entry", pe.reference_name)
900 for tax in pe.get("taxes"):
901 account_currency = get_account_currency(tax.account_head)
902
903 if self.doctype == "Purchase Invoice":
Deepesh Gargc9da1fc2021-05-19 18:38:35 +0530904 dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
905 rev_dr_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
Deepesh Garg4478c542021-07-13 11:22:55 +0530906 else:
907 dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
908 rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
Deepesh Gargc9da1fc2021-05-19 18:38:35 +0530909
910 party = self.supplier if self.doctype == "Purchase Invoice" else self.customer
911 unallocated_amount = tax.tax_amount - tax.allocated_amount
912 if tax_map.get(tax.account_head):
913 amount = tax_map.get(tax.account_head)
914 if amount < unallocated_amount:
915 unallocated_amount = amount
916
917 gl_entries.append(
918 self.get_gl_dict({
919 "account": tax.account_head,
920 "against": party,
921 dr_or_cr: unallocated_amount,
922 dr_or_cr + "_in_account_currency": unallocated_amount
923 if account_currency==self.company_currency
924 else unallocated_amount,
925 "cost_center": tax.cost_center
926 }, account_currency, item=tax))
927
928 gl_entries.append(
929 self.get_gl_dict({
930 "account": pe.advance_tax_account,
931 "against": party,
932 rev_dr_cr: unallocated_amount,
933 rev_dr_cr + "_in_account_currency": unallocated_amount
934 if account_currency==self.company_currency
935 else unallocated_amount,
936 "cost_center": tax.cost_center
937 }, account_currency, item=tax))
938
939 frappe.db.set_value("Advance Taxes and Charges", tax.name, "allocated_amount",
940 tax.allocated_amount + unallocated_amount)
941
942 tax_map[tax.account_head] -= unallocated_amount
943
Nabin Hait19d945a2013-07-29 18:35:39 +0530944 def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield):
Nabin Hait868766d2019-07-15 18:02:58 +0530945 from erpnext.controllers.status_updater import get_allowance_for
946 item_allowance = {}
947 global_qty_allowance, global_amount_allowance = None, None
Anand Doshid2946502014-04-08 20:10:03 +0530948
Nabin Hait4b8185d2014-12-25 18:19:39 +0530949 for item in self.get("items"):
Anand Doshif78d1ae2014-03-28 13:55:00 +0530950 if item.get(item_ref_dn):
Anand Doshid2946502014-04-08 20:10:03 +0530951 ref_amt = flt(frappe.db.get_value(ref_dt + " Item",
Nabin Hait868766d2019-07-15 18:02:58 +0530952 item.get(item_ref_dn), based_on), self.precision(based_on, item))
Nabin Haitdc15b4f2014-01-20 16:48:49 +0530953 if not ref_amt:
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800954 frappe.msgprint(
Nabin Hait868766d2019-07-15 18:02:58 +0530955 _("Warning: System will not check overbilling since amount for Item {0} in {1} is zero")
956 .format(item.item_code, ref_dt))
Nabin Haitdc15b4f2014-01-20 16:48:49 +0530957 else:
Nabin Hait868766d2019-07-15 18:02:58 +0530958 already_billed = frappe.db.sql("""
959 select sum(%s)
960 from `tab%s`
961 where %s=%s and docstatus=1 and parent != %s
962 """ % (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'),
963 (item.get(item_ref_dn), self.name))[0][0]
Anand Doshid2946502014-04-08 20:10:03 +0530964
965 total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)),
Nabin Hait868766d2019-07-15 18:02:58 +0530966 self.precision(based_on, item))
Anand Doshid2946502014-04-08 20:10:03 +0530967
Nabin Hait868766d2019-07-15 18:02:58 +0530968 allowance, item_allowance, global_qty_allowance, global_amount_allowance = \
969 get_allowance_for(item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount")
Anand Doshid2946502014-04-08 20:10:03 +0530970
Nabin Hait868766d2019-07-15 18:02:58 +0530971 max_allowed_amt = flt(ref_amt * (100 + allowance) / 100)
Anand Doshid2946502014-04-08 20:10:03 +0530972
rohitwaghchaure285344e2019-10-11 11:02:11 +0530973 if total_billed_amt < 0 and max_allowed_amt < 0:
974 # while making debit note against purchase return entry(purchase receipt) getting overbill error
975 total_billed_amt = abs(total_billed_amt)
976 max_allowed_amt = abs(max_allowed_amt)
977
Deepesh Gargcb718fc2021-04-19 13:25:15 +0530978 role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
979
980 if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in frappe.get_roles():
Afshan53fefd72021-06-24 10:09:02 +0530981 if self.doctype != "Purchase Invoice":
982 self.throw_overbill_exception(item, max_allowed_amt)
983 elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")):
984 self.throw_overbill_exception(item, max_allowed_amt)
985
986 def throw_overbill_exception(self, item, max_allowed_amt):
987 frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
988 .format(item.item_code, item.idx, max_allowed_amt))
Anand Doshid2946502014-04-08 20:10:03 +0530989
Rohit Waghchaure2a14f252021-07-30 12:36:35 +0530990 def get_company_default(self, fieldname, ignore_validation=False):
Rushabh Mehta1f847992013-12-12 19:12:19 +0530991 from erpnext.accounts.utils import get_company_default
Rohit Waghchaure2a14f252021-07-30 12:36:35 +0530992 return get_company_default(self.company, fieldname, ignore_validation=ignore_validation)
Anand Doshid2946502014-04-08 20:10:03 +0530993
Nabin Haita36adbd2013-08-02 14:50:12 +0530994 def get_stock_items(self):
995 stock_items = []
Nabin Haitdd38a262014-12-26 13:15:21 +0530996 item_codes = list(set(item.item_code for item in self.get("items")))
Nabin Haita36adbd2013-08-02 14:50:12 +0530997 if item_codes:
Nabin Hait868766d2019-07-15 18:02:58 +0530998 stock_items = [r[0] for r in frappe.db.sql("""
999 select name from `tabItem`
1000 where name in (%s) and is_stock_item=1
1001 """ % (", ".join((["%s"] * len(item_codes))),), item_codes)]
Anand Doshid2946502014-04-08 20:10:03 +05301002
Nabin Haita36adbd2013-08-02 14:50:12 +05301003 return stock_items
Anand Doshid2946502014-04-08 20:10:03 +05301004
Ankit Javalkar8e7ca412014-09-12 15:18:53 +05301005 def set_total_advance_paid(self):
1006 if self.doctype == "Sales Order":
Nabin Hait6e439a52015-08-28 19:24:22 +05301007 dr_or_cr = "credit_in_account_currency"
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +05301008 rev_dr_or_cr = "debit_in_account_currency"
Nabin Haitb2206d12016-01-27 15:43:12 +05301009 party = self.customer
Ankit Javalkar8e7ca412014-09-12 15:18:53 +05301010 else:
Nabin Hait6e439a52015-08-28 19:24:22 +05301011 dr_or_cr = "debit_in_account_currency"
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +05301012 rev_dr_or_cr = "credit_in_account_currency"
Nabin Haitb2206d12016-01-27 15:43:12 +05301013 party = self.supplier
Ankit Javalkar8e7ca412014-09-12 15:18:53 +05301014
Nabin Haitb2206d12016-01-27 15:43:12 +05301015 advance = frappe.db.sql("""
Ankit Javalkar8e7ca412014-09-12 15:18:53 +05301016 select
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +05301017 account_currency, sum({dr_or_cr}) - sum({rev_dr_cr}) as amount
Ankit Javalkar8e7ca412014-09-12 15:18:53 +05301018 from
Nabin Hait12e2a512016-06-26 01:37:21 +05301019 `tabGL Entry`
Ankit Javalkar8e7ca412014-09-12 15:18:53 +05301020 where
Nabin Hait12e2a512016-06-26 01:37:21 +05301021 against_voucher_type = %s and against_voucher = %s and party=%s
1022 and docstatus = 1
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +05301023 """.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 +05301024
Nabin Haitb2206d12016-01-27 15:43:12 +05301025 if advance:
Anand Doshi7c0a58a2016-01-29 12:16:24 +05301026 advance = advance[0]
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +05301027
Anand Doshi7c0a58a2016-01-29 12:16:24 +05301028 advance_paid = flt(advance.amount, self.precision("advance_paid"))
1029 formatted_advance_paid = fmt_money(advance_paid, precision=self.precision("advance_paid"),
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001030 currency=advance.account_currency)
Anand Doshi7c0a58a2016-01-29 12:16:24 +05301031
1032 frappe.db.set_value(self.doctype, self.name, "party_account_currency",
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001033 advance.account_currency)
Anand Doshi7c0a58a2016-01-29 12:16:24 +05301034
1035 if advance.account_currency == self.currency:
finbyz5efc7972019-01-05 11:12:11 +05301036 order_total = self.get("rounded_total") or self.grand_total
1037 precision = "rounded_total" if self.get("rounded_total") else "grand_total"
Nabin Haitb2206d12016-01-27 15:43:12 +05301038 else:
finbyz5efc7972019-01-05 11:12:11 +05301039 order_total = self.get("base_rounded_total") or self.base_grand_total
1040 precision = "base_rounded_total" if self.get("base_rounded_total") else "base_grand_total"
Sagar Voraf733a392019-01-07 14:24:01 +05301041
finbyz5efc7972019-01-05 11:12:11 +05301042 formatted_order_total = fmt_money(order_total, precision=self.precision(precision),
1043 currency=advance.account_currency)
Anand Doshi7c0a58a2016-01-29 12:16:24 +05301044
Nabin Hait9db1b222016-06-30 12:37:53 +05301045 if self.currency == self.company_currency and advance_paid > order_total:
Nabin Haitb2206d12016-01-27 15:43:12 +05301046 frappe.throw(_("Total advance ({0}) against Order {1} cannot be greater than the Grand Total ({2})")
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001047 .format(formatted_advance_paid, self.name, formatted_order_total))
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301048
Nabin Hait13093b42016-06-29 18:04:37 +05301049 frappe.db.set_value(self.doctype, self.name, "advance_paid", advance_paid)
Ankit Javalkar8e7ca412014-09-12 15:18:53 +05301050
ankitjavalkarworke60822b2014-08-26 14:29:06 +05301051 @property
1052 def company_abbr(self):
Nabin Hait6a5695f2018-08-09 11:30:21 +05301053 if not hasattr(self, "_abbr"):
1054 self._abbr = frappe.db.get_value('Company', self.company, "abbr")
ankitjavalkarworke60822b2014-08-26 14:29:06 +05301055
1056 return self._abbr
1057
marination4be5b5c2020-10-08 19:08:27 +05301058 def raise_missing_debit_credit_account_error(self, party_type, party):
marination53b1a9a2020-11-03 15:45:25 +05301059 """Raise an error if debit to/credit to account does not exist."""
marination4be5b5c2020-10-08 19:08:27 +05301060 db_or_cr = frappe.bold("Debit To") if self.doctype == "Sales Invoice" else frappe.bold("Credit To")
1061 rec_or_pay = "Receivable" if self.doctype == "Sales Invoice" else "Payable"
1062
1063 link_to_party = frappe.utils.get_link_to_form(party_type, party)
1064 link_to_company = frappe.utils.get_link_to_form("Company", self.company)
1065
1066 message = _("{0} Account not found against Customer {1}.").format(db_or_cr, frappe.bold(party) or '')
1067 message += "<br>" + _("Please set one of the following:") + "<br>"
1068 message += "<br><ul><li>" + _("'Account' in the Accounting section of Customer {0}").format(link_to_party) + "</li>"
1069 message += "<li>" + _("'Default {0} Account' in Company {1}").format(rec_or_pay, link_to_company) + "</li></ul>"
1070
marination53b1a9a2020-11-03 15:45:25 +05301071 frappe.throw(message, title=_("Account Missing"), exc=AccountMissingError)
marination4be5b5c2020-10-08 19:08:27 +05301072
Neil Trini Lasrado3fbbb712015-07-17 12:10:12 +05301073 def validate_party(self):
Nabin Hait4ffd7f32015-08-27 12:28:36 +05301074 party_type, party = self.get_party()
shreyaseba9ca42016-01-26 14:56:52 +05301075 validate_party_frozen_disabled(party_type, party)
Anand Doshi979326b2015-09-11 16:22:37 +05301076
Nabin Hait4ffd7f32015-08-27 12:28:36 +05301077 def get_party(self):
Neil Trini Lasrado79bf2332015-07-20 13:03:48 +05301078 party_type = None
shreyas29b565f2016-01-25 17:30:49 +05301079 if self.doctype in ("Opportunity", "Quotation", "Sales Order", "Delivery Note", "Sales Invoice"):
shreyaseba9ca42016-01-26 14:56:52 +05301080 party_type = 'Customer'
shreyas29b565f2016-01-25 17:30:49 +05301081
1082 elif self.doctype in ("Supplier Quotation", "Purchase Order", "Purchase Receipt", "Purchase Invoice"):
shreyaseba9ca42016-01-26 14:56:52 +05301083 party_type = 'Supplier'
shreyas29b565f2016-01-25 17:30:49 +05301084
1085 elif self.meta.get_field("customer"):
1086 party_type = "Customer"
Neil Trini Lasrado3fbbb712015-07-17 12:10:12 +05301087
Neil Trini Lasrado79bf2332015-07-20 13:03:48 +05301088 elif self.meta.get_field("supplier"):
shreyas29b565f2016-01-25 17:30:49 +05301089 party_type = "Supplier"
Anand Doshi979326b2015-09-11 16:22:37 +05301090
Nabin Hait4ffd7f32015-08-27 12:28:36 +05301091 party = self.get(party_type.lower()) if party_type else None
Anand Doshi979326b2015-09-11 16:22:37 +05301092
Nabin Hait4ffd7f32015-08-27 12:28:36 +05301093 return party_type, party
Anand Doshi979326b2015-09-11 16:22:37 +05301094
Nabin Hait4ffd7f32015-08-27 12:28:36 +05301095 def validate_currency(self):
Nabin Hait6e439a52015-08-28 19:24:22 +05301096 if self.get("currency"):
Nabin Hait4ffd7f32015-08-27 12:28:36 +05301097 party_type, party = self.get_party()
Nabin Hait6e439a52015-08-28 19:24:22 +05301098 if party_type and party:
Anand Doshib20baf82015-09-25 16:17:50 +05301099 party_account_currency = get_party_account_currency(party_type, party, self.company)
Anand Doshi979326b2015-09-11 16:22:37 +05301100
Anand Doshi7afaeb02015-10-01 18:55:25 +05301101 if (party_account_currency
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001102 and party_account_currency != self.company_currency
1103 and self.currency != party_account_currency):
Nabin Hait98096772015-09-03 10:28:08 +05301104 frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}")
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001105 .format(party_type, party, party_account_currency), InvalidCurrency)
Anand Doshi979326b2015-09-11 16:22:37 +05301106
shreyas29b565f2016-01-25 17:30:49 +05301107 # Note: not validating with gle account because we don't have the account
1108 # at quotation / sales order level and we shouldn't stop someone
1109 # from creating a sales invoice if sales order is already created
Rushabh Mehta2cb7a9f2016-06-15 16:45:03 +05301110
Nabin Hait297d74a2016-11-23 15:58:51 +05301111 def delink_advance_entries(self, linked_doc_name):
1112 total_allocated_amount = 0
1113 for adv in self.advances:
1114 consider_for_total_advance = True
1115 if adv.reference_name == linked_doc_name:
1116 frappe.db.sql("""delete from `tab{0} Advance`
1117 where name = %s""".format(self.doctype), adv.name)
1118 consider_for_total_advance = False
1119
1120 if consider_for_total_advance:
1121 total_allocated_amount += flt(adv.allocated_amount, adv.precision("allocated_amount"))
1122
Rushabh Mehta66958302017-01-16 16:57:53 +05301123 frappe.db.set_value(self.doctype, self.name, "total_advance",
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001124 total_allocated_amount, update_modified=False)
Rohit Waghchaure6087fe12016-04-09 14:31:09 +05301125
KanchanChauhan49a50fe2016-11-18 13:57:53 +05301126 def group_similar_items(self):
1127 group_item_qty = {}
1128 group_item_amount = {}
Shreya Shah785f1aa2018-10-11 10:14:25 +05301129 # to update serial number in print
1130 count = 0
KanchanChauhan49a50fe2016-11-18 13:57:53 +05301131
1132 for item in self.items:
1133 group_item_qty[item.item_code] = group_item_qty.get(item.item_code, 0) + item.qty
1134 group_item_amount[item.item_code] = group_item_amount.get(item.item_code, 0) + item.amount
1135
1136 duplicate_list = []
KanchanChauhan49a50fe2016-11-18 13:57:53 +05301137 for item in self.items:
1138 if item.item_code in group_item_qty:
Shreya Shah785f1aa2018-10-11 10:14:25 +05301139 count += 1
KanchanChauhan49a50fe2016-11-18 13:57:53 +05301140 item.qty = group_item_qty[item.item_code]
1141 item.amount = group_item_amount[item.item_code]
deepeshgarg007e8d21cd2019-06-24 17:46:44 +05301142
1143 if item.qty:
1144 item.rate = flt(flt(item.amount) / flt(item.qty), item.precision("rate"))
1145 else:
1146 item.rate = 0
1147
Shreya Shah785f1aa2018-10-11 10:14:25 +05301148 item.idx = count
KanchanChauhan49a50fe2016-11-18 13:57:53 +05301149 del group_item_qty[item.item_code]
1150 else:
1151 duplicate_list.append(item)
KanchanChauhan49a50fe2016-11-18 13:57:53 +05301152 for item in duplicate_list:
1153 self.remove(item)
1154
tunde32aa7c12017-09-07 06:52:15 +01001155 def set_payment_schedule(self):
Manas Solankia7f55892018-04-02 10:32:00 +05301156 if self.doctype == 'Sales Invoice' and self.is_pos:
1157 self.payment_terms_template = ''
1158 return
rohitwaghchauredb9fa782018-03-01 10:32:29 +05301159
Deepesh Garg26210162020-08-11 16:06:13 +05301160 party_account_currency = self.get('party_account_currency')
1161 if not party_account_currency:
1162 party_type, party = self.get_party()
1163
1164 if party_type and party:
1165 party_account_currency = get_party_account_currency(party_type, party, self.company)
1166
rohitwaghchaureda941af2018-01-17 16:23:04 +05301167 posting_date = self.get("bill_date") or self.get("posting_date") or self.get("transaction_date")
tundedf3a1752017-09-11 11:02:57 +01001168 date = self.get("due_date")
1169 due_date = date or posting_date
Deepesh Garg26210162020-08-11 16:06:13 +05301170
Saqib Ansarid552fe62021-04-23 14:46:52 +05301171 base_grand_total = self.get("base_rounded_total") or self.base_grand_total
1172 grand_total = self.get("rounded_total") or self.grand_total
Deepesh Garg26210162020-08-11 16:06:13 +05301173
Nabin Hait2f9f4f92017-12-20 12:24:59 +05301174 if self.doctype in ("Sales Invoice", "Purchase Invoice"):
Saqib Ansarid552fe62021-04-23 14:46:52 +05301175 base_grand_total = base_grand_total - flt(self.base_write_off_amount)
Nabin Hait2f9f4f92017-12-20 12:24:59 +05301176 grand_total = grand_total - flt(self.write_off_amount)
Deepesh Gargbcf56e62021-08-06 23:53:16 +05301177 po_or_so, doctype, fieldname = self.get_order_details()
1178 automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms'))
tunde96b8f222017-09-08 15:35:59 +01001179
Rohit Waghchaure4a267b92019-05-09 19:50:00 +05301180 if self.get("total_advance"):
Saqib Ansarid552fe62021-04-23 14:46:52 +05301181 if party_account_currency == self.company_currency:
1182 base_grand_total -= self.get("total_advance")
1183 grand_total = flt(base_grand_total / self.get("conversion_rate"), self.precision("grand_total"))
1184 else:
1185 grand_total -= self.get("total_advance")
1186 base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total"))
Rohit Waghchaure4a267b92019-05-09 19:50:00 +05301187
Nabin Hait0551f7b2017-11-21 19:58:16 +05301188 if not self.get("payment_schedule"):
Deepesh Gargbff3b092021-08-07 00:12:57 +05301189 if self.doctype in ["Sales Invoice", "Purchase Invoice"] and automatically_fetch_payment_terms \
1190 and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype):
Deepesh Gargbcf56e62021-08-06 23:53:16 +05301191 self.fetch_payment_terms_from_order(po_or_so, doctype)
1192 if self.get('payment_terms_template'):
1193 self.ignore_default_payment_terms_template = 1
1194 elif self.get("payment_terms_template"):
Saqib Ansarid552fe62021-04-23 14:46:52 +05301195 data = get_payment_terms(self.payment_terms_template, posting_date, grand_total, base_grand_total)
Nabin Hait0551f7b2017-11-21 19:58:16 +05301196 for item in data:
1197 self.append("payment_schedule", item)
GangaManoj4323f4b2021-07-22 05:57:42 +05301198 elif self.doctype not in ["Purchase Receipt"]:
Saqib Ansarida5b55c2021-04-28 14:55:33 +05301199 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 +05301200 self.append("payment_schedule", data)
Deepesh Gargbcf56e62021-08-06 23:53:16 +05301201
1202 for d in self.get("payment_schedule"):
1203 if d.invoice_portion:
1204 d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
1205 d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('base_payment_amount'))
1206 d.outstanding = d.payment_amount
1207 elif not d.invoice_portion:
1208 d.base_payment_amount = flt(base_grand_total * self.get("conversion_rate"), d.precision('base_payment_amount'))
Subin Tomfac88a32021-07-23 21:23:48 +05301209
tunde43870aa2017-08-18 11:59:30 +01001210
GangaManoj4323f4b2021-07-22 05:57:42 +05301211 def get_order_details(self):
1212 if self.doctype == "Sales Invoice":
1213 po_or_so = self.get('items')[0].get('sales_order')
1214 po_or_so_doctype = "Sales Order"
1215 po_or_so_doctype_name = "sales_order"
1216
tunde43870aa2017-08-18 11:59:30 +01001217 else:
GangaManoj4323f4b2021-07-22 05:57:42 +05301218 po_or_so = self.get('items')[0].get('purchase_order')
1219 po_or_so_doctype = "Purchase Order"
1220 po_or_so_doctype_name = "purchase_order"
Ankush Menat4551d7d2021-08-19 13:41:10 +05301221
GangaManoj4323f4b2021-07-22 05:57:42 +05301222 return po_or_so, po_or_so_doctype, po_or_so_doctype_name
1223
GangaManojc7c90242021-07-29 19:18:35 +05301224 def linked_order_has_payment_terms(self, po_or_so, fieldname, doctype):
GangaManoj4323f4b2021-07-22 05:57:42 +05301225 if po_or_so and self.all_items_have_same_po_or_so(po_or_so, fieldname):
GangaManojc7c90242021-07-29 19:18:35 +05301226 if self.linked_order_has_payment_terms_template(po_or_so, doctype):
GangaManoj4323f4b2021-07-22 05:57:42 +05301227 return True
1228 elif self.linked_order_has_payment_schedule(po_or_so):
1229 return True
Ankush Menat4551d7d2021-08-19 13:41:10 +05301230
GangaManoj4323f4b2021-07-22 05:57:42 +05301231 return False
1232
1233 def all_items_have_same_po_or_so(self, po_or_so, fieldname):
1234 for item in self.get('items'):
1235 if item.get(fieldname) != po_or_so:
1236 return False
Ankush Menat4551d7d2021-08-19 13:41:10 +05301237
GangaManoj4323f4b2021-07-22 05:57:42 +05301238 return True
1239
GangaManojc7c90242021-07-29 19:18:35 +05301240 def linked_order_has_payment_terms_template(self, po_or_so, doctype):
1241 return frappe.get_value(doctype, po_or_so, 'payment_terms_template')
GangaManoj4323f4b2021-07-22 05:57:42 +05301242
1243 def linked_order_has_payment_schedule(self, po_or_so):
1244 return frappe.get_all('Payment Schedule', filters={'parent': po_or_so})
1245
1246 def fetch_payment_terms_from_order(self, po_or_so, po_or_so_doctype):
1247 """
1248 Fetch Payment Terms from Purchase/Sales Order on creating a new Purchase/Sales Invoice.
1249 """
1250 po_or_so = frappe.get_cached_doc(po_or_so_doctype, po_or_so)
1251
1252 self.payment_schedule = []
1253 self.payment_terms_template = po_or_so.payment_terms_template
1254
1255 for schedule in po_or_so.payment_schedule:
1256 payment_schedule = {
1257 'payment_term': schedule.payment_term,
1258 'due_date': schedule.due_date,
1259 'invoice_portion': schedule.invoice_portion,
GangaManoj5b33e752021-08-05 21:50:09 +05301260 'mode_of_payment': schedule.mode_of_payment,
1261 'description': schedule.description
GangaManoj4323f4b2021-07-22 05:57:42 +05301262 }
GangaManoj5b33e752021-08-05 21:50:09 +05301263
1264 if schedule.discount_type == 'Percentage':
1265 payment_schedule['discount_type'] = schedule.discount_type
1266 payment_schedule['discount'] = schedule.discount
1267
GangaManoj4323f4b2021-07-22 05:57:42 +05301268 self.append("payment_schedule", payment_schedule)
tunde43870aa2017-08-18 11:59:30 +01001269
tundebe1b8712017-08-19 08:21:44 +01001270 def set_due_date(self):
Nabin Hait0551f7b2017-11-21 19:58:16 +05301271 due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date]
1272 if due_dates:
1273 self.due_date = max(due_dates)
tunde62af5c52017-09-22 15:16:38 +01001274
1275 def validate_payment_schedule_dates(self):
tunde77ecacc2017-09-22 23:12:55 +01001276 dates = []
tunde9bed2de2017-09-25 10:19:35 +01001277 li = []
tunde43870aa2017-08-18 11:59:30 +01001278
rohitwaghchauredb9fa782018-03-01 10:32:29 +05301279 if self.doctype == 'Sales Invoice' and self.is_pos: return
1280
tunde43870aa2017-08-18 11:59:30 +01001281 for d in self.get("payment_schedule"):
Nabin Hait2f9f4f92017-12-20 12:24:59 +05301282 if self.doctype == "Sales Order" and getdate(d.due_date) < getdate(self.transaction_date):
Michelle Alva7f2477b2020-04-09 17:23:23 +05301283 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 +01001284 elif d.due_date in dates:
mnatalia5f0e8d62018-05-22 06:38:50 +03001285 li.append(_("{0} in row {1}").format(d.due_date, d.idx))
tunde9bed2de2017-09-25 10:19:35 +01001286 dates.append(d.due_date)
1287
1288 if li:
1289 duplicates = '<br>' + '<br>'.join(li)
Faris Ansari2ab6e0e2018-09-19 13:13:59 +05301290 frappe.throw(_("Rows with duplicate due dates in other rows were found: {0}").format(duplicates))
tunde43870aa2017-08-18 11:59:30 +01001291
tunde62af5c52017-09-22 15:16:38 +01001292 def validate_payment_schedule_amount(self):
rohitwaghchauredb9fa782018-03-01 10:32:29 +05301293 if self.doctype == 'Sales Invoice' and self.is_pos: return
1294
Deepesh Garg26210162020-08-11 16:06:13 +05301295 party_account_currency = self.get('party_account_currency')
1296 if not party_account_currency:
1297 party_type, party = self.get_party()
1298
1299 if party_type and party:
1300 party_account_currency = get_party_account_currency(party_type, party, self.company)
1301
Nabin Hait0551f7b2017-11-21 19:58:16 +05301302 if self.get("payment_schedule"):
1303 total = 0
Saqib Ansarid552fe62021-04-23 14:46:52 +05301304 base_total = 0
Nabin Hait0551f7b2017-11-21 19:58:16 +05301305 for d in self.get("payment_schedule"):
Nabin Haitf592f2c2017-12-19 11:31:34 +05301306 total += flt(d.payment_amount)
Saqib Ansarid552fe62021-04-23 14:46:52 +05301307 base_total += flt(d.base_payment_amount)
tunde43870aa2017-08-18 11:59:30 +01001308
Saqib Ansarid552fe62021-04-23 14:46:52 +05301309 base_grand_total = self.get("base_rounded_total") or self.base_grand_total
1310 grand_total = self.get("rounded_total") or self.grand_total
Rohit Waghchaure4a267b92019-05-09 19:50:00 +05301311
Nabin Haite591c852017-12-21 11:46:30 +05301312 if self.doctype in ("Sales Invoice", "Purchase Invoice"):
Saqib Ansarid552fe62021-04-23 14:46:52 +05301313 base_grand_total = base_grand_total - flt(self.base_write_off_amount)
Nabin Haite591c852017-12-21 11:46:30 +05301314 grand_total = grand_total - flt(self.write_off_amount)
Saqib Ansarid552fe62021-04-23 14:46:52 +05301315
1316 if self.get("total_advance"):
1317 if party_account_currency == self.company_currency:
1318 base_grand_total -= self.get("total_advance")
1319 grand_total = flt(base_grand_total / self.get("conversion_rate"), self.precision("grand_total"))
1320 else:
1321 grand_total -= self.get("total_advance")
1322 base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total"))
Saqib Ansarid552fe62021-04-23 14:46:52 +05301323 if total != flt(grand_total, self.precision("grand_total")) or \
1324 base_total != flt(base_grand_total, self.precision("base_grand_total")):
Nabin Hait0551f7b2017-11-21 19:58:16 +05301325 frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total"))
tunde43870aa2017-08-18 11:59:30 +01001326
Nabin Hait877e1bb2017-11-17 12:27:43 +05301327 def is_rounded_total_disabled(self):
1328 if self.meta.get_field("disable_rounded_total"):
1329 return self.disable_rounded_total
1330 else:
1331 return frappe.db.get_single_value("Global Defaults", "disable_rounded_total")
1332
Deepesh Gargf17ea2c2020-12-11 21:30:39 +05301333 def set_inter_company_account(self):
1334 """
1335 Set intercompany account for inter warehouse transactions
1336 This account will be used in case billing company and internal customer's
1337 representation company is same
1338 """
1339
1340 if self.is_internal_transfer() and not self.unrealized_profit_loss_account:
1341 unrealized_profit_loss_account = frappe.db.get_value('Company', self.company, 'unrealized_profit_loss_account')
1342
1343 if not unrealized_profit_loss_account:
1344 msg = _("Please select Unrealized Profit / Loss account or add default Unrealized Profit / Loss account account for company {0}").format(
1345 frappe.bold(self.company))
1346 frappe.throw(msg)
1347
1348 self.unrealized_profit_loss_account = unrealized_profit_loss_account
1349
1350 def is_internal_transfer(self):
1351 """
1352 It will an internal transfer if its an internal customer and representation
1353 company is same as billing company
1354 """
Deepesh Gargb4be2922021-01-28 13:09:56 +05301355 if self.doctype in ('Sales Invoice', 'Delivery Note', 'Sales Order'):
Deepesh Gargf17ea2c2020-12-11 21:30:39 +05301356 internal_party_field = 'is_internal_customer'
Deepesh Gargb4be2922021-01-28 13:09:56 +05301357 elif self.doctype in ('Purchase Invoice', 'Purchase Receipt', 'Purchase Order'):
Deepesh Gargf17ea2c2020-12-11 21:30:39 +05301358 internal_party_field = 'is_internal_supplier'
1359
1360 if self.get(internal_party_field) and (self.represents_company == self.company):
1361 return True
1362
1363 return False
1364
Rushabh Mehta793ba6b2014-02-14 15:47:51 +05301365@frappe.whitelist()
Rushabh Mehtaf56d73c2013-10-10 16:35:09 +05301366def get_tax_rate(account_head):
Rushabh Mehta2cb7a9f2016-06-15 16:45:03 +05301367 return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True)
Rushabh Mehtaa33d4682015-06-01 17:15:42 +05301368
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001369
Nabin Haitffc7f3f2015-05-12 15:07:02 +05301370@frappe.whitelist()
Nabin Haita2426fc2018-01-15 17:45:46 +05301371def get_default_taxes_and_charges(master_doctype, tax_template=None, company=None):
rohitwaghchaure505661b2017-12-11 14:52:28 +05301372 if not company: return {}
1373
Nabin Haita2426fc2018-01-15 17:45:46 +05301374 if tax_template and company:
1375 tax_template_company = frappe.db.get_value(master_doctype, tax_template, "company")
1376 if tax_template_company == company:
1377 return
1378
1379 default_tax = frappe.db.get_value(master_doctype, {"is_default": 1, "company": company})
rohitwaghchaure505661b2017-12-11 14:52:28 +05301380
Rushabh Mehta98aa5812017-11-14 09:32:32 +05301381 return {
1382 'taxes_and_charges': default_tax,
1383 'taxes': get_taxes_and_charges(master_doctype, default_tax)
1384 }
Nabin Hait6b039142014-05-02 15:45:10 +05301385
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001386
Nabin Hait6b039142014-05-02 15:45:10 +05301387@frappe.whitelist()
Nabin Haitffc7f3f2015-05-12 15:07:02 +05301388def get_taxes_and_charges(master_doctype, master_name):
Nabin Hait1fe50122015-05-16 12:14:39 +05301389 if not master_name:
1390 return
Nabin Hait6b039142014-05-02 15:45:10 +05301391 from frappe.model import default_fields
1392 tax_master = frappe.get_doc(master_doctype, master_name)
1393
1394 taxes_and_charges = []
Nabin Haitffc7f3f2015-05-12 15:07:02 +05301395 for i, tax in enumerate(tax_master.get("taxes")):
Nabin Hait6b039142014-05-02 15:45:10 +05301396 tax = tax.as_dict()
1397
1398 for fieldname in default_fields:
1399 if fieldname in tax:
1400 del tax[fieldname]
1401
1402 taxes_and_charges.append(tax)
1403
1404 return taxes_and_charges
Rushabh Mehta66e08e32014-09-29 12:17:03 +05301405
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001406
Rushabh Mehta66e08e32014-09-29 12:17:03 +05301407def validate_conversion_rate(currency, conversion_rate, conversion_rate_label, company):
1408 """common validation for currency and price list currency"""
1409
Rushabh Mehta708e47a2018-08-08 16:37:31 +05301410 company_currency = frappe.get_cached_value('Company', company, "default_currency")
Rushabh Mehta66e08e32014-09-29 12:17:03 +05301411
1412 if not conversion_rate:
Saqib60212ff2020-10-26 11:17:04 +05301413 throw(
1414 _("{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}.")
1415 .format(conversion_rate_label, currency, company_currency)
1416 )
Nabin Hait613d0812015-02-23 11:58:15 +05301417
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001418
Nabin Hait613d0812015-02-23 11:58:15 +05301419def validate_taxes_and_charges(tax):
Deepesh Garg302855e2021-06-14 11:16:39 +05301420 if tax.charge_type in ['Actual', 'On Net Total', 'On Paid Amount'] and tax.row_id:
Nabin Hait613d0812015-02-23 11:58:15 +05301421 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 +05301422 elif tax.charge_type in ['On Previous Row Amount', 'On Previous Row Total']:
Nabin Hait613d0812015-02-23 11:58:15 +05301423 if cint(tax.idx) == 1:
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001424 frappe.throw(
1425 _("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"))
Nabin Hait613d0812015-02-23 11:58:15 +05301426 elif not tax.row_id:
Suraj Shetty48e9bc32020-01-29 15:06:18 +05301427 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 +05301428 elif tax.row_id and cint(tax.row_id) >= cint(tax.idx):
1429 frappe.throw(_("Cannot refer row number greater than or equal to current row number for this Charge type"))
1430
Rushabh Mehta2a21bc92015-02-25 15:08:42 +05301431 if tax.charge_type == "Actual":
Nabin Haitbc6c3602015-02-27 23:40:56 +05301432 tax.rate = None
Rushabh Mehta2a21bc92015-02-25 15:08:42 +05301433
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001434
Anuja Pawar0e337be2021-08-10 17:26:35 +05301435def validate_account_head(tax, doc):
1436 company = frappe.get_cached_value('Account',
1437 tax.account_head, 'company')
1438
1439 if company != doc.company:
1440 frappe.throw(_('Row {0}: Account {1} does not belong to Company {2}')
1441 .format(tax.idx, frappe.bold(tax.account_head), frappe.bold(doc.company)), title=_('Invalid Account'))
1442
1443
1444def validate_cost_center(tax, doc):
1445 if not tax.cost_center:
1446 return
1447
1448 company = frappe.get_cached_value('Cost Center',
1449 tax.cost_center, 'company')
1450
1451 if company != doc.company:
1452 frappe.throw(_('Row {0}: Cost Center {1} does not belong to Company {2}')
1453 .format(tax.idx, frappe.bold(tax.cost_center), frappe.bold(doc.company)), title=_('Invalid Cost Center'))
1454
1455
Nabin Hait613d0812015-02-23 11:58:15 +05301456def validate_inclusive_tax(tax, doc):
1457 def _on_previous_row_error(row_range):
Deepesh Garg302855e2021-06-14 11:16:39 +05301458 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 +05301459
1460 if cint(getattr(tax, "included_in_print_rate", None)):
1461 if tax.charge_type == "Actual":
1462 # inclusive tax cannot be of type Actual
Deepesh Garg5ef9a622021-06-14 14:34:44 +05301463 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 +05301464 elif tax.charge_type == "On Previous Row Amount" and \
1465 not cint(doc.get("taxes")[cint(tax.row_id) - 1].included_in_print_rate):
1466 # referred row should also be inclusive
1467 _on_previous_row_error(tax.row_id)
1468 elif tax.charge_type == "On Previous Row Total" and \
1469 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 +05301470 # all rows about the referred tax should be inclusive
Nabin Hait613d0812015-02-23 11:58:15 +05301471 _on_previous_row_error("1 - %d" % (tax.row_id,))
Nabin Hait93b3a372015-02-24 17:08:34 +05301472 elif tax.get("category") == "Valuation":
Nabin Hait19ea7212020-08-11 20:34:57 +05301473 frappe.throw(_("Valuation type charges can not be marked as Inclusive"))
Revant Nandgaonkar5da941b2016-02-18 16:02:05 +05301474
Revant Nandgaonkar5da941b2016-02-18 16:02:05 +05301475
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001476def set_balance_in_account_currency(gl_dict, account_currency=None, conversion_rate=None, company_currency=None):
1477 if (not conversion_rate) and (account_currency != company_currency):
1478 frappe.throw(_("Account: {0} with currency: {1} can not be selected")
1479 .format(gl_dict.account, account_currency))
1480
1481 gl_dict["account_currency"] = company_currency if account_currency == company_currency \
Revant Nandgaonkar5da941b2016-02-18 16:02:05 +05301482 else account_currency
1483
1484 # set debit/credit in account currency if not provided
1485 if flt(gl_dict.debit) and not flt(gl_dict.debit_in_account_currency):
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001486 gl_dict.debit_in_account_currency = gl_dict.debit if account_currency == company_currency \
Revant Nandgaonkar5da941b2016-02-18 16:02:05 +05301487 else flt(gl_dict.debit / conversion_rate, 2)
1488
1489 if flt(gl_dict.credit) and not flt(gl_dict.credit_in_account_currency):
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001490 gl_dict.credit_in_account_currency = gl_dict.credit if account_currency == company_currency \
Revant Nandgaonkar5da941b2016-02-18 16:02:05 +05301491 else flt(gl_dict.credit / conversion_rate, 2)
Nabin Hait1991c7b2016-06-27 20:09:05 +05301492
1493
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301494def get_advance_journal_entries(party_type, party, party_account, amount_field,
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001495 order_doctype, order_list, include_unallocated=True):
1496 dr_or_cr = "credit_in_account_currency" if party_type == "Customer" else "debit_in_account_currency"
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301497
Nabin Hait1991c7b2016-06-27 20:09:05 +05301498 conditions = []
1499 if include_unallocated:
1500 conditions.append("ifnull(t2.reference_name, '')=''")
1501
1502 if order_list:
1503 order_condition = ', '.join(['%s'] * len(order_list))
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001504 conditions.append(" (t2.reference_type = '{0}' and ifnull(t2.reference_name, '') in ({1}))" \
1505 .format(order_doctype, order_condition))
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301506
Nabin Hait1991c7b2016-06-27 20:09:05 +05301507 reference_condition = " and (" + " or ".join(conditions) + ")" if conditions else ""
Rushabh Mehta66958302017-01-16 16:57:53 +05301508
Nabin Hait1991c7b2016-06-27 20:09:05 +05301509 journal_entries = frappe.db.sql("""
1510 select
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301511 "Journal Entry" as reference_type, t1.name as reference_name,
Nabin Hait1991c7b2016-06-27 20:09:05 +05301512 t1.remark as remarks, t2.{0} as amount, t2.name as reference_row,
1513 t2.reference_name as against_order
1514 from
1515 `tabJournal Entry` t1, `tabJournal Entry Account` t2
1516 where
1517 t1.name = t2.parent and t2.account = %s
1518 and t2.party_type = %s and t2.party = %s
1519 and t2.is_advance = 'Yes' and t1.docstatus = 1
Nabin Haitca627fb2016-09-05 16:16:53 +05301520 and {1} > 0 {2}
Nabin Hait1991c7b2016-06-27 20:09:05 +05301521 order by t1.posting_date""".format(amount_field, dr_or_cr, reference_condition),
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001522 [party_account, party_type, party] + order_list, as_dict=1)
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301523
Nabin Hait1991c7b2016-06-27 20:09:05 +05301524 return list(journal_entries)
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301525
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001526
Rohit Waghchauref7258162019-01-15 18:12:04 +05301527def get_advance_payment_entries(party_type, party, party_account, order_doctype,
Nabin Hait34c551d2019-07-03 10:34:31 +05301528 order_list=None, include_unallocated=True, against_all_orders=False, limit=None):
Nabin Hait1991c7b2016-06-27 20:09:05 +05301529 party_account_field = "paid_from" if party_type == "Customer" else "paid_to"
Deepesh Gargc7eadfc2020-07-22 17:59:37 +05301530 currency_field = "paid_from_account_currency" if party_type == "Customer" else "paid_to_account_currency"
Nabin Hait1991c7b2016-06-27 20:09:05 +05301531 payment_type = "Receive" if party_type == "Customer" else "Pay"
Saqiba20999c2021-07-12 14:33:23 +05301532 exchange_rate_field = "source_exchange_rate" if payment_type == "Receive" else "target_exchange_rate"
1533
Nabin Hait1991c7b2016-06-27 20:09:05 +05301534 payment_entries_against_order, unallocated_payment_entries = [], []
Nabin Hait34c551d2019-07-03 10:34:31 +05301535 limit_cond = "limit %s" % limit if limit else ""
Nabin Haitff2f3ef2016-07-04 11:41:14 +05301536
Nabin Hait1991c7b2016-06-27 20:09:05 +05301537 if order_list or against_all_orders:
1538 if order_list:
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001539 reference_condition = " and t2.reference_name in ({0})" \
Nabin Hait1991c7b2016-06-27 20:09:05 +05301540 .format(', '.join(['%s'] * len(order_list)))
1541 else:
1542 reference_condition = ""
1543 order_list = []
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301544
Nabin Hait1991c7b2016-06-27 20:09:05 +05301545 payment_entries_against_order = frappe.db.sql("""
1546 select
1547 "Payment Entry" as reference_type, t1.name as reference_name,
1548 t1.remarks, t2.allocated_amount as amount, t2.name as reference_row,
Deepesh Gargc7eadfc2020-07-22 17:59:37 +05301549 t2.reference_name as against_order, t1.posting_date,
Saqiba20999c2021-07-12 14:33:23 +05301550 t1.{0} as currency, t1.{4} as exchange_rate
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301551 from `tabPayment Entry` t1, `tabPayment Entry Reference` t2
Nabin Hait1991c7b2016-06-27 20:09:05 +05301552 where
Deepesh Gargc7eadfc2020-07-22 17:59:37 +05301553 t1.name = t2.parent and t1.{1} = %s and t1.payment_type = %s
Nabin Hait1991c7b2016-06-27 20:09:05 +05301554 and t1.party_type = %s and t1.party = %s and t1.docstatus = 1
Deepesh Gargc7eadfc2020-07-22 17:59:37 +05301555 and t2.reference_doctype = %s {2}
1556 order by t1.posting_date {3}
Saqiba20999c2021-07-12 14:33:23 +05301557 """.format(currency_field, party_account_field, reference_condition, limit_cond, exchange_rate_field),
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001558 [party_account, payment_type, party_type, party,
1559 order_doctype] + order_list, as_dict=1)
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301560
Nabin Hait1991c7b2016-06-27 20:09:05 +05301561 if include_unallocated:
1562 unallocated_payment_entries = frappe.db.sql("""
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301563 select "Payment Entry" as reference_type, name as reference_name,
Saqiba20999c2021-07-12 14:33:23 +05301564 remarks, unallocated_amount as amount, {2} as exchange_rate
Nabin Hait1991c7b2016-06-27 20:09:05 +05301565 from `tabPayment Entry`
1566 where
1567 {0} = %s and party_type = %s and party = %s and payment_type = %s
1568 and docstatus = 1 and unallocated_amount > 0
Rohit Waghchauref7258162019-01-15 18:12:04 +05301569 order by posting_date {1}
Saqiba20999c2021-07-12 14:33:23 +05301570 """.format(party_account_field, limit_cond, exchange_rate_field),
1571 (party_account, party_type, party, payment_type), as_dict=1)
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301572
Rohit Waghchaure2f1db572016-11-08 12:39:33 +05301573 return list(payment_entries_against_order) + list(unallocated_payment_entries)
1574
1575def update_invoice_status():
1576 # Daily update the status of the invoices
1577
Rushabh Mehta66958302017-01-16 16:57:53 +05301578 frappe.db.sql(""" update `tabSales Invoice` set status = 'Overdue'
Rohit Waghchaure2f1db572016-11-08 12:39:33 +05301579 where due_date < CURDATE() and docstatus = 1 and outstanding_amount > 0""")
1580
Rushabh Mehta66958302017-01-16 16:57:53 +05301581 frappe.db.sql(""" update `tabPurchase Invoice` set status = 'Overdue'
Nabin Hait92759692017-08-15 08:23:51 +05301582 where due_date < CURDATE() and docstatus = 1 and outstanding_amount > 0""")
1583
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001584
Nabin Hait92759692017-08-15 08:23:51 +05301585@frappe.whitelist()
Saqib Ansarid552fe62021-04-23 14:46:52 +05301586def 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 +05301587 if not terms_template:
1588 return
1589
1590 terms_doc = frappe.get_doc("Payment Terms Template", terms_template)
1591
1592 schedule = []
tundefb144302017-08-19 15:01:40 +01001593 for d in terms_doc.get("terms"):
Saqib Ansarid552fe62021-04-23 14:46:52 +05301594 term_details = get_payment_term_details(d, posting_date, grand_total, base_grand_total, bill_date)
Nabin Hait92759692017-08-15 08:23:51 +05301595 schedule.append(term_details)
1596
1597 return schedule
1598
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001599
Nabin Hait92759692017-08-15 08:23:51 +05301600@frappe.whitelist()
Saqib Ansarid552fe62021-04-23 14:46:52 +05301601def 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 +05301602 term_details = frappe._dict()
Achilles Rasquinha90509982018-03-08 12:55:41 +05301603 if isinstance(term, text_type):
Nabin Hait92759692017-08-15 08:23:51 +05301604 term = frappe.get_doc("Payment Term", term)
1605 else:
1606 term_details.payment_term = term.payment_term
1607 term_details.description = term.description
1608 term_details.invoice_portion = term.invoice_portion
Rohit Waghchaure52bf56d2017-11-27 11:43:52 +05301609 term_details.payment_amount = flt(term.invoice_portion) * flt(grand_total) / 100
Saqib Ansarid552fe62021-04-23 14:46:52 +05301610 term_details.base_payment_amount = flt(term.invoice_portion) * flt(base_grand_total) / 100
Saqib0586b7d2021-03-31 15:03:53 +05301611 term_details.discount_type = term.discount_type
1612 term_details.discount = term.discount
Saqib0586b7d2021-03-31 15:03:53 +05301613 term_details.outstanding = term_details.payment_amount
1614 term_details.mode_of_payment = term.mode_of_payment
1615
Shreya Shah3a9eec22018-02-16 13:05:21 +05301616 if bill_date:
1617 term_details.due_date = get_due_date(term, bill_date)
Saqib0586b7d2021-03-31 15:03:53 +05301618 term_details.discount_date = get_discount_date(term, bill_date)
Shreya Shah3a9eec22018-02-16 13:05:21 +05301619 elif posting_date:
1620 term_details.due_date = get_due_date(term, posting_date)
Saqib0586b7d2021-03-31 15:03:53 +05301621 term_details.discount_date = get_discount_date(term, posting_date)
Shreya Shah3a9eec22018-02-16 13:05:21 +05301622
1623 if getdate(term_details.due_date) < getdate(posting_date):
1624 term_details.due_date = posting_date
1625
Nabin Hait92759692017-08-15 08:23:51 +05301626 return term_details
1627
Shreya Shah3a9eec22018-02-16 13:05:21 +05301628def get_due_date(term, posting_date=None, bill_date=None):
Nabin Hait92759692017-08-15 08:23:51 +05301629 due_date = None
Shreya Shah3a9eec22018-02-16 13:05:21 +05301630 date = bill_date or posting_date
Nabin Hait92759692017-08-15 08:23:51 +05301631 if term.due_date_based_on == "Day(s) after invoice date":
Shreya Shah3a9eec22018-02-16 13:05:21 +05301632 due_date = add_days(date, term.credit_days)
Nabin Hait92759692017-08-15 08:23:51 +05301633 elif term.due_date_based_on == "Day(s) after the end of the invoice month":
Shreya Shah3a9eec22018-02-16 13:05:21 +05301634 due_date = add_days(get_last_day(date), term.credit_days)
Nabin Hait92759692017-08-15 08:23:51 +05301635 elif term.due_date_based_on == "Month(s) after the end of the invoice month":
Shreya Shah3a9eec22018-02-16 13:05:21 +05301636 due_date = add_months(get_last_day(date), term.credit_months)
tunde96b8f222017-09-08 15:35:59 +01001637 return due_date
tundebabzyad08d4c2018-05-16 07:01:41 +01001638
Saqib0586b7d2021-03-31 15:03:53 +05301639def get_discount_date(term, posting_date=None, bill_date=None):
1640 discount_validity = None
1641 date = bill_date or posting_date
1642 if term.discount_validity_based_on == "Day(s) after invoice date":
1643 discount_validity = add_days(date, term.discount_validity)
1644 elif term.discount_validity_based_on == "Day(s) after the end of the invoice month":
1645 discount_validity = add_days(get_last_day(date), term.discount_validity)
1646 elif term.discount_validity_based_on == "Month(s) after the end of the invoice month":
1647 discount_validity = add_months(get_last_day(date), term.discount_validity)
1648 return discount_validity
tundebabzyad08d4c2018-05-16 07:01:41 +01001649
1650def get_supplier_block_status(party_name):
1651 """
1652 Returns a dict containing the values of `on_hold`, `release_date` and `hold_type` of
1653 a `Supplier`
1654 """
1655 supplier = frappe.get_doc('Supplier', party_name)
1656 info = {
1657 'on_hold': supplier.on_hold,
1658 'release_date': supplier.release_date,
1659 'hold_type': supplier.hold_type
1660 }
1661 return info
1662
marination698d9832020-08-19 14:59:46 +05301663def set_child_tax_template_and_map(item, child_item, parent_doc):
1664 args = {
1665 'item_code': item.item_code,
1666 'posting_date': parent_doc.transaction_date,
1667 'tax_category': parent_doc.get('tax_category'),
1668 'company': parent_doc.get('company')
1669 }
1670
1671 child_item.item_tax_template = _get_item_tax_template(args, item.taxes)
1672 if child_item.get("item_tax_template"):
1673 child_item.item_tax_rate = get_item_tax_map(parent_doc.get('company'), child_item.item_tax_template, as_json=True)
1674
Afshanb3bbebd2021-08-09 14:39:32 +05301675def add_taxes_from_tax_template(child_item, parent_doc, db_insert=True):
Marica3b1be2b2020-10-22 17:04:31 +05301676 add_taxes_from_item_tax_template = frappe.db.get_single_value("Accounts Settings", "add_taxes_from_item_tax_template")
1677
1678 if child_item.get("item_tax_rate") and add_taxes_from_item_tax_template:
1679 tax_map = json.loads(child_item.get("item_tax_rate"))
1680 for tax_type in tax_map:
1681 tax_rate = flt(tax_map[tax_type])
1682 taxes = parent_doc.get('taxes') or []
1683 # add new row for tax head only if missing
1684 found = any(tax.account_head == tax_type for tax in taxes)
1685 if not found:
1686 tax_row = parent_doc.append("taxes", {})
1687 tax_row.update({
1688 "description" : str(tax_type).split(' - ')[0],
1689 "charge_type" : "On Net Total",
1690 "account_head" : tax_type,
1691 "rate" : tax_rate
1692 })
1693 if parent_doc.doctype == "Purchase Order":
1694 tax_row.update({
1695 "category" : "Total",
1696 "add_deduct_tax" : "Add"
1697 })
Afshanb3bbebd2021-08-09 14:39:32 +05301698 if db_insert:
1699 tax_row.db_insert()
Marica3b1be2b2020-10-22 17:04:31 +05301700
pateljannat637ddff2021-02-09 16:17:30 +05301701def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, trans_item):
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001702 """
pateljannat637ddff2021-02-09 16:17:30 +05301703 Returns a Sales/Purchase Order Item child item containing the default values
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001704 """
Saqib438e0432020-04-03 10:02:56 +05301705 p_doc = frappe.get_doc(parent_doctype, parent_doctype_name)
pateljannat637ddff2021-02-09 16:17:30 +05301706 child_item = frappe.new_doc(child_doctype, p_doc, child_docname)
Saqib Ansarif53299e2020-04-15 22:08:12 +05301707 item = frappe.get_doc("Item", trans_item.get('item_code'))
marination0673f552021-03-31 01:38:22 +05301708
pateljannat637ddff2021-02-09 16:17:30 +05301709 for field in ("item_code", "item_name", "description", "item_group"):
marination0673f552021-03-31 01:38:22 +05301710 child_item.update({field: item.get(field)})
1711
pateljannat637ddff2021-02-09 16:17:30 +05301712 date_fieldname = "delivery_date" if child_doctype == "Sales Order Item" else "schedule_date"
Jannat Patelbb0d8f82021-02-11 20:59:28 +05301713 child_item.update({date_fieldname: trans_item.get(date_fieldname) or p_doc.get(date_fieldname)})
marination0673f552021-03-31 01:38:22 +05301714 child_item.stock_uom = item.stock_uom
Saqib61314242020-09-15 11:14:31 +05301715 child_item.uom = trans_item.get("uom") or item.stock_uom
Andy Zhu1062d7e2020-10-06 10:51:42 +13001716 child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
Saqib61314242020-09-15 11:14:31 +05301717 conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor"))
1718 child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor
marination0673f552021-03-31 01:38:22 +05301719
pateljannat637ddff2021-02-09 16:17:30 +05301720 if child_doctype == "Purchase Order Item":
marination0673f552021-03-31 01:38:22 +05301721 # Initialized value will update in parent validation
1722 child_item.base_rate = 1
1723 child_item.base_amount = 1
pateljannat637ddff2021-02-09 16:17:30 +05301724 if child_doctype == "Sales Order Item":
1725 child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
1726 if not child_item.warehouse:
1727 frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.")
1728 .format(frappe.bold("default warehouse"), frappe.bold(item.item_code)))
marination0673f552021-03-31 01:38:22 +05301729
marination698d9832020-08-19 14:59:46 +05301730 set_child_tax_template_and_map(item, child_item, p_doc)
Marica3b1be2b2020-10-22 17:04:31 +05301731 add_taxes_from_tax_template(child_item, p_doc)
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001732 return child_item
1733
marination0673f552021-03-31 01:38:22 +05301734def validate_child_on_delete(row, parent):
1735 """Check if partially transacted item (row) is being deleted."""
1736 if parent.doctype == "Sales Order":
1737 if flt(row.delivered_qty):
1738 frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been delivered").format(row.idx, row.item_code))
1739 if flt(row.work_order_qty):
1740 frappe.throw(_("Row #{0}: Cannot delete item {1} which has work order assigned to it.").format(row.idx, row.item_code))
1741 if flt(row.ordered_qty):
1742 frappe.throw(_("Row #{0}: Cannot delete item {1} which is assigned to customer's purchase order.").format(row.idx, row.item_code))
1743
1744 if parent.doctype == "Purchase Order" and flt(row.received_qty):
1745 frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been received").format(row.idx, row.item_code))
1746
1747 if flt(row.billed_amt):
1748 frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been billed.").format(row.idx, row.item_code))
1749
1750def update_bin_on_delete(row, doctype):
1751 """Update bin for deleted item (row)."""
1752 from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty, get_ordered_qty, get_indented_qty
1753 qty_dict = {}
1754
1755 if doctype == "Sales Order":
1756 qty_dict["reserved_qty"] = get_reserved_qty(row.item_code, row.warehouse)
1757 else:
1758 if row.material_request_item:
1759 qty_dict["indented_qty"] = get_indented_qty(row.item_code, row.warehouse)
1760
1761 qty_dict["ordered_qty"] = get_ordered_qty(row.item_code, row.warehouse)
1762
1763 update_bin_qty(row.item_code, row.warehouse, qty_dict)
1764
Saqib6db92fb2020-09-14 19:54:17 +05301765def validate_and_delete_children(parent, data):
Saqib0ad74492019-12-24 16:42:30 +05301766 deleted_children = []
1767 updated_item_names = [d.get("docname") for d in data]
1768 for item in parent.items:
1769 if item.name not in updated_item_names:
1770 deleted_children.append(item)
1771
1772 for d in deleted_children:
marination0673f552021-03-31 01:38:22 +05301773 validate_child_on_delete(d, parent)
Saqib0ad74492019-12-24 16:42:30 +05301774 d.cancel()
1775 d.delete()
marination0673f552021-03-31 01:38:22 +05301776
1777 # need to update ordered qty in Material Request first
1778 # bin uses Material Request Items to recalculate & update
1779 parent.update_prevdoc_status()
1780
1781 for d in deleted_children:
1782 update_bin_on_delete(d, parent.doctype)
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001783
Rohan Bansala93b5142021-04-06 17:10:52 +05301784
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001785@frappe.whitelist()
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001786def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
Saqib6db92fb2020-09-14 19:54:17 +05301787 def check_doc_permissions(doc, perm_type='create'):
Saqib Ansari2c3c8aa2020-05-29 21:55:38 +05301788 try:
1789 doc.check_permission(perm_type)
Saqib6db92fb2020-09-14 19:54:17 +05301790 except frappe.PermissionError:
marination661bf642020-09-29 18:16:45 +05301791 actions = { 'create': 'add', 'write': 'update'}
Saqib6db92fb2020-09-14 19:54:17 +05301792
1793 frappe.throw(_("You do not have permissions to {} items in a {}.")
1794 .format(actions[perm_type], parent_doctype), title=_("Insufficient Permissions"))
marination665c27a2020-09-15 12:04:38 +05301795
Saqib6db92fb2020-09-14 19:54:17 +05301796 def validate_workflow_conditions(doc):
1797 workflow = get_workflow_name(doc.doctype)
1798 if not workflow:
1799 return
1800
1801 workflow_doc = frappe.get_doc("Workflow", workflow)
1802 current_state = doc.get(workflow_doc.workflow_state_field)
1803 roles = frappe.get_roles()
1804
1805 transitions = []
1806 for transition in workflow_doc.transitions:
1807 if transition.next_state == current_state and transition.allowed in roles:
1808 if not is_transition_condition_satisfied(transition, doc):
1809 continue
1810 transitions.append(transition.as_dict())
1811
1812 if not transitions:
Saqib18318932020-09-23 13:01:49 +05301813 frappe.throw(
1814 _("You are not allowed to update as per the conditions set in {} Workflow.").format(get_link_to_form("Workflow", workflow)),
1815 title=_("Insufficient Permissions")
1816 )
Saqib Ansari2c3c8aa2020-05-29 21:55:38 +05301817
Rohit Waghchaure8d5380a2020-06-18 14:06:31 +05301818 def get_new_child_item(item_row):
Rohit Waghchaureff70e612021-03-06 22:08:08 +05301819 child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item"
pateljannat637ddff2021-02-09 16:17:30 +05301820 return set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, item_row)
Saqib Ansari2c3c8aa2020-05-29 21:55:38 +05301821
1822 def validate_quantity(child_item, d):
1823 if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
1824 frappe.throw(_("Cannot set quantity less than delivered quantity"))
1825
1826 if parent_doctype == "Purchase Order" and flt(d.get("qty")) < flt(child_item.received_qty):
1827 frappe.throw(_("Cannot set quantity less than received quantity"))
1828
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001829 data = json.loads(trans_items)
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001830
rohitwaghchaure81c21752019-10-31 15:56:10 +05301831 sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation']
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05301832 parent = frappe.get_doc(parent_doctype, parent_doctype_name)
marination665c27a2020-09-15 12:04:38 +05301833
marination661bf642020-09-29 18:16:45 +05301834 check_doc_permissions(parent, 'write')
Saqib6db92fb2020-09-14 19:54:17 +05301835 validate_and_delete_children(parent, data)
Saqib0ad74492019-12-24 16:42:30 +05301836
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001837 for d in data:
1838 new_child_flag = False
Ankush Menat6de7b8e2021-08-24 12:18:40 +05301839
1840 if not d.get("item_code"):
1841 # ignore empty rows
1842 continue
1843
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001844 if not d.get("docname"):
1845 new_child_flag = True
Saqib6db92fb2020-09-14 19:54:17 +05301846 check_doc_permissions(parent, 'create')
Rohit Waghchaure8d5380a2020-06-18 14:06:31 +05301847 child_item = get_new_child_item(d)
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001848 else:
Saqib6db92fb2020-09-14 19:54:17 +05301849 check_doc_permissions(parent, 'write')
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001850 child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname"))
Saqib Ansari2c3c8aa2020-05-29 21:55:38 +05301851
Saqib Ansaric579b082020-05-29 22:21:50 +05301852 prev_rate, new_rate = flt(child_item.get("rate")), flt(d.get("rate"))
1853 prev_qty, new_qty = flt(child_item.get("qty")), flt(d.get("qty"))
1854 prev_con_fac, new_con_fac = flt(child_item.get("conversion_factor")), flt(d.get("conversion_factor"))
Saqib61314242020-09-15 11:14:31 +05301855 prev_uom, new_uom = child_item.get("uom"), d.get("uom")
Saqib Ansaric579b082020-05-29 22:21:50 +05301856
1857 if parent_doctype == 'Sales Order':
1858 prev_date, new_date = child_item.get("delivery_date"), d.get("delivery_date")
1859 elif parent_doctype == 'Purchase Order':
marinationb7e94cc2020-06-15 11:32:42 +05301860 prev_date, new_date = child_item.get("schedule_date"), d.get("schedule_date")
Saqib Ansaric579b082020-05-29 22:21:50 +05301861
1862 rate_unchanged = prev_rate == new_rate
marinationf9c4b202020-06-12 15:49:53 +05301863 qty_unchanged = prev_qty == new_qty
Saqib61314242020-09-15 11:14:31 +05301864 uom_unchanged = prev_uom == new_uom
Saqib Ansaric579b082020-05-29 22:21:50 +05301865 conversion_factor_unchanged = prev_con_fac == new_con_fac
Frappe PR Bot44434ff2021-08-18 18:33:06 +05301866 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 +05301867 if rate_unchanged and qty_unchanged and conversion_factor_unchanged and uom_unchanged and date_unchanged:
Saqibdd374ff2020-02-27 12:51:31 +05301868 continue
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001869
Saqib Ansari2c3c8aa2020-05-29 21:55:38 +05301870 validate_quantity(child_item, d)
deepeshgarg00777cde832018-12-27 15:56:51 +05301871
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001872 child_item.qty = flt(d.get("qty"))
Saqib56fea7d2020-10-09 21:19:25 +05301873 rate_precision = child_item.precision("rate") or 2
1874 conv_fac_precision = child_item.precision("conversion_factor") or 2
1875 qty_precision = child_item.precision("qty") or 2
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001876
Saqib56fea7d2020-10-09 21:19:25 +05301877 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 +08001878 frappe.throw(_("Row #{0}: Cannot set Rate if amount is greater than billed amount for Item {1}.")
1879 .format(child_item.idx, child_item.item_code))
1880 else:
Saqib56fea7d2020-10-09 21:19:25 +05301881 child_item.rate = flt(d.get("rate"), rate_precision)
marinationf9c4b202020-06-12 15:49:53 +05301882
Saqib Ansari2c3c8aa2020-05-29 21:55:38 +05301883 if d.get("conversion_factor"):
marinationf9c4b202020-06-12 15:49:53 +05301884 if child_item.stock_uom == child_item.uom:
1885 child_item.conversion_factor = 1
1886 else:
Saqib56fea7d2020-10-09 21:19:25 +05301887 child_item.conversion_factor = flt(d.get('conversion_factor'), conv_fac_precision)
marination665c27a2020-09-15 12:04:38 +05301888
Saqib61314242020-09-15 11:14:31 +05301889 if d.get("uom"):
1890 child_item.uom = d.get("uom")
1891 conversion_factor = flt(get_conversion_factor(child_item.item_code, child_item.uom).get("conversion_factor"))
Saqib56fea7d2020-10-09 21:19:25 +05301892 child_item.conversion_factor = flt(d.get('conversion_factor'), conv_fac_precision) or conversion_factor
Mangesh-Khairnara3d52002019-08-05 10:16:40 +05301893
Saqib Ansaric579b082020-05-29 22:21:50 +05301894 if d.get("delivery_date") and parent_doctype == 'Sales Order':
1895 child_item.delivery_date = d.get('delivery_date')
marinationf9c4b202020-06-12 15:49:53 +05301896
Saqib Ansaric579b082020-05-29 22:21:50 +05301897 if d.get("schedule_date") and parent_doctype == 'Purchase Order':
1898 child_item.schedule_date = d.get('schedule_date')
1899
Mangesh-Khairnara3d52002019-08-05 10:16:40 +05301900 if flt(child_item.price_list_rate):
Maricaf0674472019-10-11 10:47:09 +05301901 if flt(child_item.rate) > flt(child_item.price_list_rate):
1902 # if rate is greater than price_list_rate, set margin
1903 # or set discount
1904 child_item.discount_percentage = 0
rohitwaghchaure81c21752019-10-31 15:56:10 +05301905
1906 if parent_doctype in sales_doctypes:
1907 child_item.margin_type = "Amount"
1908 child_item.margin_rate_or_amount = flt(child_item.rate - child_item.price_list_rate,
1909 child_item.precision("margin_rate_or_amount"))
1910 child_item.rate_with_margin = child_item.rate
Maricaf0674472019-10-11 10:47:09 +05301911 else:
1912 child_item.discount_percentage = flt((1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0,
1913 child_item.precision("discount_percentage"))
1914 child_item.discount_amount = flt(
1915 child_item.price_list_rate) - flt(child_item.rate)
rohitwaghchaure81c21752019-10-31 15:56:10 +05301916
1917 if parent_doctype in sales_doctypes:
1918 child_item.margin_type = ""
1919 child_item.margin_rate_or_amount = 0
1920 child_item.rate_with_margin = 0
Mangesh-Khairnara3d52002019-08-05 10:16:40 +05301921
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001922 child_item.flags.ignore_validate_update_after_submit = True
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001923 if new_child_flag:
Saqib Ansarif53299e2020-04-15 22:08:12 +05301924 parent.load_from_db()
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05301925 child_item.idx = len(parent.items) + 1
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001926 child_item.insert()
1927 else:
1928 child_item.save()
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001929
Rushabh Mehtafafbb022018-12-24 22:09:15 +05301930 parent.reload()
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05301931 parent.flags.ignore_validate_update_after_submit = True
1932 parent.set_qty_as_per_stock_uom()
1933 parent.calculate_taxes_and_totals()
Ankush Menatdf6e2082021-02-11 11:04:39 +05301934 parent.set_total_in_words()
Nabin Hait49a46f02019-10-21 13:35:43 +05301935 if parent_doctype == "Sales Order":
Marica3dee5272020-06-23 10:33:47 +05301936 make_packing_list(parent)
Nabin Hait49a46f02019-10-21 13:35:43 +05301937 parent.set_gross_profit()
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05301938 frappe.get_doc('Authorization Control').validate_approving_authority(parent.doctype,
1939 parent.company, parent.base_grand_total)
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001940
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05301941 parent.set_payment_schedule()
Nabin Haitb2da0822018-07-13 17:01:40 +05301942 if parent_doctype == 'Purchase Order':
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05301943 parent.validate_minimum_order_qty()
1944 parent.validate_budget()
1945 if parent.is_against_so():
1946 parent.update_status_updater()
Nabin Haitb2da0822018-07-13 17:01:40 +05301947 else:
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05301948 parent.check_credit_limit()
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05301949 parent.save()
Nabin Haitb2da0822018-07-13 17:01:40 +05301950
1951 if parent_doctype == 'Purchase Order':
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05301952 update_last_purchase_rate(parent, is_submit = 1)
1953 parent.update_prevdoc_status()
1954 parent.update_requested_qty()
1955 parent.update_ordered_qty()
1956 parent.update_ordered_and_reserved_qty()
1957 parent.update_receiving_percentage()
1958 if parent.is_subcontracted == "Yes":
1959 parent.update_reserved_qty_for_subcontract()
Saqib61314242020-09-15 11:14:31 +05301960 parent.create_raw_materials_supplied("supplied_items")
1961 parent.save()
Nabin Haitb2da0822018-07-13 17:01:40 +05301962 else:
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05301963 parent.update_reserved_qty()
1964 parent.update_project()
1965 parent.update_prevdoc_status('submit')
1966 parent.update_delivery_status()
Nabin Haitb2da0822018-07-13 17:01:40 +05301967
Saqib6db92fb2020-09-14 19:54:17 +05301968 parent.reload()
1969 validate_workflow_conditions(parent)
1970
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05301971 parent.update_blanket_order()
1972 parent.update_billing_percentage()
1973 parent.set_status()
Gauravf1e28e02019-02-13 16:46:24 +05301974
1975@erpnext.allow_regional
1976def validate_regional(doc):
1977 pass