Skip to content

Commit

Permalink
Merge pull request #41355 from frappe/version-15-hotfix
Browse files Browse the repository at this point in the history
chore: release v15
  • Loading branch information
ruthra-kumar committed May 9, 2024
2 parents 7863782 + 307e394 commit 2ac0009
Show file tree
Hide file tree
Showing 29 changed files with 295 additions and 50 deletions.
2 changes: 1 addition & 1 deletion erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -1102,7 +1102,7 @@ def test_pricing_rule_for_product_free_item_rounded_qty_and_recursion(self):
so.load_from_db()
self.assertEqual(so.items[1].is_free_item, 1)
self.assertEqual(so.items[1].item_code, "_Test Item")
self.assertEqual(so.items[1].qty, 4)
self.assertEqual(so.items[1].qty, 3)

def test_apply_multiple_pricing_rules_for_discount_percentage_and_amount(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
Expand Down
3 changes: 2 additions & 1 deletion erpnext/accounts/doctype/pricing_rule/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import copy
import json
import math

import frappe
from frappe import _, bold
Expand Down Expand Up @@ -653,7 +654,7 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
if transaction_qty:
qty = flt(transaction_qty) * qty / pricing_rule.recurse_for
if pricing_rule.round_free_qty:
qty = round(qty)
qty = math.floor(qty)

free_item_data_args = {
"item_code": free_item,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
{
"fieldname": "cost_center_name",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Cost Center",
"options": "Cost Center"
"options": "Cost Center",
"reqd": 1
}
],
"istable": 1,
"links": [],
"modified": "2020-08-03 16:56:45.744905",
"modified": "2024-05-03 17:16:51.666461",
"modified_by": "Administrator",
"module": "Accounts",
"name": "PSOA Cost Center",
Expand All @@ -27,4 +29,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class PSOACostCenter(Document):
if TYPE_CHECKING:
from frappe.types import DF

cost_center_name: DF.Link | None
cost_center_name: DF.Link
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1091,7 +1091,7 @@ def make_item_gl_entries(self, gl_entries):
)

