Skip to content

Commit 187ebaa

Browse files
authored
Merge pull request #46582 from frappe/version-15-hotfix
chore: release v15
2 parents 1ec971f + a2cb9c1 commit 187ebaa

44 files changed

Lines changed: 381 additions & 126 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ def on_cancel(self):
139139
self.cancel_gl_entries()
140140

141141
def make_gl_entries(self):
142-
if self.get_gle_count_in_selected_period() > 5000:
142+
if frappe.db.estimate_count("GL Entry") > 100_000:
143143
frappe.enqueue(
144144
process_gl_and_closing_entries,
145145
doc=self,
@@ -154,16 +154,6 @@ def make_gl_entries(self):
154154
else:
155155
process_gl_and_closing_entries(self)
156156

157-
def get_gle_count_in_selected_period(self):
158-
return frappe.db.count(
159-
"GL Entry",
160-
{
161-
"posting_date": ["between", [self.period_start_date, self.period_end_date]],
162-
"company": self.company,
163-
"is_cancelled": 0,
164-
},
165-
)
166-
167157
def get_pcv_gl_entries(self):
168158
self.pl_accounts_reverse_gle = []
169159
self.closing_account_gle = []

erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1318,7 +1318,7 @@ def make_stock_adjustment_entry(self, gl_entries, item, voucher_wise_stock_value
13181318
"account": cost_of_goods_sold_account,
13191319
"against": item.expense_account,
13201320
"debit": stock_adjustment_amt,
1321-
"debit_in_transaction_currency": item.net_amount,
1321+
"debit_in_transaction_currency": stock_adjustment_amt / self.conversion_rate,
13221322
"remarks": self.get("remarks") or _("Stock Adjustment"),
13231323
"cost_center": item.cost_center,
13241324
"project": item.project or self.project,
@@ -1330,6 +1330,38 @@ def make_stock_adjustment_entry(self, gl_entries, item, voucher_wise_stock_value
13301330

13311331
warehouse_debit_amount = stock_amount
13321332

1333+
elif self.is_return and self.update_stock and self.is_internal_supplier and warehouse_debit_amount:
1334+
net_rate = item.base_net_amount
1335+
if item.sales_incoming_rate: # for internal transfer
1336+
net_rate = item.qty * item.sales_incoming_rate
1337+
1338+
stock_amount = (
1339+
net_rate
1340+
+ item.item_tax_amount
1341+
+ flt(item.landed_cost_voucher_amount)
1342+
+ flt(item.get("amount_difference_with_purchase_invoice"))
1343+
)
1344+
1345+
if flt(stock_amount, net_amt_precision) != flt(warehouse_debit_amount, net_amt_precision):
1346+
cost_of_goods_sold_account = self.get_company_default("default_expense_account")
1347+
stock_adjustment_amt = stock_amount - warehouse_debit_amount
1348+
1349+
gl_entries.append(
1350+
self.get_gl_dict(
1351+
{
1352+
"account": cost_of_goods_sold_account,
1353+
"against": item.expense_account,
1354+
"debit": stock_adjustment_amt,
1355+
"debit_in_transaction_currency": stock_adjustment_amt / self.conversion_rate,
1356+
"remarks": self.get("remarks") or _("Stock Adjustment"),
1357+
"cost_center": item.cost_center,
1358+
"project": item.project or self.project,
1359+
},
1360+
account_currency,
1361+
item=item,
1362+
)
1363+
)
1364+
13331365
return warehouse_debit_amount
13341366

13351367
def make_tax_gl_entries(self, gl_entries):

erpnext/accounts/doctype/sales_invoice/sales_invoice.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
"allow_import": 1,
44
"autoname": "naming_series:",
55
"creation": "2022-01-25 10:29:57.771398",
6-
"default_print_format": "Sales Invoice Print",
76
"doctype": "DocType",
87
"engine": "InnoDB",
98
"field_order": [
@@ -2189,7 +2188,7 @@
21892188
"link_fieldname": "consolidated_invoice"
21902189
}
21912190
],
2192-
"modified": "2025-03-05 17:06:59.720616",
2191+
"modified": "2025-03-17 19:32:31.809658",
21932192
"modified_by": "Administrator",
21942193
"module": "Accounts",
21952194
"name": "Sales Invoice",
@@ -2245,4 +2244,4 @@
22452244
"title_field": "title",
22462245
"track_changes": 1,
22472246
"track_seen": 1
2248-
}
2247+
}

erpnext/accounts/general_ledger.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ def make_acc_dimensions_offsetting_entry(gl_map):
8181
"credit_in_account_currency": credit,
8282
"remarks": _("Offsetting for Accounting Dimension") + f" - {dimension.name}",
8383
"against_voucher": None,
84+
"account_currency": dimension.account_currency,
85+
# Party Type and Party are restricted to Receivable and Payable accounts
86+
"party_type": None,
87+
"party": None,
8488
}
8589
)
8690
offsetting_entry["against_voucher_type"] = None
@@ -108,6 +112,9 @@ def get_accounting_dimensions_for_offsetting_entry(gl_map, company):
108112
accounting_dimensions_to_offset = []
109113
for acc_dimension in acc_dimensions:
110114
values = set([entry.get(acc_dimension.fieldname) for entry in gl_map])
115+
acc_dimension.account_currency = frappe.get_cached_value(
116+
"Account", acc_dimension.offsetting_account, "account_currency"
117+
)
111118
if len(values) > 1:
112119
accounting_dimensions_to_offset.append(acc_dimension)
113120

