Merge pull request #31860 from rohitwaghchaure/delete-custom-fields-on-dimension-delete
fix: delete custom fields on deletion of inventory dimension
diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml
index 4d2dc58..9e06254 100644
--- a/.github/workflows/patch.yml
+++ b/.github/workflows/patch.yml
@@ -11,7 +11,7 @@
workflow_dispatch:
concurrency:
- group: patch-develop-${{ github.event.number }}
+ group: patch-develop-${{ github.event_name }}-${{ github.event.number || github.event_name == 'workflow_dispatch' && github.run_id || '' }}
cancel-in-progress: true
jobs:
diff --git a/.github/workflows/server-tests-mariadb.yml b/.github/workflows/server-tests-mariadb.yml
index 64134bc..e3b92fd 100644
--- a/.github/workflows/server-tests-mariadb.yml
+++ b/.github/workflows/server-tests-mariadb.yml
@@ -27,7 +27,7 @@
type: string
concurrency:
- group: server-mariadb-develop-${{ github.event.number }}
+ group: server-mariadb-develop-${{ github.event_name }}-${{ github.event.number || github.event_name == 'workflow_dispatch' && github.run_id || '' }}
cancel-in-progress: true
jobs:
diff --git a/.github/workflows/server-tests-postgres.yml b/.github/workflows/server-tests-postgres.yml
index 651c935..df43801 100644
--- a/.github/workflows/server-tests-postgres.yml
+++ b/.github/workflows/server-tests-postgres.yml
@@ -9,7 +9,7 @@
types: [opened, labelled, synchronize, reopened]
concurrency:
- group: server-postgres-develop-${{ github.event.number }}
+ group: server-postgres-develop-${{ github.event_name }}-${{ github.event.number || github.event_name == 'workflow_dispatch' && github.run_id || '' }}
cancel-in-progress: true
jobs:
diff --git a/CODEOWNERS b/CODEOWNERS
index ecbae86..b6aadb3 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -11,22 +11,18 @@
erpnext/support/ @nextchamp-saqib @deepeshgarg007
pos* @nextchamp-saqib
-erpnext/buying/ @marination @rohitwaghchaure @s-aga-r
-erpnext/e_commerce/ @marination
-erpnext/maintenance/ @marination @rohitwaghchaure @s-aga-r
-erpnext/manufacturing/ @marination @rohitwaghchaure @s-aga-r
-erpnext/portal/ @marination
+erpnext/buying/ @rohitwaghchaure @s-aga-r
+erpnext/maintenance/ @rohitwaghchaure @s-aga-r
+erpnext/manufacturing/ @rohitwaghchaure @s-aga-r
erpnext/quality_management/ @marination @rohitwaghchaure @s-aga-r
-erpnext/shopping_cart/ @marination
erpnext/stock/ @marination @rohitwaghchaure @s-aga-r
-erpnext/crm/ @NagariaHussain
-erpnext/education/ @rutwikhdev
+erpnext/crm/ @NagariaHussain
+erpnext/education/ @rutwikhdev
erpnext/projects/ @ruchamahabal
-erpnext/controllers/ @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination
-erpnext/patches/ @deepeshgarg007 @nextchamp-saqib @marination
-erpnext/public/ @nextchamp-saqib @marination
+erpnext/controllers/ @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure
+erpnext/patches/ @deepeshgarg007 @nextchamp-saqib
.github/ @ankush
-pyproject.toml @gavindsouza @ankush
+pyproject.toml @ankush
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index af5a5e2..4618d08 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -181,7 +181,11 @@
frappe.throw(_("Party is mandatory"))
_party_name = "title" if self.party_type == "Shareholder" else self.party_type.lower() + "_name"
- self.party_name = frappe.db.get_value(self.party_type, self.party, _party_name)
+
+ if frappe.db.has_column(self.party_type, _party_name):
+ self.party_name = frappe.db.get_value(self.party_type, self.party, _party_name)
+ else:
+ self.party_name = frappe.db.get_value(self.party_type, self.party, "name")
if self.party:
if not self.party_balance:
@@ -295,6 +299,9 @@
def validate_reference_documents(self):
valid_reference_doctypes = self.get_valid_reference_doctypes()
+ if not valid_reference_doctypes:
+ return
+
for d in self.get("references"):
if not d.allocated_amount:
continue
@@ -362,7 +369,7 @@
if not d.allocated_amount:
continue
- if d.reference_doctype in ("Sales Invoice", "Purchase Invoice", "Fees"):
+ if d.reference_doctype in ("Sales Invoice", "Purchase Invoice"):
outstanding_amount, is_return = frappe.get_cached_value(
d.reference_doctype, d.reference_name, ["outstanding_amount", "is_return"]
)
@@ -1184,6 +1191,7 @@
ple = qb.DocType("Payment Ledger Entry")
common_filter = []
+ posting_and_due_date = []
# confirm that Supplier is not blocked
if args.get("party_type") == "Supplier":
@@ -1200,7 +1208,7 @@
party_account_currency = get_account_currency(args.get("party_account"))
company_currency = frappe.get_cached_value("Company", args.get("company"), "default_currency")
- # Get positive outstanding sales /purchase invoices/ Fees
+ # Get positive outstanding sales /purchase invoices
condition = ""
if args.get("voucher_type") and args.get("voucher_no"):
condition = " and voucher_type={0} and voucher_no={1}".format(
@@ -1224,7 +1232,7 @@
condition += " and {0} between '{1}' and '{2}'".format(
fieldname, args.get(date_fields[0]), args.get(date_fields[1])
)
- common_filter.append(ple[fieldname][args.get(date_fields[0]) : args.get(date_fields[1])])
+ posting_and_due_date.append(ple[fieldname][args.get(date_fields[0]) : args.get(date_fields[1])])
if args.get("company"):
condition += " and company = {0}".format(frappe.db.escape(args.get("company")))
@@ -1235,6 +1243,7 @@
args.get("party"),
args.get("party_account"),
common_filter=common_filter,
+ posting_date=posting_and_due_date,
min_outstanding=args.get("outstanding_amt_greater_than"),
max_outstanding=args.get("outstanding_amt_less_than"),
)
@@ -1595,10 +1604,11 @@
elif reference_doctype != "Journal Entry":
if not total_amount:
if party_account_currency == company_currency:
- total_amount = ref_doc.base_grand_total
+ # for handling cases that don't have multi-currency (base field)
+ total_amount = ref_doc.get("grand_total") or ref_doc.get("base_grand_total")
exchange_rate = 1
else:
- total_amount = ref_doc.grand_total
+ total_amount = ref_doc.get("grand_total")
if not exchange_rate:
# Get the exchange rate from the original ref doc
# or get it based on the posting date of the ref doc.
@@ -1609,7 +1619,7 @@
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
outstanding_amount = ref_doc.get("outstanding_amount")
else:
- outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
+ outstanding_amount = flt(total_amount) - flt(ref_doc.get("advance_paid"))
else:
# Get the exchange rate based on the posting date of the ref doc.
@@ -1627,16 +1637,23 @@
@frappe.whitelist()
-def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=None):
+def get_payment_entry(
+ dt, dn, party_amount=None, bank_account=None, bank_amount=None, party_type=None, payment_type=None
+):
reference_doc = None
doc = frappe.get_doc(dt, dn)
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) > 0:
frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
- party_type = set_party_type(dt)
+ if not party_type:
+ party_type = set_party_type(dt)
+
party_account = set_party_account(dt, dn, doc, party_type)
party_account_currency = set_party_account_currency(dt, party_account, doc)
- payment_type = set_payment_type(dt, doc)
+
+ if not payment_type:
+ payment_type = set_payment_type(dt, doc)
+
grand_total, outstanding_amount = set_grand_total_and_outstanding_amount(
party_amount, dt, party_account_currency, doc
)
@@ -1786,8 +1803,6 @@
party_account = get_party_account_based_on_invoice_discounting(dn) or doc.debit_to
elif dt == "Purchase Invoice":
party_account = doc.credit_to
- elif dt == "Fees":
- party_account = doc.receivable_account
else:
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
return party_account
@@ -1803,8 +1818,7 @@
def set_payment_type(dt, doc):
if (
- dt == "Sales Order"
- or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)
+ dt == "Sales Order" or (dt in ("Sales Invoice", "Dunning") and doc.outstanding_amount > 0)
) or (dt == "Purchase Invoice" and doc.outstanding_amount < 0):
payment_type = "Receive"
else:
@@ -1822,18 +1836,15 @@
else:
grand_total = doc.rounded_total or doc.grand_total
outstanding_amount = doc.outstanding_amount
- elif dt == "Fees":
- grand_total = doc.grand_total
- outstanding_amount = doc.outstanding_amount
elif dt == "Dunning":
grand_total = doc.grand_total
outstanding_amount = doc.grand_total
else:
if party_account_currency == doc.company_currency:
- grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
+ grand_total = flt(doc.get("base_rounded_total") or doc.get("base_grand_total"))
else:
- grand_total = flt(doc.get("rounded_total") or doc.grand_total)
- outstanding_amount = grand_total - flt(doc.advance_paid)
+ grand_total = flt(doc.get("rounded_total") or doc.get("grand_total"))
+ outstanding_amount = doc.get("outstanding_amount") or (grand_total - flt(doc.advance_paid))
return grand_total, outstanding_amount
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index 5ed34d3..601fc87 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -22,6 +22,7 @@
def __init__(self, *args, **kwargs):
super(PaymentReconciliation, self).__init__(*args, **kwargs)
self.common_filter_conditions = []
+ self.ple_posting_date_filter = []
@frappe.whitelist()
def get_unreconciled_entries(self):
@@ -150,6 +151,7 @@
return_outstanding = ple_query.get_voucher_outstandings(
vouchers=return_invoices,
common_filter=self.common_filter_conditions,
+ posting_date=self.ple_posting_date_filter,
min_outstanding=-(self.minimum_payment_amount) if self.minimum_payment_amount else None,
max_outstanding=-(self.maximum_payment_amount) if self.maximum_payment_amount else None,
get_payments=True,
@@ -187,6 +189,7 @@
self.party,
self.receivable_payable_account,
common_filter=self.common_filter_conditions,
+ posting_date=self.ple_posting_date_filter,
min_outstanding=self.minimum_invoice_amount if self.minimum_invoice_amount else None,
max_outstanding=self.maximum_invoice_amount if self.maximum_invoice_amount else None,
)
@@ -350,6 +353,7 @@
def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=False):
self.common_filter_conditions.clear()
+ self.ple_posting_date_filter.clear()
ple = qb.DocType("Payment Ledger Entry")
self.common_filter_conditions.append(ple.company == self.company)
@@ -359,15 +363,15 @@
if get_invoices:
if self.from_invoice_date:
- self.common_filter_conditions.append(ple.posting_date.gte(self.from_invoice_date))
+ self.ple_posting_date_filter.append(ple.posting_date.gte(self.from_invoice_date))
if self.to_invoice_date:
- self.common_filter_conditions.append(ple.posting_date.lte(self.to_invoice_date))
+ self.ple_posting_date_filter.append(ple.posting_date.lte(self.to_invoice_date))
elif get_return_invoices:
if self.from_payment_date:
- self.common_filter_conditions.append(ple.posting_date.gte(self.from_payment_date))
+ self.ple_posting_date_filter.append(ple.posting_date.gte(self.from_payment_date))
if self.to_payment_date:
- self.common_filter_conditions.append(ple.posting_date.lte(self.to_payment_date))
+ self.ple_posting_date_filter.append(ple.posting_date.lte(self.to_payment_date))
def get_conditions(self, get_payments=False):
condition = " and company = '{0}' ".format(self.company)
diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
index 625382a..dae029b 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
@@ -283,6 +283,41 @@
self.assertEqual(len(pr.get("invoices")), 2)
self.assertEqual(len(pr.get("payments")), 2)
+ def test_filter_posting_date_case2(self):
+ """
+ Posting date should not affect outstanding amount calculation
+ """
+
+ from_date = add_days(nowdate(), -30)
+ to_date = nowdate()
+ self.create_payment_entry(amount=25, posting_date=from_date).submit()
+ self.create_sales_invoice(rate=25, qty=1, posting_date=to_date)
+
+ pr = self.create_payment_reconciliation()
+ pr.from_invoice_date = pr.from_payment_date = from_date
+ pr.to_invoice_date = pr.to_payment_date = to_date
+ pr.get_unreconciled_entries()
+
+ self.assertEqual(len(pr.invoices), 1)
+ self.assertEqual(len(pr.payments), 1)
+
+ invoices = [x.as_dict() for x in pr.invoices]
+ payments = [x.as_dict() for x in pr.payments]
+ pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+ pr.reconcile()
+
+ pr.get_unreconciled_entries()
+
+ self.assertEqual(len(pr.invoices), 0)
+ self.assertEqual(len(pr.payments), 0)
+
+ pr.from_invoice_date = pr.from_payment_date = to_date
+ pr.to_invoice_date = pr.to_payment_date = to_date
+
+ pr.get_unreconciled_entries()
+
+ self.assertEqual(len(pr.invoices), 0)
+
def test_filter_invoice_limit(self):
# check filter condition - invoice limit
transaction_date = nowdate()
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index de3927e..e6ff128 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -1791,4 +1791,6 @@
target_doc,
)
+ doc.set_onload("ignore_price_list", True)
+
return doc
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index ba4cdd6..adcbb83 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -1024,7 +1024,7 @@
]
});
- dialog.set_primary_action(__("Set"), function() {
+ dialog.set_primary_action(__("Set Loyalty Program"), function() {
dialog.hide();
return frappe.call({
method: "frappe.client.set_value",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index f8c26d1..19a234d 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -2103,13 +2103,13 @@
target_detail_field = "sales_invoice_item" if doctype == "Sales Invoice" else "sales_order_item"
source_document_warehouse_field = "target_warehouse"
target_document_warehouse_field = "from_warehouse"
+ received_items = get_received_items(source_name, target_doctype, target_detail_field)
else:
source_doc = frappe.get_doc(doctype, source_name)
target_doctype = "Sales Invoice" if doctype == "Purchase Invoice" else "Sales Order"
source_document_warehouse_field = "from_warehouse"
target_document_warehouse_field = "target_warehouse"
-
- received_items = get_received_items(source_name, target_doctype, target_detail_field)
+ received_items = {}
validate_inter_company_transaction(source_doc, doctype)
details = get_inter_company_details(source_doc, doctype)
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index e39f22b..67cf644 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -207,7 +207,7 @@
)
if company_address:
- party_details.update({"company_address": company_address})
+ party_details.company_address = company_address
else:
party_details.update(get_company_address(company))
@@ -219,12 +219,31 @@
get_regional_address_details(party_details, doctype, company)
elif doctype and doctype in ["Purchase Invoice", "Purchase Order", "Purchase Receipt"]:
- if party_details.company_address:
- party_details["shipping_address"] = shipping_address or party_details["company_address"]
- party_details.shipping_address_display = get_address_display(party_details["shipping_address"])
+ if shipping_address:
party_details.update(
- get_fetch_values(doctype, "shipping_address", party_details.shipping_address)
+ shipping_address=shipping_address,
+ shipping_address_display=get_address_display(shipping_address),
+ **get_fetch_values(doctype, "shipping_address", shipping_address)
)
+
+ if party_details.company_address:
+ # billing address
+ party_details.update(
+ billing_address=party_details.company_address,
+ billing_address_display=(
+ party_details.company_address_display or get_address_display(party_details.company_address)
+ ),
+ **get_fetch_values(doctype, "billing_address", party_details.company_address)
+ )
+
+ # shipping address - if not already set
+ if not party_details.shipping_address:
+ party_details.update(
+ shipping_address=party_details.billing_address,
+ shipping_address_display=party_details.billing_address_display,
+ **get_fetch_values(doctype, "shipping_address", party_details.billing_address)
+ )
+
get_regional_address_details(party_details, doctype, company)
return party_details.get(billing_address_field), party_details.shipping_address_name
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index c7c746b..e937edb 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -165,7 +165,7 @@
"range4",
"range5",
"future_amount",
- "remaining_balance"
+ "remaining_balance",
]
def get_voucher_balance(self, ple):
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.js b/erpnext/accounts/report/gross_profit/gross_profit.js
index 3d37b58..21205c3 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.js
+++ b/erpnext/accounts/report/gross_profit/gross_profit.js
@@ -44,14 +44,14 @@
"parent_field": "parent_invoice",
"initial_depth": 3,
"formatter": function(value, row, column, data, default_formatter) {
- if (column.fieldname == "sales_invoice" && column.options == "Item" && data.indent == 0) {
+ if (column.fieldname == "sales_invoice" && column.options == "Item" && data && data.indent == 0) {
column._options = "Sales Invoice";
} else {
column._options = "Item";
}
value = default_formatter(value, row, column, data);
- if (data && (data.indent == 0.0 || row[1].content == "Total")) {
+ if (data && (data.indent == 0.0 || (row[1] && row[1].content == "Total"))) {
value = $(`<span>${value}</span>`);
var $value = $(value).css("font-weight", "bold");
value = $value.wrap("<p></p>").parent().html();
diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
index 08d2008..db3d5d4 100644
--- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
+++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
@@ -14,9 +14,9 @@
filters.naming_series = frappe.db.get_single_value("Buying Settings", "supp_master_name")
columns = get_columns(filters)
- tds_docs, tds_accounts, tax_category_map = get_tds_docs(filters)
+ tds_docs, tds_accounts, tax_category_map, journal_entry_party_map = get_tds_docs(filters)
- res = get_result(filters, tds_docs, tds_accounts, tax_category_map)
+ res = get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map)
final_result = group_by_supplier_and_category(res)
return columns, final_result
diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
index 16e0ac1..f2809a9 100644
--- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
+++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
@@ -26,7 +26,6 @@
supplier_map = get_supplier_pan_map()
tax_rate_map = get_tax_rate_map(filters)
gle_map = get_gle_map(tds_docs)
- print(journal_entry_party_map)
out = []
for name, details in gle_map.items():
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 9dafef7..018e8f9 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -823,7 +823,13 @@
def get_outstanding_invoices(
- party_type, party, account, common_filter=None, min_outstanding=None, max_outstanding=None
+ party_type,
+ party,
+ account,
+ common_filter=None,
+ posting_date=None,
+ min_outstanding=None,
+ max_outstanding=None,
):
ple = qb.DocType("Payment Ledger Entry")
@@ -850,6 +856,7 @@
ple_query = QueryPaymentLedger()
invoice_list = ple_query.get_voucher_outstandings(
common_filter=common_filter,
+ posting_date=posting_date,
min_outstanding=min_outstanding,
max_outstanding=max_outstanding,
get_invoices=True,
@@ -1501,6 +1508,7 @@
# query filters
self.vouchers = []
self.common_filter = []
+ self.voucher_posting_date = []
self.min_outstanding = None
self.max_outstanding = None
@@ -1571,6 +1579,7 @@
.where(ple.delinked == 0)
.where(Criterion.all(filter_on_voucher_no))
.where(Criterion.all(self.common_filter))
+ .where(Criterion.all(self.voucher_posting_date))
.groupby(ple.voucher_type, ple.voucher_no, ple.party_type, ple.party)
)
@@ -1652,6 +1661,7 @@
self,
vouchers=None,
common_filter=None,
+ posting_date=None,
min_outstanding=None,
max_outstanding=None,
get_payments=False,
@@ -1671,6 +1681,7 @@
self.reset()
self.vouchers = vouchers
self.common_filter = common_filter or []
+ self.voucher_posting_date = posting_date or []
self.min_outstanding = min_outstanding
self.max_outstanding = max_outstanding
self.get_payments = get_payments
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
index 4e29ee5..31a4837 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
@@ -15,9 +15,12 @@
frm.fields_dict["suppliers"].grid.get_field("contact").get_query = function(doc, cdt, cdn) {
let d = locals[cdt][cdn];
return {
- query: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_supplier_contacts",
- filters: {'supplier': d.supplier}
- }
+ query: "frappe.contacts.doctype.contact.contact.contact_query",
+ filters: {
+ link_doctype: "Supplier",
+ link_name: d.supplier || ""
+ }
+ };
}
},
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index f319506..ee28eb6 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -112,7 +112,7 @@
def get_link(self):
# RFQ link for supplier portal
- return get_url("/rfq/" + self.name)
+ return get_url("/app/request-for-quotation/" + self.name)
def update_supplier_part_no(self, supplier):
self.vendor = supplier
@@ -287,18 +287,6 @@
@frappe.whitelist()
-@frappe.validate_and_sanitize_search_inputs
-def get_supplier_contacts(doctype, txt, searchfield, start, page_len, filters):
- return frappe.db.sql(
- """select `tabContact`.name from `tabContact`, `tabDynamic Link`
- where `tabDynamic Link`.link_doctype = 'Supplier' and (`tabDynamic Link`.link_name=%(name)s
- and `tabDynamic Link`.link_name like %(txt)s) and `tabContact`.name = `tabDynamic Link`.parent
- limit %(page_len)s offset %(start)s""",
- {"start": start, "page_len": page_len, "txt": "%%%s%%" % txt, "name": filters.get("supplier")},
- )
-
-
-@frappe.whitelist()
def make_supplier_quotation_from_rfq(source_name, target_doc=None, for_supplier=None):
def postprocess(source, target_doc):
if for_supplier:
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 036733c..7ab8f81 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -86,6 +86,7 @@
company=self.company,
party_address=self.get("supplier_address"),
shipping_address=self.get("shipping_address"),
+ company_address=self.get("billing_address"),
fetch_payment_terms_template=not self.get("ignore_default_payment_terms_template"),
ignore_permissions=self.flags.ignore_permissions,
)
@@ -307,7 +308,8 @@
rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate"))
else:
- rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), "rate")
+ field = "incoming_rate" if self.get("is_internal_supplier") else "rate"
+ rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), field)
if self.is_internal_transfer():
if rate != d.rate:
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 243ebb6..4f8b5c7 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -18,8 +18,9 @@
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def employee_query(doctype, txt, searchfield, start, page_len, filters):
+ doctype = "Employee"
conditions = []
- fields = get_fields("Employee", ["name", "employee_name"])
+ fields = get_fields(doctype, ["name", "employee_name"])
return frappe.db.sql(
"""select {fields} from `tabEmployee`
@@ -49,7 +50,8 @@
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def lead_query(doctype, txt, searchfield, start, page_len, filters):
- fields = get_fields("Lead", ["name", "lead_name", "company_name"])
+ doctype = "Lead"
+ fields = get_fields(doctype, ["name", "lead_name", "company_name"])
return frappe.db.sql(
"""select {fields} from `tabLead`
@@ -77,6 +79,7 @@
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def customer_query(doctype, txt, searchfield, start, page_len, filters):
+ doctype = "Customer"
conditions = []
cust_master_name = frappe.defaults.get_user_default("cust_master_name")
@@ -85,9 +88,9 @@
else:
fields = ["name", "customer_name", "customer_group", "territory"]
- fields = get_fields("Customer", fields)
+ fields = get_fields(doctype, fields)
- searchfields = frappe.get_meta("Customer").get_search_fields()
+ searchfields = frappe.get_meta(doctype).get_search_fields()
searchfields = " or ".join(field + " like %(txt)s" for field in searchfields)
return frappe.db.sql(
@@ -116,6 +119,7 @@
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def supplier_query(doctype, txt, searchfield, start, page_len, filters):
+ doctype = "Supplier"
supp_master_name = frappe.defaults.get_user_default("supp_master_name")
if supp_master_name == "Supplier Name":
@@ -123,7 +127,7 @@
else:
fields = ["name", "supplier_name", "supplier_group"]
- fields = get_fields("Supplier", fields)
+ fields = get_fields(doctype, fields)
return frappe.db.sql(
"""select {field} from `tabSupplier`
@@ -147,6 +151,7 @@
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
+ doctype = "Account"
company_currency = erpnext.get_company_currency(filters.get("company"))
def get_accounts(with_account_type_filter):
@@ -197,13 +202,14 @@
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
+ doctype = "Item"
conditions = []
if isinstance(filters, str):
filters = json.loads(filters)
# Get searchfields from meta and use in Item Link field query
- meta = frappe.get_meta("Item", cached=True)
+ meta = frappe.get_meta(doctype, cached=True)
searchfields = meta.get_search_fields()
# these are handled separately
@@ -257,7 +263,7 @@
filters.pop("supplier", None)
description_cond = ""
- if frappe.db.count("Item", cache=True) < 50000:
+ if frappe.db.count(doctype, cache=True) < 50000:
# scan description only if items are less than 50000
description_cond = "or tabItem.description LIKE %(txt)s"
return frappe.db.sql(
@@ -300,8 +306,9 @@
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def bom(doctype, txt, searchfield, start, page_len, filters):
+ doctype = "BOM"
conditions = []
- fields = get_fields("BOM", ["name", "item"])
+ fields = get_fields(doctype, ["name", "item"])
return frappe.db.sql(
"""select {fields}
@@ -331,6 +338,7 @@
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_project_name(doctype, txt, searchfield, start, page_len, filters):
+ doctype = "Project"
cond = ""
if filters and filters.get("customer"):
cond = """(`tabProject`.customer = %s or
@@ -338,8 +346,8 @@
frappe.db.escape(filters.get("customer"))
)
- fields = get_fields("Project", ["name", "project_name"])
- searchfields = frappe.get_meta("Project").get_search_fields()
+ fields = get_fields(doctype, ["name", "project_name"])
+ searchfields = frappe.get_meta(doctype).get_search_fields()
searchfields = " or ".join(["`tabProject`." + field + " like %(txt)s" for field in searchfields])
return frappe.db.sql(
@@ -366,7 +374,8 @@
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict):
- fields = get_fields("Delivery Note", ["name", "customer", "posting_date"])
+ doctype = "Delivery Note"
+ fields = get_fields(doctype, ["name", "customer", "posting_date"])
return frappe.db.sql(
"""
@@ -402,6 +411,7 @@
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
+ doctype = "Batch"
cond = ""
if filters.get("posting_date"):
cond = "and (batch.expiry_date is null or batch.expiry_date >= %(posting_date)s)"
@@ -420,7 +430,7 @@
if filters.get("is_return"):
having_clause = ""
- meta = frappe.get_meta("Batch", cached=True)
+ meta = frappe.get_meta(doctype, cached=True)
searchfields = meta.get_search_fields()
search_columns = ""
@@ -496,6 +506,7 @@
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_account_list(doctype, txt, searchfield, start, page_len, filters):
+ doctype = "Account"
filter_list = []
if isinstance(filters, dict):
@@ -514,7 +525,7 @@
filter_list.append([doctype, searchfield, "like", "%%%s%%" % txt])
return frappe.desk.reportview.execute(
- "Account",
+ doctype,
filters=filter_list,
fields=["name", "parent_account"],
limit_start=start,
@@ -553,6 +564,7 @@
if not filters:
filters = {}
+ doctype = "Account"
condition = ""
if filters.get("company"):
condition += "and tabAccount.company = %(company)s"
@@ -628,6 +640,7 @@
if not filters:
filters = {}
+ doctype = "Account"
condition = ""
if filters.get("company"):
condition += "and tabAccount.company = %(company)s"
@@ -650,6 +663,7 @@
@frappe.validate_and_sanitize_search_inputs
def warehouse_query(doctype, txt, searchfield, start, page_len, filters):
# Should be used when item code is passed in filters.
+ doctype = "Warehouse"
conditions, bin_conditions = [], []
filter_dict = get_doctype_wise_filters(filters)
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 440271c..49f85cd 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -36,6 +36,10 @@
pass
+class BatchExpiredError(frappe.ValidationError):
+ pass
+
+
class StockController(AccountsController):
def validate(self):
super(StockController, self).validate()
@@ -77,6 +81,10 @@
def validate_serialized_batch(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+ is_material_issue = False
+ if self.doctype == "Stock Entry" and self.purpose == "Material Issue":
+ is_material_issue = True
+
for d in self.get("items"):
if hasattr(d, "serial_no") and hasattr(d, "batch_no") and d.serial_no and d.batch_no:
serial_nos = frappe.get_all(
@@ -93,6 +101,9 @@
)
)
+ if is_material_issue:
+ continue
+
if flt(d.qty) > 0.0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2:
expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date")
@@ -100,7 +111,8 @@
frappe.throw(
_("Row #{0}: The batch {1} has already expired.").format(
d.idx, get_link_to_form("Batch", d.get("batch_no"))
- )
+ ),
+ BatchExpiredError,
)
def clean_serial_nos(self):
diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json
index c946ae4..99c00ad 100644
--- a/erpnext/crm/doctype/lead/lead.json
+++ b/erpnext/crm/doctype/lead/lead.json
@@ -340,8 +340,8 @@
"fieldname": "no_of_employees",
"fieldtype": "Select",
"label": "No of Employees",
- "options": "1-10\n11-20\n21-30\n31-100\n11-50\n51-200\n201-500\n101-500\n500-1000\n501-1000\n>1000\n1000+"
- },
+ "options": "1-10\n11-50\n51-200\n201-500\n501-1000\n1000+"
+ },
{
"fieldname": "column_break_22",
"fieldtype": "Column Break"
@@ -514,7 +514,7 @@
"idx": 5,
"image_field": "image",
"links": [],
- "modified": "2022-07-22 15:55:03.176094",
+ "modified": "2022-08-09 18:26:17.101521",
"modified_by": "Administrator",
"module": "CRM",
"name": "Lead",
diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json
index 68a2156..fed0c7c 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.json
+++ b/erpnext/crm/doctype/opportunity/opportunity.json
@@ -463,7 +463,7 @@
"fieldname": "no_of_employees",
"fieldtype": "Select",
"label": "No of Employees",
- "options": "1-10\n11-20\n21-30\n31-100\n11-50\n51-200\n201-500\n101-500\n500-1000\n501-1000\n>1000\n1000+"
+ "options": "1-10\n11-50\n51-200\n201-500\n501-1000\n1000+"
},
{
"fieldname": "annual_revenue",
@@ -622,7 +622,7 @@
"icon": "fa fa-info-sign",
"idx": 195,
"links": [],
- "modified": "2022-07-22 18:46:32.858696",
+ "modified": "2022-08-09 18:26:37.235964",
"modified_by": "Administrator",
"module": "CRM",
"name": "Opportunity",
diff --git a/erpnext/crm/doctype/prospect/prospect.json b/erpnext/crm/doctype/prospect/prospect.json
index 7f33c08..820a6c7 100644
--- a/erpnext/crm/doctype/prospect/prospect.json
+++ b/erpnext/crm/doctype/prospect/prospect.json
@@ -82,7 +82,7 @@
"fieldname": "no_of_employees",
"fieldtype": "Select",
"label": "No. of Employees",
- "options": "1-10\n11-20\n21-30\n31-100\n11-50\n51-200\n201-500\n101-500\n500-1000\n501-1000\n>1000\n1000+"
+ "options": "1-10\n11-50\n51-200\n201-500\n501-1000\n1000+"
},
{
"fieldname": "annual_revenue",
@@ -218,7 +218,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2022-06-22 15:10:26.887502",
+ "modified": "2022-08-09 18:26:56.950185",
"modified_by": "Administrator",
"module": "CRM",
"name": "Prospect",
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index aa10e31..c4f0c59 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -507,6 +507,7 @@
"Shipping Rule",
"Landed Cost Item",
"Asset Value Adjustment",
+ "Asset Repair",
"Loyalty Program",
"Stock Reconciliation",
"POS Profile",
diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py
index 0a576d6..514a5fc 100644
--- a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py
+++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py
@@ -99,7 +99,7 @@
loan_account = frappe.db.get_value("Loan", self.loan, "loan_account")
remarks = "{} against loan {}".format(self.adjustment_type.capitalize(), self.loan)
if self.reference_number:
- remarks += "with reference no. {}".format(self.reference_number)
+ remarks += " with reference no. {}".format(self.reference_number)
loan_entry = {
"account": loan_account,
diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json
index 50926d7..c7b5c03 100644
--- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json
+++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json
@@ -163,11 +163,11 @@
},
{
"fetch_from": "against_loan.disbursement_account",
+ "fetch_if_empty": 1,
"fieldname": "disbursement_account",
"fieldtype": "Link",
"label": "Disbursement Account",
- "options": "Account",
- "read_only": 1
+ "options": "Account"
},
{
"fieldname": "column_break_16",
@@ -185,7 +185,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2022-02-17 18:23:44.157598",
+ "modified": "2022-08-04 17:16:04.922444",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Disbursement",
diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
index 0c2042b..b73dee2 100644
--- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
+++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
@@ -209,6 +209,9 @@
"loan_amount",
"disbursed_amount",
"total_payment",
+ "debit_adjustment_amount",
+ "credit_adjustment_amount",
+ "refund_amount",
"total_principal_paid",
"total_interest_payable",
"status",
diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
index 0aeb448..6d62aef 100644
--- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
+++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
@@ -135,7 +135,11 @@
def make_accrual_interest_entry_for_demand_loans(
posting_date, process_loan_interest, open_loans=None, loan_type=None, accrual_type="Regular"
):
- query_filters = {"status": ("in", ["Disbursed", "Partially Disbursed"]), "docstatus": 1}
+ query_filters = {
+ "status": ("in", ["Disbursed", "Partially Disbursed"]),
+ "docstatus": 1,
+ "is_term_loan": 0,
+ }
if loan_type:
query_filters.update({"loan_type": loan_type})
@@ -147,6 +151,9 @@
"name",
"total_payment",
"total_amount_paid",
+ "debit_adjustment_amount",
+ "credit_adjustment_amount",
+ "refund_amount",
"loan_account",
"interest_income_account",
"loan_amount",
@@ -229,6 +236,7 @@
AND l.is_term_loan =1
AND rs.payment_date <= %s
AND rs.is_accrued=0 {0}
+ AND rs.interest_amount > 0
AND l.status = 'Disbursed'
ORDER BY rs.payment_date""".format(
condition
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
index 76dc8b4..3e7dc28 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
@@ -264,11 +264,11 @@
},
{
"fetch_from": "against_loan.payment_account",
+ "fetch_if_empty": 1,
"fieldname": "payment_account",
"fieldtype": "Link",
"label": "Repayment Account",
- "options": "Account",
- "read_only": 1
+ "options": "Account"
},
{
"fieldname": "column_break_36",
@@ -294,7 +294,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2022-06-21 10:10:07.742298",
+ "modified": "2022-08-04 17:13:51.964203",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Repayment",
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 07a1d0d..29da988 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -149,6 +149,9 @@
"status",
"is_secured_loan",
"total_payment",
+ "debit_adjustment_amount",
+ "credit_adjustment_amount",
+ "refund_amount",
"loan_amount",
"disbursed_amount",
"total_interest_payable",
@@ -398,7 +401,7 @@
remarks = "Repayment against loan " + self.against_loan
if self.reference_number:
- remarks += "with reference no. {}".format(self.reference_number)
+ remarks += " with reference no. {}".format(self.reference_number)
if hasattr(self, "repay_from_salary") and self.repay_from_salary:
payment_account = self.payroll_payable_account
diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
index 0ab7beb..15a9c4a 100644
--- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
+++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
@@ -57,6 +57,9 @@
self.loan,
[
"total_payment",
+ "debit_adjustment_amount",
+ "credit_adjustment_amount",
+ "refund_amount",
"total_principal_paid",
"loan_amount",
"total_interest_payable",
diff --git a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py
index 25aecf6..ae483f9 100644
--- a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py
+++ b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py
@@ -9,6 +9,9 @@
import erpnext
from erpnext.accounts.general_ledger import make_gl_entries
from erpnext.controllers.accounts_controller import AccountsController
+from erpnext.loan_management.doctype.loan_repayment.loan_repayment import (
+ get_pending_principal_amount,
+)
class LoanWriteOff(AccountsController):
@@ -22,16 +25,26 @@
def validate_write_off_amount(self):
precision = cint(frappe.db.get_default("currency_precision")) or 2
- total_payment, principal_paid, interest_payable, written_off_amount = frappe.get_value(
+
+ loan_details = frappe.get_value(
"Loan",
self.loan,
- ["total_payment", "total_principal_paid", "total_interest_payable", "written_off_amount"],
+ [
+ "total_payment",
+ "debit_adjustment_amount",
+ "credit_adjustment_amount",
+ "refund_amount",
+ "total_principal_paid",
+ "loan_amount",
+ "total_interest_payable",
+ "written_off_amount",
+ "disbursed_amount",
+ "status",
+ ],
+ as_dict=1,
)
- pending_principal_amount = flt(
- flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount),
- precision,
- )
+ pending_principal_amount = flt(get_pending_principal_amount(loan_details), precision)
if self.write_off_amount > pending_principal_amount:
frappe.throw(_("Write off amount cannot be greater than pending principal amount"))
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index b29f671..70637d3 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -189,8 +189,8 @@
self.validate_transfer_against()
self.set_routing_operations()
self.validate_operations()
- self.update_exploded_items(save=False)
self.calculate_cost()
+ self.update_exploded_items(save=False)
self.update_stock_qty()
self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate=False, save=False)
self.validate_scrap_items()
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index a190cc7..27f3cc9 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -611,6 +611,34 @@
bom.reload()
self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name)
+ def test_exploded_items_rate(self):
+ rm_item = make_item(
+ properties={"is_stock_item": 1, "valuation_rate": 99, "last_purchase_rate": 89}
+ ).name
+ fg_item = make_item(properties={"is_stock_item": 1}).name
+
+ from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
+
+ bom = make_bom(item=fg_item, raw_materials=[rm_item], do_not_save=True)
+
+ bom.rm_cost_as_per = "Last Purchase Rate"
+ bom.save()
+ self.assertEqual(bom.items[0].base_rate, 89)
+ self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate)
+
+ bom.rm_cost_as_per = "Price List"
+ bom.save()
+ self.assertEqual(bom.items[0].base_rate, 0.0)
+ self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate)
+
+ bom.rm_cost_as_per = "Valuation Rate"
+ bom.save()
+ self.assertEqual(bom.items[0].base_rate, 99)
+ self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate)
+
+ bom.submit()
+ self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate)
+
def get_default_bom(item_code="_Test FG Item 2"):
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json
index 0a8ae7b..c526611 100644
--- a/erpnext/manufacturing/doctype/bom_item/bom_item.json
+++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json
@@ -184,6 +184,7 @@
"in_list_view": 1,
"label": "Rate",
"options": "currency",
+ "read_only": 1,
"reqd": 1
},
{
@@ -288,7 +289,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2022-05-19 02:32:43.785470",
+ "modified": "2022-07-28 10:20:51.559010",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Item",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 70ccb78..2cdf8d3 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -482,7 +482,6 @@
"bom_no",
"stock_uom",
"bom_level",
- "production_plan_item",
"schedule_date",
]:
if row.get(field):
@@ -639,6 +638,9 @@
sub_assembly_items_store = [] # temporary store to process all subassembly items
for row in self.po_items:
+ if not row.item_code:
+ frappe.throw(_("Row #{0}: Please select Item Code in Assembly Items").format(row.idx))
+
bom_data = []
get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty)
self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type)
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index 040e791..e2415ad 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -11,8 +11,9 @@
get_warehouse_list,
)
from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError
+from erpnext.manufacturing.doctype.work_order.work_order import make_stock_entry as make_se_from_wo
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
-from erpnext.stock.doctype.item.test_item import create_item
+from erpnext.stock.doctype.item.test_item import create_item, make_item
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
create_stock_reconciliation,
@@ -583,9 +584,6 @@
Test Prod Plan impact via: SO -> Prod Plan -> WO -> SE -> SE (cancel)
"""
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
- from erpnext.manufacturing.doctype.work_order.work_order import (
- make_stock_entry as make_se_from_wo,
- )
make_stock_entry(
item_code="Raw Material Item 1", target="Work In Progress - _TC", qty=2, basic_rate=100
@@ -629,9 +627,6 @@
def test_production_plan_pending_qty_independent_items(self):
"Test Prod Plan impact if items are added independently (no from SO or MR)."
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
- from erpnext.manufacturing.doctype.work_order.work_order import (
- make_stock_entry as make_se_from_wo,
- )
make_stock_entry(
item_code="Raw Material Item 1", target="Work In Progress - _TC", qty=2, basic_rate=100
@@ -728,6 +723,57 @@
for po_item, subassy_item in zip(pp.po_items, pp.sub_assembly_items):
self.assertEqual(po_item.name, subassy_item.production_plan_item)
+ def test_produced_qty_for_multi_level_bom_item(self):
+ # Create Items and BOMs
+ rm_item = make_item(properties={"is_stock_item": 1}).name
+ sub_assembly_item = make_item(properties={"is_stock_item": 1}).name
+ fg_item = make_item(properties={"is_stock_item": 1}).name
+
+ make_stock_entry(
+ item_code=rm_item,
+ qty=60,
+ to_warehouse="Work In Progress - _TC",
+ rate=99,
+ purpose="Material Receipt",
+ )
+
+ make_bom(item=sub_assembly_item, raw_materials=[rm_item], rm_qty=3)
+ make_bom(item=fg_item, raw_materials=[sub_assembly_item], rm_qty=4)
+
+ # Step - 1: Create Production Plan
+ pln = create_production_plan(item_code=fg_item, planned_qty=5, skip_getting_mr_items=1)
+ pln.get_sub_assembly_items()
+
+ # Step - 2: Create Work Orders
+ pln.make_work_order()
+ work_orders = frappe.get_all("Work Order", filters={"production_plan": pln.name}, pluck="name")
+ sa_wo = fg_wo = None
+ for work_order in work_orders:
+ wo_doc = frappe.get_doc("Work Order", work_order)
+ if wo_doc.production_plan_item:
+ wo_doc.update(
+ {"wip_warehouse": "Work In Progress - _TC", "fg_warehouse": "Finished Goods - _TC"}
+ )
+ fg_wo = wo_doc.name
+ else:
+ wo_doc.update(
+ {"wip_warehouse": "Work In Progress - _TC", "fg_warehouse": "Work In Progress - _TC"}
+ )
+ sa_wo = wo_doc.name
+ wo_doc.submit()
+
+ # Step - 3: Complete Work Orders
+ se = frappe.get_doc(make_se_from_wo(sa_wo, "Manufacture"))
+ se.submit()
+
+ se = frappe.get_doc(make_se_from_wo(fg_wo, "Manufacture"))
+ se.submit()
+
+ # Step - 4: Check Production Plan Item Produced Qty
+ pln.load_from_db()
+ self.assertEqual(pln.status, "Completed")
+ self.assertEqual(pln.po_items[0].produced_qty, 5)
+
def create_production_plan(**args):
"""
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 1d5f5d7..e5beacd 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -268,6 +268,7 @@
erpnext.patches.v13_0.show_india_localisation_deprecation_warning
erpnext.patches.v13_0.show_hr_payroll_deprecation_warning
erpnext.patches.v13_0.reset_corrupt_defaults
+erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair
[post_model_sync]
execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings')
@@ -308,4 +309,5 @@
erpnext.patches.v14_0.crm_ux_cleanup
erpnext.patches.v14_0.remove_india_localisation # 14-07-2022
erpnext.patches.v13_0.fix_number_and_frequency_for_monthly_depreciation
-erpnext.patches.v14_0.remove_hr_and_payroll_modules # 20-07-2022
\ No newline at end of file
+erpnext.patches.v14_0.remove_hr_and_payroll_modules # 20-07-2022
+erpnext.patches.v14_0.fix_crm_no_of_employees
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/create_accounting_dimensions_for_asset_repair.py b/erpnext/patches/v13_0/create_accounting_dimensions_for_asset_repair.py
new file mode 100644
index 0000000..61a5c86
--- /dev/null
+++ b/erpnext/patches/v13_0/create_accounting_dimensions_for_asset_repair.py
@@ -0,0 +1,29 @@
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_field
+
+
+def execute():
+ accounting_dimensions = frappe.db.get_all(
+ "Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"]
+ )
+
+ if not accounting_dimensions:
+ return
+
+ for d in accounting_dimensions:
+ doctype = "Asset Repair"
+ field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
+
+ if field:
+ continue
+
+ df = {
+ "fieldname": d.fieldname,
+ "label": d.label,
+ "fieldtype": "Link",
+ "options": d.document_type,
+ "insert_after": "accounting_dimensions_section",
+ }
+
+ create_custom_field(doctype, df, ignore_validate=True)
+ frappe.clear_cache(doctype=doctype)
diff --git a/erpnext/patches/v14_0/fix_crm_no_of_employees.py b/erpnext/patches/v14_0/fix_crm_no_of_employees.py
new file mode 100644
index 0000000..268eb95
--- /dev/null
+++ b/erpnext/patches/v14_0/fix_crm_no_of_employees.py
@@ -0,0 +1,26 @@
+import frappe
+
+
+def execute():
+ options = {
+ "11-20": "11-50",
+ "21-30": "11-50",
+ "31-100": "51-200",
+ "101-500": "201-500",
+ "500-1000": "501-1000",
+ ">1000": "1000+",
+ }
+
+ for doctype in ("Lead", "Opportunity", "Prospect"):
+ frappe.reload_doctype(doctype)
+ for key, value in options.items():
+ frappe.db.sql(
+ """
+ update `tab{doctype}`
+ set no_of_employees = %s
+ where no_of_employees = %s
+ """.format(
+ doctype=doctype
+ ),
+ (value, key),
+ )
diff --git a/erpnext/portal/doctype/homepage_section/test_homepage_section.py b/erpnext/portal/doctype/homepage_section/test_homepage_section.py
index 27b0169..27c8fe4 100644
--- a/erpnext/portal/doctype/homepage_section/test_homepage_section.py
+++ b/erpnext/portal/doctype/homepage_section/test_homepage_section.py
@@ -57,7 +57,11 @@
self.assertEqual(cards[0].h5.text, "Card 1")
self.assertEqual(cards[0].a["href"], "/card-1")
self.assertEqual(cards[1].p.text, "Subtitle 2")
- self.assertEqual(cards[1].find(class_="website-image-lazy")["data-src"], "test.jpg")
+
+ img = cards[1].find(class_="card-img-top")
+
+ self.assertEqual(img["src"], "test.jpg")
+ self.assertEqual(img["loading"], "lazy")
# cleanup
frappe.db.rollback()
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 85485fc..c0a8c9e 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -1,7 +1,6 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
-frappe.provide('erpnext.accounts.dimensions');
erpnext.TransactionController = class TransactionController extends erpnext.taxes_and_totals {
setup() {
@@ -794,24 +793,6 @@
set_party_account(set_pricing);
});
- // Get default company billing address in Purchase Invoice, Order and Receipt
- if (this.frm.doc.company && frappe.meta.get_docfield(this.frm.doctype, "billing_address")) {
- frappe.call({
- method: "erpnext.setup.doctype.company.company.get_default_company_address",
- args: {name: this.frm.doc.company, existing_address: this.frm.doc.billing_address || ""},
- debounce: 2000,
- callback: function(r) {
- if (r.message) {
- me.frm.set_value("billing_address", r.message);
- } else {
- if (frappe.meta.get_docfield(me.frm.doctype, 'company_address')) {
- me.frm.set_value("company_address", "");
- }
- }
- }
- });
- }
-
} else {
set_party_account(set_pricing);
}
diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js
index a492b32..58594b0 100644
--- a/erpnext/public/js/utils/party.js
+++ b/erpnext/public/js/utils/party.js
@@ -3,25 +3,14 @@
frappe.provide("erpnext.utils");
+const SALES_DOCTYPES = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice'];
+const PURCHASE_DOCTYPES = ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'];
+
erpnext.utils.get_party_details = function(frm, method, args, callback) {
if (!method) {
method = "erpnext.accounts.party.get_party_details";
}
- if (args) {
- if (in_list(['Sales Invoice', 'Sales Order', 'Delivery Note'], frm.doc.doctype)) {
- if (frm.doc.company_address && (!args.company_address)) {
- args.company_address = frm.doc.company_address;
- }
- }
-
- if (in_list(['Purchase Invoice', 'Purchase Order', 'Purchase Receipt'], frm.doc.doctype)) {
- if (frm.doc.shipping_address && (!args.shipping_address)) {
- args.shipping_address = frm.doc.shipping_address;
- }
- }
- }
-
if (!args) {
if ((frm.doctype != "Purchase Order" && frm.doc.customer)
|| (frm.doc.party_name && in_list(['Quotation', 'Opportunity'], frm.doc.doctype))) {
@@ -45,41 +34,44 @@
};
}
- if (in_list(['Sales Invoice', 'Sales Order', 'Delivery Note'], frm.doc.doctype)) {
- if (!args) {
+ if (!args) {
+ if (in_list(SALES_DOCTYPES, frm.doc.doctype)) {
args = {
party: frm.doc.customer || frm.doc.party_name,
party_type: 'Customer'
- }
- }
- if (frm.doc.company_address && (!args.company_address)) {
- args.company_address = frm.doc.company_address;
+ };
}
- if (frm.doc.shipping_address_name &&(!args.shipping_address_name)) {
- args.shipping_address_name = frm.doc.shipping_address_name;
- }
- }
-
- if (in_list(['Purchase Invoice', 'Purchase Order', 'Purchase Receipt'], frm.doc.doctype)) {
- if (!args) {
+ if (in_list(PURCHASE_DOCTYPES, frm.doc.doctype)) {
args = {
party: frm.doc.supplier,
party_type: 'Supplier'
- }
- }
-
- if (frm.doc.shipping_address && (!args.shipping_address)) {
- args.shipping_address = frm.doc.shipping_address;
+ };
}
}
- if (args) {
- args.posting_date = frm.doc.posting_date || frm.doc.transaction_date;
- args.fetch_payment_terms_template = cint(!frm.doc.ignore_default_payment_terms_template);
+ if (!args || !args.party) return;
+
+ args.posting_date = frm.doc.posting_date || frm.doc.transaction_date;
+ args.fetch_payment_terms_template = cint(!frm.doc.ignore_default_payment_terms_template);
+ }
+
+ if (in_list(SALES_DOCTYPES, frm.doc.doctype)) {
+ if (!args.company_address && frm.doc.company_address) {
+ args.company_address = frm.doc.company_address;
}
}
- if (!args || !args.party) return;
+
+ if (in_list(PURCHASE_DOCTYPES, frm.doc.doctype)) {
+ if (!args.company_address && frm.doc.billing_address) {
+ args.company_address = frm.doc.billing_address;
+ }
+
+ if (!args.shipping_address && frm.doc.shipping_address) {
+ args.shipping_address = frm.doc.shipping_address;
+ }
+ }
+
if (frappe.meta.get_docfield(frm.doc.doctype, "taxes")) {
if (!erpnext.utils.validate_mandatory(frm, "Posting / Transaction Date",
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py
index 13d5069..c12a9f8 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -253,16 +253,20 @@
"POS Invoice",
filters={"customer": ["like", "%{}%".format(search_term)], "status": status},
fields=fields,
+ page_length=limit,
)
invoices_by_name = frappe.db.get_all(
"POS Invoice",
filters={"name": ["like", "%{}%".format(search_term)], "status": status},
fields=fields,
+ page_length=limit,
)
invoice_list = invoices_by_customer + invoices_by_name
elif status:
- invoice_list = frappe.db.get_all("POS Invoice", filters={"status": status}, fields=fields)
+ invoice_list = frappe.db.get_all(
+ "POS Invoice", filters={"status": status}, fields=fields, page_length=limit
+ )
return invoice_list
diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js
index eacf480..e7dd211 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_cart.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js
@@ -524,7 +524,8 @@
const currency = this.events.get_frm().doc.currency;
const taxes_html = taxes.map(t => {
if (t.tax_amount_after_discount_amount == 0.0) return;
- const description = /[0-9]+/.test(t.description) ? t.description : `${t.description} @ ${t.rate}%`;
+ // if tax rate is 0, don't print it.
+ const description = /[0-9]+/.test(t.description) ? t.description : ((t.rate != 0) ? `${t.description} @ ${t.rate}%`: t.description);
return `<div class="tax-row">
<div class="tax-label">${description}</div>
<div class="tax-value">${format_currency(t.tax_amount_after_discount_amount, currency)}</div>
diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
index eeb8523..40165c3 100644
--- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
+++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
@@ -130,7 +130,8 @@
if (!doc.taxes.length) return '';
let taxes_html = doc.taxes.map(t => {
- const description = /[0-9]+/.test(t.description) ? t.description : `${t.description} @ ${t.rate}%`;
+ // if tax rate is 0, don't print it.
+ const description = /[0-9]+/.test(t.description) ? t.description : ((t.rate != 0) ? `${t.description} @ ${t.rate}%`: t.description);
return `
<div class="tax-row">
<div class="tax-label">${description}</div>
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index 6e940d0..411176b 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -142,10 +142,6 @@
if (context.get("website_image") or "").startswith("files/"):
context["website_image"] = "/" + quote(context["website_image"])
- context["show_availability_status"] = cint(
- frappe.db.get_single_value("E Commerce Settings", "show_availability_status")
- )
-
products_template = "templates/includes/products_as_list.html"
return frappe.get_template(products_template).render(context)
diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py
index 3e470d4..271e2e0 100644
--- a/erpnext/stock/doctype/batch/test_batch.py
+++ b/erpnext/stock/doctype/batch/test_batch.py
@@ -473,7 +473,13 @@
"doctype": "Batch",
"batch_id": args.batch_id,
"item": args.item_code,
+ "expiry_date": args.expiry_date,
}
- ).insert()
+ )
+
+ if args.expiry_date:
+ batch.expiry_date = args.expiry_date
+
+ batch.insert()
return batch
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index a2f9978..b574b71 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -5,7 +5,7 @@
import frappe
from frappe.permissions import add_user_permission, remove_user_permission
from frappe.tests.utils import FrappeTestCase, change_settings
-from frappe.utils import add_days, flt, nowdate, nowtime
+from frappe.utils import add_days, flt, nowdate, nowtime, today
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.stock.doctype.item.test_item import (
@@ -1589,6 +1589,31 @@
self.assertEqual(obj.items[index].basic_rate, 200)
self.assertEqual(obj.items[index].basic_amount, 2000)
+ def test_batch_expiry(self):
+ from erpnext.controllers.stock_controller import BatchExpiredError
+ from erpnext.stock.doctype.batch.test_batch import make_new_batch
+
+ item_code = "Test Batch Expiry Test Item - 001"
+ item_doc = create_item(item_code=item_code, is_stock_item=1, valuation_rate=10)
+
+ item_doc.has_batch_no = 1
+ item_doc.save()
+
+ batch = make_new_batch(
+ batch_id=frappe.generate_hash("", 5), item_code=item_doc.name, expiry_date=add_days(today(), -1)
+ )
+
+ se = make_stock_entry(
+ item_code=item_code,
+ purpose="Material Receipt",
+ qty=4,
+ to_warehouse="_Test Warehouse - _TC",
+ batch_no=batch.name,
+ do_not_save=True,
+ )
+
+ self.assertRaises(BatchExpiredError, se.save)
+
def make_serialized_item(**args):
args = frappe._dict(args)
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index fd1aece..3524a47 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -646,6 +646,24 @@
voucher_detail_no=sle.voucher_detail_no,
sle=sle,
)
+
+ elif (
+ sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"]
+ and sle.actual_qty > 0
+ and frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_internal_supplier")
+ ):
+ sle_details = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {
+ "voucher_type": sle.voucher_type,
+ "voucher_no": sle.voucher_no,
+ "dependant_sle_voucher_detail_no": sle.voucher_detail_no,
+ },
+ ["stock_value_difference", "actual_qty"],
+ as_dict=1,
+ )
+
+ rate = abs(sle_details.stock_value_difference / sle.actual_qty)
else:
if sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"):
rate_field = "valuation_rate"
diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html
index 3e20e50..f56dc3a 100644
--- a/erpnext/templates/includes/macros.html
+++ b/erpnext/templates/includes/macros.html
@@ -46,7 +46,7 @@
<div class="col-md-{{ section.column_value }} mb-4">
<div class="card h-100 justify-content-between">
{% if card.image %}
- <div class="website-image-lazy" data-class="card-img-top h-75" data-src="{{ card.image }}" data-alt="{{ card.title }}"></div>
+ <img class="card-img-top h-75" src="{{ card.image }}" loading="lazy" alt="{{ card.title }}"></img>
{% endif %}
<div class="card-body">
<h5 class="card-title">{{ card.title }}</h5>
diff --git a/erpnext/templates/pages/home.html b/erpnext/templates/pages/home.html
index 4c69b83..27d966a 100644
--- a/erpnext/templates/pages/home.html
+++ b/erpnext/templates/pages/home.html
@@ -37,7 +37,7 @@
{% for item in homepage.products %}
<div class="col-md-4 mb-4">
<div class="card h-100 justify-content-between">
- <div class="website-image-lazy" data-class="card-img-top website-image-extra-large" data-src="{{ item.image }}" data-alt="{{ item.item_name }}"></div>
+ <img class="card-img-top website-image-extra-large" src="{{ item.image }}" loading="lazy" alt="{{ item.item_name }}"></img>
<div class="card-body flex-grow-0">
<h5 class="card-title">{{ item.item_name }}</h5>
<a href="{{ item.route }}" class="card-link">{{ _('More details') }}</a>
diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv
index 01795af..d6bceb3 100644
--- a/erpnext/translations/de.csv
+++ b/erpnext/translations/de.csv
@@ -4051,7 +4051,7 @@
Service Level Agreement has been changed to {0}.,Service Level Agreement wurde in {0} geändert.,
Service Level Agreement was reset.,Service Level Agreement wurde zurückgesetzt.,
Service Level Agreement with Entity Type {0} and Entity {1} already exists.,Service Level Agreement mit Entitätstyp {0} und Entität {1} ist bereits vorhanden.,
-Set,Menge,
+Set Loyalty Program,Treueprogramm eintragen,
Set Meta Tags,Festlegen von Meta-Tags,
Set {0} in company {1},{0} in Firma {1} festlegen,
Setup,Einstellungen,
@@ -4231,10 +4231,8 @@
Write Off,Abschreiben,
{0} Created,{0} Erstellt,
Email Id,E-Mail-ID,
-No,Kein,
Reference Doctype,Referenz-DocType,
User Id,Benutzeridentifikation,
-Yes,Ja,
Actual ,Tatsächlich,
Add to cart,In den Warenkorb legen,
Budget,Budget,