mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-18 12:39:18 +00:00
Merge pull request #43026 from frappe/version-15-hotfix
chore: release v15
This commit is contained in:
@@ -2,6 +2,7 @@ import functools
|
|||||||
import inspect
|
import inspect
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.utils.user import is_website_user
|
||||||
|
|
||||||
__version__ = "15.34.0"
|
__version__ = "15.34.0"
|
||||||
|
|
||||||
@@ -149,3 +150,13 @@ def allow_regional(fn):
|
|||||||
return frappe.get_attr(overrides[function_path][-1])(*args, **kwargs)
|
return frappe.get_attr(overrides[function_path][-1])(*args, **kwargs)
|
||||||
|
|
||||||
return caller
|
return caller
|
||||||
|
|
||||||
|
|
||||||
|
def check_app_permission():
|
||||||
|
if frappe.session.user == "Administrator":
|
||||||
|
return True
|
||||||
|
|
||||||
|
if is_website_user():
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ frappe.ui.form.on("Bank Clearance", {
|
|||||||
frm.add_custom_button(__("Get Payment Entries"), () => frm.trigger("get_payment_entries"));
|
frm.add_custom_button(__("Get Payment Entries"), () => frm.trigger("get_payment_entries"));
|
||||||
|
|
||||||
frm.change_custom_button_type(__("Get Payment Entries"), null, "primary");
|
frm.change_custom_button_type(__("Get Payment Entries"), null, "primary");
|
||||||
|
if (frm.doc.payment_entries.length) {
|
||||||
|
frm.add_custom_button(__("Update Clearance Date"), () => frm.trigger("update_clearance_date"));
|
||||||
|
frm.change_custom_button_type(__("Get Payment Entries"), null, "default");
|
||||||
|
frm.change_custom_button_type(__("Update Clearance Date"), null, "primary");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
update_clearance_date: function (frm) {
|
update_clearance_date: function (frm) {
|
||||||
@@ -45,13 +50,7 @@ frappe.ui.form.on("Bank Clearance", {
|
|||||||
method: "update_clearance_date",
|
method: "update_clearance_date",
|
||||||
doc: frm.doc,
|
doc: frm.doc,
|
||||||
callback: function (r, rt) {
|
callback: function (r, rt) {
|
||||||
frm.refresh_field("payment_entries");
|
frm.refresh();
|
||||||
frm.refresh_fields();
|
|
||||||
|
|
||||||
if (!frm.doc.payment_entries.length) {
|
|
||||||
frm.change_custom_button_type(__("Get Payment Entries"), null, "primary");
|
|
||||||
frm.change_custom_button_type(__("Update Clearance Date"), null, "default");
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -60,17 +59,8 @@ frappe.ui.form.on("Bank Clearance", {
|
|||||||
return frappe.call({
|
return frappe.call({
|
||||||
method: "get_payment_entries",
|
method: "get_payment_entries",
|
||||||
doc: frm.doc,
|
doc: frm.doc,
|
||||||
callback: function (r, rt) {
|
callback: function () {
|
||||||
frm.refresh_field("payment_entries");
|
frm.refresh();
|
||||||
|
|
||||||
if (frm.doc.payment_entries.length) {
|
|
||||||
frm.add_custom_button(__("Update Clearance Date"), () =>
|
|
||||||
frm.trigger("update_clearance_date")
|
|
||||||
);
|
|
||||||
|
|
||||||
frm.change_custom_button_type(__("Get Payment Entries"), null, "default");
|
|
||||||
frm.change_custom_button_type(__("Update Clearance Date"), null, "primary");
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -305,7 +305,7 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
|
|
||||||
set_dynamic_labels: function (frm) {
|
set_dynamic_labels: function (frm) {
|
||||||
var company_currency = frm.doc.company
|
var company_currency = frm.doc.company
|
||||||
? frappe.get_doc(":Company", frm.doc.company).default_currency
|
? frappe.get_doc(":Company", frm.doc.company)?.default_currency
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
frm.set_currency_labels(
|
frm.set_currency_labels(
|
||||||
@@ -658,7 +658,7 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
frm.set_value("source_exchange_rate", 1);
|
frm.set_value("source_exchange_rate", 1);
|
||||||
} else if (frm.doc.paid_from) {
|
} else if (frm.doc.paid_from) {
|
||||||
if (["Internal Transfer", "Pay"].includes(frm.doc.payment_type)) {
|
if (["Internal Transfer", "Pay"].includes(frm.doc.payment_type)) {
|
||||||
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
|
let company_currency = frappe.get_doc(":Company", frm.doc.company)?.default_currency;
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.setup.utils.get_exchange_rate",
|
method: "erpnext.setup.utils.get_exchange_rate",
|
||||||
args: {
|
args: {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"transaction_date",
|
"transaction_date",
|
||||||
"column_break_2",
|
"column_break_2",
|
||||||
"naming_series",
|
"naming_series",
|
||||||
|
"company",
|
||||||
"mode_of_payment",
|
"mode_of_payment",
|
||||||
"party_details",
|
"party_details",
|
||||||
"party_type",
|
"party_type",
|
||||||
@@ -390,13 +391,20 @@
|
|||||||
"options": "Payment Request",
|
"options": "Payment Request",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-06-20 13:54:55.245774",
|
"modified": "2024-08-07 16:39:54.288002",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Request",
|
"name": "Payment Request",
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ class PaymentRequest(Document):
|
|||||||
subscription_plans: DF.Table[SubscriptionPlanDetail]
|
subscription_plans: DF.Table[SubscriptionPlanDetail]
|
||||||
swift_number: DF.ReadOnly | None
|
swift_number: DF.ReadOnly | None
|
||||||
transaction_date: DF.Date | None
|
transaction_date: DF.Date | None
|
||||||
|
company: DF.Link | None
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
@@ -491,6 +492,7 @@ def make_payment_request(**args):
|
|||||||
"message": gateway_account.get("message") or get_dummy_message(ref_doc),
|
"message": gateway_account.get("message") or get_dummy_message(ref_doc),
|
||||||
"reference_doctype": args.dt,
|
"reference_doctype": args.dt,
|
||||||
"reference_name": args.dn,
|
"reference_name": args.dn,
|
||||||
|
"company": ref_doc.get("company"),
|
||||||
"party_type": args.get("party_type") or "Customer",
|
"party_type": args.get("party_type") or "Customer",
|
||||||
"party": args.get("party") or ref_doc.get("customer"),
|
"party": args.get("party") or ref_doc.get("customer"),
|
||||||
"bank_account": bank_account,
|
"bank_account": bank_account,
|
||||||
|
|||||||
@@ -1262,7 +1262,11 @@ class PurchaseInvoice(BuyingController):
|
|||||||
def update_gross_purchase_amount_for_linked_assets(self, item):
|
def update_gross_purchase_amount_for_linked_assets(self, item):
|
||||||
assets = frappe.db.get_all(
|
assets = frappe.db.get_all(
|
||||||
"Asset",
|
"Asset",
|
||||||
filters={"purchase_invoice": self.name, "item_code": item.item_code},
|
filters={
|
||||||
|
"purchase_invoice": self.name,
|
||||||
|
"item_code": item.item_code,
|
||||||
|
"purchase_invoice_item": ("in", [item.name, ""]),
|
||||||
|
},
|
||||||
fields=["name", "asset_quantity"],
|
fields=["name", "asset_quantity"],
|
||||||
)
|
)
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
|
|||||||
@@ -46,4 +46,20 @@ frappe.query_reports["Bank Reconciliation Statement"] = {
|
|||||||
fieldtype: "Check",
|
fieldtype: "Check",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
formatter: function (value, row, column, data, default_formatter, filter) {
|
||||||
|
if (column.fieldname == "payment_entry" && value == "Cheques and Deposits incorrectly cleared") {
|
||||||
|
column.link_onclick =
|
||||||
|
"frappe.query_reports['Bank Reconciliation Statement'].open_utility_report()";
|
||||||
|
}
|
||||||
|
return default_formatter(value, row, column, data);
|
||||||
|
},
|
||||||
|
open_utility_report: function () {
|
||||||
|
frappe.route_options = {
|
||||||
|
company: frappe.query_report.get_filter_value("company"),
|
||||||
|
account: frappe.query_report.get_filter_value("account"),
|
||||||
|
report_date: frappe.query_report.get_filter_value("report_date"),
|
||||||
|
};
|
||||||
|
frappe.open_in_new_tab = true;
|
||||||
|
frappe.set_route("query-report", "Cheques and Deposits Incorrectly cleared");
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -670,6 +670,11 @@ frappe.ui.form.on("Asset", {
|
|||||||
if (item.asset_location) {
|
if (item.asset_location) {
|
||||||
frm.set_value("location", item.asset_location);
|
frm.set_value("location", item.asset_location);
|
||||||
}
|
}
|
||||||
|
if (doctype === "Purchase Receipt") {
|
||||||
|
frm.set_value("purchase_receipt_item", item.name);
|
||||||
|
} else if (doctype === "Purchase Invoice") {
|
||||||
|
frm.set_value("purchase_invoice_item", item.name);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -33,14 +33,16 @@
|
|||||||
"dimension_col_break",
|
"dimension_col_break",
|
||||||
"purchase_details_section",
|
"purchase_details_section",
|
||||||
"purchase_receipt",
|
"purchase_receipt",
|
||||||
|
"purchase_receipt_item",
|
||||||
"purchase_invoice",
|
"purchase_invoice",
|
||||||
|
"purchase_invoice_item",
|
||||||
|
"purchase_date",
|
||||||
"available_for_use_date",
|
"available_for_use_date",
|
||||||
"total_asset_cost",
|
|
||||||
"additional_asset_cost",
|
|
||||||
"column_break_23",
|
"column_break_23",
|
||||||
"gross_purchase_amount",
|
"gross_purchase_amount",
|
||||||
"asset_quantity",
|
"asset_quantity",
|
||||||
"purchase_date",
|
"additional_asset_cost",
|
||||||
|
"total_asset_cost",
|
||||||
"section_break_23",
|
"section_break_23",
|
||||||
"calculate_depreciation",
|
"calculate_depreciation",
|
||||||
"column_break_33",
|
"column_break_33",
|
||||||
@@ -536,6 +538,20 @@
|
|||||||
"fieldname": "opening_number_of_booked_depreciations",
|
"fieldname": "opening_number_of_booked_depreciations",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"label": "Opening Number of Booked Depreciations"
|
"label": "Opening Number of Booked Depreciations"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "purchase_receipt_item",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Purchase Receipt Item",
|
||||||
|
"options": "Purchase Receipt Item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "purchase_invoice_item",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Purchase Invoice Item",
|
||||||
|
"options": "Purchase Invoice Item"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 72,
|
"idx": 72,
|
||||||
@@ -579,7 +595,7 @@
|
|||||||
"link_fieldname": "target_asset"
|
"link_fieldname": "target_asset"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2024-08-01 16:39:09.340973",
|
"modified": "2024-08-26 23:28:29.095139",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset",
|
"name": "Asset",
|
||||||
|
|||||||
@@ -94,7 +94,9 @@ class Asset(AccountsController):
|
|||||||
purchase_amount: DF.Currency
|
purchase_amount: DF.Currency
|
||||||
purchase_date: DF.Date | None
|
purchase_date: DF.Date | None
|
||||||
purchase_invoice: DF.Link | None
|
purchase_invoice: DF.Link | None
|
||||||
|
purchase_invoice_item: DF.Link | None
|
||||||
purchase_receipt: DF.Link | None
|
purchase_receipt: DF.Link | None
|
||||||
|
purchase_receipt_item: DF.Link | None
|
||||||
split_from: DF.Link | None
|
split_from: DF.Link | None
|
||||||
status: DF.Literal[
|
status: DF.Literal[
|
||||||
"Draft",
|
"Draft",
|
||||||
@@ -691,12 +693,17 @@ class Asset(AccountsController):
|
|||||||
return cwip_account
|
return cwip_account
|
||||||
|
|
||||||
def make_gl_entries(self):
|
def make_gl_entries(self):
|
||||||
|
if self.check_asset_capitalization_gl_entries():
|
||||||
|
return
|
||||||
|
|
||||||
gl_entries = []
|
gl_entries = []
|
||||||
|
|
||||||
purchase_document = self.get_purchase_document()
|
purchase_document = self.get_purchase_document()
|
||||||
fixed_asset_account, cwip_account = self.get_fixed_asset_account(), self.get_cwip_account()
|
fixed_asset_account, cwip_account = self.get_fixed_asset_account(), self.get_cwip_account()
|
||||||
|
|
||||||
if purchase_document and self.purchase_amount and getdate(self.available_for_use_date) <= getdate():
|
if (self.is_composite_asset or (purchase_document and self.purchase_amount)) and getdate(
|
||||||
|
self.available_for_use_date
|
||||||
|
) <= getdate():
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
self.get_gl_dict(
|
self.get_gl_dict(
|
||||||
{
|
{
|
||||||
@@ -733,6 +740,24 @@ class Asset(AccountsController):
|
|||||||
make_gl_entries(gl_entries)
|
make_gl_entries(gl_entries)
|
||||||
self.db_set("booked_fixed_asset", 1)
|
self.db_set("booked_fixed_asset", 1)
|
||||||
|
|
||||||
|
def check_asset_capitalization_gl_entries(self):
|
||||||
|
if self.is_composite_asset:
|
||||||
|
result = frappe.db.get_value(
|
||||||
|
"Asset Capitalization",
|
||||||
|
{"target_asset": self.name, "docstatus": 1},
|
||||||
|
["name", "target_fixed_asset_account"],
|
||||||
|
)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
asset_capitalization, target_fixed_asset_account = result
|
||||||
|
# Check GL entries for the retrieved Asset Capitalization and target fixed asset account
|
||||||
|
return has_gl_entries(
|
||||||
|
"Asset Capitalization", asset_capitalization, target_fixed_asset_account
|
||||||
|
)
|
||||||
|
# return if there are no submitted capitalization for given asset
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_depreciation_rate(self, args, on_validate=False):
|
def get_depreciation_rate(self, args, on_validate=False):
|
||||||
if isinstance(args, str):
|
if isinstance(args, str):
|
||||||
@@ -779,6 +804,22 @@ class Asset(AccountsController):
|
|||||||
return flt((100 * (1 - depreciation_rate)), float_precision)
|
return flt((100 * (1 - depreciation_rate)), float_precision)
|
||||||
|
|
||||||
|
|
||||||
|
def has_gl_entries(doctype, docname, target_account):
|
||||||
|
gl_entry = frappe.qb.DocType("GL Entry")
|
||||||
|
gl_entries = (
|
||||||
|
frappe.qb.from_(gl_entry)
|
||||||
|
.select(gl_entry.account)
|
||||||
|
.where(
|
||||||
|
(gl_entry.voucher_type == doctype)
|
||||||
|
& (gl_entry.voucher_no == docname)
|
||||||
|
& (gl_entry.debit != 0)
|
||||||
|
& (gl_entry.account == target_account)
|
||||||
|
)
|
||||||
|
.run(as_dict=True)
|
||||||
|
)
|
||||||
|
return len(gl_entries) > 0
|
||||||
|
|
||||||
|
|
||||||
def update_maintenance_status():
|
def update_maintenance_status():
|
||||||
assets = frappe.get_all(
|
assets = frappe.get_all(
|
||||||
"Asset", filters={"docstatus": 1, "maintenance_required": 1, "disposal_date": ("is", "not set")}
|
"Asset", filters={"docstatus": 1, "maintenance_required": 1, "disposal_date": ("is", "not set")}
|
||||||
|
|||||||
@@ -317,7 +317,16 @@ class AssetCapitalization(StockController):
|
|||||||
if not self.target_is_fixed_asset and not self.get("asset_items"):
|
if not self.target_is_fixed_asset and not self.get("asset_items"):
|
||||||
frappe.throw(_("Consumed Asset Items is mandatory for Decapitalization"))
|
frappe.throw(_("Consumed Asset Items is mandatory for Decapitalization"))
|
||||||
|
|
||||||
if not (self.get("stock_items") or self.get("asset_items") or self.get("service_items")):
|
if self.capitalization_method == "Create a new composite asset" and not (
|
||||||
|
self.get("stock_items") or self.get("asset_items")
|
||||||
|
):
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"Consumed Stock Items or Consumed Asset Items are mandatory for creating new composite asset"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
elif not (self.get("stock_items") or self.get("asset_items") or self.get("service_items")):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"Consumed Stock Items, Consumed Asset Items or Consumed Service Items is mandatory for Capitalization"
|
"Consumed Stock Items, Consumed Asset Items or Consumed Service Items is mandatory for Capitalization"
|
||||||
@@ -460,13 +469,24 @@ class AssetCapitalization(StockController):
|
|||||||
self.get_gl_entries_for_consumed_asset_items(gl_entries, target_account, target_against, precision)
|
self.get_gl_entries_for_consumed_asset_items(gl_entries, target_account, target_against, precision)
|
||||||
self.get_gl_entries_for_consumed_service_items(gl_entries, target_account, target_against, precision)
|
self.get_gl_entries_for_consumed_service_items(gl_entries, target_account, target_against, precision)
|
||||||
|
|
||||||
self.get_gl_entries_for_target_item(gl_entries, target_against, precision)
|
self.get_gl_entries_for_target_item(gl_entries, target_account, target_against, precision)
|
||||||
|
|
||||||
return gl_entries
|
return gl_entries
|
||||||
|
|
||||||
def get_target_account(self):
|
def get_target_account(self):
|
||||||
if self.target_is_fixed_asset:
|
if self.target_is_fixed_asset:
|
||||||
return self.target_fixed_asset_account
|
from erpnext.assets.doctype.asset.asset import is_cwip_accounting_enabled
|
||||||
|
|
||||||
|
asset_category = frappe.get_cached_value("Asset", self.target_asset, "asset_category")
|
||||||
|
if is_cwip_accounting_enabled(asset_category):
|
||||||
|
target_account = get_asset_category_account(
|
||||||
|
"capital_work_in_progress_account",
|
||||||
|
asset_category=asset_category,
|
||||||
|
company=self.company,
|
||||||
|
)
|
||||||
|
return target_account if target_account else self.target_fixed_asset_account
|
||||||
|
else:
|
||||||
|
return self.target_fixed_asset_account
|
||||||
else:
|
else:
|
||||||
return self.warehouse_account[self.target_warehouse]["account"]
|
return self.warehouse_account[self.target_warehouse]["account"]
|
||||||
|
|
||||||
@@ -554,13 +574,13 @@ class AssetCapitalization(StockController):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_gl_entries_for_target_item(self, gl_entries, target_against, precision):
|
def get_gl_entries_for_target_item(self, gl_entries, target_account, target_against, precision):
|
||||||
if self.target_is_fixed_asset:
|
if self.target_is_fixed_asset:
|
||||||
# Capitalization
|
# Capitalization
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
self.get_gl_dict(
|
self.get_gl_dict(
|
||||||
{
|
{
|
||||||
"account": self.target_fixed_asset_account,
|
"account": target_account,
|
||||||
"against": ", ".join(target_against),
|
"against": ", ".join(target_against),
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
||||||
"debit": flt(self.total_value, precision),
|
"debit": flt(self.total_value, precision),
|
||||||
|
|||||||
@@ -31,6 +31,12 @@ class TestAssetCapitalization(unittest.TestCase):
|
|||||||
def test_capitalization_with_perpetual_inventory(self):
|
def test_capitalization_with_perpetual_inventory(self):
|
||||||
company = "_Test Company with perpetual inventory"
|
company = "_Test Company with perpetual inventory"
|
||||||
set_depreciation_settings_in_company(company=company)
|
set_depreciation_settings_in_company(company=company)
|
||||||
|
name = frappe.db.get_value(
|
||||||
|
"Asset Category Account",
|
||||||
|
filters={"parent": "Computers", "company_name": company},
|
||||||
|
fieldname=["name"],
|
||||||
|
)
|
||||||
|
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", "")
|
||||||
|
|
||||||
# Variables
|
# Variables
|
||||||
consumed_asset_value = 100000
|
consumed_asset_value = 100000
|
||||||
@@ -187,9 +193,10 @@ class TestAssetCapitalization(unittest.TestCase):
|
|||||||
# Test General Ledger Entries
|
# Test General Ledger Entries
|
||||||
default_expense_account = frappe.db.get_value("Company", company, "default_expense_account")
|
default_expense_account = frappe.db.get_value("Company", company, "default_expense_account")
|
||||||
expected_gle = {
|
expected_gle = {
|
||||||
"_Test Fixed Asset - _TC": 3000,
|
"_Test Fixed Asset - _TC": -100000.0,
|
||||||
"Expenses Included In Asset Valuation - _TC": -1000,
|
default_expense_account: -2000.0,
|
||||||
default_expense_account: -2000,
|
"CWIP Account - _TC": 103000.0,
|
||||||
|
"Expenses Included In Asset Valuation - _TC": -1000.0,
|
||||||
}
|
}
|
||||||
actual_gle = get_actual_gle_dict(asset_capitalization.name)
|
actual_gle = get_actual_gle_dict(asset_capitalization.name)
|
||||||
|
|
||||||
@@ -214,6 +221,12 @@ class TestAssetCapitalization(unittest.TestCase):
|
|||||||
def test_capitalization_with_wip_composite_asset(self):
|
def test_capitalization_with_wip_composite_asset(self):
|
||||||
company = "_Test Company with perpetual inventory"
|
company = "_Test Company with perpetual inventory"
|
||||||
set_depreciation_settings_in_company(company=company)
|
set_depreciation_settings_in_company(company=company)
|
||||||
|
name = frappe.db.get_value(
|
||||||
|
"Asset Category Account",
|
||||||
|
filters={"parent": "Computers", "company_name": company},
|
||||||
|
fieldname=["name"],
|
||||||
|
)
|
||||||
|
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", "")
|
||||||
|
|
||||||
stock_rate = 1000
|
stock_rate = 1000
|
||||||
stock_qty = 2
|
stock_qty = 2
|
||||||
@@ -424,7 +437,7 @@ class TestAssetCapitalization(unittest.TestCase):
|
|||||||
self.assertEqual(target_asset.purchase_amount, total_amount)
|
self.assertEqual(target_asset.purchase_amount, total_amount)
|
||||||
|
|
||||||
expected_gle = {
|
expected_gle = {
|
||||||
"_Test Fixed Asset - _TC": 1000.0,
|
"CWIP Account - _TC": 1000.0,
|
||||||
"Expenses Included In Asset Valuation - _TC": -1000.0,
|
"Expenses Included In Asset Valuation - _TC": -1000.0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -822,6 +822,8 @@ class BuyingController(SubcontractingController):
|
|||||||
"asset_quantity": asset_quantity,
|
"asset_quantity": asset_quantity,
|
||||||
"purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None,
|
"purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None,
|
||||||
"purchase_invoice": self.name if self.doctype == "Purchase Invoice" else None,
|
"purchase_invoice": self.name if self.doctype == "Purchase Invoice" else None,
|
||||||
|
"purchase_receipt_item": row.name if self.doctype == "Purchase Receipt" else None,
|
||||||
|
"purchase_invoice_item": row.name if self.doctype == "Purchase Invoice" else None,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -366,7 +366,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
|
|
||||||
|
|
||||||
def get_empty_batches(filters, start, page_len, filtered_batches=None, txt=None):
|
def get_empty_batches(filters, start, page_len, filtered_batches=None, txt=None):
|
||||||
query_filter = {"item": filters.get("item_code")}
|
query_filter = {"item": filters.get("item_code"), "disabled": 0}
|
||||||
if txt:
|
if txt:
|
||||||
query_filter["name"] = ("like", f"%{txt}%")
|
query_filter["name"] = ("like", f"%{txt}%")
|
||||||
|
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ add_to_apps_screen = [
|
|||||||
"logo": "/assets/erpnext/images/erpnext-logo-blue.png",
|
"logo": "/assets/erpnext/images/erpnext-logo-blue.png",
|
||||||
"title": "ERPNext",
|
"title": "ERPNext",
|
||||||
"route": "/app/home",
|
"route": "/app/home",
|
||||||
# "has_permission": "erpnext.api.permission.has_app_permission"
|
"has_permission": "erpnext.check_app_permission",
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
develop_version = "14.x.x-develop"
|
develop_version = "15.x.x-develop"
|
||||||
|
|
||||||
app_include_js = "erpnext.bundle.js"
|
app_include_js = "erpnext.bundle.js"
|
||||||
app_include_css = "erpnext.bundle.css"
|
app_include_css = "erpnext.bundle.css"
|
||||||
|
|||||||
@@ -5,18 +5,17 @@
|
|||||||
"document_type": "Document",
|
"document_type": "Document",
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
|
"bom_and_work_order_tab",
|
||||||
"raw_materials_consumption_section",
|
"raw_materials_consumption_section",
|
||||||
"material_consumption",
|
"material_consumption",
|
||||||
"get_rm_cost_from_consumption_entry",
|
"get_rm_cost_from_consumption_entry",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"backflush_raw_materials_based_on",
|
"backflush_raw_materials_based_on",
|
||||||
"capacity_planning",
|
"validate_components_quantities_per_bom",
|
||||||
"disable_capacity_planning",
|
"bom_section",
|
||||||
"allow_overtime",
|
"update_bom_costs_automatically",
|
||||||
"allow_production_on_holidays",
|
"column_break_lhyt",
|
||||||
"column_break_5",
|
"manufacture_sub_assembly_in_operation",
|
||||||
"capacity_planning_for_days",
|
|
||||||
"mins_between_operations",
|
|
||||||
"section_break_6",
|
"section_break_6",
|
||||||
"default_wip_warehouse",
|
"default_wip_warehouse",
|
||||||
"default_fg_warehouse",
|
"default_fg_warehouse",
|
||||||
@@ -30,8 +29,14 @@
|
|||||||
"add_corrective_operation_cost_in_finished_good_valuation",
|
"add_corrective_operation_cost_in_finished_good_valuation",
|
||||||
"column_break_24",
|
"column_break_24",
|
||||||
"job_card_excess_transfer",
|
"job_card_excess_transfer",
|
||||||
|
"capacity_planning",
|
||||||
|
"disable_capacity_planning",
|
||||||
|
"allow_overtime",
|
||||||
|
"allow_production_on_holidays",
|
||||||
|
"column_break_5",
|
||||||
|
"capacity_planning_for_days",
|
||||||
|
"mins_between_operations",
|
||||||
"other_settings_section",
|
"other_settings_section",
|
||||||
"update_bom_costs_automatically",
|
|
||||||
"set_op_cost_and_scrape_from_sub_assemblies",
|
"set_op_cost_and_scrape_from_sub_assemblies",
|
||||||
"column_break_23",
|
"column_break_23",
|
||||||
"make_serial_no_batch_from_work_order"
|
"make_serial_no_batch_from_work_order"
|
||||||
@@ -149,7 +154,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "raw_materials_consumption_section",
|
"fieldname": "raw_materials_consumption_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Raw Materials Consumption"
|
"label": "Raw Materials Consumption "
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_16",
|
"fieldname": "column_break_16",
|
||||||
@@ -183,8 +188,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "job_card_section",
|
"fieldname": "job_card_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Job Card"
|
"label": "Job Card and Capacity Planning"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_24",
|
"fieldname": "column_break_24",
|
||||||
@@ -210,13 +215,41 @@
|
|||||||
"fieldname": "get_rm_cost_from_consumption_entry",
|
"fieldname": "get_rm_cost_from_consumption_entry",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Get Raw Materials Cost from Consumption Entry"
|
"label": "Get Raw Materials Cost from Consumption Entry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "bom_and_work_order_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "BOM and Production"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "bom_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "BOM"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_lhyt",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "If enabled then system will manufacture Sub-assembly against the Job Card (operation).",
|
||||||
|
"fieldname": "manufacture_sub_assembly_in_operation",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Manufacture Sub-assembly in Operation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval:doc.backflush_raw_materials_based_on == \"BOM\"",
|
||||||
|
"fieldname": "validate_components_quantities_per_bom",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Validate Components Quantities Per BOM"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-wrench",
|
"icon": "icon-wrench",
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-02-08 19:00:37.561244",
|
"modified": "2024-09-02 12:12:03.132567",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Manufacturing Settings",
|
"name": "Manufacturing Settings",
|
||||||
@@ -234,4 +267,4 @@
|
|||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,15 +29,22 @@ class ManufacturingSettings(Document):
|
|||||||
get_rm_cost_from_consumption_entry: DF.Check
|
get_rm_cost_from_consumption_entry: DF.Check
|
||||||
job_card_excess_transfer: DF.Check
|
job_card_excess_transfer: DF.Check
|
||||||
make_serial_no_batch_from_work_order: DF.Check
|
make_serial_no_batch_from_work_order: DF.Check
|
||||||
|
manufacture_sub_assembly_in_operation: DF.Check
|
||||||
material_consumption: DF.Check
|
material_consumption: DF.Check
|
||||||
mins_between_operations: DF.Int
|
mins_between_operations: DF.Int
|
||||||
overproduction_percentage_for_sales_order: DF.Percent
|
overproduction_percentage_for_sales_order: DF.Percent
|
||||||
overproduction_percentage_for_work_order: DF.Percent
|
overproduction_percentage_for_work_order: DF.Percent
|
||||||
set_op_cost_and_scrape_from_sub_assemblies: DF.Check
|
set_op_cost_and_scrape_from_sub_assemblies: DF.Check
|
||||||
update_bom_costs_automatically: DF.Check
|
update_bom_costs_automatically: DF.Check
|
||||||
|
validate_components_quantities_per_bom: DF.Check
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
pass
|
def before_save(self):
|
||||||
|
self.reset_values()
|
||||||
|
|
||||||
|
def reset_values(self):
|
||||||
|
if self.backflush_raw_materials_based_on != "BOM" and self.validate_components_quantities_per_bom:
|
||||||
|
self.validate_components_quantities_per_bom = 0
|
||||||
|
|
||||||
|
|
||||||
def get_mins_between_operations():
|
def get_mins_between_operations():
|
||||||
|
|||||||
@@ -2102,6 +2102,59 @@ class TestWorkOrder(FrappeTestCase):
|
|||||||
|
|
||||||
stock_entry.submit()
|
stock_entry.submit()
|
||||||
|
|
||||||
|
def test_components_qty_for_bom_based_manufacture_entry(self):
|
||||||
|
frappe.db.set_single_value("Manufacturing Settings", "backflush_raw_materials_based_on", "BOM")
|
||||||
|
frappe.db.set_single_value("Manufacturing Settings", "validate_components_quantities_per_bom", 1)
|
||||||
|
|
||||||
|
fg_item = "Test FG Item For Component Validation"
|
||||||
|
source_warehouse = "Stores - _TC"
|
||||||
|
raw_materials = ["Test Component Validation RM Item 1", "Test Component Validation RM Item 2"]
|
||||||
|
|
||||||
|
make_item(fg_item, {"is_stock_item": 1})
|
||||||
|
for item in raw_materials:
|
||||||
|
make_item(item, {"is_stock_item": 1})
|
||||||
|
test_stock_entry.make_stock_entry(
|
||||||
|
item_code=item,
|
||||||
|
target=source_warehouse,
|
||||||
|
qty=10,
|
||||||
|
basic_rate=100,
|
||||||
|
)
|
||||||
|
|
||||||
|
make_bom(item=fg_item, source_warehouse=source_warehouse, raw_materials=raw_materials)
|
||||||
|
|
||||||
|
wo = make_wo_order_test_record(
|
||||||
|
item=fg_item,
|
||||||
|
qty=10,
|
||||||
|
source_warehouse=source_warehouse,
|
||||||
|
)
|
||||||
|
|
||||||
|
transfer_entry = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture", 10))
|
||||||
|
transfer_entry.save()
|
||||||
|
for row in transfer_entry.items:
|
||||||
|
row.qty = 5
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, transfer_entry.save)
|
||||||
|
|
||||||
|
transfer_entry.reload()
|
||||||
|
for row in transfer_entry.items:
|
||||||
|
self.assertEqual(row.qty, 10)
|
||||||
|
|
||||||
|
transfer_entry.submit()
|
||||||
|
|
||||||
|
manufacture_entry = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 10))
|
||||||
|
manufacture_entry.save()
|
||||||
|
for row in manufacture_entry.items:
|
||||||
|
if not row.s_warehouse:
|
||||||
|
continue
|
||||||
|
|
||||||
|
row.qty = 5
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, manufacture_entry.save)
|
||||||
|
manufacture_entry.reload()
|
||||||
|
manufacture_entry.submit()
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Manufacturing Settings", "validate_components_quantities_per_bom", 0)
|
||||||
|
|
||||||
|
|
||||||
def make_operation(**kwargs):
|
def make_operation(**kwargs):
|
||||||
kwargs = frappe._dict(kwargs)
|
kwargs = frappe._dict(kwargs)
|
||||||
|
|||||||
@@ -374,3 +374,4 @@ erpnext.patches.v15_0.do_not_use_batchwise_valuation
|
|||||||
erpnext.patches.v15_0.drop_index_posting_datetime_from_sle
|
erpnext.patches.v15_0.drop_index_posting_datetime_from_sle
|
||||||
erpnext.patches.v15_0.add_disassembly_order_stock_entry_type #1
|
erpnext.patches.v15_0.add_disassembly_order_stock_entry_type #1
|
||||||
erpnext.patches.v15_0.set_standard_stock_entry_type
|
erpnext.patches.v15_0.set_standard_stock_entry_type
|
||||||
|
erpnext.patches.v15_0.link_purchase_item_to_asset_doc
|
||||||
|
|||||||
74
erpnext/patches/v15_0/link_purchase_item_to_asset_doc.py
Normal file
74
erpnext/patches/v15_0/link_purchase_item_to_asset_doc.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
if frappe.db.has_column("Asset", "purchase_invoice_item") and frappe.db.has_column(
|
||||||
|
"Asset", "purchase_receipt_item"
|
||||||
|
):
|
||||||
|
# Get all assets with their related Purchase Invoice and Purchase Receipt
|
||||||
|
assets = frappe.get_all(
|
||||||
|
"Asset",
|
||||||
|
filters={"docstatus": 0},
|
||||||
|
fields=[
|
||||||
|
"name",
|
||||||
|
"item_code",
|
||||||
|
"purchase_invoice",
|
||||||
|
"purchase_receipt",
|
||||||
|
"gross_purchase_amount",
|
||||||
|
"asset_quantity",
|
||||||
|
"purchase_invoice_item",
|
||||||
|
"purchase_receipt_item",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
for asset in assets:
|
||||||
|
# Get Purchase Invoice Items
|
||||||
|
if asset.purchase_invoice and not asset.purchase_invoice_item:
|
||||||
|
purchase_invoice_item = get_linked_item(
|
||||||
|
"Purchase Invoice Item",
|
||||||
|
asset.purchase_invoice,
|
||||||
|
asset.item_code,
|
||||||
|
asset.gross_purchase_amount,
|
||||||
|
asset.asset_quantity,
|
||||||
|
)
|
||||||
|
frappe.db.set_value("Asset", asset.name, "purchase_invoice_item", purchase_invoice_item)
|
||||||
|
|
||||||
|
# Get Purchase Receipt Items
|
||||||
|
if asset.purchase_receipt and not asset.purchase_receipt_item:
|
||||||
|
purchase_receipt_item = get_linked_item(
|
||||||
|
"Purchase Receipt Item",
|
||||||
|
asset.purchase_receipt,
|
||||||
|
asset.item_code,
|
||||||
|
asset.gross_purchase_amount,
|
||||||
|
asset.asset_quantity,
|
||||||
|
)
|
||||||
|
frappe.db.set_value("Asset", asset.name, "purchase_receipt_item", purchase_receipt_item)
|
||||||
|
|
||||||
|
|
||||||
|
def get_linked_item(doctype, parent, item_code, amount, quantity):
|
||||||
|
items = frappe.get_all(
|
||||||
|
doctype,
|
||||||
|
filters={
|
||||||
|
"parenttype": doctype.replace(" Item", ""),
|
||||||
|
"parent": parent,
|
||||||
|
"item_code": item_code,
|
||||||
|
},
|
||||||
|
fields=["name", "rate", "amount", "qty", "landed_cost_voucher_amount"],
|
||||||
|
)
|
||||||
|
if len(items) == 1:
|
||||||
|
# If only one item exists, return it directly
|
||||||
|
return items[0].name
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
landed_cost = item.get("landed_cost_voucher_amount", 0)
|
||||||
|
# Check if the asset is grouped
|
||||||
|
if quantity > 1:
|
||||||
|
if item.amount + landed_cost == amount and item.qty == quantity:
|
||||||
|
return item.name
|
||||||
|
elif item.qty == quantity:
|
||||||
|
return item.name
|
||||||
|
else:
|
||||||
|
if item.rate + (landed_cost / item.qty) == amount:
|
||||||
|
return item.name
|
||||||
|
|
||||||
|
return items[0].name if items else None
|
||||||
@@ -116,14 +116,17 @@ erpnext.financial_statements = {
|
|||||||
erpnext.financial_statements.filters = get_filters();
|
erpnext.financial_statements.filters = get_filters();
|
||||||
|
|
||||||
let fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today());
|
let fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today());
|
||||||
|
var filters = report.get_values();
|
||||||
|
|
||||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function (r) {
|
if (!filters.period_start_date || !filters.period_end_date) {
|
||||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
frappe.model.with_doc("Fiscal Year", fiscal_year, function (r) {
|
||||||
frappe.query_report.set_filter_value({
|
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||||
period_start_date: fy.year_start_date,
|
frappe.query_report.set_filter_value({
|
||||||
period_end_date: fy.year_end_date,
|
period_start_date: fy.year_start_date,
|
||||||
|
period_end_date: fy.year_end_date,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
if (report.page) {
|
if (report.page) {
|
||||||
const views_menu = report.page.add_custom_button_group(__("Financial Statements"));
|
const views_menu = report.page.add_custom_button_group(__("Financial Statements"));
|
||||||
|
|||||||
@@ -368,8 +368,28 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_batch_qty(batch_no, callback) {
|
||||||
|
let warehouse = this.item.s_warehouse || this.item.t_warehouse || this.item.warehouse;
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.stock.doctype.batch.batch.get_batch_qty",
|
||||||
|
args: {
|
||||||
|
batch_no: batch_no,
|
||||||
|
warehouse: warehouse,
|
||||||
|
item_code: this.item.item_code,
|
||||||
|
posting_date: this.frm.doc.posting_date,
|
||||||
|
posting_time: this.frm.doc.posting_time,
|
||||||
|
},
|
||||||
|
callback: (r) => {
|
||||||
|
if (r.message) {
|
||||||
|
callback(flt(r.message));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
get_dialog_table_fields() {
|
get_dialog_table_fields() {
|
||||||
let fields = [];
|
let fields = [];
|
||||||
|
let me = this;
|
||||||
|
|
||||||
if (this.item.has_serial_no) {
|
if (this.item.has_serial_no) {
|
||||||
fields.push({
|
fields.push({
|
||||||
@@ -395,6 +415,15 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
|||||||
fieldname: "batch_no",
|
fieldname: "batch_no",
|
||||||
label: __("Batch No"),
|
label: __("Batch No"),
|
||||||
in_list_view: 1,
|
in_list_view: 1,
|
||||||
|
change() {
|
||||||
|
let doc = this.doc;
|
||||||
|
if (!doc.qty && me.item.type_of_transaction === "Outward") {
|
||||||
|
me.get_batch_qty(doc.batch_no, (qty) => {
|
||||||
|
doc.qty = qty;
|
||||||
|
this.grid.set_value("qty", qty, doc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
get_query: () => {
|
get_query: () => {
|
||||||
let is_inward = false;
|
let is_inward = false;
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ frappe.ui.form.on("Company", {
|
|||||||
},
|
},
|
||||||
setup: function (frm) {
|
setup: function (frm) {
|
||||||
frm.__rename_queue = "long";
|
frm.__rename_queue = "long";
|
||||||
erpnext.company.setup_queries(frm);
|
|
||||||
|
|
||||||
frm.set_query("parent_company", function () {
|
frm.set_query("parent_company", function () {
|
||||||
return {
|
return {
|
||||||
@@ -81,6 +80,8 @@ frappe.ui.form.on("Company", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
refresh: function (frm) {
|
refresh: function (frm) {
|
||||||
|
erpnext.company.setup_queries(frm);
|
||||||
|
|
||||||
frm.toggle_display("address_html", !frm.is_new());
|
frm.toggle_display("address_html", !frm.is_new());
|
||||||
|
|
||||||
if (!frm.is_new()) {
|
if (!frm.is_new()) {
|
||||||
|
|||||||
@@ -837,7 +837,11 @@ class PurchaseReceipt(BuyingController):
|
|||||||
def update_assets(self, item, valuation_rate):
|
def update_assets(self, item, valuation_rate):
|
||||||
assets = frappe.db.get_all(
|
assets = frappe.db.get_all(
|
||||||
"Asset",
|
"Asset",
|
||||||
filters={"purchase_receipt": self.name, "item_code": item.item_code},
|
filters={
|
||||||
|
"purchase_receipt": self.name,
|
||||||
|
"item_code": item.item_code,
|
||||||
|
"purchase_receipt_item": ("in", [item.name, ""]),
|
||||||
|
},
|
||||||
fields=["name", "asset_quantity"],
|
fields=["name", "asset_quantity"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -232,6 +232,7 @@ class StockEntry(StockController):
|
|||||||
self.validate_serialized_batch()
|
self.validate_serialized_batch()
|
||||||
self.calculate_rate_and_amount()
|
self.calculate_rate_and_amount()
|
||||||
self.validate_putaway_capacity()
|
self.validate_putaway_capacity()
|
||||||
|
self.validate_component_quantities()
|
||||||
|
|
||||||
if not self.get("purpose") == "Manufacture":
|
if not self.get("purpose") == "Manufacture":
|
||||||
# ignore scrap item wh difference and empty source/target wh
|
# ignore scrap item wh difference and empty source/target wh
|
||||||
@@ -747,6 +748,34 @@ class StockEntry(StockController):
|
|||||||
title=_("Insufficient Stock"),
|
title=_("Insufficient Stock"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def validate_component_quantities(self):
|
||||||
|
if self.purpose not in ["Manufacture", "Material Transfer for Manufacture"]:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not frappe.db.get_single_value("Manufacturing Settings", "validate_components_quantities_per_bom"):
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.fg_completed_qty:
|
||||||
|
return
|
||||||
|
|
||||||
|
raw_materials = self.get_bom_raw_materials(self.fg_completed_qty)
|
||||||
|
|
||||||
|
precision = frappe.get_precision("Stock Entry Detail", "qty")
|
||||||
|
for row in self.items:
|
||||||
|
if not row.s_warehouse:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if details := raw_materials.get(row.item_code):
|
||||||
|
if flt(details.get("qty"), precision) != flt(row.qty, precision):
|
||||||
|
frappe.throw(
|
||||||
|
_("For the item {0}, the quantity should be {1} according to the BOM {2}.").format(
|
||||||
|
frappe.bold(row.item_code),
|
||||||
|
flt(details.get("qty"), precision),
|
||||||
|
get_link_to_form("BOM", self.bom_no),
|
||||||
|
),
|
||||||
|
title=_("Incorrect Component Quantity"),
|
||||||
|
)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_stock_and_rate(self):
|
def get_stock_and_rate(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -807,14 +807,10 @@ def get_price_list_rate(args, item_doc, out=None):
|
|||||||
if price_list_rate is None or frappe.db.get_single_value(
|
if price_list_rate is None or frappe.db.get_single_value(
|
||||||
"Stock Settings", "update_existing_price_list_rate"
|
"Stock Settings", "update_existing_price_list_rate"
|
||||||
):
|
):
|
||||||
if args.get("is_internal_supplier") or args.get("is_internal_customer"):
|
insert_item_price(args)
|
||||||
return out
|
|
||||||
|
|
||||||
if args.price_list and args.rate:
|
if price_list_rate is None:
|
||||||
insert_item_price(args)
|
return out
|
||||||
|
|
||||||
if not price_list_rate:
|
|
||||||
return out
|
|
||||||
|
|
||||||
out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) / flt(args.conversion_rate)
|
out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) / flt(args.conversion_rate)
|
||||||
|
|
||||||
@@ -835,6 +831,14 @@ def get_price_list_rate(args, item_doc, out=None):
|
|||||||
|
|
||||||
def insert_item_price(args):
|
def insert_item_price(args):
|
||||||
"""Insert Item Price if Price List and Price List Rate are specified and currency is the same"""
|
"""Insert Item Price if Price List and Price List Rate are specified and currency is the same"""
|
||||||
|
if (
|
||||||
|
not args.price_list
|
||||||
|
or not args.rate
|
||||||
|
or args.get("is_internal_supplier")
|
||||||
|
or args.get("is_internal_customer")
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
if frappe.db.get_value("Price List", args.price_list, "currency", cache=True) == args.currency and cint(
|
if frappe.db.get_value("Price List", args.price_list, "currency", cache=True) == args.currency and cint(
|
||||||
frappe.db.get_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing")
|
frappe.db.get_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing")
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -354,9 +354,14 @@ def get_email_list(company):
|
|||||||
|
|
||||||
|
|
||||||
def get_comapny_wise_users(company):
|
def get_comapny_wise_users(company):
|
||||||
|
companies = [company]
|
||||||
|
|
||||||
|
if parent_company := frappe.db.get_value("Company", company, "parent_company"):
|
||||||
|
companies.append(parent_company)
|
||||||
|
|
||||||
users = frappe.get_all(
|
users = frappe.get_all(
|
||||||
"User Permission",
|
"User Permission",
|
||||||
filters={"allow": "Company", "for_value": company, "apply_to_all_doctypes": 1},
|
filters={"allow": "Company", "for_value": ("in", companies), "apply_to_all_doctypes": 1},
|
||||||
fields=["user"],
|
fields=["user"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1532,7 +1532,7 @@ def get_previous_sle_of_current_voucher(args, operator="<", exclude_current_vouc
|
|||||||
operator = "<="
|
operator = "<="
|
||||||
voucher_condition = f"and creation < '{creation}'"
|
voucher_condition = f"and creation < '{creation}'"
|
||||||
|
|
||||||
sle = frappe.db.sql(
|
sle = frappe.db.sql( # nosemgrep
|
||||||
f"""
|
f"""
|
||||||
select *, posting_datetime as "timestamp"
|
select *, posting_datetime as "timestamp"
|
||||||
from `tabStock Ledger Entry`
|
from `tabStock Ledger Entry`
|
||||||
@@ -1629,6 +1629,7 @@ def get_stock_ledger_entries(
|
|||||||
if extra_cond:
|
if extra_cond:
|
||||||
conditions += f"{extra_cond}"
|
conditions += f"{extra_cond}"
|
||||||
|
|
||||||
|
# nosemgrep
|
||||||
return frappe.db.sql(
|
return frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
select *, posting_datetime as "timestamp"
|
select *, posting_datetime as "timestamp"
|
||||||
@@ -1744,7 +1745,7 @@ def get_valuation_rate(
|
|||||||
return batch_obj.get_incoming_rate()
|
return batch_obj.get_incoming_rate()
|
||||||
|
|
||||||
# Get valuation rate from last sle for the same item and warehouse
|
# Get valuation rate from last sle for the same item and warehouse
|
||||||
if last_valuation_rate := frappe.db.sql(
|
if last_valuation_rate := frappe.db.sql( # nosemgrep
|
||||||
"""select valuation_rate
|
"""select valuation_rate
|
||||||
from `tabStock Ledger Entry` force index (item_warehouse)
|
from `tabStock Ledger Entry` force index (item_warehouse)
|
||||||
where
|
where
|
||||||
@@ -1824,7 +1825,7 @@ def update_qty_in_future_sle(args, allow_negative_stock=False):
|
|||||||
detail = next_stock_reco_detail[0]
|
detail = next_stock_reco_detail[0]
|
||||||
datetime_limit_condition = get_datetime_limit_condition(detail)
|
datetime_limit_condition = get_datetime_limit_condition(detail)
|
||||||
|
|
||||||
frappe.db.sql(
|
frappe.db.sql( # nosemgrep
|
||||||
f"""
|
f"""
|
||||||
update `tabStock Ledger Entry`
|
update `tabStock Ledger Entry`
|
||||||
set qty_after_transaction = qty_after_transaction + {qty_shift}
|
set qty_after_transaction = qty_after_transaction + {qty_shift}
|
||||||
@@ -1990,8 +1991,8 @@ def is_negative_with_precision(neg_sle, is_batch=False):
|
|||||||
return qty_deficit < 0 and abs(qty_deficit) > 0.0001
|
return qty_deficit < 0 and abs(qty_deficit) > 0.0001
|
||||||
|
|
||||||
|
|
||||||
def get_future_sle_with_negative_qty(args):
|
def get_future_sle_with_negative_qty(sle_args):
|
||||||
return frappe.db.sql(
|
return frappe.db.sql( # nosemgrep
|
||||||
"""
|
"""
|
||||||
select
|
select
|
||||||
qty_after_transaction, posting_date, posting_time,
|
qty_after_transaction, posting_date, posting_time,
|
||||||
@@ -2007,13 +2008,13 @@ def get_future_sle_with_negative_qty(args):
|
|||||||
order by posting_date asc, posting_time asc
|
order by posting_date asc, posting_time asc
|
||||||
limit 1
|
limit 1
|
||||||
""",
|
""",
|
||||||
args,
|
sle_args,
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_future_sle_with_negative_batch_qty(args):
|
def get_future_sle_with_negative_batch_qty(sle_args):
|
||||||
return frappe.db.sql(
|
return frappe.db.sql( # nosemgrep
|
||||||
"""
|
"""
|
||||||
with batch_ledger as (
|
with batch_ledger as (
|
||||||
select
|
select
|
||||||
@@ -2033,7 +2034,7 @@ def get_future_sle_with_negative_batch_qty(args):
|
|||||||
and posting_datetime >= %(posting_datetime)s
|
and posting_datetime >= %(posting_datetime)s
|
||||||
limit 1
|
limit 1
|
||||||
""",
|
""",
|
||||||
args,
|
sle_args,
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user