mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-24 23:49:19 +00:00
Merge branch 'version-15-hotfix' into mergify/bp/version-15-hotfix/pr-41721
This commit is contained in:
@@ -8,7 +8,7 @@ from collections import defaultdict
|
||||
import frappe
|
||||
from frappe import _, bold, qb, throw
|
||||
from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied
|
||||
from frappe.query_builder import Criterion
|
||||
from frappe.query_builder import Criterion, DocType
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.query_builder.functions import Abs, Sum
|
||||
from frappe.utils import (
|
||||
@@ -165,12 +165,54 @@ class AccountsController(TransactionBase):
|
||||
raise_exception=1,
|
||||
)
|
||||
|
||||
def validate_against_voucher_outstanding(self):
|
||||
from frappe.model.meta import get_meta
|
||||
|
||||
if not get_meta(self.doctype).has_field("outstanding_amount"):
|
||||
return
|
||||
|
||||
if self.get("is_return") and self.return_against and not self.get("is_pos"):
|
||||
against_voucher_outstanding = frappe.get_value(
|
||||
self.doctype, self.return_against, "outstanding_amount"
|
||||
)
|
||||
document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note"
|
||||
|
||||
msg = ""
|
||||
if self.get("update_outstanding_for_self"):
|
||||
msg = (
|
||||
"We can see {0} is made against {1}. If you want {1}'s outstanding to be updated, "
|
||||
"uncheck '{2}' checkbox. <br><br>Or"
|
||||
).format(
|
||||
frappe.bold(document_type),
|
||||
get_link_to_form(self.doctype, self.get("return_against")),
|
||||
frappe.bold(_("Update Outstanding for Self")),
|
||||
)
|
||||
|
||||
elif not self.update_outstanding_for_self and (
|
||||
abs(flt(self.rounded_total) or flt(self.grand_total)) > flt(against_voucher_outstanding)
|
||||
):
|
||||
self.update_outstanding_for_self = 1
|
||||
msg = (
|
||||
"The outstanding amount {} in {} is lesser than {}. Updating the outstanding to this invoice. <br><br>And"
|
||||
).format(
|
||||
against_voucher_outstanding,
|
||||
get_link_to_form(self.doctype, self.get("return_against")),
|
||||
flt(abs(self.outstanding_amount)),
|
||||
)
|
||||
|
||||
if msg:
|
||||
msg += " you can use {} tool to reconcile against {} later.".format(
|
||||
get_link_to_form("Payment Reconciliation", "Payment Reconciliation"),
|
||||
get_link_to_form(self.doctype, self.get("return_against")),
|
||||
)
|
||||
frappe.msgprint(_(msg))
|
||||
|
||||
def validate(self):
|
||||
if not self.get("is_return") and not self.get("is_debit_note"):
|
||||
self.validate_qty_is_not_zero()
|
||||
|
||||
if (
|
||||
self.doctype in ["Sales Invoice", "Purchase Invoice"]
|
||||
self.doctype in ["Sales Invoice", "Purchase Invoice", "POS Invoice"]
|
||||
and self.get("is_return")
|
||||
and self.get("update_stock")
|
||||
):
|
||||
@@ -193,6 +235,15 @@ class AccountsController(TransactionBase):
|
||||
self.disable_tax_included_prices_for_internal_transfer()
|
||||
self.set_incoming_rate()
|
||||
self.init_internal_values()
|
||||
self.validate_against_voucher_outstanding()
|
||||
|
||||
# Need to set taxes based on taxes_and_charges template
|
||||
# before calculating taxes and totals
|
||||
if self.meta.get_field("taxes_and_charges"):
|
||||
self.validate_enabled_taxes_and_charges()
|
||||
self.validate_tax_account_company()
|
||||
|
||||
self.set_taxes_and_charges()
|
||||
|
||||
if self.meta.get_field("currency"):
|
||||
self.calculate_taxes_and_totals()
|
||||
@@ -204,10 +255,6 @@ class AccountsController(TransactionBase):
|
||||
|
||||
self.validate_all_documents_schedule()
|
||||
|
||||
if self.meta.get_field("taxes_and_charges"):
|
||||
self.validate_enabled_taxes_and_charges()
|
||||
self.validate_tax_account_company()
|
||||
|
||||
self.validate_party()
|
||||
self.validate_currency()
|
||||
self.validate_party_account_currency()
|
||||
@@ -224,20 +271,6 @@ class AccountsController(TransactionBase):
|
||||
)
|
||||
)
|
||||
|
||||
if self.get("is_return") and self.get("return_against") and not self.get("is_pos"):
|
||||
if self.get("update_outstanding_for_self"):
|
||||
document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note"
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"We can see {0} is made against {1}. If you want {1}'s outstanding to be updated, uncheck '{2}' checkbox. <br><br> Or you can use {3} tool to reconcile against {1} later."
|
||||
).format(
|
||||
frappe.bold(document_type),
|
||||
get_link_to_form(self.doctype, self.get("return_against")),
|
||||
frappe.bold(_("Update Outstanding for Self")),
|
||||
get_link_to_form("Payment Reconciliation", "Payment Reconciliation"),
|
||||
)
|
||||
)
|
||||
|
||||
pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid"
|
||||
if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
|
||||
self.set_advances()
|
||||
@@ -266,6 +299,8 @@ class AccountsController(TransactionBase):
|
||||
|
||||
self.set_total_in_words()
|
||||
self.set_default_letter_head()
|
||||
self.validate_company_in_accounting_dimension()
|
||||
self.validate_party_address_and_contact()
|
||||
|
||||
def set_default_letter_head(self):
|
||||
if hasattr(self, "letter_head") and not self.letter_head:
|
||||
@@ -345,13 +380,30 @@ class AccountsController(TransactionBase):
|
||||
repost_doc.flags.ignore_links = True
|
||||
repost_doc.save(ignore_permissions=True)
|
||||
|
||||
def _remove_advance_payment_ledger_entries(self):
|
||||
adv = qb.DocType("Advance Payment Ledger Entry")
|
||||
qb.from_(adv).delete().where(adv.voucher_type.eq(self.doctype) & adv.voucher_no.eq(self.name)).run()
|
||||
|
||||
advance_payment_doctypes = frappe.get_hooks("advance_payment_doctypes")
|
||||
|
||||
if self.doctype in advance_payment_doctypes:
|
||||
qb.from_(adv).delete().where(
|
||||
adv.against_voucher_type.eq(self.doctype) & adv.against_voucher_no.eq(self.name)
|
||||
).run()
|
||||
|
||||
def on_trash(self):
|
||||
from erpnext.accounts.utils import delete_exchange_gain_loss_journal
|
||||
|
||||
self._remove_advance_payment_ledger_entries()
|
||||
self._remove_references_in_repost_doctypes()
|
||||
self._remove_references_in_unreconcile()
|
||||
self.remove_serial_and_batch_bundle()
|
||||
|
||||
# delete sl and gl entries on deletion of transaction
|
||||
if frappe.db.get_single_value("Accounts Settings", "delete_linked_ledger_entries"):
|
||||
# delete linked exchange gain/loss journal
|
||||
delete_exchange_gain_loss_journal(self)
|
||||
|
||||
ple = frappe.qb.DocType("Payment Ledger Entry")
|
||||
frappe.qb.from_(ple).delete().where(
|
||||
(ple.voucher_type == self.doctype) & (ple.voucher_no == self.name)
|
||||
@@ -362,13 +414,14 @@ class AccountsController(TransactionBase):
|
||||
== 1
|
||||
)
|
||||
).run()
|
||||
frappe.db.sql(
|
||||
"delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name)
|
||||
)
|
||||
frappe.db.sql(
|
||||
"delete from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s",
|
||||
(self.doctype, self.name),
|
||||
)
|
||||
gle = frappe.qb.DocType("GL Entry")
|
||||
frappe.qb.from_(gle).delete().where(
|
||||
(gle.voucher_type == self.doctype) & (gle.voucher_no == self.name)
|
||||
).run()
|
||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||
frappe.qb.from_(sle).delete().where(
|
||||
(sle.voucher_type == self.doctype) & (sle.voucher_no == self.name)
|
||||
).run()
|
||||
|
||||
def remove_serial_and_batch_bundle(self):
|
||||
bundles = frappe.get_all(
|
||||
@@ -385,15 +438,91 @@ class AccountsController(TransactionBase):
|
||||
for row in batches:
|
||||
frappe.delete_doc("Batch", row.name)
|
||||
|
||||
def validate_company_in_accounting_dimension(self):
|
||||
doc_field = DocType("DocField")
|
||||
accounting_dimension = DocType("Accounting Dimension")
|
||||
dimension_list = (
|
||||
frappe.qb.from_(accounting_dimension)
|
||||
.select(accounting_dimension.document_type)
|
||||
.join(doc_field)
|
||||
.on(doc_field.parent == accounting_dimension.document_type)
|
||||
.where(doc_field.fieldname == "company")
|
||||
).run(as_list=True)
|
||||
|
||||
dimension_list = sum(dimension_list, ["Project", "Cost Center"])
|
||||
self.validate_company(dimension_list)
|
||||
|
||||
for child in self.get_all_children() or []:
|
||||
self.validate_company(dimension_list, child)
|
||||
|
||||
def validate_company(self, dimension_list, child=None):
|
||||
for dimension in dimension_list:
|
||||
if not child:
|
||||
dimension_value = self.get(frappe.scrub(dimension))
|
||||
else:
|
||||
dimension_value = child.get(frappe.scrub(dimension))
|
||||
|
||||
if dimension_value:
|
||||
company = frappe.get_cached_value(dimension, dimension_value, "company")
|
||||
if company and company != self.company:
|
||||
frappe.throw(
|
||||
_("{0}: {1} does not belong to the Company: {2}").format(
|
||||
dimension, frappe.bold(dimension_value), self.company
|
||||
)
|
||||
)
|
||||
|
||||
def validate_party_address_and_contact(self):
|
||||
party_type, party = self.get_party()
|
||||
|
||||
if not (party_type and party):
|
||||
return
|
||||
|
||||
if party_type == "Customer":
|
||||
billing_address, shipping_address = (
|
||||
self.get("customer_address"),
|
||||
self.get("shipping_address_name"),
|
||||
)
|
||||
self.validate_party_address(party, party_type, billing_address, shipping_address)
|
||||
elif party_type == "Supplier":
|
||||
billing_address = self.get("supplier_address")
|
||||
self.validate_party_address(party, party_type, billing_address)
|
||||
|
||||
self.validate_party_contact(party, party_type)
|
||||
|
||||
def validate_party_address(self, party, party_type, billing_address, shipping_address=None):
|
||||
if billing_address or shipping_address:
|
||||
party_address = frappe.get_all(
|
||||
"Dynamic Link",
|
||||
{"link_doctype": party_type, "link_name": party, "parenttype": "Address"},
|
||||
pluck="parent",
|
||||
)
|
||||
if billing_address and billing_address not in party_address:
|
||||
frappe.throw(_("Billing Address does not belong to the {0}").format(party))
|
||||
elif shipping_address and shipping_address not in party_address:
|
||||
frappe.throw(_("Shipping Address does not belong to the {0}").format(party))
|
||||
|
||||
def validate_party_contact(self, party, party_type):
|
||||
if self.get("contact_person"):
|
||||
contact = frappe.get_all(
|
||||
"Dynamic Link",
|
||||
{"link_doctype": party_type, "link_name": party, "parenttype": "Contact"},
|
||||
pluck="parent",
|
||||
)
|
||||
if self.contact_person and self.contact_person not in contact:
|
||||
frappe.throw(_("Contact Person does not belong to the {0}").format(party))
|
||||
|
||||
def validate_return_against_account(self):
|
||||
if self.doctype in ["Sales Invoice", "Purchase Invoice"] and self.is_return and self.return_against:
|
||||
cr_dr_account_field = "debit_to" if self.doctype == "Sales Invoice" else "credit_to"
|
||||
cr_dr_account_label = "Debit To" if self.doctype == "Sales Invoice" else "Credit To"
|
||||
cr_dr_account = self.get(cr_dr_account_field)
|
||||
if frappe.get_value(self.doctype, self.return_against, cr_dr_account_field) != cr_dr_account:
|
||||
original_account = frappe.get_value(self.doctype, self.return_against, cr_dr_account_field)
|
||||
if original_account != self.get(cr_dr_account_field):
|
||||
frappe.throw(
|
||||
_("'{0}' account: '{1}' should match the Return Against Invoice").format(
|
||||
frappe.bold(cr_dr_account_label), frappe.bold(cr_dr_account)
|
||||
_(
|
||||
"Please set {0} to {1}, the same account that was used in the original invoice {2}."
|
||||
).format(
|
||||
frappe.bold(_(self.meta.get_label(cr_dr_account_field), context=self.doctype)),
|
||||
frappe.bold(original_account),
|
||||
frappe.bold(self.return_against),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -443,6 +572,18 @@ class AccountsController(TransactionBase):
|
||||
)
|
||||
|
||||
def validate_invoice_documents_schedule(self):
|
||||
if (
|
||||
self.is_return
|
||||
or (self.doctype == "Purchase Invoice" and self.is_paid)
|
||||
or (self.doctype == "Sales Invoice" and self.is_pos)
|
||||
or self.get("is_opening") == "Yes"
|
||||
):
|
||||
self.payment_terms_template = ""
|
||||
self.payment_schedule = []
|
||||
|
||||
if self.is_return:
|
||||
return
|
||||
|
||||
self.validate_payment_schedule_dates()
|
||||
self.set_due_date()
|
||||
self.set_payment_schedule()
|
||||
@@ -457,7 +598,7 @@ class AccountsController(TransactionBase):
|
||||
self.validate_payment_schedule_amount()
|
||||
|
||||
def validate_all_documents_schedule(self):
|
||||
if self.doctype in ("Sales Invoice", "Purchase Invoice") and not self.is_return:
|
||||
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||
self.validate_invoice_documents_schedule()
|
||||
elif self.doctype in ("Quotation", "Purchase Order", "Sales Order"):
|
||||
self.validate_non_invoice_documents_schedule()
|
||||
@@ -744,9 +885,23 @@ class AccountsController(TransactionBase):
|
||||
ret = get_item_details(args, self, for_validate=for_validate, overwrite_warehouse=False)
|
||||
for fieldname, value in ret.items():
|
||||
if item.meta.get_field(fieldname) and value is not None:
|
||||
if item.get(fieldname) is None or fieldname in force_item_fields:
|
||||
if (
|
||||
item.get(fieldname) is None
|
||||
or fieldname in force_item_fields
|
||||
or (
|
||||
fieldname in ["serial_no", "batch_no"]
|
||||
and item.get("use_serial_batch_fields")
|
||||
)
|
||||
):
|
||||
item.set(fieldname, value)
|
||||
|
||||
if fieldname == "batch_no" and item.batch_no and not item.is_free_item:
|
||||
if ret.get("rate"):
|
||||
item.set("rate", ret.get("rate"))
|
||||
|
||||
if not item.get("price_list_rate") and ret.get("price_list_rate"):
|
||||
item.set("price_list_rate", ret.get("price_list_rate"))
|
||||
|
||||
elif fieldname in ["cost_center", "conversion_factor"] and not item.get(
|
||||
fieldname
|
||||
):
|
||||
@@ -895,6 +1050,12 @@ class AccountsController(TransactionBase):
|
||||
):
|
||||
return True
|
||||
|
||||
def set_taxes_and_charges(self):
|
||||
if frappe.db.get_single_value("Accounts Settings", "add_taxes_from_item_tax_template"):
|
||||
if hasattr(self, "taxes_and_charges") and not self.get("taxes") and not self.get("is_pos"):
|
||||
if tax_master_doctype := self.meta.get_field("taxes_and_charges").options:
|
||||
self.append_taxes_from_master(tax_master_doctype)
|
||||
|
||||
def append_taxes_from_master(self, tax_master_doctype=None):
|
||||
if self.get("taxes_and_charges"):
|
||||
if not tax_master_doctype:
|
||||
@@ -1028,18 +1189,19 @@ class AccountsController(TransactionBase):
|
||||
)
|
||||
|
||||
# Update details in transaction currency
|
||||
gl_dict.update(
|
||||
{
|
||||
"transaction_currency": self.get("currency") or self.company_currency,
|
||||
"transaction_exchange_rate": self.get("conversion_rate", 1),
|
||||
"debit_in_transaction_currency": self.get_value_in_transaction_currency(
|
||||
account_currency, gl_dict, "debit"
|
||||
),
|
||||
"credit_in_transaction_currency": self.get_value_in_transaction_currency(
|
||||
account_currency, gl_dict, "credit"
|
||||
),
|
||||
}
|
||||
)
|
||||
if self.doctype not in ["Purchase Invoice", "Sales Invoice", "Journal Entry", "Payment Entry"]:
|
||||
gl_dict.update(
|
||||
{
|
||||
"transaction_currency": self.get("currency") or self.company_currency,
|
||||
"transaction_exchange_rate": self.get("conversion_rate", 1),
|
||||
"debit_in_transaction_currency": self.get_value_in_transaction_currency(
|
||||
account_currency, gl_dict, "debit"
|
||||
),
|
||||
"credit_in_transaction_currency": self.get_value_in_transaction_currency(
|
||||
account_currency, gl_dict, "credit"
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
if not args.get("against_voucher_type") and self.get("against_voucher_type"):
|
||||
gl_dict.update({"against_voucher_type": self.get("against_voucher_type")})
|
||||
@@ -1056,16 +1218,26 @@ class AccountsController(TransactionBase):
|
||||
"Stock Entry": "stock_entry_type",
|
||||
"Asset Capitalization": "entry_type",
|
||||
}
|
||||
|
||||
for method_name in frappe.get_hooks("voucher_subtypes"):
|
||||
voucher_subtype = frappe.get_attr(method_name)(self)
|
||||
|
||||
if voucher_subtype:
|
||||
return voucher_subtype
|
||||
|
||||
if self.doctype in voucher_subtypes:
|
||||
return self.get(voucher_subtypes[self.doctype])
|
||||
elif self.doctype == "Purchase Receipt" and self.is_return:
|
||||
return "Purchase Return"
|
||||
elif self.doctype == "Delivery Note" and self.is_return:
|
||||
return "Sales Return"
|
||||
elif (self.doctype == "Sales Invoice" and self.is_return) or self.doctype == "Purchase Invoice":
|
||||
elif self.doctype == "Sales Invoice" and self.is_return:
|
||||
return "Credit Note"
|
||||
elif (self.doctype == "Purchase Invoice" and self.is_return) or self.doctype == "Sales Invoice":
|
||||
elif self.doctype == "Sales Invoice" and self.is_debit_note:
|
||||
return "Debit Note"
|
||||
elif self.doctype == "Purchase Invoice" and self.is_return:
|
||||
return "Debit Note"
|
||||
|
||||
return self.doctype
|
||||
|
||||
def get_value_in_transaction_currency(self, account_currency, gl_dict, field):
|
||||
@@ -1113,11 +1285,12 @@ class AccountsController(TransactionBase):
|
||||
def clear_unallocated_advances(self, childtype, parentfield):
|
||||
self.set(parentfield, self.get(parentfield, {"allocated_amount": ["not in", [0, None, ""]]}))
|
||||
|
||||
frappe.db.sql(
|
||||
"""delete from `tab{}` where parentfield={} and parent = {}
|
||||
and allocated_amount = 0""".format(childtype, "%s", "%s"),
|
||||
(parentfield, self.name),
|
||||
)
|
||||
doctype = frappe.qb.DocType(childtype)
|
||||
frappe.qb.from_(doctype).delete().where(
|
||||
(doctype.parentfield == parentfield)
|
||||
& (doctype.parent == self.name)
|
||||
& (doctype.allocated_amount == 0)
|
||||
).run()
|
||||
|
||||
@frappe.whitelist()
|
||||
def apply_shipping_rule(self):
|
||||
@@ -1167,6 +1340,7 @@ class AccountsController(TransactionBase):
|
||||
"advance_amount": flt(d.amount),
|
||||
"allocated_amount": allocated_amount,
|
||||
"ref_exchange_rate": flt(d.exchange_rate), # exchange_rate of advance entry
|
||||
"difference_posting_date": self.posting_date,
|
||||
}
|
||||
if d.get("paid_from"):
|
||||
advance_row["account"] = d.paid_from
|
||||
@@ -1177,7 +1351,9 @@ class AccountsController(TransactionBase):
|
||||
|
||||
def get_advance_entries(self, include_unallocated=True):
|
||||
party_account = []
|
||||
if self.doctype == "Sales Invoice":
|
||||
default_advance_account = None
|
||||
|
||||
if self.doctype in ["Sales Invoice", "POS Invoice"]:
|
||||
party_type = "Customer"
|
||||
party = self.customer
|
||||
amount_field = "credit_in_account_currency"
|
||||
@@ -1192,10 +1368,14 @@ class AccountsController(TransactionBase):
|
||||
order_doctype = "Purchase Order"
|
||||
party_account.append(self.credit_to)
|
||||
|
||||
party_account.extend(
|
||||
get_party_account(party_type, party=party, company=self.company, include_advance=True)
|
||||
party_accounts = get_party_account(
|
||||
party_type, party=party, company=self.company, include_advance=True
|
||||
)
|
||||
|
||||
if party_accounts:
|
||||
party_account.append(party_accounts[0])
|
||||
default_advance_account = party_accounts[1] if len(party_accounts) == 2 else None
|
||||
|
||||
order_list = list(set(d.get(order_field) for d in self.get("items") if d.get(order_field)))
|
||||
|
||||
journal_entries = get_advance_journal_entries(
|
||||
@@ -1203,7 +1383,13 @@ class AccountsController(TransactionBase):
|
||||
)
|
||||
|
||||
payment_entries = get_advance_payment_entries_for_regional(
|
||||
party_type, party, party_account, order_doctype, order_list, include_unallocated
|
||||
party_type,
|
||||
party,
|
||||
party_account,
|
||||
order_doctype,
|
||||
order_list,
|
||||
default_advance_account,
|
||||
include_unallocated,
|
||||
)
|
||||
|
||||
res = journal_entries + payment_entries
|
||||
@@ -1261,7 +1447,11 @@ class AccountsController(TransactionBase):
|
||||
d.exchange_gain_loss = difference
|
||||
|
||||
def make_precision_loss_gl_entry(self, gl_entries):
|
||||
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
|
||||
(
|
||||
round_off_account,
|
||||
round_off_cost_center,
|
||||
round_off_for_opening,
|
||||
) = get_round_off_account_and_cost_center(
|
||||
self.company, "Purchase Invoice", self.name, self.use_company_roundoff_cost_center
|
||||
)
|
||||
|
||||
@@ -1456,7 +1646,6 @@ class AccountsController(TransactionBase):
|
||||
gain_loss_account = frappe.get_cached_value(
|
||||
"Company", self.company, "exchange_gain_loss_account"
|
||||
)
|
||||
|
||||
je = create_gain_loss_journal(
|
||||
self.company,
|
||||
args.get("difference_posting_date") if args else self.posting_date,
|
||||
@@ -1542,6 +1731,7 @@ class AccountsController(TransactionBase):
|
||||
"Company", self.company, "exchange_gain_loss_account"
|
||||
),
|
||||
"exchange_gain_loss": flt(d.get("exchange_gain_loss")),
|
||||
"difference_posting_date": d.get("difference_posting_date"),
|
||||
}
|
||||
)
|
||||
lst.append(args)
|
||||
@@ -1580,6 +1770,7 @@ class AccountsController(TransactionBase):
|
||||
remove_from_bank_transaction,
|
||||
)
|
||||
from erpnext.accounts.utils import (
|
||||
cancel_common_party_journal,
|
||||
cancel_exchange_gain_loss_journal,
|
||||
unlink_ref_doc_from_payment_entries,
|
||||
)
|
||||
@@ -1591,6 +1782,7 @@ class AccountsController(TransactionBase):
|
||||
|
||||
# Cancel Exchange Gain/Loss Journal before unlinking
|
||||
cancel_exchange_gain_loss_journal(self)
|
||||
cancel_common_party_journal(self)
|
||||
|
||||
if frappe.db.get_single_value("Accounts Settings", "unlink_payment_on_cancellation_of_invoice"):
|
||||
unlink_ref_doc_from_payment_entries(self)
|
||||
@@ -1789,22 +1981,22 @@ class AccountsController(TransactionBase):
|
||||
continue
|
||||
|
||||
ref_amt = flt(reference_details.get(item.get(item_ref_dn)), self.precision(based_on, item))
|
||||
based_on_amt = flt(item.get(based_on))
|
||||
|
||||
if not ref_amt:
|
||||
frappe.msgprint(
|
||||
_("System will not check over billing since amount for Item {0} in {1} is zero").format(
|
||||
item.item_code, ref_dt
|
||||
),
|
||||
title=_("Warning"),
|
||||
indicator="orange",
|
||||
)
|
||||
if based_on_amt: # Skip warning for free items
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"System will not check over billing since amount for Item {0} in {1} is zero"
|
||||
).format(item.item_code, ref_dt),
|
||||
title=_("Warning"),
|
||||
indicator="orange",
|
||||
)
|
||||
continue
|
||||
|
||||
already_billed = self.get_billed_amount_for_item(item, item_ref_dn, based_on)
|
||||
|
||||
total_billed_amt = flt(
|
||||
flt(already_billed) + flt(item.get(based_on)), self.precision(based_on, item)
|
||||
)
|
||||
total_billed_amt = flt(flt(already_billed) + based_on_amt, self.precision(based_on, item))
|
||||
|
||||
allowance, item_allowance, global_qty_allowance, global_amount_allowance = get_allowance_for(
|
||||
item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount"
|
||||
@@ -1913,21 +2105,23 @@ class AccountsController(TransactionBase):
|
||||
|
||||
return stock_items
|
||||
|
||||
def set_total_advance_paid(self):
|
||||
ple = frappe.qb.DocType("Payment Ledger Entry")
|
||||
party = self.customer if self.doctype == "Sales Order" else self.supplier
|
||||
def calculate_total_advance_from_ledger(self):
|
||||
adv = frappe.qb.DocType("Advance Payment Ledger Entry")
|
||||
advance = (
|
||||
frappe.qb.from_(ple)
|
||||
.select(ple.account_currency, Abs(Sum(ple.amount_in_account_currency)).as_("amount"))
|
||||
frappe.qb.from_(adv)
|
||||
.select(adv.currency.as_("account_currency"), Abs(Sum(adv.amount)).as_("amount"))
|
||||
.where(
|
||||
(ple.against_voucher_type == self.doctype)
|
||||
& (ple.against_voucher_no == self.name)
|
||||
& (ple.party == party)
|
||||
& (ple.delinked == 0)
|
||||
& (ple.company == self.company)
|
||||
(adv.against_voucher_type == self.doctype)
|
||||
& (adv.against_voucher_no == self.name)
|
||||
& (adv.company == self.company)
|
||||
)
|
||||
.run(as_dict=True)
|
||||
)
|
||||
return advance
|
||||
|
||||
def set_total_advance_paid(self):
|
||||
advance = self.calculate_total_advance_from_ledger()
|
||||
advance_paid, order_total = None, None
|
||||
|
||||
if advance:
|
||||
advance = advance[0]
|
||||
@@ -1960,7 +2154,7 @@ class AccountsController(TransactionBase):
|
||||
).format(formatted_advance_paid, self.name, formatted_order_total)
|
||||
)
|
||||
|
||||
frappe.db.set_value(self.doctype, self.name, "advance_paid", advance_paid)
|
||||
self.db_set("advance_paid", advance_paid)
|
||||
|
||||
@property
|
||||
def company_abbr(self):
|
||||
@@ -2074,11 +2268,9 @@ class AccountsController(TransactionBase):
|
||||
for adv in self.advances:
|
||||
consider_for_total_advance = True
|
||||
if adv.reference_name == linked_doc_name:
|
||||
frappe.db.sql(
|
||||
f"""delete from `tab{self.doctype} Advance`
|
||||
where name = %s""",
|
||||
adv.name,
|
||||
)
|
||||
doctype = frappe.qb.DocType(self.doctype + " Advance")
|
||||
frappe.qb.from_(doctype).delete().where(doctype.name == adv.name).run()
|
||||
|
||||
consider_for_total_advance = False
|
||||
|
||||
if consider_for_total_advance:
|
||||
@@ -2168,7 +2360,9 @@ class AccountsController(TransactionBase):
|
||||
and automatically_fetch_payment_terms
|
||||
and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype)
|
||||
):
|
||||
self.fetch_payment_terms_from_order(po_or_so, doctype)
|
||||
self.fetch_payment_terms_from_order(
|
||||
po_or_so, doctype, grand_total, base_grand_total, automatically_fetch_payment_terms
|
||||
)
|
||||
if self.get("payment_terms_template"):
|
||||
self.ignore_default_payment_terms_template = 1
|
||||
elif self.get("payment_terms_template"):
|
||||
@@ -2204,12 +2398,17 @@ class AccountsController(TransactionBase):
|
||||
base_grand_total * flt(d.invoice_portion) / 100, d.precision("base_payment_amount")
|
||||
)
|
||||
d.outstanding = d.payment_amount
|
||||
d.base_outstanding = flt(
|
||||
d.payment_amount * self.get("conversion_rate"), d.precision("base_outstanding")
|
||||
)
|
||||
elif not d.invoice_portion:
|
||||
d.base_payment_amount = flt(
|
||||
d.payment_amount * self.get("conversion_rate"), d.precision("base_payment_amount")
|
||||
)
|
||||
else:
|
||||
self.fetch_payment_terms_from_order(po_or_so, doctype)
|
||||
self.fetch_payment_terms_from_order(
|
||||
po_or_so, doctype, grand_total, base_grand_total, automatically_fetch_payment_terms
|
||||
)
|
||||
self.ignore_default_payment_terms_template = 1
|
||||
|
||||
def get_order_details(self):
|
||||
@@ -2247,7 +2446,9 @@ class AccountsController(TransactionBase):
|
||||
def linked_order_has_payment_schedule(self, po_or_so):
|
||||
return frappe.get_all("Payment Schedule", filters={"parent": po_or_so})
|
||||
|
||||
def fetch_payment_terms_from_order(self, po_or_so, po_or_so_doctype):
|
||||
def fetch_payment_terms_from_order(
|
||||
self, po_or_so, po_or_so_doctype, grand_total, base_grand_total, automatically_fetch_payment_terms
|
||||
):
|
||||
"""
|
||||
Fetch Payment Terms from Purchase/Sales Order on creating a new Purchase/Sales Invoice.
|
||||
"""
|
||||
@@ -2263,12 +2464,25 @@ class AccountsController(TransactionBase):
|
||||
"invoice_portion": schedule.invoice_portion,
|
||||
"mode_of_payment": schedule.mode_of_payment,
|
||||
"description": schedule.description,
|
||||
"payment_amount": schedule.payment_amount,
|
||||
"base_payment_amount": schedule.base_payment_amount,
|
||||
"outstanding": schedule.outstanding,
|
||||
"paid_amount": schedule.paid_amount,
|
||||
}
|
||||
|
||||
if automatically_fetch_payment_terms:
|
||||
payment_schedule["payment_amount"] = flt(
|
||||
grand_total * flt(payment_schedule["invoice_portion"]) / 100,
|
||||
schedule.precision("payment_amount"),
|
||||
)
|
||||
payment_schedule["base_payment_amount"] = flt(
|
||||
base_grand_total * flt(payment_schedule["invoice_portion"]) / 100,
|
||||
schedule.precision("base_payment_amount"),
|
||||
)
|
||||
payment_schedule["outstanding"] = payment_schedule["payment_amount"]
|
||||
else:
|
||||
payment_schedule["base_payment_amount"] = flt(
|
||||
schedule.base_payment_amount * self.get("conversion_rate"),
|
||||
schedule.precision("base_payment_amount"),
|
||||
)
|
||||
|
||||
if schedule.discount_type == "Percentage":
|
||||
payment_schedule["discount_type"] = schedule.discount_type
|
||||
payment_schedule["discount"] = schedule.discount
|
||||
@@ -2291,6 +2505,7 @@ class AccountsController(TransactionBase):
|
||||
return
|
||||
|
||||
for d in self.get("payment_schedule"):
|
||||
d.validate_from_to_dates("discount_date", "due_date")
|
||||
if self.doctype == "Sales Order" and getdate(d.due_date) < getdate(self.transaction_date):
|
||||
frappe.throw(
|
||||
_("Row {0}: Due Date in the Payment Terms table cannot be before Posting Date").format(
|
||||
@@ -2343,10 +2558,15 @@ class AccountsController(TransactionBase):
|
||||
)
|
||||
|
||||
if (
|
||||
flt(total, self.precision("grand_total")) - flt(grand_total, self.precision("grand_total"))
|
||||
abs(
|
||||
flt(total, self.precision("grand_total"))
|
||||
- flt(grand_total, self.precision("grand_total"))
|
||||
)
|
||||
> 0.1
|
||||
or flt(base_total, self.precision("base_grand_total"))
|
||||
- flt(base_grand_total, self.precision("base_grand_total"))
|
||||
or abs(
|
||||
flt(base_total, self.precision("base_grand_total"))
|
||||
- flt(base_grand_total, self.precision("base_grand_total"))
|
||||
)
|
||||
> 0.1
|
||||
):
|
||||
frappe.throw(
|
||||
@@ -2421,12 +2641,21 @@ class AccountsController(TransactionBase):
|
||||
|
||||
primary_account = get_party_account(primary_party_type, primary_party, self.company)
|
||||
secondary_account = get_party_account(secondary_party_type, secondary_party, self.company)
|
||||
primary_account_currency = get_account_currency(primary_account)
|
||||
secondary_account_currency = get_account_currency(secondary_account)
|
||||
default_currency = erpnext.get_company_currency(self.company)
|
||||
|
||||
# Determine if multi-currency journal entry is needed
|
||||
multi_currency = (
|
||||
primary_account_currency != default_currency or secondary_account_currency != default_currency
|
||||
)
|
||||
|
||||
jv = frappe.new_doc("Journal Entry")
|
||||
jv.voucher_type = "Journal Entry"
|
||||
jv.posting_date = self.posting_date
|
||||
jv.company = self.company
|
||||
jv.remark = f"Adjustment for {self.doctype} {self.name}"
|
||||
jv.is_system_generated = True
|
||||
|
||||
reconcilation_entry = frappe._dict()
|
||||
advance_entry = frappe._dict()
|
||||
@@ -2444,7 +2673,7 @@ class AccountsController(TransactionBase):
|
||||
advance_entry.cost_center = self.cost_center or erpnext.get_default_cost_center(self.company)
|
||||
advance_entry.is_advance = "Yes"
|
||||
|
||||
# update dimesions
|
||||
# Update dimensions
|
||||
dimensions_dict = frappe._dict()
|
||||
active_dimensions = get_dimensions()[0]
|
||||
for dim in active_dimensions:
|
||||
@@ -2453,13 +2682,58 @@ class AccountsController(TransactionBase):
|
||||
reconcilation_entry.update(dimensions_dict)
|
||||
advance_entry.update(dimensions_dict)
|
||||
|
||||
if self.doctype == "Sales Invoice":
|
||||
reconcilation_entry.credit_in_account_currency = self.outstanding_amount
|
||||
advance_entry.debit_in_account_currency = self.outstanding_amount
|
||||
else:
|
||||
advance_entry.credit_in_account_currency = self.outstanding_amount
|
||||
reconcilation_entry.debit_in_account_currency = self.outstanding_amount
|
||||
# Calculate exchange rates if necessary
|
||||
if multi_currency:
|
||||
# Exchange rates for primary and secondary accounts
|
||||
exc_rate_primary_to_default = (
|
||||
1
|
||||
if primary_account_currency == default_currency
|
||||
else get_exchange_rate(primary_account_currency, default_currency, self.posting_date)
|
||||
)
|
||||
exc_rate_secondary_to_default = (
|
||||
1
|
||||
if secondary_account_currency == default_currency
|
||||
else get_exchange_rate(secondary_account_currency, default_currency, self.posting_date)
|
||||
)
|
||||
exc_rate_secondary_to_primary = (
|
||||
1
|
||||
if secondary_account_currency == primary_account_currency
|
||||
else get_exchange_rate(
|
||||
secondary_account_currency, primary_account_currency, self.posting_date
|
||||
)
|
||||
)
|
||||
|
||||
# Convert outstanding amount from secondary to primary account currency, if needed
|
||||
|
||||
os_in_default_currency = self.outstanding_amount * exc_rate_secondary_to_default
|
||||
os_in_primary_currency = self.outstanding_amount * exc_rate_secondary_to_primary
|
||||
|
||||
if self.doctype == "Sales Invoice":
|
||||
# Calculate credit and debit values for reconciliation and advance entries
|
||||
reconcilation_entry.credit_in_account_currency = self.outstanding_amount
|
||||
reconcilation_entry.credit = os_in_default_currency
|
||||
|
||||
advance_entry.debit_in_account_currency = os_in_primary_currency
|
||||
advance_entry.debit = os_in_default_currency
|
||||
else:
|
||||
advance_entry.credit_in_account_currency = os_in_primary_currency
|
||||
advance_entry.credit = os_in_default_currency
|
||||
|
||||
reconcilation_entry.debit_in_account_currency = self.outstanding_amount
|
||||
reconcilation_entry.debit = os_in_default_currency
|
||||
|
||||
# Set exchange rates for entries
|
||||
reconcilation_entry.exchange_rate = exc_rate_secondary_to_default
|
||||
advance_entry.exchange_rate = exc_rate_primary_to_default
|
||||
else:
|
||||
if self.doctype == "Sales Invoice":
|
||||
reconcilation_entry.credit_in_account_currency = self.outstanding_amount
|
||||
advance_entry.debit_in_account_currency = self.outstanding_amount
|
||||
else:
|
||||
advance_entry.credit_in_account_currency = self.outstanding_amount
|
||||
reconcilation_entry.debit_in_account_currency = self.outstanding_amount
|
||||
|
||||
jv.multi_currency = multi_currency
|
||||
jv.append("accounts", reconcilation_entry)
|
||||
jv.append("accounts", advance_entry)
|
||||
|
||||
@@ -2470,12 +2744,17 @@ class AccountsController(TransactionBase):
|
||||
default_currency = erpnext.get_company_currency(self.company)
|
||||
if not default_currency:
|
||||
throw(_("Please enter default currency in Company Master"))
|
||||
if (
|
||||
(self.currency == default_currency and flt(self.conversion_rate) != 1.00)
|
||||
or not self.conversion_rate
|
||||
or (self.currency != default_currency and flt(self.conversion_rate) == 1.00)
|
||||
):
|
||||
throw(_("Conversion rate cannot be 0 or 1"))
|
||||
|
||||
if not self.conversion_rate:
|
||||
throw(_("Conversion rate cannot be 0"))
|
||||
|
||||
if self.currency == default_currency and flt(self.conversion_rate) != 1.00:
|
||||
throw(_("Conversion rate must be 1.00 if document currency is same as company currency"))
|
||||
|
||||
if self.currency != default_currency and flt(self.conversion_rate) == 1.00:
|
||||
frappe.msgprint(
|
||||
_("Conversion rate is 1.00, but document currency is different from company currency")
|
||||
)
|
||||
|
||||
def check_finance_books(self, item, asset):
|
||||
if (
|
||||
@@ -2517,6 +2796,72 @@ class AccountsController(TransactionBase):
|
||||
repost_ledger.insert()
|
||||
repost_ledger.submit()
|
||||
|
||||
def get_advance_payment_doctypes(self) -> list:
|
||||
return frappe.get_hooks("advance_payment_doctypes")
|
||||
|
||||
def make_advance_payment_ledger_for_journal(self):
|
||||
advance_payment_doctypes = self.get_advance_payment_doctypes()
|
||||
advance_doctype_references = [
|
||||
x for x in self.accounts if x.reference_type in advance_payment_doctypes
|
||||
]
|
||||
|
||||
for x in advance_doctype_references:
|
||||
# Looking for payments
|
||||
dr_or_cr = (
|
||||
"credit_in_account_currency"
|
||||
if x.account_type == "Receivable"
|
||||
else "debit_in_account_currency"
|
||||
)
|
||||
|
||||
amount = x.get(dr_or_cr)
|
||||
if amount > 0:
|
||||
doc = frappe.new_doc("Advance Payment Ledger Entry")
|
||||
doc.company = self.company
|
||||
doc.voucher_type = self.doctype
|
||||
doc.voucher_no = self.name
|
||||
doc.against_voucher_type = x.reference_type
|
||||
doc.against_voucher_no = x.reference_name
|
||||
doc.amount = amount if self.docstatus == 1 else -1 * amount
|
||||
doc.event = "Submit" if self.docstatus == 1 else "Cancel"
|
||||
doc.currency = x.account_currency
|
||||
doc.flags.ignore_permissions = 1
|
||||
doc.save()
|
||||
|
||||
def make_advance_payment_ledger_for_payment(self):
|
||||
advance_payment_doctypes = self.get_advance_payment_doctypes()
|
||||
advance_doctype_references = [
|
||||
x for x in self.references if x.reference_doctype in advance_payment_doctypes
|
||||
]
|
||||
currency = (
|
||||
self.paid_from_account_currency
|
||||
if self.payment_type == "Receive"
|
||||
else self.paid_to_account_currency
|
||||
)
|
||||
for x in advance_doctype_references:
|
||||
doc = frappe.new_doc("Advance Payment Ledger Entry")
|
||||
doc.company = self.company
|
||||
doc.voucher_type = self.doctype
|
||||
doc.voucher_no = self.name
|
||||
doc.against_voucher_type = x.reference_doctype
|
||||
doc.against_voucher_no = x.reference_name
|
||||
doc.amount = x.allocated_amount if self.docstatus == 1 else -1 * x.allocated_amount
|
||||
doc.currency = currency
|
||||
doc.event = "Submit" if self.docstatus == 1 else "Cancel"
|
||||
doc.flags.ignore_permissions = 1
|
||||
doc.save()
|
||||
|
||||
def make_advance_payment_ledger_entries(self):
|
||||
if self.docstatus != 0:
|
||||
if self.doctype == "Journal Entry":
|
||||
self.make_advance_payment_ledger_for_journal()
|
||||
elif self.doctype == "Payment Entry":
|
||||
self.make_advance_payment_ledger_for_payment()
|
||||
|
||||
def set_transaction_currency_and_rate_in_gl_map(self, gl_entries):
|
||||
for x in gl_entries:
|
||||
x["transaction_currency"] = self.currency
|
||||
x["transaction_exchange_rate"] = self.get("conversion_rate") or 1
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_tax_rate(account_head):
|
||||
@@ -2759,6 +3104,7 @@ def get_advance_payment_entries(
|
||||
party_account,
|
||||
order_doctype,
|
||||
order_list=None,
|
||||
default_advance_account=None,
|
||||
include_unallocated=True,
|
||||
against_all_orders=False,
|
||||
limit=None,
|
||||
@@ -2772,6 +3118,7 @@ def get_advance_payment_entries(
|
||||
party_type,
|
||||
party,
|
||||
party_account,
|
||||
default_advance_account,
|
||||
limit,
|
||||
condition,
|
||||
)
|
||||
@@ -2782,6 +3129,7 @@ def get_advance_payment_entries(
|
||||
(payment_ref.allocated_amount).as_("amount"),
|
||||
(payment_ref.name).as_("reference_row"),
|
||||
(payment_ref.reference_name).as_("against_order"),
|
||||
(payment_entry.book_advance_payments_in_separate_party_account),
|
||||
)
|
||||
|
||||
q = q.where(payment_ref.reference_doctype == order_doctype)
|
||||
@@ -2795,6 +3143,7 @@ def get_advance_payment_entries(
|
||||
party_type,
|
||||
party,
|
||||
party_account,
|
||||
default_advance_account,
|
||||
limit,
|
||||
condition,
|
||||
)
|
||||
@@ -2810,6 +3159,7 @@ def get_common_query(
|
||||
party_type,
|
||||
party,
|
||||
party_account,
|
||||
default_advance_account,
|
||||
limit,
|
||||
condition,
|
||||
):
|
||||
@@ -2824,6 +3174,7 @@ def get_common_query(
|
||||
(payment_entry.name).as_("reference_name"),
|
||||
payment_entry.posting_date,
|
||||
(payment_entry.remarks).as_("remarks"),
|
||||
(payment_entry.book_advance_payments_in_separate_party_account),
|
||||
)
|
||||
.where(payment_entry.payment_type == payment_type)
|
||||
.where(payment_entry.party_type == party_type)
|
||||
@@ -2831,14 +3182,22 @@ def get_common_query(
|
||||
.where(payment_entry.docstatus == 1)
|
||||
)
|
||||
|
||||
if payment_type == "Receive":
|
||||
q = q.select((payment_entry.paid_from_account_currency).as_("currency"))
|
||||
q = q.select(payment_entry.paid_from)
|
||||
q = q.where(payment_entry.paid_from.isin(party_account))
|
||||
field = "paid_from" if payment_type == "Receive" else "paid_to"
|
||||
|
||||
q = q.select((payment_entry[f"{field}_account_currency"]).as_("currency"))
|
||||
q = q.select(payment_entry[field])
|
||||
account_condition = payment_entry[field].isin(party_account)
|
||||
if default_advance_account:
|
||||
q = q.where(
|
||||
account_condition
|
||||
| (
|
||||
(payment_entry[field] == default_advance_account)
|
||||
& (payment_entry.book_advance_payments_in_separate_party_account == 1)
|
||||
)
|
||||
)
|
||||
|
||||
else:
|
||||
q = q.select((payment_entry.paid_to_account_currency).as_("currency"))
|
||||
q = q.select(payment_entry.paid_to)
|
||||
q = q.where(payment_entry.paid_to.isin(party_account))
|
||||
q = q.where(account_condition)
|
||||
|
||||
if payment_type == "Receive":
|
||||
q = q.select((payment_entry.source_exchange_rate).as_("exchange_rate"))
|
||||
@@ -3032,6 +3391,7 @@ def set_child_tax_template_and_map(item, child_item, parent_doc):
|
||||
"posting_date": parent_doc.transaction_date,
|
||||
"tax_category": parent_doc.get("tax_category"),
|
||||
"company": parent_doc.get("company"),
|
||||
"base_net_rate": item.get("base_net_rate"),
|
||||
}
|
||||
|
||||
child_item.item_tax_template = _get_item_tax_template(args, item.taxes)
|
||||
@@ -3296,7 +3656,6 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
items_added_or_removed = False # updated to true if any new item is added or removed
|
||||
any_conversion_factor_changed = False
|
||||
|
||||
sales_doctypes = ["Sales Order", "Sales Invoice", "Delivery Note", "Quotation"]
|
||||
parent = frappe.get_doc(parent_doctype, parent_doctype_name)
|
||||
|
||||
check_doc_permissions(parent, "write")
|
||||
@@ -3407,30 +3766,29 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
if d.get("schedule_date") and parent_doctype == "Purchase Order":
|
||||
child_item.schedule_date = d.get("schedule_date")
|
||||
|
||||
if d.get("bom_no") and parent_doctype == "Sales Order":
|
||||
child_item.bom_no = d.get("bom_no")
|
||||
|
||||
if flt(child_item.price_list_rate):
|
||||
if flt(child_item.rate) > flt(child_item.price_list_rate):
|
||||
# if rate is greater than price_list_rate, set margin
|
||||
# or set discount
|
||||
child_item.discount_percentage = 0
|
||||
|
||||
if parent_doctype in sales_doctypes:
|
||||
child_item.margin_type = "Amount"
|
||||
child_item.margin_rate_or_amount = flt(
|
||||
child_item.rate - child_item.price_list_rate,
|
||||
child_item.precision("margin_rate_or_amount"),
|
||||
)
|
||||
child_item.rate_with_margin = child_item.rate
|
||||
child_item.margin_type = "Amount"
|
||||
child_item.margin_rate_or_amount = flt(
|
||||
child_item.rate - child_item.price_list_rate,
|
||||
child_item.precision("margin_rate_or_amount"),
|
||||
)
|
||||
child_item.rate_with_margin = child_item.rate
|
||||
else:
|
||||
child_item.discount_percentage = flt(
|
||||
(1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0,
|
||||
child_item.precision("discount_percentage"),
|
||||
)
|
||||
child_item.discount_amount = flt(child_item.price_list_rate) - flt(child_item.rate)
|
||||
|
||||
if parent_doctype in sales_doctypes:
|
||||
child_item.margin_type = ""
|
||||
child_item.margin_rate_or_amount = 0
|
||||
child_item.rate_with_margin = 0
|
||||
child_item.margin_type = ""
|
||||
child_item.margin_rate_or_amount = 0
|
||||
child_item.rate_with_margin = 0
|
||||
|
||||
child_item.flags.ignore_validate_update_after_submit = True
|
||||
if new_child_flag:
|
||||
@@ -3492,6 +3850,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
).format(frappe.bold(parent.name))
|
||||
)
|
||||
else: # Sales Order
|
||||
parent.validate_for_duplicate_items()
|
||||
parent.validate_warehouse()
|
||||
parent.update_reserved_qty()
|
||||
parent.update_project()
|
||||
@@ -3505,6 +3864,9 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
parent.update_billing_percentage()
|
||||
parent.set_status()
|
||||
|
||||
parent.validate_uom_is_integer("uom", "qty")
|
||||
parent.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||
|
||||
# Cancel and Recreate Stock Reservation Entries.
|
||||
if parent_doctype == "Sales Order":
|
||||
from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
|
||||
|
||||
Reference in New Issue
Block a user