erpnext/accounts/report/accounts_receivable/accounts_receivable.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ def get_payment_terms(self, row):
520520
ps.description, ps.paid_amount, ps.discounted_amount
521521
from `tab{row.voucher_type}` si, `tabPayment Schedule` ps
522522
where
523-
si.name = ps.parent and
523+
si.name = ps.parent and ps.parenttype = '{row.voucher_type}' and
524524
si.name = %s and
525525
si.is_return = 0
526526
order by ps.paid_amount desc, due_date
@@ -729,11 +729,13 @@ def get_return_entries(self):
729729
"company": self.filters.company,
730730
"update_outstanding_for_self": 0,
731731
}
732+
732733
or_filters = {}
733-
for party_type in self.party_type:
734+
if party_type := self.filters.party_type:
734735
party_field = scrub(party_type)
735-
if self.filters.get(party_field):
736-
or_filters.update({party_field: self.filters.get(party_field)})
736+
if parties := self.filters.get("party"):
737+
or_filters.update({party_field: ["in", parties]})
738+
737739
self.return_entries = frappe._dict(
738740
frappe.get_all(
739741
doctype, filters=filters, or_filters=or_filters, fields=["name", "return_against"], as_list=1

erpnext/assets/doctype/asset_capitalization/asset_capitalization.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ def on_submit(self):
123123
self.make_bundle_using_old_serial_batch_fields()
124124
self.update_stock_ledger()
125125
self.make_gl_entries()
126+
self.repost_future_sle_and_gle()
126127
self.update_target_asset()
127128

128129
def on_cancel(self):
@@ -136,6 +137,7 @@ def on_cancel(self):
136137
)
137138
self.update_stock_ledger()
138139
self.make_gl_entries()
140+
self.repost_future_sle_and_gle()
139141
self.restore_consumed_asset_items()
140142

141143
def set_title(self):

erpnext/buying/doctype/purchase_order_item/purchase_order_item.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,7 @@
913913
},
914914
{
915915
"allow_on_submit": 1,
916+
"depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
916917
"fieldname": "subcontracted_quantity",
917918
"fieldtype": "Float",
918919
"label": "Subcontracted Quantity",
@@ -926,7 +927,7 @@
926927
"index_web_pages_for_search": 1,
927928
"istable": 1,
928929
"links": [],
929-
"modified": "2025-03-02 16:58:26.059601",
930+
"modified": "2025-03-13 17:27:43.468602",
930931
"modified_by": "Administrator",
931932
"module": "Buying",
932933
"name": "Purchase Order Item",
@@ -940,4 +941,4 @@
940941
"sort_order": "DESC",
941942
"states": [],
942943
"track_changes": 1
943-
}
944+
}

erpnext/controllers/accounts_controller.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ def validate(self):
271271
self.set_total_in_words()
272272
self.set_default_letter_head()
273273
self.validate_company_in_accounting_dimension()
274+
self.validate_party_address_and_contact()
274275

275276
def set_default_letter_head(self):
276277
if hasattr(self, "letter_head") and not self.letter_head:
@@ -441,6 +442,45 @@ def validate_company(self, dimension_list, child=None):
441442
)
442443
)
443444

445+
def validate_party_address_and_contact(self):
446+
party, party_type = None, None
447+
if self.get("customer"):
448+
party, party_type = self.customer, "Customer"
449+
billing_address, shipping_address = (
450+
self.get("customer_address"),
451+
self.get("shipping_address_name"),
452+
)
453+
self.validate_party_address(party, party_type, billing_address, shipping_address)
454+
elif self.get("supplier"):
455+
party, party_type = self.supplier, "Supplier"
456+
billing_address = self.get("supplier_address")
457+
self.validate_party_address(party, party_type, billing_address)
458+
459+
if party and party_type:
460+
self.validate_party_contact(party, party_type)
461+
462+
def validate_party_address(self, party, party_type, billing_address, shipping_address=None):
463+
if billing_address or shipping_address:
464+
party_address = frappe.get_all(
465+
"Dynamic Link",
466+
{"link_doctype": party_type, "link_name": party, "parenttype": "Address"},
467+
pluck="parent",
468+
)
469+
if billing_address and billing_address not in party_address:
470+
frappe.throw(_("Billing Address does not belong to the {0}").format(party))
471+
elif shipping_address and shipping_address not in party_address:
472+
frappe.throw(_("Shipping Address does not belong to the {0}").format(party))
473+
474+
def validate_party_contact(self, party, party_type):
475+
if self.get("contact_person"):
476+
contact = frappe.get_all(
477+
"Dynamic Link",
478+
{"link_doctype": party_type, "link_name": party, "parenttype": "Contact"},
479+
pluck="parent",
480+
)
481+
if self.contact_person and self.contact_person not in contact:
482+
frappe.throw(_("Contact Person does not belong to the {0}").format(party))
483+
444484
def validate_return_against_account(self):
445485
if self.doctype in ["Sales Invoice", "Purchase Invoice"] and self.is_return and self.return_against:
446486
cr_dr_account_field = "debit_to" if self.doctype == "Sales Invoice" else "credit_to"
@@ -3666,6 +3706,9 @@ def validate_fg_item_for_subcontracting(new_data, is_new):
36663706
if d.get("schedule_date") and parent_doctype == "Purchase Order":
36673707
child_item.schedule_date = d.get("schedule_date")
36683708