# check if the exchange rate has changed
if item.get("purchase_receipt"):
if item.get("purchase_receipt") and self.auto_accounting_for_stock:
if (
exchange_rate_map[item.purchase_receipt]
and self.conversion_rate != exchange_rate_map[item.purchase_receipt]
Expand Down
3 changes: 3 additions & 0 deletions erpnext/accounts/doctype/sales_invoice/sales_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,9 @@ def validate_income_account(self):
validate_account_head(item.idx, item.income_account, self.company, "Income")

def set_tax_withholding(self):
if self.get("is_opening") == "Yes":
return

tax_withholding_details = get_party_tax_withholding_details(self)

if not tax_withholding_details:
Expand Down
43 changes: 43 additions & 0 deletions erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -1766,6 +1766,49 @@ def test_multi_currency_gle(self):

self.assertTrue(gle)

def test_gle_in_transaction_currency(self):
# create multi currency sales invoice with 2 items with same income account
si = create_sales_invoice(
customer="_Test Customer USD",
debit_to="_Test Receivable USD - _TC",
currency="USD",
conversion_rate=50,
do_not_submit=True,
)
# add 2nd item with same income account
si.append(
"items",
{
"item_code": "_Test Item",
"qty": 1,
"rate": 80,
"income_account": "Sales - _TC",
"cost_center": "_Test Cost Center - _TC",
},
)
si.submit()

gl_entries = frappe.db.sql(
"""select transaction_currency, transaction_exchange_rate,
debit_in_transaction_currency, credit_in_transaction_currency
from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s and account = 'Sales - _TC'
order by account asc""",
si.name,
as_dict=1,
)

expected_gle = {
"transaction_currency": "USD",
"transaction_exchange_rate": 50,
"debit_in_transaction_currency": 0,
"credit_in_transaction_currency": 180,
}

for gle in gl_entries:
for field in expected_gle:
self.assertEqual(expected_gle[field], gle[field])

def test_invoice_exchange_rate(self):
si = create_sales_invoice(
customer="_Test Customer USD",
Expand Down
22 changes: 5 additions & 17 deletions erpnext/accounts/doctype/subscription/subscription.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,7 @@ def get_current_invoice_start(self, date: DateTimeLikeObject | None = None) -> D
"""
_current_invoice_start = None

if (
self.is_new_subscription()
and self.trial_period_end
and getdate(self.trial_period_end) > getdate(self.start_date)
):
if self.trial_period_end and getdate(self.trial_period_end) > getdate(self.start_date):
_current_invoice_start = add_days(self.trial_period_end, 1)
elif self.trial_period_start and self.is_trialling():
_current_invoice_start = self.trial_period_start
Expand All @@ -143,7 +139,7 @@ def get_current_invoice_end(self, date: DateTimeLikeObject | None = None) -> Dat
else:
billing_cycle_info = self.get_billing_cycle_data()
if billing_cycle_info:
if self.is_new_subscription() and getdate(self.start_date) < getdate(date):
if getdate(self.start_date) < getdate(date):
_current_invoice_end = add_to_date(self.start_date, **billing_cycle_info)

# For cases where trial period is for an entire billing interval
Expand Down Expand Up @@ -234,14 +230,14 @@ def set_subscription_status(self, posting_date: DateTimeLikeObject | None = None
self.cancelation_date = getdate(posting_date) if self.status == "Cancelled" else None
elif self.current_invoice_is_past_due() and not self.is_past_grace_period():
self.status = "Past Due Date"
elif not self.has_outstanding_invoice() or self.is_new_subscription():
elif not self.has_outstanding_invoice():
self.status = "Active"

def is_trialling(self) -> bool:
"""
Returns `True` if the `Subscription` is in trial period.
"""
return not self.period_has_passed(self.trial_period_end) and self.is_new_subscription()
return not self.period_has_passed(self.trial_period_end)

@staticmethod
def period_has_passed(
Expand Down Expand Up @@ -288,14 +284,6 @@ def current_invoice_is_past_due(self, posting_date: DateTimeLikeObject | None =
def invoice_document_type(self) -> str:
return "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"

def is_new_subscription(self) -> bool:
"""
Returns `True` if `Subscription` has never generated an invoice
"""
return self.is_new() or not frappe.db.exists(
{"doctype": self.invoice_document_type, "subscription": self.name}
)

def validate(self) -> None:
self.validate_trial_period()
self.validate_plans_billing_cycle(self.get_billing_cycle_and_interval())
Expand Down Expand Up @@ -604,7 +592,7 @@ def can_generate_new_invoice(self, posting_date: DateTimeLikeObject | None = Non
return False

if self.generate_invoice_at == "Beginning of the current subscription period" and (
getdate(posting_date) == getdate(self.current_invoice_start) or self.is_new_subscription()
getdate(posting_date) == getdate(self.current_invoice_start)
):
return True
elif self.generate_invoice_at == "Days before the current subscription period" and (
Expand Down
6 changes: 3 additions & 3 deletions erpnext/accounts/doctype/subscription/test_subscription.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,11 +445,11 @@ def test_subscription_without_generate_invoice_past_due(self):

# Process subscription and create first invoice
# Subscription status will be unpaid since due date has already passed
subscription.process()
subscription.process(posting_date="2018-01-01")
self.assertEqual(len(subscription.invoices), 1)
self.assertEqual(subscription.status, "Unpaid")

subscription.process()
subscription.process(posting_date="2018-04-01")
self.assertEqual(len(subscription.invoices), 1)

def test_multi_currency_subscription(self):
Expand All @@ -462,7 +462,7 @@ def test_multi_currency_subscription(self):
party=party,
)

subscription.process()
subscription.process(posting_date="2018-01-01")
self.assertEqual(len(subscription.invoices), 1)
self.assertEqual(subscription.status, "Unpaid")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from frappe.query_builder.functions import Abs, Sum
from frappe.utils import cint, flt, getdate

from erpnext.controllers.accounts_controller import validate_account_head


class TaxWithholdingCategory(Document):
# begin: auto-generated types
Expand Down Expand Up @@ -53,6 +55,7 @@ def validate_accounts(self):
if d.get("account") in existing_accounts:
frappe.throw(_("Account {0} added multiple times").format(frappe.bold(d.get("account"))))

validate_account_head(d.idx, d.get("account"), d.get("company"))
existing_accounts.append(d.get("account"))

def validate_thresholds(self):
Expand Down
6 changes: 6 additions & 0 deletions erpnext/accounts/general_ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,16 @@ def merge_similar_entries(gl_map, precision=None):
same_head.debit_in_account_currency = flt(same_head.debit_in_account_currency) + flt(
entry.debit_in_account_currency
)
same_head.debit_in_transaction_currency = flt(same_head.debit_in_transaction_currency) + flt(
entry.debit_in_transaction_currency
)
same_head.credit = flt(same_head.credit) + flt(entry.credit)
same_head.credit_in_account_currency = flt(same_head.credit_in_account_currency) + flt(
entry.credit_in_account_currency
)
same_head.credit_in_transaction_currency = flt(same_head.credit_in_transaction_currency) + flt(
entry.credit_in_transaction_currency
)
else:
merged_gl_map.append(entry)

Expand Down
12 changes: 7 additions & 5 deletions erpnext/accounts/report/gross_profit/gross_profit.py
Original file line number Diff line number Diff line change
Expand Up @@ -720,20 +720,22 @@ def get_last_purchase_rate(self, item_code, row):
frappe.qb.from_(purchase_invoice_item)
.inner_join(purchase_invoice)
.on(purchase_invoice.name == purchase_invoice_item.parent)
.select(purchase_invoice_item.base_rate / purchase_invoice_item.conversion_factor)
.select(
purchase_invoice.name,
purchase_invoice_item.base_rate / purchase_invoice_item.conversion_factor,
)
.where(purchase_invoice.docstatus == 1)
.where(purchase_invoice.posting_date <= self.filters.to_date)
.where(purchase_invoice_item.item_code == item_code)
)

if row.project:
query.where(purchase_invoice_item.project == row.project)
query = query.where(purchase_invoice_item.project == row.project)

if row.cost_center:
query.where(purchase_invoice_item.cost_center == row.cost_center)
query = query.where(purchase_invoice_item.cost_center == row.cost_center)

query.orderby(purchase_invoice.posting_date, order=frappe.qb.desc)
query.limit(1)
query = query.orderby(purchase_invoice.posting_date, order=frappe.qb.desc).limit(1)
last_purchase_rate = query.run()

return flt(last_purchase_rate[0][0]) if last_purchase_rate else 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ def on_cancel(self):
self.make_gl_entries()
self.restore_consumed_asset_items()

def on_trash(self):
frappe.db.set_value("Asset", self.target_asset, "capitalized_in", None)
super(AssetCapitalization, self).on_trash()

def cancel_target_asset(self):
if self.entry_type == "Capitalization" and self.target_asset:
asset_doc = frappe.get_doc("Asset", self.target_asset)
Expand Down
14 changes: 14 additions & 0 deletions erpnext/buying/doctype/purchase_order/purchase_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,20 @@ def can_update_items(self) -> bool:

return result

def update_ordered_qty_in_so_for_removed_items(self, removed_items):
"""
Updates ordered_qty in linked SO when item rows are removed using Update Items
"""
if not self.is_against_so():
return
for item in removed_items:
prev_ordered_qty = frappe.get_cached_value(
"Sales Order Item", item.get("sales_order_item"), "ordered_qty"
)
frappe.db.set_value(
"Sales Order Item", item.get("sales_order_item"), "ordered_qty", prev_ordered_qty - item.qty
)

def auto_create_subcontracting_order(self):
if self.is_subcontracted and not self.is_old_subcontracting_flow:
if frappe.db.get_single_value("Buying Settings", "auto_create_subcontracting_order"):
Expand Down
3 changes: 3 additions & 0 deletions erpnext/controllers/accounts_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -3158,6 +3158,9 @@ def validate_and_delete_children(parent, data) -> bool:
d.cancel()
d.delete()

if parent.doctype == "Purchase Order":
parent.update_ordered_qty_in_so_for_removed_items(deleted_children)

# need to update ordered qty in Material Request first
# bin uses Material Request Items to recalculate & update
parent.update_prevdoc_status()
Expand Down
4 changes: 3 additions & 1 deletion erpnext/patches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -360,4 +360,6 @@ erpnext.patches.v15_0.create_accounting_dimensions_in_payment_request
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20
erpnext.patches.v14_0.set_maintain_stock_for_bom_item
erpnext.patches.v15_0.delete_orphaned_asset_movement_item_records
erpnext.patches.v15_0.delete_orphaned_asset_movement_item_records
erpnext.patches.v15_0.fix_debit_credit_in_transaction_currency
erpnext.patches.v15_0.remove_cancelled_asset_capitalization_from_asset
21 changes: 21 additions & 0 deletions erpnext/patches/v15_0/fix_debit_credit_in_transaction_currency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import frappe


def execute():
# update debit and credit in transaction currency:
# if transaction currency is same as account currency,
# then debit and credit in transaction currency is same as debit and credit in account currency
# else debit and credit divided by exchange rate

# nosemgrep
frappe.db.sql(
"""
UPDATE `tabGL Entry`
SET
debit_in_transaction_currency = IF(transaction_currency = account_currency, debit_in_account_currency, debit / transaction_exchange_rate),
credit_in_transaction_currency = IF(transaction_currency = account_currency, credit_in_account_currency, credit / transaction_exchange_rate)
WHERE
transaction_exchange_rate > 0
and transaction_currency is not null
"""
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import frappe


def execute():
cancelled_asset_capitalizations = frappe.get_all(
"Asset Capitalization",
filters={"docstatus": 2},
fields=["name", "target_asset"],
)
for asset_capitalization in cancelled_asset_capitalizations:
frappe.db.set_value("Asset", asset_capitalization.target_asset, "capitalized_in", None)
3 changes: 3 additions & 0 deletions erpnext/stock/doctype/item/item.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ frappe.ui.form.on("Item", {
frm.add_fetch("tax_type", "tax_rate", "tax_rate");

frm.make_methods = {
Quotation: () => {
open_form(frm, "Quotation", "Quotation Item", "items");
},
"Sales Order": () => {
open_form(frm, "Sales Order", "Sales Order Item", "items");
},
Expand Down
Loading

0 comments on commit 2ac0009

Please sign in to comment.