blob: 32c5d3a3b142810bd2f7c2e7f1145e61752928d3 [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
Nabin Hait1d218422015-07-17 15:19:02 +053025
marination53b1a9a2020-11-03 15:45:25 +053026class AccountMissingError(frappe.ValidationError): pass
27
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +053028force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules")
Doridel Cahanap6f06cc22018-05-17 16:28:58 +080029
Nabin Haitbf495c92013-01-30 12:49:08 +053030class AccountsController(TransactionBase):
Nabin Hait7eba1a32017-10-02 15:59:27 +053031 def __init__(self, *args, **kwargs):
32 super(AccountsController, self).__init__(*args, **kwargs)
Anand Doshi979326b2015-09-11 16:22:37 +053033
Nabin Hait4ffd7f32015-08-27 12:28:36 +053034 @property
35 def company_currency(self):
36 if not hasattr(self, "__company_currency"):
Rushabh Mehtacc8b2b22017-03-31 12:44:29 +053037 self.__company_currency = erpnext.get_company_currency(self.company)
Anand Doshi979326b2015-09-11 16:22:37 +053038
Nabin Hait4ffd7f32015-08-27 12:28:36 +053039 return self.__company_currency
Anand Doshi979326b2015-09-11 16:22:37 +053040
Rohit Waghchaure7d529892016-10-06 14:35:04 +053041 def onload(self):
Nabin Hait34c551d2019-07-03 10:34:31 +053042 self.set_onload("make_payment_via_journal_entry",
43 frappe.db.get_single_value('Accounts Settings', 'make_payment_via_journal_entry'))
Nabin Hait0551f7b2017-11-21 19:58:16 +053044
tundee52bb822017-09-25 09:02:23 +010045 if self.is_new():
Nabin Hait0551f7b2017-11-21 19:58:16 +053046 relevant_docs = ("Quotation", "Purchase Order", "Sales Order",
Doridel Cahanap6f06cc22018-05-17 16:28:58 +080047 "Purchase Invoice", "Sales Invoice")
tundee52bb822017-09-25 09:02:23 +010048 if self.doctype in relevant_docs:
49 self.set_payment_schedule()
Rohit Waghchaure7d529892016-10-06 14:35:04 +053050
tundebabzyad08d4c2018-05-16 07:01:41 +010051 def ensure_supplier_is_not_blocked(self):
52 is_supplier_payment = self.doctype == 'Payment Entry' and self.party_type == 'Supplier'
53 is_buying_invoice = self.doctype in ['Purchase Invoice', 'Purchase Order']
54 supplier = None
55 supplier_name = None
56
57 if is_buying_invoice or is_supplier_payment:
58 supplier_name = self.supplier if is_buying_invoice else self.party
59 supplier = frappe.get_doc('Supplier', supplier_name)
60
61 if supplier and supplier_name and supplier.on_hold:
62 if (is_buying_invoice and supplier.hold_type in ['All', 'Invoices']) or \
63 (is_supplier_payment and supplier.hold_type in ['All', 'Payments']):
64 if not supplier.release_date or getdate(nowdate()) <= supplier.release_date:
65 frappe.msgprint(
Suraj Shetty48e9bc32020-01-29 15:06:18 +053066 _('{0} is blocked so this transaction cannot proceed').format(supplier_name), raise_exception=1)
tundebabzyad08d4c2018-05-16 07:01:41 +010067
Saurabh6f753182013-03-20 12:55:28 +053068 def validate(self):
Deepesh Gargd301d262019-07-31 15:58:19 +053069 if not self.get('is_return'):
70 self.validate_qty_is_not_zero()
71
nabinhait23cce732014-07-03 12:25:06 +053072 if self.get("_action") and self._action != "update_after_submit":
Nabin Hait704a4532014-06-05 16:55:31 +053073 self.set_missing_values(for_validate=True)
tunde62af5c52017-09-22 15:16:38 +010074
tundebabzyad08d4c2018-05-16 07:01:41 +010075 self.ensure_supplier_is_not_blocked()
76
Nabin Haitcfecd2b2013-07-11 17:49:18 +053077 self.validate_date_with_fiscal_year()
Rushabh Mehtab16b9cd2015-08-03 16:13:33 +053078
Anand Doshi3543f302013-05-24 19:25:01 +053079 if self.meta.get_field("currency"):
Anand Doshi923d41d2013-05-28 17:23:36 +053080 self.calculate_taxes_and_totals()
Rohit Waghchaure6087fe12016-04-09 14:31:09 +053081
Nabin Hait1d218422015-07-17 15:19:02 +053082 if not self.meta.get_field("is_return") or not self.is_return:
83 self.validate_value("base_grand_total", ">=", 0)
Rushabh Mehtab16b9cd2015-08-03 16:13:33 +053084
Nabin Hait3cf67a42015-07-24 13:26:36 +053085 validate_return(self)
Anand Doshi3543f302013-05-24 19:25:01 +053086 self.set_total_in_words()
Anand Doshid2946502014-04-08 20:10:03 +053087
tunde62af5c52017-09-22 15:16:38 +010088 self.validate_all_documents_schedule()
Anand Doshid2946502014-04-08 20:10:03 +053089
ankitjavalkarwork6c29d872014-10-06 13:20:53 +053090 if self.meta.get_field("taxes_and_charges"):
91 self.validate_enabled_taxes_and_charges()
Nabin Haita2426fc2018-01-15 17:45:46 +053092 self.validate_tax_account_company()
Rushabh Mehtab16b9cd2015-08-03 16:13:33 +053093
Neil Trini Lasrado3fbbb712015-07-17 12:10:12 +053094 self.validate_party()
Nabin Hait895029d2015-08-20 14:55:39 +053095 self.validate_currency()
Rushabh Mehta2cb7a9f2016-06-15 16:45:03 +053096
Rohit Waghchaure6087fe12016-04-09 14:31:09 +053097 if self.doctype == 'Purchase Invoice':
Mangesh-Khairnar7bcb24e2019-09-11 10:49:33 +053098 self.calculate_paid_amount()
Rohit Waghchaure6087fe12016-04-09 14:31:09 +053099
Nabin Hait041a5c22018-08-01 18:07:39 +0530100 if self.doctype in ['Purchase Invoice', 'Sales Invoice']:
Nabin Hait4a994d42019-04-25 17:47:26 +0530101 pos_check_field = "is_pos" if self.doctype=="Sales Invoice" else "is_paid"
102 if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
Nabin Hait041a5c22018-08-01 18:07:39 +0530103 self.set_advances()
104
105 if self.is_return:
106 self.validate_qty()
Nabin Haitacdd5082019-12-04 15:30:01 +0530107 else:
108 self.validate_deferred_start_and_end_date()
rohitwaghchaure70489252018-06-11 12:02:14 +0530109
Deepesh Gargf17ea2c2020-12-11 21:30:39 +0530110 self.set_inter_company_account()
111
Gauravf1e28e02019-02-13 16:46:24 +0530112 validate_regional(self)
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530113 if self.doctype != 'Material Request':
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530114 apply_pricing_rule_on_transaction(self)
Gauravf1e28e02019-02-13 16:46:24 +0530115
Nabin Haitacdd5082019-12-04 15:30:01 +0530116 def validate_deferred_start_and_end_date(self):
117 for d in self.items:
118 if d.get("enable_deferred_revenue") or d.get("enable_deferred_expense"):
119 if not (d.service_start_date and d.service_end_date):
120 frappe.throw(_("Row #{0}: Service Start and End Date is required for deferred accounting").format(d.idx))
121 elif getdate(d.service_start_date) > getdate(d.service_end_date):
122 frappe.throw(_("Row #{0}: Service Start Date cannot be greater than Service End Date").format(d.idx))
123 elif getdate(self.posting_date) > getdate(d.service_end_date):
124 frappe.throw(_("Row #{0}: Service End Date cannot be before Invoice Posting Date").format(d.idx))
125
tunde62af5c52017-09-22 15:16:38 +0100126 def validate_invoice_documents_schedule(self):
Nabin Hait0551f7b2017-11-21 19:58:16 +0530127 self.validate_payment_schedule_dates()
128 self.set_due_date()
Nabin Hait0551f7b2017-11-21 19:58:16 +0530129 self.set_payment_schedule()
130 self.validate_payment_schedule_amount()
tunde62af5c52017-09-22 15:16:38 +0100131 self.validate_due_date()
132 self.validate_advance_entries()
133
134 def validate_non_invoice_documents_schedule(self):
Nabin Hait0551f7b2017-11-21 19:58:16 +0530135 self.set_payment_schedule()
Nabin Hait2f9f4f92017-12-20 12:24:59 +0530136 self.validate_payment_schedule_dates()
Nabin Hait0551f7b2017-11-21 19:58:16 +0530137 self.validate_payment_schedule_amount()
tunde62af5c52017-09-22 15:16:38 +0100138
139 def validate_all_documents_schedule(self):
140 if self.doctype in ("Sales Invoice", "Purchase Invoice") and not self.is_return:
141 self.validate_invoice_documents_schedule()
142 elif self.doctype in ("Quotation", "Purchase Order", "Sales Order"):
143 self.validate_non_invoice_documents_schedule()
144
KanchanChauhan49a50fe2016-11-18 13:57:53 +0530145 def before_print(self):
Zarrarb9f54ca2018-05-28 17:41:09 +0530146 if self.doctype in ['Purchase Order', 'Sales Order', 'Sales Invoice', 'Purchase Invoice',
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800147 'Supplier Quotation', 'Purchase Receipt', 'Delivery Note', 'Quotation']:
KanchanChauhan49a50fe2016-11-18 13:57:53 +0530148 if self.get("group_same_items"):
149 self.group_similar_items()
150
Zarrar5be6d192018-11-08 12:16:26 +0530151 df = self.meta.get_field("discount_amount")
152 if self.get("discount_amount") and hasattr(self, "taxes") and not len(self.taxes):
153 df.set("print_hide", 0)
154 self.discount_amount = -self.discount_amount
155 else:
156 df.set("print_hide", 1)
157
Mangesh-Khairnar7bcb24e2019-09-11 10:49:33 +0530158 def calculate_paid_amount(self):
Saurabh43520f92016-03-21 18:32:48 +0530159 if hasattr(self, "is_pos") or hasattr(self, "is_paid"):
160 is_paid = self.get("is_pos") or self.get("is_paid")
Mangesh-Khairnar7bcb24e2019-09-11 10:49:33 +0530161
162 if is_paid:
163 if not self.cash_bank_account:
164 # show message that the amount is not paid
165 frappe.throw(_("Note: Payment Entry will not be created since 'Cash or Bank Account' was not specified"))
Marica23d7b092019-09-25 17:17:36 +0530166
Mangesh-Khairnar7bcb24e2019-09-11 10:49:33 +0530167 if cint(self.is_return) and self.grand_total > self.paid_amount:
168 self.paid_amount = flt(flt(self.grand_total), self.precision("paid_amount"))
169
170 elif not flt(self.paid_amount) and flt(self.outstanding_amount) > 0:
171 self.paid_amount = flt(flt(self.outstanding_amount), self.precision("paid_amount"))
172
173 self.base_paid_amount = flt(self.paid_amount * self.conversion_rate,
174 self.precision("base_paid_amount"))
Saurabh43520f92016-03-21 18:32:48 +0530175
Anand Doshiabc10032013-06-14 17:44:03 +0530176 def set_missing_values(self, for_validate=False):
Rohit Waghchaure5d97d892016-05-12 12:58:29 +0530177 if frappe.flags.in_test:
tunde62af5c52017-09-22 15:16:38 +0100178 for fieldname in ["posting_date", "transaction_date"]:
Rohit Waghchaure5d97d892016-05-12 12:58:29 +0530179 if self.meta.get_field(fieldname) and not self.get(fieldname):
180 self.set(fieldname, today())
181 break
Anand Doshid2946502014-04-08 20:10:03 +0530182
Nabin Hait3237c752015-02-17 11:11:11 +0530183 def calculate_taxes_and_totals(self):
Nabin Haitfe81da22015-02-18 12:23:18 +0530184 from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals
185 calculate_taxes_and_totals(self)
Nabin Hait3237c752015-02-17 11:11:11 +0530186
187 if self.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]:
188 self.calculate_commission()
189 self.calculate_contribution()
190
Nabin Haitcfecd2b2013-07-11 17:49:18 +0530191 def validate_date_with_fiscal_year(self):
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800192 if self.meta.get_field("fiscal_year"):
Nabin Haitcfecd2b2013-07-11 17:49:18 +0530193 date_field = ""
194 if self.meta.get_field("posting_date"):
195 date_field = "posting_date"
196 elif self.meta.get_field("transaction_date"):
197 date_field = "transaction_date"
Anand Doshid2946502014-04-08 20:10:03 +0530198
Rushabh Mehtaf2227d02014-03-31 23:37:40 +0530199 if date_field and self.get(date_field):
Rushabh Mehta66958302017-01-16 16:57:53 +0530200 validate_fiscal_year(self.get(date_field), self.fiscal_year, self.company,
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800201 self.meta.get_label(date_field), self)
Anand Doshid2946502014-04-08 20:10:03 +0530202
Nabin Haite9daefe2014-08-27 16:46:33 +0530203 def validate_due_date(self):
rohitwaghchaureea943c62018-07-06 12:28:58 +0530204 if self.get('is_pos'): return
205
Nabin Haite9daefe2014-08-27 16:46:33 +0530206 from erpnext.accounts.party import validate_due_date
207 if self.doctype == "Sales Invoice":
Nabin Hait04d244a2015-07-22 17:47:49 +0530208 if not self.due_date:
209 frappe.throw(_("Due Date is mandatory"))
Rushabh Mehtab16b9cd2015-08-03 16:13:33 +0530210
rohitwaghchaure3ffe8962018-07-13 17:40:48 +0530211 validate_due_date(self.posting_date, self.due_date,
212 "Customer", self.customer, self.company, self.payment_terms_template)
Nabin Haite9daefe2014-08-27 16:46:33 +0530213 elif self.doctype == "Purchase Invoice":
Rohit Waghchaured1a85a32018-11-26 18:42:29 +0530214 validate_due_date(self.bill_date or self.posting_date, self.due_date,
Saurabhd7897f12018-07-18 17:08:16 +0530215 "Supplier", self.supplier, self.company, self.bill_date, self.payment_terms_template)
Nabin Haite9daefe2014-08-27 16:46:33 +0530216
Nabin Hait096d3632013-10-17 17:01:14 +0530217 def set_price_list_currency(self, buying_or_selling):
Nabin Hait1cc55fb2016-12-08 14:09:23 +0530218 if self.meta.get_field("posting_date"):
Nabin Hait288a18e2016-12-08 15:36:23 +0530219 transaction_date = self.posting_date
Nabin Hait1cc55fb2016-12-08 14:09:23 +0530220 else:
Nabin Hait288a18e2016-12-08 15:36:23 +0530221 transaction_date = self.transaction_date
Rushabh Mehta66958302017-01-16 16:57:53 +0530222
Rushabh Mehta4a404e92013-08-09 18:11:35 +0530223 if self.meta.get_field("currency"):
Nabin Hait7a75e102013-09-17 10:21:20 +0530224 # price list part
Shreya3f778522018-05-15 16:59:20 +0530225 if buying_or_selling.lower() == "selling":
226 fieldname = "selling_price_list"
227 args = "for_selling"
228 else:
229 fieldname = "buying_price_list"
230 args = "for_buying"
231
Anand Doshif78d1ae2014-03-28 13:55:00 +0530232 if self.meta.get_field(fieldname) and self.get(fieldname):
233 self.price_list_currency = frappe.db.get_value("Price List",
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800234 self.get(fieldname), "currency")
Anand Doshid2946502014-04-08 20:10:03 +0530235
Nabin Hait6e439a52015-08-28 19:24:22 +0530236 if self.price_list_currency == self.company_currency:
Anand Doshif78d1ae2014-03-28 13:55:00 +0530237 self.plc_conversion_rate = 1.0
Nabin Hait11f41952013-09-24 14:36:55 +0530238
Anand Doshif78d1ae2014-03-28 13:55:00 +0530239 elif not self.plc_conversion_rate:
Rushabh Mehta66958302017-01-16 16:57:53 +0530240 self.plc_conversion_rate = get_exchange_rate(self.price_list_currency,
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800241 self.company_currency, transaction_date, args)
Anand Doshid2946502014-04-08 20:10:03 +0530242
Nabin Hait7a75e102013-09-17 10:21:20 +0530243 # currency
Anand Doshif78d1ae2014-03-28 13:55:00 +0530244 if not self.currency:
245 self.currency = self.price_list_currency
246 self.conversion_rate = self.plc_conversion_rate
Nabin Hait6e439a52015-08-28 19:24:22 +0530247 elif self.currency == self.company_currency:
Anand Doshif78d1ae2014-03-28 13:55:00 +0530248 self.conversion_rate = 1.0
249 elif not self.conversion_rate:
Anand Doshidffec8f2014-07-01 17:45:15 +0530250 self.conversion_rate = get_exchange_rate(self.currency,
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800251 self.company_currency, transaction_date, args)
Nabin Hait7a75e102013-09-17 10:21:20 +0530252
Nabin Haitcccc45e2016-10-05 17:15:43 +0530253 def set_missing_item_details(self, for_validate=False):
Anand Doshi3543f302013-05-24 19:25:01 +0530254 """set missing item values"""
Nabin Haitf37d43d2017-07-18 12:14:42 +0530255 from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
Rushabh Mehtab46069d2016-01-15 16:59:26 +0530256
Nabin Haitdd38a262014-12-26 13:15:21 +0530257 if hasattr(self, "items"):
Nabin Hait444f9562014-06-20 15:59:49 +0530258 parent_dict = {}
Pratik Vyasf7daab72014-04-10 17:53:30 +0530259 for fieldname in self.meta.get_valid_columns():
260 parent_dict[fieldname] = self.get(fieldname)
261
mbauskara52472c2016-03-05 15:10:25 +0530262 if self.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]:
263 document_type = "{} Item".format(self.doctype)
mbauskarc97becb2016-01-18 16:28:21 +0530264 parent_dict.update({"document_type": document_type})
265
Nabin Hait34c551d2019-07-03 10:34:31 +0530266 # party_name field used for customer in quotation
267 if self.doctype == "Quotation" and self.quotation_to == "Customer" and parent_dict.get("party_name"):
268 parent_dict.update({"customer": parent_dict.get("party_name")})
269
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530270 self.pricing_rules = []
Nabin Haitdd38a262014-12-26 13:15:21 +0530271 for item in self.get("items"):
Rushabh Mehtaf14b8092014-04-03 14:30:42 +0530272 if item.get("item_code"):
Nabin Hait444f9562014-06-20 15:59:49 +0530273 args = parent_dict.copy()
274 args.update(item.as_dict())
Rushabh Mehtab46069d2016-01-15 16:59:26 +0530275
Nabin Hait34d28222016-01-19 15:45:49 +0530276 args["doctype"] = self.doctype
277 args["name"] = self.name
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530278 args["child_docname"] = item.name
Rushabh Mehtab46069d2016-01-15 16:59:26 +0530279
Nabin Haite2f054c2015-03-09 14:54:37 +0530280 if not args.get("transaction_date"):
281 args["transaction_date"] = args.get("posting_date")
Anand Doshi2b2b6392015-03-20 14:18:09 +0530282
283 if self.get("is_subcontracted"):
284 args["is_subcontracted"] = self.is_subcontracted
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530285
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530286 ret = get_item_details(args, self, for_validate=True, overwrite_warehouse=False)
Rohit Waghchaure8bfe3302019-03-18 14:34:19 +0530287
Rushabh Mehtaf14b8092014-04-03 14:30:42 +0530288 for fieldname, value in ret.items():
Anand Doshi602e8252015-11-16 19:05:46 +0530289 if item.meta.get_field(fieldname) and value is not None:
290 if (item.get(fieldname) is None or fieldname in force_item_fields):
Rushabh Mehtaf14b8092014-04-03 14:30:42 +0530291 item.set(fieldname, value)
Anand Doshid2946502014-04-08 20:10:03 +0530292
Rohit Waghchauredc981dc2017-04-03 14:17:08 +0530293 elif fieldname in ['cost_center', 'conversion_factor'] and not item.get(fieldname):
Anand Doshi602e8252015-11-16 19:05:46 +0530294 item.set(fieldname, value)
295
Rohit Waghchauredc981dc2017-04-03 14:17:08 +0530296 elif fieldname == "serial_no":
Alchez9155e402018-07-09 16:57:42 +0530297 # Ensure that serial numbers are matched against Stock UOM
298 item_conversion_factor = item.get("conversion_factor") or 1.0
299 item_qty = abs(item.get("qty")) * item_conversion_factor
Alchez8f2a0f32018-07-06 14:36:52 +0530300
301 if item_qty != len(get_serial_nos(item.get('serial_no'))):
Rohit Waghchauredc981dc2017-04-03 14:17:08 +0530302 item.set(fieldname, value)
Nabin Haita74468b2014-12-30 18:33:52 +0530303
Rohit Waghchauref6f503a2018-12-19 15:06:44 +0530304 if self.doctype in ["Purchase Invoice", "Sales Invoice"] and item.meta.get_field('is_fixed_asset'):
305 item.set('is_fixed_asset', ret.get('is_fixed_asset', 0))
306
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530307 if ret.get("pricing_rules"):
308 self.apply_pricing_rule_on_items(item, ret)
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530309 self.set_pricing_rule_details(item, ret)
Rushabh Mehtab46069d2016-01-15 16:59:26 +0530310
Nabin Hait14aa9c52016-04-18 15:54:01 +0530311 if self.doctype == "Purchase Invoice":
Nabin Haitcccc45e2016-10-05 17:15:43 +0530312 self.set_expense_account(for_validate)
Nabin Haita3dd72a2014-05-28 12:49:20 +0530313
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530314 def apply_pricing_rule_on_items(self, item, pricing_rule_args):
315 if not pricing_rule_args.get("validate_applied_rule", 0):
316 # if user changed the discount percentage then set user's discount percentage ?
317 if pricing_rule_args.get("price_or_product_discount") == 'Price':
318 item.set("pricing_rules", pricing_rule_args.get("pricing_rules"))
319 item.set("discount_percentage", pricing_rule_args.get("discount_percentage"))
320 item.set("discount_amount", pricing_rule_args.get("discount_amount"))
321 if pricing_rule_args.get("pricing_rule_for") == "Rate":
322 item.set("price_list_rate", pricing_rule_args.get("price_list_rate"))
323
324 if item.get("price_list_rate"):
325 item.rate = flt(item.price_list_rate *
326 (1.0 - (flt(item.discount_percentage) / 100.0)), item.precision("rate"))
327
328 if item.get('discount_amount'):
329 item.rate = item.price_list_rate - item.discount_amount
330
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530331 if item.get("apply_discount_on_discounted_rate") and pricing_rule_args.get("rate"):
332 item.rate = pricing_rule_args.get("rate")
333
Rohit Waghchaure21fe97e2019-12-13 16:18:38 +0530334 elif pricing_rule_args.get('free_item_data'):
335 apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data'))
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530336
337 elif pricing_rule_args.get("validate_applied_rule"):
Rushabh Mehtaff5d1962020-08-25 11:59:57 +0530338 for pricing_rule in get_applied_pricing_rules(item.get('pricing_rules')):
rohitwaghchaurea85ddf22019-11-19 18:47:48 +0530339 pricing_rule_doc = frappe.get_cached_doc("Pricing Rule", pricing_rule)
340 for field in ['discount_percentage', 'discount_amount', 'rate']:
341 if item.get(field) < pricing_rule_doc.get(field):
342 title = get_link_to_form("Pricing Rule", pricing_rule)
343
344 frappe.msgprint(_("Row {0}: user has not applied the rule {1} on the item {2}")
345 .format(item.idx, frappe.bold(title), frappe.bold(item.item_code)))
346
Rohit Waghchaurea248dfb2020-07-16 17:27:26 +0530347 def set_pricing_rule_details(self, item_row, args):
348 pricing_rules = get_applied_pricing_rules(args.get("pricing_rules"))
349 if not pricing_rules: return
350
351 for pricing_rule in pricing_rules:
352 self.append("pricing_rules", {
353 "pricing_rule": pricing_rule,
354 "item_code": item_row.item_code,
355 "child_docname": item_row.name,
356 "rule_applied": True
357 })
358
Nabin Haitffc7f3f2015-05-12 15:07:02 +0530359 def set_taxes(self):
360 if not self.meta.get_field("taxes"):
Anand Doshi3543f302013-05-24 19:25:01 +0530361 return
Anand Doshid2946502014-04-08 20:10:03 +0530362
Nabin Haitffc7f3f2015-05-12 15:07:02 +0530363 tax_master_doctype = self.meta.get_field("taxes_and_charges").options
Anand Doshid2946502014-04-08 20:10:03 +0530364
rohitwaghchaure57914f12018-04-24 19:19:47 +0530365 if (self.is_new() or self.is_pos_profile_changed()) and not self.get("taxes"):
rohitwaghchaure505661b2017-12-11 14:52:28 +0530366 if self.company and not self.get("taxes_and_charges"):
Anand Doshi3543f302013-05-24 19:25:01 +0530367 # get the default tax master
rohitwaghchaure505661b2017-12-11 14:52:28 +0530368 self.taxes_and_charges = frappe.db.get_value(tax_master_doctype,
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800369 {"is_default": 1, 'company': self.company})
Anand Doshid2946502014-04-08 20:10:03 +0530370
Nabin Haitffc7f3f2015-05-12 15:07:02 +0530371 self.append_taxes_from_master(tax_master_doctype)
Anand Doshid2946502014-04-08 20:10:03 +0530372
rohitwaghchaure57914f12018-04-24 19:19:47 +0530373 def is_pos_profile_changed(self):
374 if (self.doctype == 'Sales Invoice' and self.is_pos and
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800375 self.pos_profile != frappe.db.get_value('Sales Invoice', self.name, 'pos_profile')):
rohitwaghchaure57914f12018-04-24 19:19:47 +0530376 return True
377
Nabin Haitffc7f3f2015-05-12 15:07:02 +0530378 def append_taxes_from_master(self, tax_master_doctype=None):
379 if self.get("taxes_and_charges"):
Anand Doshi99100a42013-07-04 17:13:53 +0530380 if not tax_master_doctype:
Nabin Haitffc7f3f2015-05-12 15:07:02 +0530381 tax_master_doctype = self.meta.get_field("taxes_and_charges").options
Anand Doshid2946502014-04-08 20:10:03 +0530382
Nabin Haitffc7f3f2015-05-12 15:07:02 +0530383 self.extend("taxes", get_taxes_and_charges(tax_master_doctype, self.get("taxes_and_charges")))
Akhilesh Darjee4cdb7992014-01-30 13:56:57 +0530384
Anand Doshiac32bad2014-04-18 01:30:14 +0530385 def set_other_charges(self):
Nabin Haite7d15362014-12-25 16:01:55 +0530386 self.set("taxes", [])
Nabin Haitffc7f3f2015-05-12 15:07:02 +0530387 self.set_taxes()
Anand Doshid2946502014-04-08 20:10:03 +0530388
ankitjavalkarwork6c29d872014-10-06 13:20:53 +0530389 def validate_enabled_taxes_and_charges(self):
390 taxes_and_charges_doctype = self.meta.get_options("taxes_and_charges")
391 if frappe.db.get_value(taxes_and_charges_doctype, self.taxes_and_charges, "disabled"):
392 frappe.throw(_("{0} '{1}' is disabled").format(taxes_and_charges_doctype, self.taxes_and_charges))
393
Nabin Haita2426fc2018-01-15 17:45:46 +0530394 def validate_tax_account_company(self):
395 for d in self.get("taxes"):
396 if d.account_head:
397 tax_account_company = frappe.db.get_value("Account", d.account_head, "company")
398 if tax_account_company != self.company:
399 frappe.throw(_("Row #{0}: Account {1} does not belong to company {2}")
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800400 .format(d.idx, d.account_head, self.company))
Nabin Haita2426fc2018-01-15 17:45:46 +0530401
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530402 def get_gl_dict(self, args, account_currency=None, item=None):
Nabin Hait2df4d542013-01-29 11:34:39 +0530403 """this method populates the common properties of a gl entry record"""
Rushabh Mehta2cb7a9f2016-06-15 16:45:03 +0530404
Rohit Waghchaureaa7b4342018-05-11 01:56:05 +0530405 posting_date = args.get('posting_date') or self.get('posting_date')
406 fiscal_years = get_fiscal_years(posting_date, company=self.company)
Nabin Hait2ed0b592016-03-29 13:14:17 +0530407 if len(fiscal_years) > 1:
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800408 frappe.throw(_("Multiple fiscal years exist for the date {0}. Please set company in Fiscal Year").format(
409 formatdate(posting_date)))
Nabin Hait2ed0b592016-03-29 13:14:17 +0530410 else:
411 fiscal_year = fiscal_years[0][0]
Rushabh Mehta2cb7a9f2016-06-15 16:45:03 +0530412
Rushabh Mehta793ba6b2014-02-14 15:47:51 +0530413 gl_dict = frappe._dict({
Anand Doshid2946502014-04-08 20:10:03 +0530414 'company': self.company,
Rohit Waghchaureaa7b4342018-05-11 01:56:05 +0530415 'posting_date': posting_date,
Nabin Hait2ed0b592016-03-29 13:14:17 +0530416 'fiscal_year': fiscal_year,
Anand Doshif78d1ae2014-03-28 13:55:00 +0530417 'voucher_type': self.doctype,
418 'voucher_no': self.name,
Nabin Hait5cd04a62019-05-27 11:44:06 +0530419 'remarks': self.get("remarks") or self.get("remark"),
Nabin Hait2df4d542013-01-29 11:34:39 +0530420 'debit': 0,
421 'credit': 0,
Nabin Hait46bcbaf2015-08-19 13:49:10 +0530422 'debit_in_account_currency': 0,
423 'credit_in_account_currency': 0,
Anand Doshif78d1ae2014-03-28 13:55:00 +0530424 'is_opening': self.get("is_opening") or "No",
Nabin Hait32251022014-08-29 11:18:32 +0530425 'party_type': None,
Nabin Hait591a5ab2016-05-26 17:41:39 +0530426 'party': None,
427 'project': self.get("project")
Nabin Hait2e296fa2013-08-28 18:53:11 +0530428 })
deepeshgarg007d83cf652019-05-12 18:34:23 +0530429
430 accounting_dimensions = get_accounting_dimensions()
431 dimension_dict = frappe._dict()
432
433 for dimension in accounting_dimensions:
434 dimension_dict[dimension] = self.get(dimension)
deepeshgarg0073d11ac02019-05-19 00:02:01 +0530435 if item and item.get(dimension):
436 dimension_dict[dimension] = item.get(dimension)
deepeshgarg007d83cf652019-05-12 18:34:23 +0530437
438 gl_dict.update(dimension_dict)
Nabin Hait2df4d542013-01-29 11:34:39 +0530439 gl_dict.update(args)
Anand Doshi979326b2015-09-11 16:22:37 +0530440
Nabin Hait895029d2015-08-20 14:55:39 +0530441 if not account_currency:
Anand Doshicd0989e2015-09-28 13:31:17 +0530442 account_currency = get_account_currency(gl_dict.account)
Anand Doshi979326b2015-09-11 16:22:37 +0530443
Rushabh Mehtabc4e2cd2017-10-17 12:30:34 +0530444 if gl_dict.account and self.doctype not in ["Journal Entry",
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800445 "Period Closing Voucher", "Payment Entry"]:
Anand Doshibe6cfdd2015-09-17 21:07:04 +0530446 self.validate_account_currency(gl_dict.account, account_currency)
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800447 set_balance_in_account_currency(gl_dict, account_currency, self.get("conversion_rate"),
448 self.company_currency)
Anand Doshi979326b2015-09-11 16:22:37 +0530449
Nabin Haitc561a492015-08-19 19:22:34 +0530450 return gl_dict
Anand Doshi979326b2015-09-11 16:22:37 +0530451
Anurag Mishrae657fe82018-11-26 15:19:17 +0530452 def validate_qty_is_not_zero(self):
Maricac68a9402019-12-03 12:59:22 +0530453 if self.doctype != "Purchase Receipt":
454 for item in self.items:
455 if not item.qty:
456 frappe.throw(_("Item quantity can not be zero"))
Anurag Mishrae657fe82018-11-26 15:19:17 +0530457
Nabin Hait895029d2015-08-20 14:55:39 +0530458 def validate_account_currency(self, account, account_currency=None):
Nabin Hait6e439a52015-08-28 19:24:22 +0530459 valid_currency = [self.company_currency]
460 if self.get("currency") and self.currency != self.company_currency:
461 valid_currency.append(self.currency)
462
Nabin Hait895029d2015-08-20 14:55:39 +0530463 if account_currency not in valid_currency:
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530464 frappe.throw(_("Account {0} is invalid. Account Currency must be {1}")
Suraj Shettyda6806e2020-04-08 09:32:41 +0530465 .format(account, (' ' + _("or") + ' ').join(valid_currency)))
Anand Doshi979326b2015-09-11 16:22:37 +0530466
Anand Doshi613cb6a2013-02-06 17:33:46 +0530467 def clear_unallocated_advances(self, childtype, parentfield):
Rushabh Mehtaf191f852014-04-02 18:09:34 +0530468 self.set(parentfield, self.get(parentfield, {"allocated_amount": ["not in", [0, None, ""]]}))
469
Anand Doshid2946502014-04-08 20:10:03 +0530470 frappe.db.sql("""delete from `tab%s` where parentfield=%s and parent = %s
Anand Doshi602e8252015-11-16 19:05:46 +0530471 and allocated_amount = 0""" % (childtype, '%s', '%s'), (parentfield, self.name))
Anand Doshid2946502014-04-08 20:10:03 +0530472
Rushabh Mehta30dc9a12017-11-17 14:31:09 +0530473 def apply_shipping_rule(self):
474 if self.shipping_rule:
475 shipping_rule = frappe.get_doc("Shipping Rule", self.shipping_rule)
476 shipping_rule.apply(self)
477 self.calculate_taxes_and_totals()
478
479 def get_shipping_address(self):
480 '''Returns Address object from shipping address fields if present'''
481
482 # shipping address fields can be `shipping_address_name` or `shipping_address`
483 # try getting value from both
484
485 for fieldname in ('shipping_address_name', 'shipping_address'):
486 shipping_field = self.meta.get_field(fieldname)
487 if shipping_field and shipping_field.fieldtype == 'Link':
488 if self.get(fieldname):
489 return frappe.get_doc('Address', self.get(fieldname))
490
491 return {}
492
Nabin Hait041a5c22018-08-01 18:07:39 +0530493 def set_advances(self):
494 """Returns list of advances against Account, Party, Reference"""
495
496 res = self.get_advance_entries()
497
498 self.set("advances", [])
499 advance_allocated = 0
500 for d in res:
501 if d.against_order:
502 allocated_amount = flt(d.amount)
503 else:
Deepesh Garg26210162020-08-11 16:06:13 +0530504 if self.get('party_account_currency') == self.company_currency:
505 amount = self.get('base_rounded_total') or self.base_grand_total
506 else:
507 amount = self.get('rounded_total') or self.grand_total
508
Rohit Waghchaure6cc2f522018-11-27 12:07:03 +0530509 allocated_amount = min(amount - advance_allocated, d.amount)
Nabin Hait041a5c22018-08-01 18:07:39 +0530510 advance_allocated += flt(allocated_amount)
Rushabh Mehta708e47a2018-08-08 16:37:31 +0530511
Nabin Hait041a5c22018-08-01 18:07:39 +0530512 self.append("advances", {
513 "doctype": self.doctype + " Advance",
514 "reference_type": d.reference_type,
515 "reference_name": d.reference_name,
516 "reference_row": d.reference_row,
517 "remarks": d.remarks,
518 "advance_amount": flt(d.amount),
519 "allocated_amount": allocated_amount
520 })
521
Nabin Hait28a05282016-06-27 17:41:39 +0530522 def get_advance_entries(self, include_unallocated=True):
523 if self.doctype == "Sales Invoice":
524 party_account = self.debit_to
525 party_type = "Customer"
526 party = self.customer
527 amount_field = "credit_in_account_currency"
528 order_field = "sales_order"
529 order_doctype = "Sales Order"
530 else:
531 party_account = self.credit_to
532 party_type = "Supplier"
533 party = self.supplier
534 amount_field = "debit_in_account_currency"
535 order_field = "purchase_order"
536 order_doctype = "Purchase Order"
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530537
538 order_list = list(set([d.get(order_field)
Nabin Hait868766d2019-07-15 18:02:58 +0530539 for d in self.get("items") if d.get(order_field)]))
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530540
541 journal_entries = get_advance_journal_entries(party_type, party, party_account,
Nabin Hait868766d2019-07-15 18:02:58 +0530542 amount_field, order_doctype, order_list, include_unallocated)
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530543
544 payment_entries = get_advance_payment_entries(party_type, party, party_account,
Nabin Hait868766d2019-07-15 18:02:58 +0530545 order_doctype, order_list, include_unallocated)
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530546
Nabin Hait28a05282016-06-27 17:41:39 +0530547 res = journal_entries + payment_entries
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530548
Nabin Hait28a05282016-06-27 17:41:39 +0530549 return res
Nabin Hait48f5fa62014-09-18 15:03:54 +0530550
rohitwaghchaurebf4c1142018-01-08 15:20:15 +0530551 def is_inclusive_tax(self):
Nabin Hait868766d2019-07-15 18:02:58 +0530552 is_inclusive = cint(frappe.db.get_single_value("Accounts Settings", "show_inclusive_tax_in_print"))
rohitwaghchaurebf4c1142018-01-08 15:20:15 +0530553
554 if is_inclusive:
555 is_inclusive = 0
556 if self.get("taxes", filters={"included_in_print_rate": 1}):
557 is_inclusive = 1
558
559 return is_inclusive
560
Nabin Hait28a05282016-06-27 17:41:39 +0530561 def validate_advance_entries(self):
Nabin Hait05aefbb2016-06-28 19:42:19 +0530562 order_field = "sales_order" if self.doctype == "Sales Invoice" else "purchase_order"
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530563 order_list = list(set([d.get(order_field)
Nabin Hait868766d2019-07-15 18:02:58 +0530564 for d in self.get("items") if d.get(order_field)]))
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530565
Nabin Hait05aefbb2016-06-28 19:42:19 +0530566 if not order_list: return
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530567
Nabin Hait28a05282016-06-27 17:41:39 +0530568 advance_entries = self.get_advance_entries(include_unallocated=False)
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530569
Nabin Hait28a05282016-06-27 17:41:39 +0530570 if advance_entries:
571 advance_entries_against_si = [d.reference_name for d in self.get("advances")]
572 for d in advance_entries:
573 if not advance_entries_against_si or d.reference_name not in advance_entries_against_si:
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800574 frappe.msgprint(_(
575 "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 +0530576 .format(d.reference_name, d.against_order))
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530577
Nabin Hait28a05282016-06-27 17:41:39 +0530578 def update_against_document_in_jv(self):
579 """
580 Links invoice and advance voucher:
581 1. cancel advance voucher
582 2. split into multiple rows if partially adjusted, assign against voucher
583 3. submit advance voucher
584 """
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530585
Nabin Hait28a05282016-06-27 17:41:39 +0530586 if self.doctype == "Sales Invoice":
Nabin Haitff2f3ef2016-07-04 11:41:14 +0530587 party_type = "Customer"
Nabin Hait28a05282016-06-27 17:41:39 +0530588 party = self.customer
589 party_account = self.debit_to
590 dr_or_cr = "credit_in_account_currency"
591 else:
Nabin Haitff2f3ef2016-07-04 11:41:14 +0530592 party_type = "Supplier"
Nabin Hait28a05282016-06-27 17:41:39 +0530593 party = self.supplier
594 party_account = self.credit_to
595 dr_or_cr = "debit_in_account_currency"
Nabin Hait48f5fa62014-09-18 15:03:54 +0530596
Nabin Hait28a05282016-06-27 17:41:39 +0530597 lst = []
598 for d in self.get('advances'):
599 if flt(d.allocated_amount) > 0:
600 args = frappe._dict({
601 'voucher_type': d.reference_type,
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800602 'voucher_no': d.reference_name,
603 'voucher_detail_no': d.reference_row,
604 'against_voucher_type': self.doctype,
605 'against_voucher': self.name,
606 'account': party_account,
Nabin Haitff2f3ef2016-07-04 11:41:14 +0530607 'party_type': party_type,
Nabin Hait28a05282016-06-27 17:41:39 +0530608 'party': party,
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800609 'is_advance': 'Yes',
610 'dr_or_cr': dr_or_cr,
611 'unadjusted_amount': flt(d.advance_amount),
612 'allocated_amount': flt(d.allocated_amount),
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530613 'exchange_rate': (self.conversion_rate
Nabin Hait868766d2019-07-15 18:02:58 +0530614 if self.party_account_currency != self.company_currency else 1),
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530615 'grand_total': (self.base_grand_total
Nabin Hait868766d2019-07-15 18:02:58 +0530616 if self.party_account_currency == self.company_currency else self.grand_total),
Nabin Hait28a05282016-06-27 17:41:39 +0530617 'outstanding_amount': self.outstanding_amount
618 })
619 lst.append(args)
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530620
Nabin Hait28a05282016-06-27 17:41:39 +0530621 if lst:
622 from erpnext.accounts.utils import reconcile_against_document
623 reconcile_against_document(lst)
Anand Doshid2946502014-04-08 20:10:03 +0530624
Rohit Waghchaure5fa9a7a2019-04-01 00:40:38 +0530625 def on_cancel(self):
626 from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
627
628 if self.doctype in ["Sales Invoice", "Purchase Invoice"]:
629 if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
630 unlink_ref_doc_from_payment_entries(self)
631
632 elif self.doctype in ["Sales Order", "Purchase Order"]:
Rohit Waghchaure9835c892019-04-01 01:02:06 +0530633 if frappe.db.get_single_value('Accounts Settings', 'unlink_advance_payment_on_cancelation_of_order'):
Rohit Waghchaure5fa9a7a2019-04-01 00:40:38 +0530634 unlink_ref_doc_from_payment_entries(self)
635
Nabin Hait19d945a2013-07-29 18:35:39 +0530636 def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield):
Nabin Hait868766d2019-07-15 18:02:58 +0530637 from erpnext.controllers.status_updater import get_allowance_for
638 item_allowance = {}
639 global_qty_allowance, global_amount_allowance = None, None
Anand Doshid2946502014-04-08 20:10:03 +0530640
Nabin Hait4b8185d2014-12-25 18:19:39 +0530641 for item in self.get("items"):
Anand Doshif78d1ae2014-03-28 13:55:00 +0530642 if item.get(item_ref_dn):
Anand Doshid2946502014-04-08 20:10:03 +0530643 ref_amt = flt(frappe.db.get_value(ref_dt + " Item",
Nabin Hait868766d2019-07-15 18:02:58 +0530644 item.get(item_ref_dn), based_on), self.precision(based_on, item))
Nabin Haitdc15b4f2014-01-20 16:48:49 +0530645 if not ref_amt:
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800646 frappe.msgprint(
Nabin Hait868766d2019-07-15 18:02:58 +0530647 _("Warning: System will not check overbilling since amount for Item {0} in {1} is zero")
648 .format(item.item_code, ref_dt))
Nabin Haitdc15b4f2014-01-20 16:48:49 +0530649 else:
Nabin Hait868766d2019-07-15 18:02:58 +0530650 already_billed = frappe.db.sql("""
651 select sum(%s)
652 from `tab%s`
653 where %s=%s and docstatus=1 and parent != %s
654 """ % (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'),
655 (item.get(item_ref_dn), self.name))[0][0]
Anand Doshid2946502014-04-08 20:10:03 +0530656
657 total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)),
Nabin Hait868766d2019-07-15 18:02:58 +0530658 self.precision(based_on, item))
Anand Doshid2946502014-04-08 20:10:03 +0530659
Nabin Hait868766d2019-07-15 18:02:58 +0530660 allowance, item_allowance, global_qty_allowance, global_amount_allowance = \
661 get_allowance_for(item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount")
Anand Doshid2946502014-04-08 20:10:03 +0530662
Nabin Hait868766d2019-07-15 18:02:58 +0530663 max_allowed_amt = flt(ref_amt * (100 + allowance) / 100)
Anand Doshid2946502014-04-08 20:10:03 +0530664
rohitwaghchaure285344e2019-10-11 11:02:11 +0530665 if total_billed_amt < 0 and max_allowed_amt < 0:
666 # while making debit note against purchase return entry(purchase receipt) getting overbill error
667 total_billed_amt = abs(total_billed_amt)
668 max_allowed_amt = abs(max_allowed_amt)
669
Nabin Haitdc15b4f2014-01-20 16:48:49 +0530670 if total_billed_amt - max_allowed_amt > 0.01:
rohitwaghchaure285344e2019-10-11 11:02:11 +0530671 frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
Nabin Hait868766d2019-07-15 18:02:58 +0530672 .format(item.item_code, item.idx, max_allowed_amt))
Anand Doshid2946502014-04-08 20:10:03 +0530673
Nabin Hait0fc24542013-03-25 11:06:00 +0530674 def get_company_default(self, fieldname):
Rushabh Mehta1f847992013-12-12 19:12:19 +0530675 from erpnext.accounts.utils import get_company_default
Anand Doshif78d1ae2014-03-28 13:55:00 +0530676 return get_company_default(self.company, fieldname)
Anand Doshid2946502014-04-08 20:10:03 +0530677
Nabin Haita36adbd2013-08-02 14:50:12 +0530678 def get_stock_items(self):
679 stock_items = []
Nabin Haitdd38a262014-12-26 13:15:21 +0530680 item_codes = list(set(item.item_code for item in self.get("items")))
Nabin Haita36adbd2013-08-02 14:50:12 +0530681 if item_codes:
Nabin Hait868766d2019-07-15 18:02:58 +0530682 stock_items = [r[0] for r in frappe.db.sql("""
683 select name from `tabItem`
684 where name in (%s) and is_stock_item=1
685 """ % (", ".join((["%s"] * len(item_codes))),), item_codes)]
Anand Doshid2946502014-04-08 20:10:03 +0530686
Nabin Haita36adbd2013-08-02 14:50:12 +0530687 return stock_items
Anand Doshid2946502014-04-08 20:10:03 +0530688
Ankit Javalkar8e7ca412014-09-12 15:18:53 +0530689 def set_total_advance_paid(self):
690 if self.doctype == "Sales Order":
Nabin Hait6e439a52015-08-28 19:24:22 +0530691 dr_or_cr = "credit_in_account_currency"
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530692 rev_dr_or_cr = "debit_in_account_currency"
Nabin Haitb2206d12016-01-27 15:43:12 +0530693 party = self.customer
Ankit Javalkar8e7ca412014-09-12 15:18:53 +0530694 else:
Nabin Hait6e439a52015-08-28 19:24:22 +0530695 dr_or_cr = "debit_in_account_currency"
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530696 rev_dr_or_cr = "credit_in_account_currency"
Nabin Haitb2206d12016-01-27 15:43:12 +0530697 party = self.supplier
Ankit Javalkar8e7ca412014-09-12 15:18:53 +0530698
Nabin Haitb2206d12016-01-27 15:43:12 +0530699 advance = frappe.db.sql("""
Ankit Javalkar8e7ca412014-09-12 15:18:53 +0530700 select
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530701 account_currency, sum({dr_or_cr}) - sum({rev_dr_cr}) as amount
Ankit Javalkar8e7ca412014-09-12 15:18:53 +0530702 from
Nabin Hait12e2a512016-06-26 01:37:21 +0530703 `tabGL Entry`
Ankit Javalkar8e7ca412014-09-12 15:18:53 +0530704 where
Nabin Hait12e2a512016-06-26 01:37:21 +0530705 against_voucher_type = %s and against_voucher = %s and party=%s
706 and docstatus = 1
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530707 """.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 +0530708
Nabin Haitb2206d12016-01-27 15:43:12 +0530709 if advance:
Anand Doshi7c0a58a2016-01-29 12:16:24 +0530710 advance = advance[0]
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +0530711
Anand Doshi7c0a58a2016-01-29 12:16:24 +0530712 advance_paid = flt(advance.amount, self.precision("advance_paid"))
713 formatted_advance_paid = fmt_money(advance_paid, precision=self.precision("advance_paid"),
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800714 currency=advance.account_currency)
Anand Doshi7c0a58a2016-01-29 12:16:24 +0530715
716 frappe.db.set_value(self.doctype, self.name, "party_account_currency",
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800717 advance.account_currency)
Anand Doshi7c0a58a2016-01-29 12:16:24 +0530718
719 if advance.account_currency == self.currency:
finbyz5efc7972019-01-05 11:12:11 +0530720 order_total = self.get("rounded_total") or self.grand_total
721 precision = "rounded_total" if self.get("rounded_total") else "grand_total"
Nabin Haitb2206d12016-01-27 15:43:12 +0530722 else:
finbyz5efc7972019-01-05 11:12:11 +0530723 order_total = self.get("base_rounded_total") or self.base_grand_total
724 precision = "base_rounded_total" if self.get("base_rounded_total") else "base_grand_total"
Sagar Voraf733a392019-01-07 14:24:01 +0530725
finbyz5efc7972019-01-05 11:12:11 +0530726 formatted_order_total = fmt_money(order_total, precision=self.precision(precision),
727 currency=advance.account_currency)
Anand Doshi7c0a58a2016-01-29 12:16:24 +0530728
Nabin Hait9db1b222016-06-30 12:37:53 +0530729 if self.currency == self.company_currency and advance_paid > order_total:
Nabin Haitb2206d12016-01-27 15:43:12 +0530730 frappe.throw(_("Total advance ({0}) against Order {1} cannot be greater than the Grand Total ({2})")
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800731 .format(formatted_advance_paid, self.name, formatted_order_total))
Rushabh Mehtaea0ff232016-07-07 14:02:26 +0530732
Nabin Hait13093b42016-06-29 18:04:37 +0530733 frappe.db.set_value(self.doctype, self.name, "advance_paid", advance_paid)
Ankit Javalkar8e7ca412014-09-12 15:18:53 +0530734
ankitjavalkarworke60822b2014-08-26 14:29:06 +0530735 @property
736 def company_abbr(self):
Nabin Hait6a5695f2018-08-09 11:30:21 +0530737 if not hasattr(self, "_abbr"):
738 self._abbr = frappe.db.get_value('Company', self.company, "abbr")
ankitjavalkarworke60822b2014-08-26 14:29:06 +0530739
740 return self._abbr
741
marination4be5b5c2020-10-08 19:08:27 +0530742 def raise_missing_debit_credit_account_error(self, party_type, party):
marination53b1a9a2020-11-03 15:45:25 +0530743 """Raise an error if debit to/credit to account does not exist."""
marination4be5b5c2020-10-08 19:08:27 +0530744 db_or_cr = frappe.bold("Debit To") if self.doctype == "Sales Invoice" else frappe.bold("Credit To")
745 rec_or_pay = "Receivable" if self.doctype == "Sales Invoice" else "Payable"
746
747 link_to_party = frappe.utils.get_link_to_form(party_type, party)
748 link_to_company = frappe.utils.get_link_to_form("Company", self.company)
749
750 message = _("{0} Account not found against Customer {1}.").format(db_or_cr, frappe.bold(party) or '')
751 message += "<br>" + _("Please set one of the following:") + "<br>"
752 message += "<br><ul><li>" + _("'Account' in the Accounting section of Customer {0}").format(link_to_party) + "</li>"
753 message += "<li>" + _("'Default {0} Account' in Company {1}").format(rec_or_pay, link_to_company) + "</li></ul>"
754
marination53b1a9a2020-11-03 15:45:25 +0530755 frappe.throw(message, title=_("Account Missing"), exc=AccountMissingError)
marination4be5b5c2020-10-08 19:08:27 +0530756
Neil Trini Lasrado3fbbb712015-07-17 12:10:12 +0530757 def validate_party(self):
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530758 party_type, party = self.get_party()
shreyaseba9ca42016-01-26 14:56:52 +0530759 validate_party_frozen_disabled(party_type, party)
Anand Doshi979326b2015-09-11 16:22:37 +0530760
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530761 def get_party(self):
Neil Trini Lasrado79bf2332015-07-20 13:03:48 +0530762 party_type = None
shreyas29b565f2016-01-25 17:30:49 +0530763 if self.doctype in ("Opportunity", "Quotation", "Sales Order", "Delivery Note", "Sales Invoice"):
shreyaseba9ca42016-01-26 14:56:52 +0530764 party_type = 'Customer'
shreyas29b565f2016-01-25 17:30:49 +0530765
766 elif self.doctype in ("Supplier Quotation", "Purchase Order", "Purchase Receipt", "Purchase Invoice"):
shreyaseba9ca42016-01-26 14:56:52 +0530767 party_type = 'Supplier'
shreyas29b565f2016-01-25 17:30:49 +0530768
769 elif self.meta.get_field("customer"):
770 party_type = "Customer"
Neil Trini Lasrado3fbbb712015-07-17 12:10:12 +0530771
Neil Trini Lasrado79bf2332015-07-20 13:03:48 +0530772 elif self.meta.get_field("supplier"):
shreyas29b565f2016-01-25 17:30:49 +0530773 party_type = "Supplier"
Anand Doshi979326b2015-09-11 16:22:37 +0530774
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530775 party = self.get(party_type.lower()) if party_type else None
Anand Doshi979326b2015-09-11 16:22:37 +0530776
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530777 return party_type, party
Anand Doshi979326b2015-09-11 16:22:37 +0530778
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530779 def validate_currency(self):
Nabin Hait6e439a52015-08-28 19:24:22 +0530780 if self.get("currency"):
Nabin Hait4ffd7f32015-08-27 12:28:36 +0530781 party_type, party = self.get_party()
Nabin Hait6e439a52015-08-28 19:24:22 +0530782 if party_type and party:
Anand Doshib20baf82015-09-25 16:17:50 +0530783 party_account_currency = get_party_account_currency(party_type, party, self.company)
Anand Doshi979326b2015-09-11 16:22:37 +0530784
Anand Doshi7afaeb02015-10-01 18:55:25 +0530785 if (party_account_currency
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800786 and party_account_currency != self.company_currency
787 and self.currency != party_account_currency):
Nabin Hait98096772015-09-03 10:28:08 +0530788 frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}")
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800789 .format(party_type, party, party_account_currency), InvalidCurrency)
Anand Doshi979326b2015-09-11 16:22:37 +0530790
shreyas29b565f2016-01-25 17:30:49 +0530791 # Note: not validating with gle account because we don't have the account
792 # at quotation / sales order level and we shouldn't stop someone
793 # from creating a sales invoice if sales order is already created
Rushabh Mehta2cb7a9f2016-06-15 16:45:03 +0530794
Nabin Hait297d74a2016-11-23 15:58:51 +0530795 def delink_advance_entries(self, linked_doc_name):
796 total_allocated_amount = 0
797 for adv in self.advances:
798 consider_for_total_advance = True
799 if adv.reference_name == linked_doc_name:
800 frappe.db.sql("""delete from `tab{0} Advance`
801 where name = %s""".format(self.doctype), adv.name)
802 consider_for_total_advance = False
803
804 if consider_for_total_advance:
805 total_allocated_amount += flt(adv.allocated_amount, adv.precision("allocated_amount"))
806
Rushabh Mehta66958302017-01-16 16:57:53 +0530807 frappe.db.set_value(self.doctype, self.name, "total_advance",
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800808 total_allocated_amount, update_modified=False)
Rohit Waghchaure6087fe12016-04-09 14:31:09 +0530809
KanchanChauhan49a50fe2016-11-18 13:57:53 +0530810 def group_similar_items(self):
811 group_item_qty = {}
812 group_item_amount = {}
Shreya Shah785f1aa2018-10-11 10:14:25 +0530813 # to update serial number in print
814 count = 0
KanchanChauhan49a50fe2016-11-18 13:57:53 +0530815
816 for item in self.items:
817 group_item_qty[item.item_code] = group_item_qty.get(item.item_code, 0) + item.qty
818 group_item_amount[item.item_code] = group_item_amount.get(item.item_code, 0) + item.amount
819
820 duplicate_list = []
KanchanChauhan49a50fe2016-11-18 13:57:53 +0530821 for item in self.items:
822 if item.item_code in group_item_qty:
Shreya Shah785f1aa2018-10-11 10:14:25 +0530823 count += 1
KanchanChauhan49a50fe2016-11-18 13:57:53 +0530824 item.qty = group_item_qty[item.item_code]
825 item.amount = group_item_amount[item.item_code]
deepeshgarg007e8d21cd2019-06-24 17:46:44 +0530826
827 if item.qty:
828 item.rate = flt(flt(item.amount) / flt(item.qty), item.precision("rate"))
829 else:
830 item.rate = 0
831
Shreya Shah785f1aa2018-10-11 10:14:25 +0530832 item.idx = count
KanchanChauhan49a50fe2016-11-18 13:57:53 +0530833 del group_item_qty[item.item_code]
834 else:
835 duplicate_list.append(item)
KanchanChauhan49a50fe2016-11-18 13:57:53 +0530836 for item in duplicate_list:
837 self.remove(item)
838
tunde32aa7c12017-09-07 06:52:15 +0100839 def set_payment_schedule(self):
Manas Solankia7f55892018-04-02 10:32:00 +0530840 if self.doctype == 'Sales Invoice' and self.is_pos:
841 self.payment_terms_template = ''
842 return
rohitwaghchauredb9fa782018-03-01 10:32:29 +0530843
Deepesh Garg26210162020-08-11 16:06:13 +0530844 party_account_currency = self.get('party_account_currency')
845 if not party_account_currency:
846 party_type, party = self.get_party()
847
848 if party_type and party:
849 party_account_currency = get_party_account_currency(party_type, party, self.company)
850
rohitwaghchaureda941af2018-01-17 16:23:04 +0530851 posting_date = self.get("bill_date") or self.get("posting_date") or self.get("transaction_date")
tundedf3a1752017-09-11 11:02:57 +0100852 date = self.get("due_date")
853 due_date = date or posting_date
Deepesh Garg26210162020-08-11 16:06:13 +0530854
855 if party_account_currency == self.company_currency:
856 grand_total = self.get("base_rounded_total") or self.base_grand_total
857 else:
858 grand_total = self.get("rounded_total") or self.grand_total
859
Nabin Hait2f9f4f92017-12-20 12:24:59 +0530860 if self.doctype in ("Sales Invoice", "Purchase Invoice"):
861 grand_total = grand_total - flt(self.write_off_amount)
tunde96b8f222017-09-08 15:35:59 +0100862
Rohit Waghchaure4a267b92019-05-09 19:50:00 +0530863 if self.get("total_advance"):
864 grand_total -= self.get("total_advance")
865
Nabin Hait0551f7b2017-11-21 19:58:16 +0530866 if not self.get("payment_schedule"):
867 if self.get("payment_terms_template"):
868 data = get_payment_terms(self.payment_terms_template, posting_date, grand_total)
869 for item in data:
870 self.append("payment_schedule", item)
871 else:
872 data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total)
873 self.append("payment_schedule", data)
874 else:
875 for d in self.get("payment_schedule"):
Nabin Hait2f9f4f92017-12-20 12:24:59 +0530876 if d.invoice_portion:
Deepesh Garg525204b2020-04-23 16:07:36 +0530877 d.payment_amount = flt(grand_total * flt(d.invoice_portion) / 100, d.precision('payment_amount'))
tunde43870aa2017-08-18 11:59:30 +0100878
tundebe1b8712017-08-19 08:21:44 +0100879 def set_due_date(self):
Nabin Hait0551f7b2017-11-21 19:58:16 +0530880 due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date]
881 if due_dates:
882 self.due_date = max(due_dates)
tunde62af5c52017-09-22 15:16:38 +0100883
884 def validate_payment_schedule_dates(self):
tunde77ecacc2017-09-22 23:12:55 +0100885 dates = []
tunde9bed2de2017-09-25 10:19:35 +0100886 li = []
tunde43870aa2017-08-18 11:59:30 +0100887
rohitwaghchauredb9fa782018-03-01 10:32:29 +0530888 if self.doctype == 'Sales Invoice' and self.is_pos: return
889
tunde43870aa2017-08-18 11:59:30 +0100890 for d in self.get("payment_schedule"):
Nabin Hait2f9f4f92017-12-20 12:24:59 +0530891 if self.doctype == "Sales Order" and getdate(d.due_date) < getdate(self.transaction_date):
Michelle Alva7f2477b2020-04-09 17:23:23 +0530892 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 +0100893 elif d.due_date in dates:
mnatalia5f0e8d62018-05-22 06:38:50 +0300894 li.append(_("{0} in row {1}").format(d.due_date, d.idx))
tunde9bed2de2017-09-25 10:19:35 +0100895 dates.append(d.due_date)
896
897 if li:
898 duplicates = '<br>' + '<br>'.join(li)
Faris Ansari2ab6e0e2018-09-19 13:13:59 +0530899 frappe.throw(_("Rows with duplicate due dates in other rows were found: {0}").format(duplicates))
tunde43870aa2017-08-18 11:59:30 +0100900
tunde62af5c52017-09-22 15:16:38 +0100901 def validate_payment_schedule_amount(self):
rohitwaghchauredb9fa782018-03-01 10:32:29 +0530902 if self.doctype == 'Sales Invoice' and self.is_pos: return
903
Deepesh Garg26210162020-08-11 16:06:13 +0530904 party_account_currency = self.get('party_account_currency')
905 if not party_account_currency:
906 party_type, party = self.get_party()
907
908 if party_type and party:
909 party_account_currency = get_party_account_currency(party_type, party, self.company)
910
Nabin Hait0551f7b2017-11-21 19:58:16 +0530911 if self.get("payment_schedule"):
912 total = 0
913 for d in self.get("payment_schedule"):
Nabin Haitf592f2c2017-12-19 11:31:34 +0530914 total += flt(d.payment_amount)
tunde43870aa2017-08-18 11:59:30 +0100915
Deepesh Garg26210162020-08-11 16:06:13 +0530916 if party_account_currency == self.company_currency:
917 total = flt(total, self.precision("base_grand_total"))
918 grand_total = flt(self.get("base_rounded_total") or self.base_grand_total, self.precision('base_grand_total'))
919 else:
920 total = flt(total, self.precision("grand_total"))
921 grand_total = flt(self.get("rounded_total") or self.grand_total, self.precision('grand_total'))
922
Rohit Waghchaure4a267b92019-05-09 19:50:00 +0530923 if self.get("total_advance"):
924 grand_total -= self.get("total_advance")
925
Nabin Haite591c852017-12-21 11:46:30 +0530926 if self.doctype in ("Sales Invoice", "Purchase Invoice"):
927 grand_total = grand_total - flt(self.write_off_amount)
Mangesh-Khairnar88620d62019-07-22 11:29:26 +0530928 if total != flt(grand_total, self.precision("grand_total")):
Nabin Hait0551f7b2017-11-21 19:58:16 +0530929 frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total"))
tunde43870aa2017-08-18 11:59:30 +0100930
Nabin Hait877e1bb2017-11-17 12:27:43 +0530931 def is_rounded_total_disabled(self):
932 if self.meta.get_field("disable_rounded_total"):
933 return self.disable_rounded_total
934 else:
935 return frappe.db.get_single_value("Global Defaults", "disable_rounded_total")
936
Deepesh Gargf17ea2c2020-12-11 21:30:39 +0530937 def set_inter_company_account(self):
938 """
939 Set intercompany account for inter warehouse transactions
940 This account will be used in case billing company and internal customer's
941 representation company is same
942 """
943
944 if self.is_internal_transfer() and not self.unrealized_profit_loss_account:
945 unrealized_profit_loss_account = frappe.db.get_value('Company', self.company, 'unrealized_profit_loss_account')
946
947 if not unrealized_profit_loss_account:
948 msg = _("Please select Unrealized Profit / Loss account or add default Unrealized Profit / Loss account account for company {0}").format(
949 frappe.bold(self.company))
950 frappe.throw(msg)
951
952 self.unrealized_profit_loss_account = unrealized_profit_loss_account
953
954 def is_internal_transfer(self):
955 """
956 It will an internal transfer if its an internal customer and representation
957 company is same as billing company
958 """
959 if self.doctype == 'Sales Invoice':
960 internal_party_field = 'is_internal_customer'
961 else:
962 internal_party_field = 'is_internal_supplier'
963
964 if self.get(internal_party_field) and (self.represents_company == self.company):
965 return True
966
967 return False
968
Rushabh Mehta793ba6b2014-02-14 15:47:51 +0530969@frappe.whitelist()
Rushabh Mehtaf56d73c2013-10-10 16:35:09 +0530970def get_tax_rate(account_head):
Rushabh Mehta2cb7a9f2016-06-15 16:45:03 +0530971 return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True)
Rushabh Mehtaa33d4682015-06-01 17:15:42 +0530972
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800973
Nabin Haitffc7f3f2015-05-12 15:07:02 +0530974@frappe.whitelist()
Nabin Haita2426fc2018-01-15 17:45:46 +0530975def get_default_taxes_and_charges(master_doctype, tax_template=None, company=None):
rohitwaghchaure505661b2017-12-11 14:52:28 +0530976 if not company: return {}
977
Nabin Haita2426fc2018-01-15 17:45:46 +0530978 if tax_template and company:
979 tax_template_company = frappe.db.get_value(master_doctype, tax_template, "company")
980 if tax_template_company == company:
981 return
982
983 default_tax = frappe.db.get_value(master_doctype, {"is_default": 1, "company": company})
rohitwaghchaure505661b2017-12-11 14:52:28 +0530984
Rushabh Mehta98aa5812017-11-14 09:32:32 +0530985 return {
986 'taxes_and_charges': default_tax,
987 'taxes': get_taxes_and_charges(master_doctype, default_tax)
988 }
Nabin Hait6b039142014-05-02 15:45:10 +0530989
Doridel Cahanap6f06cc22018-05-17 16:28:58 +0800990
Nabin Hait6b039142014-05-02 15:45:10 +0530991@frappe.whitelist()
Nabin Haitffc7f3f2015-05-12 15:07:02 +0530992def get_taxes_and_charges(master_doctype, master_name):
Nabin Hait1fe50122015-05-16 12:14:39 +0530993 if not master_name:
994 return
Nabin Hait6b039142014-05-02 15:45:10 +0530995 from frappe.model import default_fields
996 tax_master = frappe.get_doc(master_doctype, master_name)
997
998 taxes_and_charges = []
Nabin Haitffc7f3f2015-05-12 15:07:02 +0530999 for i, tax in enumerate(tax_master.get("taxes")):
Nabin Hait6b039142014-05-02 15:45:10 +05301000 tax = tax.as_dict()
1001
1002 for fieldname in default_fields:
1003 if fieldname in tax:
1004 del tax[fieldname]
1005
1006 taxes_and_charges.append(tax)
1007
1008 return taxes_and_charges
Rushabh Mehta66e08e32014-09-29 12:17:03 +05301009
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001010
Rushabh Mehta66e08e32014-09-29 12:17:03 +05301011def validate_conversion_rate(currency, conversion_rate, conversion_rate_label, company):
1012 """common validation for currency and price list currency"""
1013
Rushabh Mehta708e47a2018-08-08 16:37:31 +05301014 company_currency = frappe.get_cached_value('Company', company, "default_currency")
Rushabh Mehta66e08e32014-09-29 12:17:03 +05301015
1016 if not conversion_rate:
Saqib60212ff2020-10-26 11:17:04 +05301017 throw(
1018 _("{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}.")
1019 .format(conversion_rate_label, currency, company_currency)
1020 )
Nabin Hait613d0812015-02-23 11:58:15 +05301021
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001022
Nabin Hait613d0812015-02-23 11:58:15 +05301023def validate_taxes_and_charges(tax):
Nabin Hait37b047d2015-02-23 16:01:33 +05301024 if tax.charge_type in ['Actual', 'On Net Total'] and tax.row_id:
Nabin Hait613d0812015-02-23 11:58:15 +05301025 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 +05301026 elif tax.charge_type in ['On Previous Row Amount', 'On Previous Row Total']:
Nabin Hait613d0812015-02-23 11:58:15 +05301027 if cint(tax.idx) == 1:
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001028 frappe.throw(
1029 _("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"))
Nabin Hait613d0812015-02-23 11:58:15 +05301030 elif not tax.row_id:
Suraj Shetty48e9bc32020-01-29 15:06:18 +05301031 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 +05301032 elif tax.row_id and cint(tax.row_id) >= cint(tax.idx):
1033 frappe.throw(_("Cannot refer row number greater than or equal to current row number for this Charge type"))
1034
Rushabh Mehta2a21bc92015-02-25 15:08:42 +05301035 if tax.charge_type == "Actual":
Nabin Haitbc6c3602015-02-27 23:40:56 +05301036 tax.rate = None
Rushabh Mehta2a21bc92015-02-25 15:08:42 +05301037
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001038
Nabin Hait613d0812015-02-23 11:58:15 +05301039def validate_inclusive_tax(tax, doc):
1040 def _on_previous_row_error(row_range):
1041 throw(_("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(tax.idx,
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001042 row_range))
Nabin Hait613d0812015-02-23 11:58:15 +05301043
1044 if cint(getattr(tax, "included_in_print_rate", None)):
1045 if tax.charge_type == "Actual":
1046 # inclusive tax cannot be of type Actual
1047 throw(_("Charge of type 'Actual' in row {0} cannot be included in Item Rate").format(tax.idx))
1048 elif tax.charge_type == "On Previous Row Amount" and \
1049 not cint(doc.get("taxes")[cint(tax.row_id) - 1].included_in_print_rate):
1050 # referred row should also be inclusive
1051 _on_previous_row_error(tax.row_id)
1052 elif tax.charge_type == "On Previous Row Total" and \
1053 not all([cint(t.included_in_print_rate) for t in doc.get("taxes")[:cint(tax.row_id) - 1]]):
1054 # all rows about the reffered tax should be inclusive
1055 _on_previous_row_error("1 - %d" % (tax.row_id,))
Nabin Hait93b3a372015-02-24 17:08:34 +05301056 elif tax.get("category") == "Valuation":
Nabin Hait19ea7212020-08-11 20:34:57 +05301057 frappe.throw(_("Valuation type charges can not be marked as Inclusive"))
Revant Nandgaonkar5da941b2016-02-18 16:02:05 +05301058
Revant Nandgaonkar5da941b2016-02-18 16:02:05 +05301059
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001060def set_balance_in_account_currency(gl_dict, account_currency=None, conversion_rate=None, company_currency=None):
1061 if (not conversion_rate) and (account_currency != company_currency):
1062 frappe.throw(_("Account: {0} with currency: {1} can not be selected")
1063 .format(gl_dict.account, account_currency))
1064
1065 gl_dict["account_currency"] = company_currency if account_currency == company_currency \
Revant Nandgaonkar5da941b2016-02-18 16:02:05 +05301066 else account_currency
1067
1068 # set debit/credit in account currency if not provided
1069 if flt(gl_dict.debit) and not flt(gl_dict.debit_in_account_currency):
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001070 gl_dict.debit_in_account_currency = gl_dict.debit if account_currency == company_currency \
Revant Nandgaonkar5da941b2016-02-18 16:02:05 +05301071 else flt(gl_dict.debit / conversion_rate, 2)
1072
1073 if flt(gl_dict.credit) and not flt(gl_dict.credit_in_account_currency):
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001074 gl_dict.credit_in_account_currency = gl_dict.credit if account_currency == company_currency \
Revant Nandgaonkar5da941b2016-02-18 16:02:05 +05301075 else flt(gl_dict.credit / conversion_rate, 2)
Nabin Hait1991c7b2016-06-27 20:09:05 +05301076
1077
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301078def get_advance_journal_entries(party_type, party, party_account, amount_field,
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001079 order_doctype, order_list, include_unallocated=True):
1080 dr_or_cr = "credit_in_account_currency" if party_type == "Customer" else "debit_in_account_currency"
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301081
Nabin Hait1991c7b2016-06-27 20:09:05 +05301082 conditions = []
1083 if include_unallocated:
1084 conditions.append("ifnull(t2.reference_name, '')=''")
1085
1086 if order_list:
1087 order_condition = ', '.join(['%s'] * len(order_list))
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001088 conditions.append(" (t2.reference_type = '{0}' and ifnull(t2.reference_name, '') in ({1}))" \
1089 .format(order_doctype, order_condition))
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301090
Nabin Hait1991c7b2016-06-27 20:09:05 +05301091 reference_condition = " and (" + " or ".join(conditions) + ")" if conditions else ""
Rushabh Mehta66958302017-01-16 16:57:53 +05301092
Nabin Hait1991c7b2016-06-27 20:09:05 +05301093 journal_entries = frappe.db.sql("""
1094 select
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301095 "Journal Entry" as reference_type, t1.name as reference_name,
Nabin Hait1991c7b2016-06-27 20:09:05 +05301096 t1.remark as remarks, t2.{0} as amount, t2.name as reference_row,
1097 t2.reference_name as against_order
1098 from
1099 `tabJournal Entry` t1, `tabJournal Entry Account` t2
1100 where
1101 t1.name = t2.parent and t2.account = %s
1102 and t2.party_type = %s and t2.party = %s
1103 and t2.is_advance = 'Yes' and t1.docstatus = 1
Nabin Haitca627fb2016-09-05 16:16:53 +05301104 and {1} > 0 {2}
Nabin Hait1991c7b2016-06-27 20:09:05 +05301105 order by t1.posting_date""".format(amount_field, dr_or_cr, reference_condition),
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001106 [party_account, party_type, party] + order_list, as_dict=1)
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301107
Nabin Hait1991c7b2016-06-27 20:09:05 +05301108 return list(journal_entries)
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301109
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001110
Rohit Waghchauref7258162019-01-15 18:12:04 +05301111def get_advance_payment_entries(party_type, party, party_account, order_doctype,
Nabin Hait34c551d2019-07-03 10:34:31 +05301112 order_list=None, include_unallocated=True, against_all_orders=False, limit=None):
Nabin Hait1991c7b2016-06-27 20:09:05 +05301113 party_account_field = "paid_from" if party_type == "Customer" else "paid_to"
Deepesh Gargc7eadfc2020-07-22 17:59:37 +05301114 currency_field = "paid_from_account_currency" if party_type == "Customer" else "paid_to_account_currency"
Nabin Hait1991c7b2016-06-27 20:09:05 +05301115 payment_type = "Receive" if party_type == "Customer" else "Pay"
1116 payment_entries_against_order, unallocated_payment_entries = [], []
Nabin Hait34c551d2019-07-03 10:34:31 +05301117 limit_cond = "limit %s" % limit if limit else ""
Nabin Haitff2f3ef2016-07-04 11:41:14 +05301118
Nabin Hait1991c7b2016-06-27 20:09:05 +05301119 if order_list or against_all_orders:
1120 if order_list:
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001121 reference_condition = " and t2.reference_name in ({0})" \
Nabin Hait1991c7b2016-06-27 20:09:05 +05301122 .format(', '.join(['%s'] * len(order_list)))
1123 else:
1124 reference_condition = ""
1125 order_list = []
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301126
Nabin Hait1991c7b2016-06-27 20:09:05 +05301127 payment_entries_against_order = frappe.db.sql("""
1128 select
1129 "Payment Entry" as reference_type, t1.name as reference_name,
1130 t1.remarks, t2.allocated_amount as amount, t2.name as reference_row,
Deepesh Gargc7eadfc2020-07-22 17:59:37 +05301131 t2.reference_name as against_order, t1.posting_date,
1132 t1.{0} as currency
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301133 from `tabPayment Entry` t1, `tabPayment Entry Reference` t2
Nabin Hait1991c7b2016-06-27 20:09:05 +05301134 where
Deepesh Gargc7eadfc2020-07-22 17:59:37 +05301135 t1.name = t2.parent and t1.{1} = %s and t1.payment_type = %s
Nabin Hait1991c7b2016-06-27 20:09:05 +05301136 and t1.party_type = %s and t1.party = %s and t1.docstatus = 1
Deepesh Gargc7eadfc2020-07-22 17:59:37 +05301137 and t2.reference_doctype = %s {2}
1138 order by t1.posting_date {3}
1139 """.format(currency_field, party_account_field, reference_condition, limit_cond),
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001140 [party_account, payment_type, party_type, party,
1141 order_doctype] + order_list, as_dict=1)
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301142
Nabin Hait1991c7b2016-06-27 20:09:05 +05301143 if include_unallocated:
1144 unallocated_payment_entries = frappe.db.sql("""
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301145 select "Payment Entry" as reference_type, name as reference_name,
Nabin Hait1991c7b2016-06-27 20:09:05 +05301146 remarks, unallocated_amount as amount
1147 from `tabPayment Entry`
1148 where
1149 {0} = %s and party_type = %s and party = %s and payment_type = %s
1150 and docstatus = 1 and unallocated_amount > 0
Rohit Waghchauref7258162019-01-15 18:12:04 +05301151 order by posting_date {1}
1152 """.format(party_account_field, limit_cond), (party_account, party_type, party, payment_type), as_dict=1)
Rushabh Mehtaea0ff232016-07-07 14:02:26 +05301153
Rohit Waghchaure2f1db572016-11-08 12:39:33 +05301154 return list(payment_entries_against_order) + list(unallocated_payment_entries)
1155
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001156
Rohit Waghchaure2f1db572016-11-08 12:39:33 +05301157def update_invoice_status():
1158 # Daily update the status of the invoices
1159
Rushabh Mehta66958302017-01-16 16:57:53 +05301160 frappe.db.sql(""" update `tabSales Invoice` set status = 'Overdue'
Rohit Waghchaure2f1db572016-11-08 12:39:33 +05301161 where due_date < CURDATE() and docstatus = 1 and outstanding_amount > 0""")
1162
Rushabh Mehta66958302017-01-16 16:57:53 +05301163 frappe.db.sql(""" update `tabPurchase Invoice` set status = 'Overdue'
Nabin Hait92759692017-08-15 08:23:51 +05301164 where due_date < CURDATE() and docstatus = 1 and outstanding_amount > 0""")
1165
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001166
Nabin Hait92759692017-08-15 08:23:51 +05301167@frappe.whitelist()
Shreya Shah3a9eec22018-02-16 13:05:21 +05301168def get_payment_terms(terms_template, posting_date=None, grand_total=None, bill_date=None):
Nabin Hait92759692017-08-15 08:23:51 +05301169 if not terms_template:
1170 return
1171
1172 terms_doc = frappe.get_doc("Payment Terms Template", terms_template)
1173
1174 schedule = []
tundefb144302017-08-19 15:01:40 +01001175 for d in terms_doc.get("terms"):
Shreya Shah3a9eec22018-02-16 13:05:21 +05301176 term_details = get_payment_term_details(d, posting_date, grand_total, bill_date)
Nabin Hait92759692017-08-15 08:23:51 +05301177 schedule.append(term_details)
1178
1179 return schedule
1180
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001181
Nabin Hait92759692017-08-15 08:23:51 +05301182@frappe.whitelist()
Shreya Shah3a9eec22018-02-16 13:05:21 +05301183def get_payment_term_details(term, posting_date=None, grand_total=None, bill_date=None):
Nabin Hait92759692017-08-15 08:23:51 +05301184 term_details = frappe._dict()
Achilles Rasquinha90509982018-03-08 12:55:41 +05301185 if isinstance(term, text_type):
Nabin Hait92759692017-08-15 08:23:51 +05301186 term = frappe.get_doc("Payment Term", term)
1187 else:
1188 term_details.payment_term = term.payment_term
1189 term_details.description = term.description
1190 term_details.invoice_portion = term.invoice_portion
Rohit Waghchaure52bf56d2017-11-27 11:43:52 +05301191 term_details.payment_amount = flt(term.invoice_portion) * flt(grand_total) / 100
Shreya Shah3a9eec22018-02-16 13:05:21 +05301192 if bill_date:
1193 term_details.due_date = get_due_date(term, bill_date)
1194 elif posting_date:
1195 term_details.due_date = get_due_date(term, posting_date)
1196
1197 if getdate(term_details.due_date) < getdate(posting_date):
1198 term_details.due_date = posting_date
tundebabzye69747c2018-03-09 07:53:53 +01001199 term_details.mode_of_payment = term.mode_of_payment
Shreya Shah3a9eec22018-02-16 13:05:21 +05301200
Nabin Hait92759692017-08-15 08:23:51 +05301201 return term_details
1202
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001203
Shreya Shah3a9eec22018-02-16 13:05:21 +05301204def get_due_date(term, posting_date=None, bill_date=None):
Nabin Hait92759692017-08-15 08:23:51 +05301205 due_date = None
Shreya Shah3a9eec22018-02-16 13:05:21 +05301206 date = bill_date or posting_date
Nabin Hait92759692017-08-15 08:23:51 +05301207 if term.due_date_based_on == "Day(s) after invoice date":
Shreya Shah3a9eec22018-02-16 13:05:21 +05301208 due_date = add_days(date, term.credit_days)
Nabin Hait92759692017-08-15 08:23:51 +05301209 elif term.due_date_based_on == "Day(s) after the end of the invoice month":
Shreya Shah3a9eec22018-02-16 13:05:21 +05301210 due_date = add_days(get_last_day(date), term.credit_days)
Nabin Hait92759692017-08-15 08:23:51 +05301211 elif term.due_date_based_on == "Month(s) after the end of the invoice month":
Shreya Shah3a9eec22018-02-16 13:05:21 +05301212 due_date = add_months(get_last_day(date), term.credit_months)
tunde96b8f222017-09-08 15:35:59 +01001213 return due_date
tundebabzyad08d4c2018-05-16 07:01:41 +01001214
1215
1216def get_supplier_block_status(party_name):
1217 """
1218 Returns a dict containing the values of `on_hold`, `release_date` and `hold_type` of
1219 a `Supplier`
1220 """
1221 supplier = frappe.get_doc('Supplier', party_name)
1222 info = {
1223 'on_hold': supplier.on_hold,
1224 'release_date': supplier.release_date,
1225 'hold_type': supplier.hold_type
1226 }
1227 return info
1228
marination698d9832020-08-19 14:59:46 +05301229def set_child_tax_template_and_map(item, child_item, parent_doc):
1230 args = {
1231 'item_code': item.item_code,
1232 'posting_date': parent_doc.transaction_date,
1233 'tax_category': parent_doc.get('tax_category'),
1234 'company': parent_doc.get('company')
1235 }
1236
1237 child_item.item_tax_template = _get_item_tax_template(args, item.taxes)
1238 if child_item.get("item_tax_template"):
1239 child_item.item_tax_rate = get_item_tax_map(parent_doc.get('company'), child_item.item_tax_template, as_json=True)
1240
Marica3b1be2b2020-10-22 17:04:31 +05301241def add_taxes_from_tax_template(child_item, parent_doc):
1242 add_taxes_from_item_tax_template = frappe.db.get_single_value("Accounts Settings", "add_taxes_from_item_tax_template")
1243
1244 if child_item.get("item_tax_rate") and add_taxes_from_item_tax_template:
1245 tax_map = json.loads(child_item.get("item_tax_rate"))
1246 for tax_type in tax_map:
1247 tax_rate = flt(tax_map[tax_type])
1248 taxes = parent_doc.get('taxes') or []
1249 # add new row for tax head only if missing
1250 found = any(tax.account_head == tax_type for tax in taxes)
1251 if not found:
1252 tax_row = parent_doc.append("taxes", {})
1253 tax_row.update({
1254 "description" : str(tax_type).split(' - ')[0],
1255 "charge_type" : "On Net Total",
1256 "account_head" : tax_type,
1257 "rate" : tax_rate
1258 })
1259 if parent_doc.doctype == "Purchase Order":
1260 tax_row.update({
1261 "category" : "Total",
1262 "add_deduct_tax" : "Add"
1263 })
1264 tax_row.db_insert()
1265
Saqib Ansarif53299e2020-04-15 22:08:12 +05301266def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, trans_item):
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001267 """
1268 Returns a Sales Order Item child item containing the default values
1269 """
Saqib438e0432020-04-03 10:02:56 +05301270 p_doc = frappe.get_doc(parent_doctype, parent_doctype_name)
1271 child_item = frappe.new_doc('Sales Order Item', p_doc, child_docname)
Saqib Ansarif53299e2020-04-15 22:08:12 +05301272 item = frappe.get_doc("Item", trans_item.get('item_code'))
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001273 child_item.item_code = item.item_code
1274 child_item.item_name = item.item_name
1275 child_item.description = item.description
Saqib Ansarif53299e2020-04-15 22:08:12 +05301276 child_item.delivery_date = trans_item.get('delivery_date') or p_doc.delivery_date
Saqib61314242020-09-15 11:14:31 +05301277 child_item.uom = trans_item.get("uom") or item.stock_uom
1278 conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor"))
1279 child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor
marination665c27a2020-09-15 12:04:38 +05301280 set_child_tax_template_and_map(item, child_item, p_doc)
Marica3b1be2b2020-10-22 17:04:31 +05301281 add_taxes_from_tax_template(child_item, p_doc)
Saqib438e0432020-04-03 10:02:56 +05301282 child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
Saqib Ansarif53299e2020-04-15 22:08:12 +05301283 if not child_item.warehouse:
1284 frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.")
1285 .format(frappe.bold("default warehouse"), frappe.bold(item.item_code)))
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001286 return child_item
1287
1288
Saqib Ansarif53299e2020-04-15 22:08:12 +05301289def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, trans_item):
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001290 """
1291 Returns a Purchase Order Item child item containing the default values
1292 """
Saqib438e0432020-04-03 10:02:56 +05301293 p_doc = frappe.get_doc(parent_doctype, parent_doctype_name)
1294 child_item = frappe.new_doc('Purchase Order Item', p_doc, child_docname)
Saqib Ansarif53299e2020-04-15 22:08:12 +05301295 item = frappe.get_doc("Item", trans_item.get('item_code'))
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001296 child_item.item_code = item.item_code
1297 child_item.item_name = item.item_name
1298 child_item.description = item.description
Saqib Ansarif53299e2020-04-15 22:08:12 +05301299 child_item.schedule_date = trans_item.get('schedule_date') or p_doc.schedule_date
Saqib61314242020-09-15 11:14:31 +05301300 child_item.uom = trans_item.get("uom") or item.stock_uom
1301 conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor"))
1302 child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001303 child_item.base_rate = 1 # Initiallize value will update in parent validation
1304 child_item.base_amount = 1 # Initiallize value will update in parent validation
marination698d9832020-08-19 14:59:46 +05301305 set_child_tax_template_and_map(item, child_item, p_doc)
Marica3b1be2b2020-10-22 17:04:31 +05301306 add_taxes_from_tax_template(child_item, p_doc)
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001307 return child_item
1308
Saqib6db92fb2020-09-14 19:54:17 +05301309def validate_and_delete_children(parent, data):
Saqib0ad74492019-12-24 16:42:30 +05301310 deleted_children = []
1311 updated_item_names = [d.get("docname") for d in data]
1312 for item in parent.items:
1313 if item.name not in updated_item_names:
1314 deleted_children.append(item)
1315
1316 for d in deleted_children:
thefalconx332073eeb2019-12-27 16:26:03 +05301317 if parent.doctype == "Sales Order":
1318 if flt(d.delivered_qty):
1319 frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been delivered").format(d.idx, d.item_code))
1320 if flt(d.work_order_qty):
1321 frappe.throw(_("Row #{0}: Cannot delete item {1} which has work order assigned to it.").format(d.idx, d.item_code))
1322 if flt(d.ordered_qty):
1323 frappe.throw(_("Row #{0}: Cannot delete item {1} which is assigned to customer's purchase order.").format(d.idx, d.item_code))
Saqib0ad74492019-12-24 16:42:30 +05301324
1325 if parent.doctype == "Purchase Order" and flt(d.received_qty):
1326 frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been received").format(d.idx, d.item_code))
Suraj Shetty48e9bc32020-01-29 15:06:18 +05301327
Saqib0ad74492019-12-24 16:42:30 +05301328 if flt(d.billed_amt):
1329 frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been billed.").format(d.idx, d.item_code))
1330
1331 d.cancel()
1332 d.delete()
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001333
1334@frappe.whitelist()
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001335def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
Saqib6db92fb2020-09-14 19:54:17 +05301336 def check_doc_permissions(doc, perm_type='create'):
Saqib Ansari2c3c8aa2020-05-29 21:55:38 +05301337 try:
1338 doc.check_permission(perm_type)
Saqib6db92fb2020-09-14 19:54:17 +05301339 except frappe.PermissionError:
marination661bf642020-09-29 18:16:45 +05301340 actions = { 'create': 'add', 'write': 'update'}
Saqib6db92fb2020-09-14 19:54:17 +05301341
1342 frappe.throw(_("You do not have permissions to {} items in a {}.")
1343 .format(actions[perm_type], parent_doctype), title=_("Insufficient Permissions"))
marination665c27a2020-09-15 12:04:38 +05301344
Saqib6db92fb2020-09-14 19:54:17 +05301345 def validate_workflow_conditions(doc):
1346 workflow = get_workflow_name(doc.doctype)
1347 if not workflow:
1348 return
1349
1350 workflow_doc = frappe.get_doc("Workflow", workflow)
1351 current_state = doc.get(workflow_doc.workflow_state_field)
1352 roles = frappe.get_roles()
1353
1354 transitions = []
1355 for transition in workflow_doc.transitions:
1356 if transition.next_state == current_state and transition.allowed in roles:
1357 if not is_transition_condition_satisfied(transition, doc):
1358 continue
1359 transitions.append(transition.as_dict())
1360
1361 if not transitions:
Saqib18318932020-09-23 13:01:49 +05301362 frappe.throw(
1363 _("You are not allowed to update as per the conditions set in {} Workflow.").format(get_link_to_form("Workflow", workflow)),
1364 title=_("Insufficient Permissions")
1365 )
Saqib Ansari2c3c8aa2020-05-29 21:55:38 +05301366
Rohit Waghchaure8d5380a2020-06-18 14:06:31 +05301367 def get_new_child_item(item_row):
Saqib6db92fb2020-09-14 19:54:17 +05301368 new_child_function = set_sales_order_defaults if parent_doctype == "Sales Order" else set_purchase_order_defaults
1369 return new_child_function(parent_doctype, parent_doctype_name, child_docname, item_row)
Saqib Ansari2c3c8aa2020-05-29 21:55:38 +05301370
1371 def validate_quantity(child_item, d):
1372 if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
1373 frappe.throw(_("Cannot set quantity less than delivered quantity"))
1374
1375 if parent_doctype == "Purchase Order" and flt(d.get("qty")) < flt(child_item.received_qty):
1376 frappe.throw(_("Cannot set quantity less than received quantity"))
1377
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001378 data = json.loads(trans_items)
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001379
rohitwaghchaure81c21752019-10-31 15:56:10 +05301380 sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation']
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05301381 parent = frappe.get_doc(parent_doctype, parent_doctype_name)
marination665c27a2020-09-15 12:04:38 +05301382
marination661bf642020-09-29 18:16:45 +05301383 check_doc_permissions(parent, 'write')
Saqib6db92fb2020-09-14 19:54:17 +05301384 validate_and_delete_children(parent, data)
Saqib0ad74492019-12-24 16:42:30 +05301385
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001386 for d in data:
1387 new_child_flag = False
1388 if not d.get("docname"):
1389 new_child_flag = True
Saqib6db92fb2020-09-14 19:54:17 +05301390 check_doc_permissions(parent, 'create')
Rohit Waghchaure8d5380a2020-06-18 14:06:31 +05301391 child_item = get_new_child_item(d)
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001392 else:
Saqib6db92fb2020-09-14 19:54:17 +05301393 check_doc_permissions(parent, 'write')
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001394 child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname"))
Saqib Ansari2c3c8aa2020-05-29 21:55:38 +05301395
Saqib Ansaric579b082020-05-29 22:21:50 +05301396 prev_rate, new_rate = flt(child_item.get("rate")), flt(d.get("rate"))
1397 prev_qty, new_qty = flt(child_item.get("qty")), flt(d.get("qty"))
1398 prev_con_fac, new_con_fac = flt(child_item.get("conversion_factor")), flt(d.get("conversion_factor"))
Saqib61314242020-09-15 11:14:31 +05301399 prev_uom, new_uom = child_item.get("uom"), d.get("uom")
Saqib Ansaric579b082020-05-29 22:21:50 +05301400
1401 if parent_doctype == 'Sales Order':
1402 prev_date, new_date = child_item.get("delivery_date"), d.get("delivery_date")
1403 elif parent_doctype == 'Purchase Order':
marinationb7e94cc2020-06-15 11:32:42 +05301404 prev_date, new_date = child_item.get("schedule_date"), d.get("schedule_date")
Saqib Ansaric579b082020-05-29 22:21:50 +05301405
1406 rate_unchanged = prev_rate == new_rate
marinationf9c4b202020-06-12 15:49:53 +05301407 qty_unchanged = prev_qty == new_qty
Saqib61314242020-09-15 11:14:31 +05301408 uom_unchanged = prev_uom == new_uom
Saqib Ansaric579b082020-05-29 22:21:50 +05301409 conversion_factor_unchanged = prev_con_fac == new_con_fac
1410 date_unchanged = prev_date == new_date if prev_date and new_date else False # in case of delivery note etc
Saqib61314242020-09-15 11:14:31 +05301411 if rate_unchanged and qty_unchanged and conversion_factor_unchanged and uom_unchanged and date_unchanged:
Saqibdd374ff2020-02-27 12:51:31 +05301412 continue
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001413
Saqib Ansari2c3c8aa2020-05-29 21:55:38 +05301414 validate_quantity(child_item, d)
deepeshgarg00777cde832018-12-27 15:56:51 +05301415
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001416 child_item.qty = flt(d.get("qty"))
Saqib56fea7d2020-10-09 21:19:25 +05301417 rate_precision = child_item.precision("rate") or 2
1418 conv_fac_precision = child_item.precision("conversion_factor") or 2
1419 qty_precision = child_item.precision("qty") or 2
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001420
Saqib56fea7d2020-10-09 21:19:25 +05301421 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 +08001422 frappe.throw(_("Row #{0}: Cannot set Rate if amount is greater than billed amount for Item {1}.")
1423 .format(child_item.idx, child_item.item_code))
1424 else:
Saqib56fea7d2020-10-09 21:19:25 +05301425 child_item.rate = flt(d.get("rate"), rate_precision)
marinationf9c4b202020-06-12 15:49:53 +05301426
Saqib Ansari2c3c8aa2020-05-29 21:55:38 +05301427 if d.get("conversion_factor"):
marinationf9c4b202020-06-12 15:49:53 +05301428 if child_item.stock_uom == child_item.uom:
1429 child_item.conversion_factor = 1
1430 else:
Saqib56fea7d2020-10-09 21:19:25 +05301431 child_item.conversion_factor = flt(d.get('conversion_factor'), conv_fac_precision)
marination665c27a2020-09-15 12:04:38 +05301432
Saqib61314242020-09-15 11:14:31 +05301433 if d.get("uom"):
1434 child_item.uom = d.get("uom")
1435 conversion_factor = flt(get_conversion_factor(child_item.item_code, child_item.uom).get("conversion_factor"))
Saqib56fea7d2020-10-09 21:19:25 +05301436 child_item.conversion_factor = flt(d.get('conversion_factor'), conv_fac_precision) or conversion_factor
Mangesh-Khairnara3d52002019-08-05 10:16:40 +05301437
Saqib Ansaric579b082020-05-29 22:21:50 +05301438 if d.get("delivery_date") and parent_doctype == 'Sales Order':
1439 child_item.delivery_date = d.get('delivery_date')
marinationf9c4b202020-06-12 15:49:53 +05301440
Saqib Ansaric579b082020-05-29 22:21:50 +05301441 if d.get("schedule_date") and parent_doctype == 'Purchase Order':
1442 child_item.schedule_date = d.get('schedule_date')
1443
Mangesh-Khairnara3d52002019-08-05 10:16:40 +05301444 if flt(child_item.price_list_rate):
Maricaf0674472019-10-11 10:47:09 +05301445 if flt(child_item.rate) > flt(child_item.price_list_rate):
1446 # if rate is greater than price_list_rate, set margin
1447 # or set discount
1448 child_item.discount_percentage = 0
rohitwaghchaure81c21752019-10-31 15:56:10 +05301449
1450 if parent_doctype in sales_doctypes:
1451 child_item.margin_type = "Amount"
1452 child_item.margin_rate_or_amount = flt(child_item.rate - child_item.price_list_rate,
1453 child_item.precision("margin_rate_or_amount"))
1454 child_item.rate_with_margin = child_item.rate
Maricaf0674472019-10-11 10:47:09 +05301455 else:
1456 child_item.discount_percentage = flt((1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0,
1457 child_item.precision("discount_percentage"))
1458 child_item.discount_amount = flt(
1459 child_item.price_list_rate) - flt(child_item.rate)
rohitwaghchaure81c21752019-10-31 15:56:10 +05301460
1461 if parent_doctype in sales_doctypes:
1462 child_item.margin_type = ""
1463 child_item.margin_rate_or_amount = 0
1464 child_item.rate_with_margin = 0
Mangesh-Khairnara3d52002019-08-05 10:16:40 +05301465
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001466 child_item.flags.ignore_validate_update_after_submit = True
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001467 if new_child_flag:
Saqib Ansarif53299e2020-04-15 22:08:12 +05301468 parent.load_from_db()
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05301469 child_item.idx = len(parent.items) + 1
Stavros Anastasiadis3d82b742018-12-10 13:00:55 +01001470 child_item.insert()
1471 else:
1472 child_item.save()
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001473
Rushabh Mehtafafbb022018-12-24 22:09:15 +05301474 parent.reload()
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05301475 parent.flags.ignore_validate_update_after_submit = True
1476 parent.set_qty_as_per_stock_uom()
1477 parent.calculate_taxes_and_totals()
Nabin Hait49a46f02019-10-21 13:35:43 +05301478 if parent_doctype == "Sales Order":
Marica3dee5272020-06-23 10:33:47 +05301479 make_packing_list(parent)
Nabin Hait49a46f02019-10-21 13:35:43 +05301480 parent.set_gross_profit()
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05301481 frappe.get_doc('Authorization Control').validate_approving_authority(parent.doctype,
1482 parent.company, parent.base_grand_total)
Doridel Cahanap6f06cc22018-05-17 16:28:58 +08001483
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05301484 parent.set_payment_schedule()
Nabin Haitb2da0822018-07-13 17:01:40 +05301485 if parent_doctype == 'Purchase Order':
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05301486 parent.validate_minimum_order_qty()
1487 parent.validate_budget()
1488 if parent.is_against_so():
1489 parent.update_status_updater()
Nabin Haitb2da0822018-07-13 17:01:40 +05301490 else:
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05301491 parent.check_credit_limit()
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05301492 parent.save()
Nabin Haitb2da0822018-07-13 17:01:40 +05301493
1494 if parent_doctype == 'Purchase Order':
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05301495 update_last_purchase_rate(parent, is_submit = 1)
1496 parent.update_prevdoc_status()
1497 parent.update_requested_qty()
1498 parent.update_ordered_qty()
1499 parent.update_ordered_and_reserved_qty()
1500 parent.update_receiving_percentage()
1501 if parent.is_subcontracted == "Yes":
1502 parent.update_reserved_qty_for_subcontract()
Saqib61314242020-09-15 11:14:31 +05301503 parent.create_raw_materials_supplied("supplied_items")
1504 parent.save()
Nabin Haitb2da0822018-07-13 17:01:40 +05301505 else:
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05301506 parent.update_reserved_qty()
1507 parent.update_project()
1508 parent.update_prevdoc_status('submit')
1509 parent.update_delivery_status()
Nabin Haitb2da0822018-07-13 17:01:40 +05301510
Saqib6db92fb2020-09-14 19:54:17 +05301511 parent.reload()
1512 validate_workflow_conditions(parent)
1513
Rushabh Mehtac9b1a352018-12-24 20:55:35 +05301514 parent.update_blanket_order()
1515 parent.update_billing_percentage()
1516 parent.set_status()
Gauravf1e28e02019-02-13 16:46:24 +05301517
1518@erpnext.allow_regional
1519def validate_regional(doc):
1520 pass