3709+
if d.get("bom_no") and parent_doctype == "Sales Order":
3710+
child_item.bom_no = d.get("bom_no")
3711+
36693712
if flt(child_item.price_list_rate):
36703713
if flt(child_item.rate) > flt(child_item.price_list_rate):
36713714
# if rate is greater than price_list_rate, set margin

erpnext/controllers/tests/test_accounts_controller.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2175,3 +2175,59 @@ def test_company_validation_in_dimension(self):
21752175
si_1 = create_sales_invoice(do_not_submit=True)
21762176
si_1.items[0].project = project.name
21772177
self.assertRaises(frappe.ValidationError, si_1.save)
2178+
2179+
def test_party_billing_and_shipping_address(self):
2180+
from erpnext.crm.doctype.prospect.test_prospect import make_address
2181+
2182+
customer_billing = make_address(address_title="Customer")
2183+
customer_billing.append("links", {"link_doctype": "Customer", "link_name": "_Test Customer"})
2184+
customer_billing.save()
2185+
supplier_billing = make_address(address_title="Supplier", address_line1="2", city="Ahmedabad")
2186+
supplier_billing.append("links", {"link_doctype": "Supplier", "link_name": "_Test Supplier"})
2187+
supplier_billing.save()
2188+
2189+
customer_shipping = make_address(
2190+
address_title="Customer", address_type="Shipping", address_line1="10"
2191+
)
2192+
customer_shipping.append("links", {"link_doctype": "Customer", "link_name": "_Test Customer"})
2193+
customer_shipping.save()
2194+
supplier_shipping = make_address(
2195+
address_title="Supplier", address_type="Shipping", address_line1="20", city="Ahmedabad"
2196+
)
2197+
supplier_shipping.append("links", {"link_doctype": "Supplier", "link_name": "_Test Supplier"})
2198+
supplier_shipping.save()
2199+
2200+
si = create_sales_invoice(do_not_save=True)
2201+
si.customer_address = supplier_billing.name
2202+
self.assertRaises(frappe.ValidationError, si.save)
2203+
si.customer_address = customer_billing.name
2204+
si.save()
2205+
2206+
si.shipping_address_name = supplier_shipping.name
2207+
self.assertRaises(frappe.ValidationError, si.save)
2208+
si.shipping_address_name = customer_shipping.name
2209+
si.reload()
2210+
si.save()
2211+
2212+
pi = make_purchase_invoice(do_not_save=True)
2213+
pi.supplier_address = customer_shipping.name
2214+
self.assertRaises(frappe.ValidationError, pi.save)
2215+
pi.supplier_address = supplier_shipping.name
2216+
pi.save()
2217+
2218+
def test_party_contact(self):
2219+
from frappe.contacts.doctype.contact.test_contact import create_contact
2220+
2221+
customer_contact = create_contact(name="Customer", salutation="Mr", save=False)
2222+
customer_contact.append("links", {"link_doctype": "Customer", "link_name": "_Test Customer"})
2223+
customer_contact.save()
2224+
2225+
supplier_contact = create_contact(name="Supplier", salutation="Mr", save=False)
2226+
supplier_contact.append("links", {"link_doctype": "Supplier", "link_name": "_Test Supplier"})
2227+
supplier_contact.save()
2228+
2229+
si = create_sales_invoice(do_not_save=True)
2230+
si.contact_person = supplier_contact.name
2231+
self.assertRaises(frappe.ValidationError, si.save)
2232+
si.contact_person = customer_contact.name
2233+
si.save()

erpnext/manufacturing/doctype/bom/bom.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,8 +440,8 @@ def get_bom_material_detail(self, args=None):
440440
"description": item and args["description"] or "",
441441
"image": item and args["image"] or "",
442442
"stock_uom": item and args["stock_uom"] or "",
443-
"uom": args["uom"] if hasattr(args, "uom") else item and args["stock_uom"] or "",
444-
"conversion_factor": args["conversion_factor"] if hasattr(args, "conversion_factor") else 1,
443+
"uom": args["uom"] if args.get("uom") else item and args["stock_uom"] or "",
444+
"conversion_factor": args["conversion_factor"] if args.get("conversion_factor") else 1,
445445
"bom_no": args["bom_no"],
446446
"rate": rate,
447447
"qty": args.get("qty") or args.get("stock_qty") or 1,

0 commit comments

Comments
 (0)