mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-07 07:20:26 +00:00
* fix(Sales Order): only show permitted actions (cherry picked from commitc29d955371) * fix(Delivery Note): only show permitted actions (cherry picked from commit418bdc1dcc) * fix: do not show zero balance stock items in stock balance report (backport #41958) (#41961) fix: do not show zero balance stock in stock balance (cherry picked from commit7f7b363d48) Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com> * fix: add string for translation (backport #41903) (#41963) fix: add string for translation (#41903) fix: add string for translation (cherry picked from commitf28c692dca) Co-authored-by: mahsem <137205921+mahsem@users.noreply.github.com> * refactor: remove use of can_create for Payment Request (#41647) (cherry picked from commit47bc5691a1) * fix: move condition for shipment * fix: incorrect discount on other item When discount is applied on other item, don't update `discount_amount` as the amount is calculated for current item (cherry picked from commit654764e398) * fix: incorrect against_account upon reposting (cherry picked from commit20c4098399) * fix: decimal issue in pick list (backport #41972) (#41982) fix: decimal issue in pick list (#41972) (cherry picked from commit21adc7b63e) Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com> * refactor: renamed number of depreciations booked to opening booked de… (#41515) * refactor: renamed number of depreciations booked to opening booked depreciations * feat: introduced new field for showing total number of booked depreciations * fix: reload asset when creating asset depreciation * chore: added nosemgrep for security checks * feat: default account head for operating cost (backport #41985) (#41987) * feat: default account head for operating cost (#41985) (cherry picked from commitfd7666a029) # Conflicts: # erpnext/manufacturing/doctype/bom/bom.py # erpnext/setup/doctype/company/company.json * chore: fix conflicts * chore: fix conflicts * chore: fix conflicts * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com> * perf: dont run queries unnecessarily, improved filters (#41993) * perf: dont run queries unnecessarily, improved filters * perf: dont run query if `in` filter is empty (cherry picked from commitac6d85aed6) * chore: remove validation on payment entry (cherry picked from commite7740033ca) * refactor: convert amount to base currency for advances (cherry picked from commitc9ede1ffbe) * refactor: for advances uses the party account in references table (cherry picked from commit7dce6e03c7) * refactor(test): simpler create_account helper method (cherry picked from commit475e0ddeee) * test: exc gain/loss booking on advances under asset/liability (cherry picked from commit827d67d02f) * test: advance against purchase invoice (cherry picked from commit90c84822d0) * refactor: validation to force accounts to be on same currency (cherry picked from commit0f0b4d88bc) * refactor: validation in customer group (cherry picked from commit4f9a228175) * refactor: validation in Supplier Group (cherry picked from commit107b614518) * refactor: better error messages (cherry picked from commit83ff94b9b8) * chore: fix test data (cherry picked from commit07d59443b7) * chore: remove dead code (cherry picked from commit7e318c0132) * fix(test): incorrect field for customer default billing currency (cherry picked from commitc696d13a5e) * refactor(test): enfore use of customer/supplier master While using advance accounts in foreign currency, always use Customer/Supplier master to maintain them (cherry picked from commit64e63887be) * refactor(test): make and use a different party for subscription (cherry picked from commit3fabf4aaa4) * fix: pricing rule with and without 'apply multiple' and priority Either all of the pricing rules identified for an item should have 'apply multiple' enabled. If not, Priority is applied and only the highest priority is applied (cherry picked from commit5e875b238c) * test: priority takes effect on with and without apply multiple (cherry picked from commitefebc3662e) * feat: accounting dimension filters in gp report (cherry picked from commitd165638bbb) * feat(gp): group by cost center (cherry picked from commite26bc17c75) * fix: Wrong Delete Batch on Purchase Receipt (backport #42007) (#42012) fix: Wrong Delete Batch on Purchase Receipt (#42007) (cherry picked from commitd50487ce53) Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com> * fix: incorrect Difference Amount (backport #42008) (#42013) fix: incorrect Difference Amount (#42008) (cherry picked from commit7d91c6cbd5) Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com> * fix: valuation rate for the legacy batches (backport #42011) (#42020) fix: valuation rate for the legacy batches (#42011) (cherry picked from commit9ab333d105) Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com> * fix: timeout while cancelling LCV (backport #42030) (backport #42031) (#42032) fix: timeout while cancelling LCV (backport #42030) (#42031) fix: timeout while cancelling LCV (#42030) fix: timeout while canelling LCV (cherry picked from commit21bf7fd1f8) Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com> (cherry picked from commit2e76b9f9db) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> * fix: Stock Reservation Entry was not getting created (backport #42033) (#42035) fix: Stock Reservation Entry was not getting created (#42033) (cherry picked from commit1a9899b32b) Co-authored-by: Poorvi-R-Bhat <poorvi.r.bhat@gmail.com> * fix: manufacturing date issue in the batch (backport #42034) (#42037) * fix: manufacturing date issue in the batch (#42034) (cherry picked from commiteca3e02f8d) # Conflicts: # erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com> * fix: fixed asset value in Fixed Asset Register (backport #41930) (#42027) fix: fixed asset value in Fixed Asset Register (#41930) (cherry picked from commit1c643a0ead) Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> * feat: Turkish Chart Of Accounts (backport #41756) (#42028) * feat: Create Turkish Chart Of Accounts (cherry picked from commit5c8ea86a3f) * feat: Create Turkish Chart Of Accounts (cherry picked from commitb401ba2c26) --------- Co-authored-by: fzozyurt <fzozyurt@outlook.com> * perf: code optimization to handle large asset creation (backport #42018) (#42025) perf: code optimization to handle large asset creation (#42018) (cherry picked from commit5738d93f95) Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> * fix: incorrect time period in asset depreciation schedule (backport #41805) (#42043) fix: incorrect time period in asset depreciation schedule (#41805) * fix(wip): depreciation calculation for existing asset * fix(wip): added validation for incorrect depreciation period * fix: depreciation schedule time period issue for existing asset * chore: run pre-commit checks and apply fixes * style: apply formatting changes * style: made some necessary changes * chore: modified test (cherry picked from commit625f16dee0) Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> * chore: patch to enable total number of booked depreciations field (backport #41940) (#42042) * chore: patch to enable total number of booked depreciations field (#41940) * chore: patch to enable total number of booked depreciations field * fix: conflict resolved * refactor: replaced fb_row.db_set with set_value (cherry picked from commit5fdd1d3278) # Conflicts: # erpnext/patches.txt * fix: resolved conflicts * fix: removed unmerged patches --------- Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> * fix: lead status filter (backport #41816) (#42046) fix: lead status filter (#41816) (cherry picked from commit8ae2b8ff8c) Co-authored-by: Nihantra C. Patel <141945075+Nihantra-Patel@users.noreply.github.com> * fix: unhide serial no field (backport #42045) (#42047) * fix: unhide serial no field (#42045) (cherry picked from commit80c6981cfa) # Conflicts: # erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json * fix: resolved conflicts --------- Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> --------- Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com> Co-authored-by: mahsem <137205921+mahsem@users.noreply.github.com> Co-authored-by: ruthra kumar <ruthra@erpnext.com> Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Co-authored-by: Sagar Vora <sagar@resilient.tech> Co-authored-by: Dany Robert <danyrt@wahni.com> Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com> Co-authored-by: Poorvi-R-Bhat <poorvi.r.bhat@gmail.com> Co-authored-by: fzozyurt <fzozyurt@outlook.com> Co-authored-by: Nihantra C. Patel <141945075+Nihantra-Patel@users.noreply.github.com>
289 lines
9.2 KiB
Python
289 lines
9.2 KiB
Python
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
|
# License: GNU General Public License v3. See license.txt
|
|
|
|
|
|
import frappe
|
|
import frappe.share
|
|
from frappe import _
|
|
from frappe.utils import cint, flt, get_time, now_datetime
|
|
|
|
from erpnext.controllers.status_updater import StatusUpdater
|
|
|
|
|
|
class UOMMustBeIntegerError(frappe.ValidationError):
|
|
pass
|
|
|
|
|
|
class TransactionBase(StatusUpdater):
|
|
def validate_posting_time(self):
|
|
# set Edit Posting Date and Time to 1 while data import
|
|
if frappe.flags.in_import and self.posting_date:
|
|
self.set_posting_time = 1
|
|
|
|
if not getattr(self, "set_posting_time", None):
|
|
now = now_datetime()
|
|
self.posting_date = now.strftime("%Y-%m-%d")
|
|
self.posting_time = now.strftime("%H:%M:%S.%f")
|
|
elif self.posting_time:
|
|
try:
|
|
get_time(self.posting_time)
|
|
except ValueError:
|
|
frappe.throw(_("Invalid Posting Time"))
|
|
|
|
def validate_uom_is_integer(self, uom_field, qty_fields, child_dt=None):
|
|
validate_uom_is_integer(self, uom_field, qty_fields, child_dt)
|
|
|
|
def validate_with_previous_doc(self, ref):
|
|
self.exclude_fields = ["conversion_factor", "uom"] if self.get("is_return") else []
|
|
|
|
for key, val in ref.items():
|
|
is_child = val.get("is_child_table")
|
|
ref_doc = {}
|
|
item_ref_dn = []
|
|
for d in self.get_all_children(self.doctype + " Item"):
|
|
ref_dn = d.get(val["ref_dn_field"])
|
|
if ref_dn:
|
|
if is_child:
|
|
self.compare_values({key: [ref_dn]}, val["compare_fields"], d)
|
|
if ref_dn not in item_ref_dn:
|
|
item_ref_dn.append(ref_dn)
|
|
elif not val.get("allow_duplicate_prev_row_id"):
|
|
frappe.throw(_("Duplicate row {0} with same {1}").format(d.idx, key))
|
|
elif ref_dn:
|
|
ref_doc.setdefault(key, [])
|
|
if ref_dn not in ref_doc[key]:
|
|
ref_doc[key].append(ref_dn)
|
|
if ref_doc:
|
|
self.compare_values(ref_doc, val["compare_fields"])
|
|
|
|
def compare_values(self, ref_doc, fields, doc=None):
|
|
for reference_doctype, ref_dn_list in ref_doc.items():
|
|
prev_doc_detail_map = self.get_prev_doc_reference_details(ref_dn_list, reference_doctype, fields)
|
|
for reference_name in ref_dn_list:
|
|
prevdoc_values = prev_doc_detail_map.get(reference_name)
|
|
if not prevdoc_values:
|
|
frappe.throw(_("Invalid reference {0} {1}").format(reference_doctype, reference_name))
|
|
|
|
for field, condition in fields:
|
|
if prevdoc_values[field] is not None and field not in self.exclude_fields:
|
|
self.validate_value(field, condition, prevdoc_values[field], doc)
|
|
|
|
def get_prev_doc_reference_details(self, reference_names, reference_doctype, fields):
|
|
prev_doc_detail_map = {}
|
|
details = frappe.get_all(
|
|
reference_doctype,
|
|
filters={"name": ("in", reference_names)},
|
|
fields=["name"] + [d[0] for d in fields],
|
|
)
|
|
|
|
for d in details:
|
|
prev_doc_detail_map.setdefault(d.name, d)
|
|
|
|
return prev_doc_detail_map
|
|
|
|
def validate_rate_with_reference_doc(self, ref_details):
|
|
if self.get("is_internal_supplier"):
|
|
return
|
|
|
|
buying_doctypes = ["Purchase Order", "Purchase Invoice", "Purchase Receipt"]
|
|
|
|
if self.doctype in buying_doctypes:
|
|
action, role_allowed_to_override = frappe.get_cached_value(
|
|
"Buying Settings", "None", ["maintain_same_rate_action", "role_to_override_stop_action"]
|
|
)
|
|
else:
|
|
action, role_allowed_to_override = frappe.get_cached_value(
|
|
"Selling Settings", "None", ["maintain_same_rate_action", "role_to_override_stop_action"]
|
|
)
|
|
|
|
stop_actions = []
|
|
for ref_dt, ref_dn_field, ref_link_field in ref_details:
|
|
reference_names = [d.get(ref_link_field) for d in self.get("items") if d.get(ref_link_field)]
|
|
reference_details = self.get_reference_details(reference_names, ref_dt + " Item")
|
|
for d in self.get("items"):
|
|
if d.get(ref_link_field):
|
|
ref_rate = reference_details.get(d.get(ref_link_field))
|
|
|
|
if abs(flt(d.rate - ref_rate, d.precision("rate"))) >= 0.01:
|
|
if action == "Stop":
|
|
if role_allowed_to_override not in frappe.get_roles():
|
|
stop_actions.append(
|
|
_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4})").format(
|
|
d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate
|
|
)
|
|
)
|
|
else:
|
|
frappe.msgprint(
|
|
_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4})").format(
|
|
d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate
|
|
),
|
|
title=_("Warning"),
|
|
indicator="orange",
|
|
)
|
|
if stop_actions:
|
|
frappe.throw(stop_actions, as_list=True)
|
|
|
|
def get_reference_details(self, reference_names, reference_doctype):
|
|
return frappe._dict(
|
|
frappe.get_all(
|
|
reference_doctype,
|
|
filters={"name": ("in", reference_names)},
|
|
fields=["name", "rate"],
|
|
as_list=1,
|
|
)
|
|
)
|
|
|
|
def get_link_filters(self, for_doctype):
|
|
if hasattr(self, "prev_link_mapper") and self.prev_link_mapper.get(for_doctype):
|
|
fieldname = self.prev_link_mapper[for_doctype]["fieldname"]
|
|
|
|
values = filter(None, tuple(item.as_dict()[fieldname] for item in self.items))
|
|
|
|
if values:
|
|
ret = {for_doctype: {"filters": [[for_doctype, "name", "in", values]]}}
|
|
else:
|
|
ret = None
|
|
else:
|
|
ret = None
|
|
|
|
return ret
|
|
|
|
def reset_default_field_value(self, default_field: str, child_table: str, child_table_field: str):
|
|
"""Reset "Set default X" fields on forms to avoid confusion.
|
|
|
|
example:
|
|
doc = {
|
|
"set_from_warehouse": "Warehouse A",
|
|
"items": [{"from_warehouse": "warehouse B"}, {"from_warehouse": "warehouse A"}],
|
|
}
|
|
Since this has dissimilar values in child table, the default field will be erased.
|
|
|
|
doc.reset_default_field_value("set_from_warehouse", "items", "from_warehouse")
|
|
"""
|
|
child_table_values = set()
|
|
|
|
for row in self.get(child_table):
|
|
child_table_values.add(row.get(child_table_field))
|
|
|
|
if len(child_table_values) > 1:
|
|
self.set(default_field, None)
|
|
|
|
def validate_currency_for_receivable_payable_and_advance_account(self):
|
|
if self.doctype in ["Customer", "Supplier"]:
|
|
account_type = "Receivable" if self.doctype == "Customer" else "Payable"
|
|
for x in self.accounts:
|
|
company_default_currency = frappe.get_cached_value("Company", x.company, "default_currency")
|
|
receivable_payable_account_currency = None
|
|
advance_account_currency = None
|
|
|
|
if x.account:
|
|
receivable_payable_account_currency = frappe.get_cached_value(
|
|
"Account", x.account, "account_currency"
|
|
)
|
|
|
|
if x.advance_account:
|
|
advance_account_currency = frappe.get_cached_value(
|
|
"Account", x.advance_account, "account_currency"
|
|
)
|
|
if receivable_payable_account_currency and (
|
|
receivable_payable_account_currency != self.default_currency
|
|
and receivable_payable_account_currency != company_default_currency
|
|
):
|
|
frappe.throw(
|
|
_(
|
|
"{0} Account: {1} ({2}) must be in either customer billing currency: {3} or Company default currency: {4}"
|
|
).format(
|
|
account_type,
|
|
frappe.bold(x.account),
|
|
frappe.bold(receivable_payable_account_currency),
|
|
frappe.bold(self.default_currency),
|
|
frappe.bold(company_default_currency),
|
|
)
|
|
)
|
|
|
|
if advance_account_currency and (
|
|
advance_account_currency != self.default_currency
|
|
and advance_account_currency != company_default_currency
|
|
):
|
|
frappe.throw(
|
|
_(
|
|
"Advance Account: {0} must be in either customer billing currency: {1} or Company default currency: {2}"
|
|
).format(
|
|
frappe.bold(x.advance_account),
|
|
frappe.bold(self.default_currency),
|
|
frappe.bold(company_default_currency),
|
|
)
|
|
)
|
|
|
|
if (
|
|
receivable_payable_account_currency
|
|
and advance_account_currency
|
|
and receivable_payable_account_currency != advance_account_currency
|
|
):
|
|
frappe.throw(
|
|
_(
|
|
"Both {0} Account: {1} and Advance Account: {2} must be of same currency for company: {3}"
|
|
).format(
|
|
account_type,
|
|
frappe.bold(x.account),
|
|
frappe.bold(x.advance_account),
|
|
frappe.bold(x.company),
|
|
)
|
|
)
|
|
|
|
|
|
def delete_events(ref_type, ref_name):
|
|
events = (
|
|
frappe.db.sql_list(
|
|
""" SELECT
|
|
distinct `tabEvent`.name
|
|
from
|
|
`tabEvent`, `tabEvent Participants`
|
|
where
|
|
`tabEvent`.name = `tabEvent Participants`.parent
|
|
and `tabEvent Participants`.reference_doctype = %s
|
|
and `tabEvent Participants`.reference_docname = %s
|
|
""",
|
|
(ref_type, ref_name),
|
|
)
|
|
or []
|
|
)
|
|
|
|
if events:
|
|
frappe.delete_doc("Event", events, for_reload=True)
|
|
|
|
|
|
def validate_uom_is_integer(doc, uom_field, qty_fields, child_dt=None):
|
|
if isinstance(qty_fields, str):
|
|
qty_fields = [qty_fields]
|
|
|
|
distinct_uoms = list(set(d.get(uom_field) for d in doc.get_all_children()))
|
|
integer_uoms = list(
|
|
filter(
|
|
lambda uom: frappe.db.get_value("UOM", uom, "must_be_whole_number", cache=True) or None,
|
|
distinct_uoms,
|
|
)
|
|
)
|
|
|
|
if not integer_uoms:
|
|
return
|
|
|
|
for d in doc.get_all_children(parenttype=child_dt):
|
|
if d.get(uom_field) in integer_uoms:
|
|
for f in qty_fields:
|
|
qty = d.get(f)
|
|
if qty:
|
|
precision = d.precision(f)
|
|
if abs(cint(qty) - flt(qty, precision)) > 0.0000001:
|
|
frappe.throw(
|
|
_(
|
|
"Row {1}: Quantity ({0}) cannot be a fraction. To allow this, disable '{2}' in UOM {3}."
|
|
).format(
|
|
flt(qty, precision),
|
|
d.idx,
|
|
frappe.bold(_("Must be Whole Number")),
|
|
frappe.bold(d.get(uom_field)),
|
|
),
|
|
UOMMustBeIntegerError,
|
|
)
|