mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-23 06:59:20 +00:00
Merge pull request #33702 from frappe/version-13-hotfix
chore: release v13
This commit is contained in:
3
.github/helper/.flake8_strict
vendored
3
.github/helper/.flake8_strict
vendored
@@ -66,7 +66,8 @@ ignore =
|
|||||||
F841,
|
F841,
|
||||||
E713,
|
E713,
|
||||||
E712,
|
E712,
|
||||||
B023
|
B023,
|
||||||
|
B028
|
||||||
|
|
||||||
|
|
||||||
max-line-length = 200
|
max-line-length = 200
|
||||||
|
|||||||
@@ -132,6 +132,10 @@ frappe.ui.form.on('Asset', {
|
|||||||
}, __("Manage"));
|
}, __("Manage"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (frm.doc.depr_entry_posting_status === "Failed") {
|
||||||
|
frm.trigger("set_depr_posting_failure_alert");
|
||||||
|
}
|
||||||
|
|
||||||
frm.trigger("setup_chart");
|
frm.trigger("setup_chart");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,6 +146,19 @@ frappe.ui.form.on('Asset', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
set_depr_posting_failure_alert: function (frm) {
|
||||||
|
const alert = `
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 col-sm-6">
|
||||||
|
<span class="indicator whitespace-nowrap red">
|
||||||
|
<span>Failed to post depreciation entries</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
frm.dashboard.set_headline_alert(alert);
|
||||||
|
},
|
||||||
|
|
||||||
toggle_reference_doc: function(frm) {
|
toggle_reference_doc: function(frm) {
|
||||||
if (frm.doc.purchase_receipt && frm.doc.purchase_invoice && frm.doc.docstatus === 1) {
|
if (frm.doc.purchase_receipt && frm.doc.purchase_invoice && frm.doc.docstatus === 1) {
|
||||||
frm.set_df_property('purchase_invoice', 'read_only', 1);
|
frm.set_df_property('purchase_invoice', 'read_only', 1);
|
||||||
|
|||||||
@@ -68,6 +68,7 @@
|
|||||||
"column_break_51",
|
"column_break_51",
|
||||||
"purchase_receipt_amount",
|
"purchase_receipt_amount",
|
||||||
"default_finance_book",
|
"default_finance_book",
|
||||||
|
"depr_entry_posting_status",
|
||||||
"amended_from"
|
"amended_from"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@@ -473,6 +474,16 @@
|
|||||||
"fieldname": "section_break_36",
|
"fieldname": "section_break_36",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Finance Books"
|
"label": "Finance Books"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "depr_entry_posting_status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Depreciation Entry Posting Status",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "\nSuccessful\nFailed",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 72,
|
"idx": 72,
|
||||||
@@ -487,7 +498,7 @@
|
|||||||
{
|
{
|
||||||
"group": "Repair",
|
"group": "Repair",
|
||||||
"link_doctype": "Asset Repair",
|
"link_doctype": "Asset Repair",
|
||||||
"link_fieldname": "asset_name"
|
"link_fieldname": "asset"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "Value",
|
"group": "Value",
|
||||||
@@ -495,7 +506,7 @@
|
|||||||
"link_fieldname": "asset"
|
"link_fieldname": "asset"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2022-07-20 16:22:44.437579",
|
"modified": "2023-01-17 00:28:37.789345",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset",
|
"name": "Asset",
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cint, flt, getdate, today
|
from frappe.utils import cint, flt, get_link_to_form, getdate, today
|
||||||
|
from frappe.utils.user import get_users_with_role
|
||||||
|
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||||
get_checks_for_pl_and_bs_accounts,
|
get_checks_for_pl_and_bs_accounts,
|
||||||
@@ -20,9 +21,22 @@ def post_depreciation_entries(date=None):
|
|||||||
|
|
||||||
if not date:
|
if not date:
|
||||||
date = today()
|
date = today()
|
||||||
for asset in get_depreciable_assets(date):
|
|
||||||
make_depreciation_entry(asset, date)
|
failed_asset_names = []
|
||||||
frappe.db.commit()
|
|
||||||
|
for asset_name in get_depreciable_assets(date):
|
||||||
|
try:
|
||||||
|
make_depreciation_entry(asset_name, date)
|
||||||
|
frappe.db.commit()
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
failed_asset_names.append(asset_name)
|
||||||
|
|
||||||
|
if failed_asset_names:
|
||||||
|
set_depr_entry_posting_status_for_failed_assets(failed_asset_names)
|
||||||
|
notify_depr_entry_posting_error(failed_asset_names)
|
||||||
|
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
|
||||||
def get_depreciable_assets(date):
|
def get_depreciable_assets(date):
|
||||||
@@ -121,6 +135,8 @@ def make_depreciation_entry(asset_name, date=None):
|
|||||||
finance_books.value_after_depreciation -= d.depreciation_amount
|
finance_books.value_after_depreciation -= d.depreciation_amount
|
||||||
finance_books.db_update()
|
finance_books.db_update()
|
||||||
|
|
||||||
|
frappe.db.set_value("Asset", asset_name, "depr_entry_posting_status", "Successful")
|
||||||
|
|
||||||
asset.set_status()
|
asset.set_status()
|
||||||
|
|
||||||
return asset
|
return asset
|
||||||
@@ -184,6 +200,42 @@ def get_credit_and_debit_accounts(accumulated_depreciation_account, depreciation
|
|||||||
return credit_account, debit_account
|
return credit_account, debit_account
|
||||||
|
|
||||||
|
|
||||||
|
def set_depr_entry_posting_status_for_failed_assets(failed_asset_names):
|
||||||
|
for asset_name in failed_asset_names:
|
||||||
|
frappe.db.set_value("Asset", asset_name, "depr_entry_posting_status", "Failed")
|
||||||
|
|
||||||
|
|
||||||
|
def notify_depr_entry_posting_error(failed_asset_names):
|
||||||
|
recipients = get_users_with_role("Accounts Manager")
|
||||||
|
|
||||||
|
if not recipients:
|
||||||
|
recipients = get_users_with_role("System Manager")
|
||||||
|
|
||||||
|
subject = _("Error while posting depreciation entries")
|
||||||
|
|
||||||
|
asset_links = get_comma_separated_asset_links(failed_asset_names)
|
||||||
|
|
||||||
|
message = (
|
||||||
|
_("Hi,")
|
||||||
|
+ "<br>"
|
||||||
|
+ _("The following assets have failed to post depreciation entries: {0}").format(asset_links)
|
||||||
|
+ "."
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.sendmail(recipients=recipients, subject=subject, message=message)
|
||||||
|
|
||||||
|
|
||||||
|
def get_comma_separated_asset_links(asset_names):
|
||||||
|
asset_links = []
|
||||||
|
|
||||||
|
for asset_name in asset_names:
|
||||||
|
asset_links.append(get_link_to_form("Asset", asset_name))
|
||||||
|
|
||||||
|
asset_links = ", ".join(asset_links)
|
||||||
|
|
||||||
|
return asset_links
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def scrap_asset(asset_name):
|
def scrap_asset(asset_name):
|
||||||
asset = frappe.get_doc("Asset", asset_name)
|
asset = frappe.get_doc("Asset", asset_name)
|
||||||
|
|||||||
@@ -1387,6 +1387,7 @@ def create_asset(**args):
|
|||||||
"location": args.location or "Test Location",
|
"location": args.location or "Test Location",
|
||||||
"asset_owner": args.asset_owner or "Company",
|
"asset_owner": args.asset_owner or "Company",
|
||||||
"is_existing_asset": args.is_existing_asset or 1,
|
"is_existing_asset": args.is_existing_asset or 1,
|
||||||
|
"depr_entry_posting_status": args.depr_entry_posting_status or "",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ def get_data(filters):
|
|||||||
"status",
|
"status",
|
||||||
"department",
|
"department",
|
||||||
"cost_center",
|
"cost_center",
|
||||||
|
"calculate_depreciation",
|
||||||
"purchase_receipt",
|
"purchase_receipt",
|
||||||
"asset_category",
|
"asset_category",
|
||||||
"purchase_date",
|
"purchase_date",
|
||||||
@@ -98,11 +99,7 @@ def get_data(filters):
|
|||||||
assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields)
|
assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields)
|
||||||
|
|
||||||
for asset in assets_record:
|
for asset in assets_record:
|
||||||
asset_value = (
|
asset_value = get_asset_value(asset, filters.finance_book)
|
||||||
asset.gross_purchase_amount
|
|
||||||
- flt(asset.opening_accumulated_depreciation)
|
|
||||||
- flt(depreciation_amount_map.get(asset.name))
|
|
||||||
)
|
|
||||||
row = {
|
row = {
|
||||||
"asset_id": asset.asset_id,
|
"asset_id": asset.asset_id,
|
||||||
"asset_name": asset.asset_name,
|
"asset_name": asset.asset_name,
|
||||||
@@ -125,6 +122,21 @@ def get_data(filters):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def get_asset_value(asset, finance_book=None):
|
||||||
|
if not asset.calculate_depreciation:
|
||||||
|
return flt(asset.gross_purchase_amount) - flt(asset.opening_accumulated_depreciation)
|
||||||
|
|
||||||
|
finance_book_filter = ["finance_book", "is", "not set"]
|
||||||
|
if finance_book:
|
||||||
|
finance_book_filter = ["finance_book", "=", finance_book]
|
||||||
|
|
||||||
|
return frappe.db.get_value(
|
||||||
|
doctype="Asset Finance Book",
|
||||||
|
filters=[["parent", "=", asset.asset_id], finance_book_filter],
|
||||||
|
fieldname="value_after_depreciation",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def prepare_chart_data(data, filters):
|
def prepare_chart_data(data, filters):
|
||||||
labels_values_map = {}
|
labels_values_map = {}
|
||||||
date_field = frappe.scrub(filters.date_based_on)
|
date_field = frappe.scrub(filters.date_based_on)
|
||||||
|
|||||||
@@ -383,7 +383,7 @@ class AccountsController(TransactionBase):
|
|||||||
self.get("inter_company_reference")
|
self.get("inter_company_reference")
|
||||||
or self.get("inter_company_invoice_reference")
|
or self.get("inter_company_invoice_reference")
|
||||||
or self.get("inter_company_order_reference")
|
or self.get("inter_company_order_reference")
|
||||||
):
|
) and not self.get("is_return"):
|
||||||
msg = _("Internal Sale or Delivery Reference missing.")
|
msg = _("Internal Sale or Delivery Reference missing.")
|
||||||
msg += _("Please create purchase from internal sale or delivery document itself")
|
msg += _("Please create purchase from internal sale or delivery document itself")
|
||||||
frappe.throw(msg, title=_("Internal Sales Reference Missing"))
|
frappe.throw(msg, title=_("Internal Sales Reference Missing"))
|
||||||
|
|||||||
@@ -191,14 +191,7 @@ def get_list_context(context=None):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_sales_order(source_name, target_doc=None):
|
def make_sales_order(source_name: str, target_doc=None):
|
||||||
quotation = frappe.db.get_value(
|
|
||||||
"Quotation", source_name, ["transaction_date", "valid_till"], as_dict=1
|
|
||||||
)
|
|
||||||
if quotation.valid_till and (
|
|
||||||
quotation.valid_till < quotation.transaction_date or quotation.valid_till < getdate(nowdate())
|
|
||||||
):
|
|
||||||
frappe.throw(_("Validity period of this quotation has ended."))
|
|
||||||
return _make_sales_order(source_name, target_doc)
|
return _make_sales_order(source_name, target_doc)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -118,17 +118,20 @@ class TestQuotation(FrappeTestCase):
|
|||||||
sales_order.payment_schedule[1].due_date, getdate(add_days(quotation.transaction_date, 30))
|
sales_order.payment_schedule[1].due_date, getdate(add_days(quotation.transaction_date, 30))
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_valid_till(self):
|
def test_valid_till_before_transaction_date(self):
|
||||||
from erpnext.selling.doctype.quotation.quotation import make_sales_order
|
|
||||||
|
|
||||||
quotation = frappe.copy_doc(test_records[0])
|
quotation = frappe.copy_doc(test_records[0])
|
||||||
quotation.valid_till = add_days(quotation.transaction_date, -1)
|
quotation.valid_till = add_days(quotation.transaction_date, -1)
|
||||||
self.assertRaises(frappe.ValidationError, quotation.validate)
|
self.assertRaises(frappe.ValidationError, quotation.validate)
|
||||||
|
|
||||||
|
def test_so_from_expired_quotation(self):
|
||||||
|
from erpnext.selling.doctype.quotation.quotation import make_sales_order
|
||||||
|
|
||||||
|
quotation = frappe.copy_doc(test_records[0])
|
||||||
quotation.valid_till = add_days(nowdate(), -1)
|
quotation.valid_till = add_days(nowdate(), -1)
|
||||||
quotation.insert()
|
quotation.insert()
|
||||||
quotation.submit()
|
quotation.submit()
|
||||||
self.assertRaises(frappe.ValidationError, make_sales_order, quotation.name)
|
|
||||||
|
make_sales_order(quotation.name)
|
||||||
|
|
||||||
def test_shopping_cart_without_website_item(self):
|
def test_shopping_cart_without_website_item(self):
|
||||||
if frappe.db.exists("Website Item", {"item_code": "_Test Item Home Desktop 100"}):
|
if frappe.db.exists("Website Item", {"item_code": "_Test Item Home Desktop 100"}):
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ def get_data():
|
|||||||
},
|
},
|
||||||
"internal_links": {
|
"internal_links": {
|
||||||
"Quotation": ["items", "prevdoc_docname"],
|
"Quotation": ["items", "prevdoc_docname"],
|
||||||
"Material Request": ["items", "material_request"],
|
|
||||||
},
|
},
|
||||||
"transactions": [
|
"transactions": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -809,7 +809,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-04-27 03:15:34.366563",
|
"modified": "2022-12-25 02:51:10.247569",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Sales Order Item",
|
"name": "Sales Order Item",
|
||||||
|
|||||||
@@ -104,7 +104,6 @@ class TestItem(FrappeTestCase):
|
|||||||
"conversion_factor": 1.0,
|
"conversion_factor": 1.0,
|
||||||
"reserved_qty": 1,
|
"reserved_qty": 1,
|
||||||
"actual_qty": 5,
|
"actual_qty": 5,
|
||||||
"ordered_qty": 10,
|
|
||||||
"projected_qty": 14,
|
"projected_qty": 14,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -198,7 +198,8 @@ class PickList(Document):
|
|||||||
frappe.throw(_("Qty of Finished Goods Item should be greater than 0."))
|
frappe.throw(_("Qty of Finished Goods Item should be greater than 0."))
|
||||||
|
|
||||||
def before_print(self, settings=None):
|
def before_print(self, settings=None):
|
||||||
self.group_similar_items()
|
if self.group_same_items:
|
||||||
|
self.group_similar_items()
|
||||||
|
|
||||||
def group_similar_items(self):
|
def group_similar_items(self):
|
||||||
group_item_qty = defaultdict(float)
|
group_item_qty = defaultdict(float)
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
def get_data():
|
def get_data():
|
||||||
return {
|
return {
|
||||||
"fieldname": "pick_list",
|
"fieldname": "pick_list",
|
||||||
|
"internal_links": {
|
||||||
|
"Sales Order": ["locations", "sales_order"],
|
||||||
|
},
|
||||||
"transactions": [
|
"transactions": [
|
||||||
{"items": ["Stock Entry", "Delivery Note"]},
|
{"items": ["Stock Entry", "Sales Order", "Delivery Note"]},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,10 +432,10 @@ class TestPickList(FrappeTestCase):
|
|||||||
pl.before_print()
|
pl.before_print()
|
||||||
self.assertEqual(len(pl.locations), 4)
|
self.assertEqual(len(pl.locations), 4)
|
||||||
|
|
||||||
# grouping should halve the number of items
|
# grouping should not happen if group_same_items is False
|
||||||
pl = frappe.get_doc(
|
pl = frappe.get_doc(
|
||||||
doctype="Pick List",
|
doctype="Pick List",
|
||||||
group_same_items=True,
|
group_same_items=False,
|
||||||
locations=[
|
locations=[
|
||||||
_dict(item_code="A", warehouse="X", qty=5, picked_qty=1),
|
_dict(item_code="A", warehouse="X", qty=5, picked_qty=1),
|
||||||
_dict(item_code="B", warehouse="Y", qty=4, picked_qty=2),
|
_dict(item_code="B", warehouse="Y", qty=4, picked_qty=2),
|
||||||
@@ -444,6 +444,11 @@ class TestPickList(FrappeTestCase):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
pl.before_print()
|
pl.before_print()
|
||||||
|
self.assertEqual(len(pl.locations), 4)
|
||||||
|
|
||||||
|
# grouping should halve the number of items
|
||||||
|
pl.group_same_items = True
|
||||||
|
pl.before_print()
|
||||||
self.assertEqual(len(pl.locations), 2)
|
self.assertEqual(len(pl.locations), 2)
|
||||||
|
|
||||||
expected_items = [
|
expected_items = [
|
||||||
|
|||||||
@@ -1161,7 +1161,7 @@ def get_projected_qty(item_code, warehouse):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_bin_details(item_code, warehouse, company=None, include_child_warehouses=False):
|
def get_bin_details(item_code, warehouse, company=None, include_child_warehouses=False):
|
||||||
bin_details = {"projected_qty": 0, "actual_qty": 0, "reserved_qty": 0, "ordered_qty": 0}
|
bin_details = {"projected_qty": 0, "actual_qty": 0, "reserved_qty": 0}
|
||||||
|
|
||||||
if warehouse:
|
if warehouse:
|
||||||
from frappe.query_builder.functions import Coalesce, Sum
|
from frappe.query_builder.functions import Coalesce, Sum
|
||||||
@@ -1177,7 +1177,6 @@ def get_bin_details(item_code, warehouse, company=None, include_child_warehouses
|
|||||||
Coalesce(Sum(bin.projected_qty), 0).as_("projected_qty"),
|
Coalesce(Sum(bin.projected_qty), 0).as_("projected_qty"),
|
||||||
Coalesce(Sum(bin.actual_qty), 0).as_("actual_qty"),
|
Coalesce(Sum(bin.actual_qty), 0).as_("actual_qty"),
|
||||||
Coalesce(Sum(bin.reserved_qty), 0).as_("reserved_qty"),
|
Coalesce(Sum(bin.reserved_qty), 0).as_("reserved_qty"),
|
||||||
Coalesce(Sum(bin.ordered_qty), 0).as_("ordered_qty"),
|
|
||||||
)
|
)
|
||||||
.where((bin.item_code == item_code) & (bin.warehouse.isin(warehouses)))
|
.where((bin.item_code == item_code) & (bin.warehouse.isin(warehouses)))
|
||||||
).run(as_dict=True)[0]
|
).run(as_dict=True)[0]
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ def get_data(report_filters):
|
|||||||
key = (d.voucher_type, d.voucher_no)
|
key = (d.voucher_type, d.voucher_no)
|
||||||
gl_data = voucher_wise_gl_data.get(key) or {}
|
gl_data = voucher_wise_gl_data.get(key) or {}
|
||||||
d.account_value = gl_data.get("account_value", 0)
|
d.account_value = gl_data.get("account_value", 0)
|
||||||
d.difference_value = abs(d.stock_value - d.account_value)
|
d.difference_value = d.stock_value - d.account_value
|
||||||
if abs(d.difference_value) > 0.1:
|
if abs(d.difference_value) > 0.1:
|
||||||
data.append(d)
|
data.append(d)
|
||||||
|
|
||||||
|
|||||||
@@ -1184,20 +1184,6 @@ def get_valuation_rate(
|
|||||||
(item_code, warehouse, voucher_no, voucher_type),
|
(item_code, warehouse, voucher_no, voucher_type),
|
||||||
)
|
)
|
||||||
|
|
||||||
if not last_valuation_rate:
|
|
||||||
# Get valuation rate from last sle for the item against any warehouse
|
|
||||||
last_valuation_rate = frappe.db.sql(
|
|
||||||
"""select valuation_rate
|
|
||||||
from `tabStock Ledger Entry` force index (item_code)
|
|
||||||
where
|
|
||||||
item_code = %s
|
|
||||||
AND valuation_rate > 0
|
|
||||||
AND is_cancelled = 0
|
|
||||||
AND NOT(voucher_no = %s AND voucher_type = %s)
|
|
||||||
order by posting_date desc, posting_time desc, name desc limit 1""",
|
|
||||||
(item_code, voucher_no, voucher_type),
|
|
||||||
)
|
|
||||||
|
|
||||||
if last_valuation_rate:
|
if last_valuation_rate:
|
||||||
return flt(last_valuation_rate[0][0])
|
return flt(last_valuation_rate[0][0])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user