Merge pull request #38533 from barredterra/date-format-system-settings
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index 3bc22af..275442a 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -28,4 +28,7 @@
494bd9ef78313436f0424b918f200dab8fc7c20b
# bulk format python code with black
-baec607ff5905b1c67531096a9cf50ec7ff00a5d
\ No newline at end of file
+baec607ff5905b1c67531096a9cf50ec7ff00a5d
+
+# bulk refactor with sourcery
+eb9ee3f79b94e594fc6dfa4f6514580e125eee8c
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 5c58f84..48ebe92 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -36,7 +36,7 @@
if not frappe.flags.company_cost_center:
frappe.flags.company_cost_center = {}
- if not company in frappe.flags.company_cost_center:
+ if company not in frappe.flags.company_cost_center:
frappe.flags.company_cost_center[company] = frappe.get_cached_value(
"Company", company, "cost_center"
)
@@ -47,7 +47,7 @@
"""Returns the default company currency"""
if not frappe.flags.company_currency:
frappe.flags.company_currency = {}
- if not company in frappe.flags.company_currency:
+ if company not in frappe.flags.company_currency:
frappe.flags.company_currency[company] = frappe.db.get_value(
"Company", company, "default_currency", cache=True
)
@@ -81,7 +81,7 @@
if not hasattr(frappe.local, "enable_perpetual_inventory"):
frappe.local.enable_perpetual_inventory = {}
- if not company in frappe.local.enable_perpetual_inventory:
+ if company not in frappe.local.enable_perpetual_inventory:
frappe.local.enable_perpetual_inventory[company] = (
frappe.get_cached_value("Company", company, "enable_perpetual_inventory") or 0
)
@@ -96,7 +96,7 @@
if not hasattr(frappe.local, "default_finance_book"):
frappe.local.default_finance_book = {}
- if not company in frappe.local.default_finance_book:
+ if company not in frappe.local.default_finance_book:
frappe.local.default_finance_book[company] = frappe.get_cached_value(
"Company", company, "default_finance_book"
)
@@ -108,7 +108,7 @@
if not hasattr(frappe.local, "party_account_types"):
frappe.local.party_account_types = {}
- if not party_type in frappe.local.party_account_types:
+ if party_type not in frappe.local.party_account_types:
frappe.local.party_account_types[party_type] = (
frappe.db.get_value("Party Type", party_type, "account_type") or ""
)
diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py
index d0940c7..367b017 100644
--- a/erpnext/accounts/deferred_revenue.py
+++ b/erpnext/accounts/deferred_revenue.py
@@ -232,7 +232,7 @@
if amount + already_booked_amount_in_account_currency > item.net_amount:
amount = item.net_amount - already_booked_amount_in_account_currency
- if not (get_first_day(start_date) == start_date and get_last_day(end_date) == end_date):
+ if get_first_day(start_date) != start_date or get_last_day(end_date) != end_date:
partial_month = flt(date_diff(end_date, start_date)) / flt(
date_diff(get_last_day(end_date), get_first_day(start_date))
)
diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
index e9cbb33..8be09db 100644
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
+++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
@@ -44,7 +44,7 @@
self.set_total_gain_loss()
def validate_rounding_loss_allowance(self):
- if not (self.rounding_loss_allowance >= 0 and self.rounding_loss_allowance < 1):
+ if self.rounding_loss_allowance < 0 or self.rounding_loss_allowance >= 1:
frappe.throw(_("Rounding Loss Allowance should be between 0 and 1"))
def set_total_gain_loss(self):
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index 9684a0d..266154d 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -8,7 +8,7 @@
frappe.ui.form.on("Journal Entry", {
setup: function(frm) {
frm.add_fetch("bank_account", "account", "account");
- frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', 'Asset Depreciation Schedule', "Repost Accounting Ledger"];
+ frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', 'Asset Depreciation Schedule', "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"];
},
refresh: function(frm) {
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index e4f1645..40d552b 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -170,6 +170,8 @@
"Repost Payment Ledger Items",
"Repost Accounting Ledger",
"Repost Accounting Ledger Items",
+ "Unreconcile Payment",
+ "Unreconcile Payment Entries",
)
self.make_gl_entries(1)
self.update_advance_paid()
@@ -629,7 +631,7 @@
)
# set totals
- if not d.reference_name in self.reference_totals:
+ if d.reference_name not in self.reference_totals:
self.reference_totals[d.reference_name] = 0.0
if self.voucher_type not in ("Deferred Revenue", "Deferred Expense"):
diff --git a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
index 3641ac4..cbfb17b 100644
--- a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
+++ b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
@@ -140,7 +140,7 @@
"Loyalty Point Entry",
{"invoice_type": "Sales Invoice", "invoice": si.name, "customer": si.customer},
)
- self.assertEqual(True, not (lpe is None))
+ self.assertEqual(True, lpe is not None)
# cancelling sales invoice
si.cancel()
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index dfb5caf..a6ddce5 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -106,9 +106,17 @@
self.set_status()
def set_liability_account(self):
- if not self.book_advance_payments_in_separate_party_account:
+ # Auto setting liability account should only be done during 'draft' status
+ if self.docstatus > 0:
return
+ if not frappe.db.get_value(
+ "Company", self.company, "book_advance_payments_in_separate_party_account"
+ ):
+ return
+
+ # Important to set this flag for the gl building logic to work properly
+ self.book_advance_payments_in_separate_party_account = True
account_type = frappe.get_value(
"Account", {"name": self.party_account, "company": self.company}, "account_type"
)
@@ -118,11 +126,13 @@
):
return
- if self.unallocated_amount == 0:
- for d in self.references:
- if d.reference_doctype in ["Sales Order", "Purchase Order"]:
- break
- else:
+ if self.references:
+ allowed_types = frozenset(["Sales Order", "Purchase Order"])
+ reference_types = set([x.reference_doctype for x in self.references])
+
+ # If there are referencers other than `allowed_types`, treat this as a normal payment entry
+ if reference_types - allowed_types:
+ self.book_advance_payments_in_separate_party_account = False
return
liability_account = get_party_account(
@@ -359,12 +369,12 @@
self.set(self.party_account_field, party_account)
self.party_account = party_account
- if self.paid_from and not (self.paid_from_account_currency or self.paid_from_account_balance):
+ if self.paid_from and not self.paid_from_account_currency and not self.paid_from_account_balance:
acc = get_account_details(self.paid_from, self.posting_date, self.cost_center)
self.paid_from_account_currency = acc.account_currency
self.paid_from_account_balance = acc.account_balance
- if self.paid_to and not (self.paid_to_account_currency or self.paid_to_account_balance):
+ if self.paid_to and not self.paid_to_account_currency and not self.paid_to_account_balance:
acc = get_account_details(self.paid_to, self.posting_date, self.cost_center)
self.paid_to_account_currency = acc.account_currency
self.paid_to_account_balance = acc.account_balance
@@ -380,8 +390,9 @@
) -> None:
for d in self.get("references"):
if d.allocated_amount:
- if update_ref_details_only_for and (
- not (d.reference_doctype, d.reference_name) in update_ref_details_only_for
+ if (
+ update_ref_details_only_for
+ and (d.reference_doctype, d.reference_name) not in update_ref_details_only_for
):
continue
@@ -692,7 +703,7 @@
self.db_set("status", self.status, update_modified=True)
def set_tax_withholding(self):
- if not self.party_type == "Supplier":
+ if self.party_type != "Supplier":
return
if not self.apply_tax_withholding_amount:
@@ -783,7 +794,7 @@
self.base_received_amount = self.base_paid_amount
if (
self.paid_from_account_currency == self.paid_to_account_currency
- and not self.payment_type == "Internal Transfer"
+ and self.payment_type != "Internal Transfer"
):
self.received_amount = self.paid_amount
@@ -1747,7 +1758,7 @@
"Payment Schedule", filters={"parent": invoice.voucher_no}, fields=["*"], order_by="due_date"
)
for payment_term in payment_schedule:
- if not payment_term.outstanding > 0.1:
+ if payment_term.outstanding <= 0.1:
continue
doc_details = exc_rates.get(payment_term.parent, None)
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index fbc4d24..ed0921b 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -594,6 +594,27 @@
invoice_exchange_map.update(purchase_invoice_map)
+ journals = [
+ d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Journal Entry"
+ ]
+ journals.extend(
+ [d.get("reference_name") for d in payments if d.get("reference_type") == "Journal Entry"]
+ )
+ if journals:
+ journals = list(set(journals))
+ journals_map = frappe._dict(
+ frappe.db.get_all(
+ "Journal Entry Account",
+ filters={"parent": ("in", journals), "account": ("in", [self.receivable_payable_account])},
+ fields=[
+ "parent as `name`",
+ "exchange_rate",
+ ],
+ as_list=1,
+ )
+ )
+ invoice_exchange_map.update(journals_map)
+
return invoice_exchange_map
def validate_allocation(self):
diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py
index a4141af..b57ebec 100644
--- a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py
@@ -34,4 +34,6 @@
unreconciled_amount: DF.Currency
# end: auto-generated types
- pass
+ @staticmethod
+ def get_list(args):
+ pass
diff --git a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.py b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.py
index 1e9f6ce..fa18ccd 100644
--- a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.py
+++ b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.py
@@ -26,4 +26,6 @@
parenttype: DF.Data
# end: auto-generated types
- pass
+ @staticmethod
+ def get_list(args):
+ pass
diff --git a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.py b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.py
index aa956fe..4ab80ec 100644
--- a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.py
+++ b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.py
@@ -30,4 +30,6 @@
remark: DF.SmallText | None
# end: auto-generated types
- pass
+ @staticmethod
+ def get_list(args):
+ pass
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
index b41cf53..82bd662 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
@@ -286,7 +286,7 @@
def validate_price_list_with_currency(self):
if self.currency and self.for_price_list:
price_list_currency = frappe.db.get_value("Price List", self.for_price_list, "currency", True)
- if not self.currency == price_list_currency:
+ if self.currency != price_list_currency:
throw(_("Currency should be same as Price List Currency: {0}").format(price_list_currency))
def validate_dates(self):
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index 57feaa0..18aa682 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -581,6 +581,8 @@
if d.price_or_product_discount == "Price":
if d.apply_discount_on:
doc.set("apply_discount_on", d.apply_discount_on)
+ # Variable to track whether the condition has been met
+ condition_met = False
for field in ["additional_discount_percentage", "discount_amount"]:
pr_field = "discount_percentage" if field == "additional_discount_percentage" else field
@@ -603,6 +605,11 @@
if coupon_code_pricing_rule == d.name:
# if selected coupon code is linked with pricing rule
doc.set(field, d.get(pr_field))
+
+ # Set the condition_met variable to True and break out of the loop
+ condition_met = True
+ break
+
else:
# reset discount if not linked
doc.set(field, 0)
@@ -611,6 +618,10 @@
doc.set(field, 0)
doc.calculate_taxes_and_totals()
+
+ # Break out of the main loop if the condition is met
+ if condition_met:
+ break
elif d.price_or_product_discount == "Product":
item_details = frappe._dict({"parenttype": doc.doctype, "free_item_data": []})
get_product_discount_rule(d, item_details, doc=doc)
diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py
index 67a7f90..f44b14c 100644
--- a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py
+++ b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py
@@ -475,7 +475,7 @@
frappe.db.set_value("Process Payment Reconciliation", doc, "status", "Completed")
else:
- if not (frappe.db.get_value("Process Payment Reconciliation", doc, "status") == "Paused"):
+ if frappe.db.get_value("Process Payment Reconciliation", doc, "status") != "Paused":
# trigger next batch in job
# generate reconcile job name
allocation = get_next_allocation(log)
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 4b0df12..cebd61a 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -35,7 +35,7 @@
super.onload();
// Ignore linked advances
- this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger"];
+ this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"];
if(!this.frm.doc.__islocal) {
// show credit_to in print format
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 262732c..ae377eb 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -371,7 +371,7 @@
check_list = []
for d in self.get("items"):
- if d.purchase_order and not d.purchase_order in check_list and not d.purchase_receipt:
+ if d.purchase_order and d.purchase_order not in check_list and not d.purchase_receipt:
check_list.append(d.purchase_order)
check_on_hold_or_closed_status("Purchase Order", d.purchase_order)
@@ -1449,6 +1449,8 @@
"Repost Payment Ledger Items",
"Repost Accounting Ledger",
"Repost Accounting Ledger Items",
+ "Unreconcile Payment",
+ "Unreconcile Payment Entries",
"Payment Ledger Entry",
"Tax Withheld Vouchers",
"Serial and Batch Bundle",
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
index 8c23c67..7aa631b 100644
--- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
+++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
@@ -126,7 +126,7 @@
return rendered_page
def on_submit(self):
- if len(self.vouchers) > 1:
+ if len(self.vouchers) > 5:
job_name = "repost_accounting_ledger_" + self.name
frappe.enqueue(
method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost",
@@ -170,8 +170,6 @@
doc.make_gl_entries(1)
doc.make_gl_entries()
- frappe.db.commit()
-
def get_allowed_types_from_settings():
return [
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py
index dda0ec7..d6f7096 100644
--- a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py
+++ b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py
@@ -20,18 +20,11 @@
self.create_company()
self.create_customer()
self.create_item()
- self.update_repost_settings()
+ update_repost_settings()
- def teadDown(self):
+ def tearDown(self):
frappe.db.rollback()
- def update_repost_settings(self):
- allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
- repost_settings = frappe.get_doc("Repost Accounting Ledger Settings")
- for x in allowed_types:
- repost_settings.append("allowed_types", {"document_type": x, "allowed": True})
- repost_settings.save()
-
def test_01_basic_functions(self):
si = create_sales_invoice(
item=self.item,
@@ -90,9 +83,6 @@
# Submit repost document
ral.save().submit()
- # background jobs don't run on test cases. Manually triggering repost function.
- start_repost(ral.name)
-
res = (
qb.from_(gl)
.select(gl.voucher_no, Sum(gl.debit).as_("debit"), Sum(gl.credit).as_("credit"))
@@ -177,26 +167,6 @@
pe = get_payment_entry(si.doctype, si.name)
pe.save().submit()
- # without deletion flag set
- ral = frappe.new_doc("Repost Accounting Ledger")
- ral.company = self.company
- ral.delete_cancelled_entries = False
- ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
- ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
- ral.save()
-
- # assert preview data is generated
- preview = ral.generate_preview()
- self.assertIsNotNone(preview)
-
- ral.save().submit()
-
- # background jobs don't run on test cases. Manually triggering repost function.
- start_repost(ral.name)
-
- self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
- self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
-
# with deletion flag set
ral = frappe.new_doc("Repost Accounting Ledger")
ral.company = self.company
@@ -205,6 +175,38 @@
ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
ral.save().submit()
- start_repost(ral.name)
self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
+
+ def test_05_without_deletion_flag(self):
+ si = create_sales_invoice(
+ item=self.item,
+ company=self.company,
+ customer=self.customer,
+ debit_to=self.debit_to,
+ parent_cost_center=self.cost_center,
+ cost_center=self.cost_center,
+ rate=100,
+ )
+
+ pe = get_payment_entry(si.doctype, si.name)
+ pe.save().submit()
+
+ # without deletion flag set
+ ral = frappe.new_doc("Repost Accounting Ledger")
+ ral.company = self.company
+ ral.delete_cancelled_entries = False
+ ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
+ ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
+ ral.save().submit()
+
+ self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
+ self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
+
+
+def update_repost_settings():
+ allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
+ repost_settings = frappe.get_doc("Repost Accounting Ledger Settings")
+ for x in allowed_types:
+ repost_settings.append("allowed_types", {"document_type": x, "allowed": True})
+ repost_settings.save()
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 96a557b..f2f4dda 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -458,7 +458,7 @@
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
self.check_credit_limit()
- if not cint(self.is_pos) == 1 and not self.is_return:
+ if cint(self.is_pos) != 1 and not self.is_return:
self.update_against_document_in_jv()
self.update_time_sheet(self.name)
@@ -1960,9 +1960,9 @@
if inter_company_reference:
doc = frappe.get_doc(ref_doc, inter_company_reference)
ref_party = doc.supplier if doctype in ["Sales Invoice", "Sales Order"] else doc.customer
- if not frappe.db.get_value(partytype, {"represents_company": doc.company}, "name") == party:
+ if frappe.db.get_value(partytype, {"represents_company": doc.company}, "name") != party:
frappe.throw(_("Invalid {0} for Inter Company Transaction.").format(_(partytype)))
- if not frappe.get_cached_value(ref_partytype, ref_party, "represents_company") == company:
+ if frappe.get_cached_value(ref_partytype, ref_party, "represents_company") != company:
frappe.throw(_("Invalid Company for Inter Company Transaction."))
elif frappe.db.get_value(partytype, {"name": party, internal: 1}, "name") == party:
@@ -1972,7 +1972,7 @@
filters={"parenttype": partytype, "parent": party},
)
companies = [d.company for d in companies]
- if not company in companies:
+ if company not in companies:
frappe.throw(
_("{0} not allowed to transact with {1}. Please change the Company.").format(
_(partytype), company
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index e9b71dd..6163749 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -2793,6 +2793,12 @@
@change_settings("Selling Settings", {"enable_discount_accounting": 1})
def test_additional_discount_for_sales_invoice_with_discount_accounting_enabled(self):
+ from erpnext.accounts.doctype.repost_accounting_ledger.test_repost_accounting_ledger import (
+ update_repost_settings,
+ )
+
+ update_repost_settings()
+
additional_discount_account = create_account(
account_name="Discount Account",
parent_account="Indirect Expenses - _TC",
diff --git a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
index 52a60ac..6877a74 100644
--- a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
+++ b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
@@ -142,12 +142,12 @@
}
if self.shipping_rule_type == "Selling":
# check if not applied on purchase
- if not doc.meta.get_field("taxes").options == "Sales Taxes and Charges":
+ if doc.meta.get_field("taxes").options != "Sales Taxes and Charges":
frappe.throw(_("Shipping rule only applicable for Selling"))
shipping_charge["doctype"] = "Sales Taxes and Charges"
else:
# check if not applied on sales
- if not doc.meta.get_field("taxes").options == "Purchase Taxes and Charges":
+ if doc.meta.get_field("taxes").options != "Purchase Taxes and Charges":
frappe.throw(_("Shipping rule only applicable for Buying"))
shipping_charge["doctype"] = "Purchase Taxes and Charges"
diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py
index 6cc2d1e..aba1b64 100644
--- a/erpnext/accounts/doctype/subscription/subscription.py
+++ b/erpnext/accounts/doctype/subscription/subscription.py
@@ -676,7 +676,7 @@
to_generate_invoice = (
True
if self.status == "Active"
- and not self.generate_invoice_at == "Beginning of the current subscription period"
+ and self.generate_invoice_at != "Beginning of the current subscription period"
else False
)
self.status = "Cancelled"
@@ -694,7 +694,7 @@
subscription and the `Subscription` will lose all the history of generated invoices
it has.
"""
- if not self.status == "Cancelled":
+ if self.status != "Cancelled":
frappe.throw(_("You cannot restart a Subscription that is not cancelled."), InvoiceNotCancelled)
self.status = "Active"
diff --git a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py
index e258a73..9b56952 100644
--- a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py
+++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py
@@ -37,7 +37,7 @@
def validate(self):
self.supported_types = ["Payment Entry", "Journal Entry"]
- if not self.voucher_type in self.supported_types:
+ if self.voucher_type not in self.supported_types:
frappe.throw(_("Only {0} are supported").format(comma_and(self.supported_types)))
@frappe.whitelist()
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 5c18e50..008614e 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -195,7 +195,7 @@
company_address=None,
shipping_address=None,
*,
- ignore_permissions=False
+ ignore_permissions=False,
):
billing_address_field = (
"customer_address" if party_type == "Lead" else party_type.lower() + "_address"
@@ -239,7 +239,7 @@
shipping_address_display=render_address(
shipping_address, check_permissions=not ignore_permissions
),
- **get_fetch_values(doctype, "shipping_address", shipping_address)
+ **get_fetch_values(doctype, "shipping_address", shipping_address),
)
if party_details.company_address:
@@ -250,7 +250,7 @@
party_details.company_address_display
or render_address(party_details.company_address, check_permissions=False)
),
- **get_fetch_values(doctype, "billing_address", party_details.company_address)
+ **get_fetch_values(doctype, "billing_address", party_details.company_address),
)
# shipping address - if not already set
@@ -258,7 +258,7 @@
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_fetch_values(doctype, "shipping_address", party_details.billing_address),
)
party_address, shipping_address = (
@@ -775,7 +775,7 @@
frozen_accounts_modifier = frappe.db.get_single_value(
"Accounts Settings", "frozen_accounts_modifier"
)
- if not frozen_accounts_modifier in frappe.get_roles():
+ if frozen_accounts_modifier not in frappe.get_roles():
frappe.throw(_("{0} {1} is frozen").format(party_type, party_name), PartyFrozen)
elif party_type == "Employee":
@@ -981,6 +981,9 @@
if party:
query = query.where(ple.party == party)
+ if invoice_doctypes := frappe.get_hooks("invoice_doctypes"):
+ query = query.where(ple.voucher_type.notin(invoice_doctypes))
+
data = query.run()
if data:
return frappe._dict(data)
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 7948e5f..50d5eae 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -123,7 +123,7 @@
else:
key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party)
- if not key in self.voucher_balance:
+ if key not in self.voucher_balance:
self.voucher_balance[key] = frappe._dict(
voucher_type=ple.voucher_type,
voucher_no=ple.voucher_no,
@@ -938,7 +938,7 @@
return True
def get_party_details(self, party):
- if not party in self.party_details:
+ if party not in self.party_details:
if self.account_type == "Receivable":
fields = ["customer_name", "territory", "customer_group", "customer_primary_contact"]
@@ -1087,7 +1087,7 @@
)
if self.filters.show_remarks:
- self.add_column(label=_("Remarks"), fieldname="remarks", fieldtype="Text", width=200),
+ self.add_column(label=_("Remarks"), fieldname="remarks", fieldtype="Text", width=200)
def add_column(self, label, fieldname=None, fieldtype="Currency", options=None, width=120):
if not fieldname:
diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py
index cad5325..eebd61c 100644
--- a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py
+++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py
@@ -97,7 +97,7 @@
if base_amount + already_booked_amount > self.base_net_amount:
base_amount = self.base_net_amount - already_booked_amount
- if not (get_first_day(start_date) == start_date and get_last_day(end_date) == end_date):
+ if get_first_day(start_date) != start_date or get_last_day(end_date) != end_date:
partial_month = flt(date_diff(end_date, start_date)) / flt(
date_diff(get_last_day(end_date), get_first_day(start_date))
)
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index 096bb10..7355c4b 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -8,7 +8,17 @@
import frappe
from frappe import _
-from frappe.utils import add_days, add_months, cint, cstr, flt, formatdate, get_first_day, getdate
+from frappe.utils import (
+ add_days,
+ add_months,
+ cint,
+ cstr,
+ flt,
+ formatdate,
+ get_first_day,
+ getdate,
+ today,
+)
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
@@ -43,6 +53,8 @@
year_start_date = getdate(period_start_date)
year_end_date = getdate(period_end_date)
+ year_end_date = getdate(today()) if year_end_date > getdate(today()) else year_end_date
+
months_to_add = {"Yearly": 12, "Half-Yearly": 6, "Quarterly": 3, "Monthly": 1}[periodicity]
period_list = []
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index fa557a1..ac06a12 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -289,7 +289,8 @@
if accounting_dimensions:
for dimension in accounting_dimensions:
- if not dimension.disabled:
+ # Ignore 'Finance Book' set up as dimension in below logic, as it is already handled in above section
+ if not dimension.disabled and dimension.document_type != "Finance Book":
if filters.get(dimension.fieldname):
if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
filters[dimension.fieldname] = get_dimension_with_children(
diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py
index f0ca405..604bc01 100644
--- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py
+++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py
@@ -170,7 +170,7 @@
totals[node["account"]] += value
parent = node["parent_account"]
- if not parent == "":
+ if parent != "":
return set_total(
next(item for item in complete_list if item["account"] == parent), value, complete_list, totals
)
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index 38060bb..e4efefe 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -695,7 +695,7 @@
def get_average_buying_rate(self, row, item_code):
args = row
- if not item_code in self.average_buying_rate:
+ if item_code not in self.average_buying_rate:
args.update(
{
"voucher_type": row.parenttype,
diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
index ad196a9..9c6e2d0 100644
--- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
+++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
@@ -319,7 +319,7 @@
`tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
`tabPurchase Invoice`.unrealized_profit_loss_account,
- `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
+ `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description, `tabPurchase Invoice Item`.`item_group`,
`tabPurchase Invoice Item`.`item_name` as pi_item_name, `tabPurchase Invoice Item`.`item_group` as pi_item_group,
`tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group,
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
index f6c7bd3..ba946c3 100644
--- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
+++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
@@ -154,7 +154,7 @@
)
for d in gle:
- if not d.voucher_no in gle_map:
+ if d.voucher_no not in gle_map:
gle_map[d.voucher_no] = [d]
else:
gle_map[d.voucher_no].append(d)
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index f88e26e..6b59827 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -1062,11 +1062,11 @@
if (
min_outstanding
and max_outstanding
- and not (outstanding_amount >= min_outstanding and outstanding_amount <= max_outstanding)
+ and (outstanding_amount < min_outstanding or outstanding_amount > max_outstanding)
):
continue
- if not d.voucher_type == "Purchase Invoice" or d.voucher_no not in held_invoices:
+ if d.voucher_type != "Purchase Invoice" or d.voucher_no not in held_invoices:
outstanding_invoices.append(
frappe._dict(
{
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 5fb2d36..707ce19 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -313,7 +313,7 @@
frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError)
if is_cwip_accounting_enabled(self.asset_category):
- if not self.is_existing_asset and not (self.purchase_receipt or self.purchase_invoice):
+ if not self.is_existing_asset and not self.purchase_receipt and not self.purchase_invoice:
frappe.throw(
_("Please create purchase receipt or purchase invoice for the item {0}").format(
self.item_code
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
index 0021140..67234cc 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
@@ -340,6 +340,10 @@
n == 0
and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
and not self.opening_accumulated_depreciation
+ and get_updated_rate_of_depreciation_for_wdv_and_dd(
+ asset_doc, value_after_depreciation, row, False
+ )
+ == row.rate_of_depreciation
):
from_date = add_days(
asset_doc.available_for_use_date, -1
@@ -605,7 +609,9 @@
@erpnext.allow_regional
-def get_updated_rate_of_depreciation_for_wdv_and_dd(asset, depreciable_value, fb_row):
+def get_updated_rate_of_depreciation_for_wdv_and_dd(
+ asset, depreciable_value, fb_row, show_msg=True
+):
return fb_row.rate_of_depreciation
diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py
index 0d8efcb..ff52643 100644
--- a/erpnext/assets/doctype/asset_movement/asset_movement.py
+++ b/erpnext/assets/doctype/asset_movement/asset_movement.py
@@ -84,7 +84,7 @@
frappe.throw(_("Source and Target Location cannot be same"))
if self.purpose == "Receipt":
- if not (d.source_location) and not (d.target_location or d.to_employee):
+ if not (d.source_location) and not d.target_location and not d.to_employee:
frappe.throw(
_("Target Location or To Employee is required while receiving Asset {0}").format(d.asset)
)
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 eea8cd5..5b8be44 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -119,6 +119,15 @@
supplier.quote_status = "Pending"
self.send_to_supplier()
+ def before_print(self, settings=None):
+ """Use the first suppliers data to render the print preview."""
+ if self.vendor or not self.suppliers:
+ # If a specific supplier is already set, via Tools > Download PDF,
+ # we don't want to override it.
+ return
+
+ self.update_supplier_part_no(self.suppliers[0].supplier)
+
def on_cancel(self):
self.db_set("status", "Cancelled")
diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py
index b6e4630..b88efe1 100644
--- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py
+++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py
@@ -114,7 +114,7 @@
if filters.get("group_by_po"):
po_name = row["purchase_order"]
- if not po_name in purchase_order_map:
+ if po_name not in purchase_order_map:
# create an entry
row_copy = copy.deepcopy(row)
purchase_order_map[po_name] = row_copy
diff --git a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py
index 0718735..d431010 100644
--- a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py
+++ b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py
@@ -110,7 +110,7 @@
for row in data:
# item wise map for charts
- if not row["item_code"] in item_qty_map:
+ if row["item_code"] not in item_qty_map:
item_qty_map[row["item_code"]] = {
"qty": flt(row["stock_qty"], precision),
"stock_qty": flt(row["stock_qty"], precision),
@@ -127,7 +127,7 @@
if filters.get("group_by_mr"):
# consolidated material request map for group by filter
- if not row["material_request"] in material_request_map:
+ if row["material_request"] not in material_request_map:
# create an entry with mr as key
row_copy = copy.deepcopy(row)
material_request_map[row["material_request"]] = row_copy
diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py
index 01ff28d..73b7d45 100644
--- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py
+++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py
@@ -126,7 +126,7 @@
# map for chart preparation of the form {'supplier1': {'qty': 'price'}}
supplier = data.get("supplier_name")
if filters.get("item_code"):
- if not supplier in supplier_qty_price_map:
+ if supplier not in supplier_qty_price_map:
supplier_qty_price_map[supplier] = {}
supplier_qty_price_map[supplier][row["qty"]] = row["price"]
@@ -169,7 +169,7 @@
for supplier in suppliers:
entry = supplier_qty_price_map[supplier]
for qty in qty_list:
- if not qty in data_points_map:
+ if qty not in data_points_map:
data_points_map[qty] = []
if qty in entry:
data_points_map[qty].append(entry[qty])
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 1b8f66c..abcea44 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -292,6 +292,7 @@
def on_trash(self):
self._remove_references_in_repost_doctypes()
self._remove_references_in_unreconcile()
+ self.remove_serial_and_batch_bundle()
# delete sl and gl entries on deletion of transaction
if frappe.db.get_single_value("Accounts Settings", "delete_linked_ledger_entries"):
@@ -307,6 +308,15 @@
(self.doctype, self.name),
)
+ def remove_serial_and_batch_bundle(self):
+ bundles = frappe.get_all(
+ "Serial and Batch Bundle",
+ filters={"voucher_type": self.doctype, "voucher_no": self.name, "docstatus": ("!=", 1)},
+ )
+
+ for bundle in bundles:
+ frappe.delete_doc("Serial and Batch Bundle", bundle.name)
+
def validate_deferred_income_expense_account(self):
field_map = {
"Sales Invoice": "deferred_revenue_account",
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 199732b..63dca63 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -222,7 +222,7 @@
searchfields = meta.get_search_fields()
columns = ""
- extra_searchfields = [field for field in searchfields if not field in ["name", "description"]]
+ extra_searchfields = [field for field in searchfields if field not in ["name", "description"]]
if extra_searchfields:
columns += ", " + ", ".join(extra_searchfields)
@@ -233,8 +233,13 @@
searchfields = searchfields + [
field
- for field in [searchfield or "name", "item_code", "item_group", "item_name"]
- if not field in searchfields
+ for field in [
+ searchfield or "name",
+ "item_code",
+ "item_group",
+ "item_name",
+ ]
+ if field not in searchfields
]
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
@@ -872,7 +877,7 @@
meta = frappe.get_meta(doctype)
fields.extend(meta.get_search_fields())
- if meta.title_field and not meta.title_field.strip() in fields:
+ if meta.title_field and meta.title_field.strip() not in fields:
fields.insert(1, meta.title_field.strip())
return unique(fields)
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index fc45c7a..2fda9cc 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -642,7 +642,7 @@
)
qa_docstatus = frappe.db.get_value("Quality Inspection", row.quality_inspection, "docstatus")
- if not qa_docstatus == 1:
+ if qa_docstatus != 1:
link = frappe.utils.get_link_to_form("Quality Inspection", row.quality_inspection)
msg = (
f"Row #{row.idx}: Quality Inspection {link} is not submitted for the item: {row.item_code}"
diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py
index 7be6fdc..a735510 100644
--- a/erpnext/crm/doctype/appointment/appointment.py
+++ b/erpnext/crm/doctype/appointment/appointment.py
@@ -55,7 +55,7 @@
"Appointment", filters={"scheduled_time": self.scheduled_time}
)
number_of_agents = frappe.db.get_single_value("Appointment Booking Settings", "number_of_agents")
- if not number_of_agents == 0:
+ if number_of_agents != 0:
if number_of_appointments_in_same_slot >= number_of_agents:
frappe.throw(_("Time slot is not available"))
# Link lead
@@ -110,7 +110,7 @@
cal_event.save(ignore_permissions=True)
def set_verified(self, email):
- if not email == self.customer_email:
+ if email != self.customer_email:
frappe.throw(_("Email verification failed."))
# Create new lead
self.create_lead_and_link()
diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json
index dafbd9f..92f446d 100644
--- a/erpnext/crm/doctype/lead/lead.json
+++ b/erpnext/crm/doctype/lead/lead.json
@@ -516,7 +516,7 @@
"idx": 5,
"image_field": "image",
"links": [],
- "modified": "2023-08-28 22:28:00.104413",
+ "modified": "2023-12-01 18:46:49.468526",
"modified_by": "Administrator",
"module": "CRM",
"name": "Lead",
@@ -577,6 +577,7 @@
],
"search_fields": "lead_name,lead_owner,status",
"sender_field": "email_id",
+ "sender_name_field": "lead_name",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py
index f0fc1aa..f3c7e57 100644
--- a/erpnext/crm/doctype/lead/lead.py
+++ b/erpnext/crm/doctype/lead/lead.py
@@ -16,6 +16,7 @@
from erpnext.accounts.party import set_taxes
from erpnext.controllers.selling_controller import SellingController
from erpnext.crm.utils import CRMNote, copy_comments, link_communications, link_open_events
+from erpnext.selling.doctype.customer.customer import parse_full_name
class Lead(SellingController, CRMNote):
@@ -105,7 +106,7 @@
if self.source == "Existing Customer" and self.customer:
contact = frappe.db.get_value(
"Dynamic Link",
- {"link_doctype": "Customer", "link_name": self.customer},
+ {"link_doctype": "Customer", "parenttype": "Contact", "link_name": self.customer},
"parent",
)
if contact:
@@ -113,6 +114,10 @@
return
self.contact_doc = self.create_contact()
+ # leads created by email inbox only have the full name set
+ if self.lead_name and not any([self.first_name, self.middle_name, self.last_name]):
+ self.first_name, self.middle_name, self.last_name = parse_full_name(self.lead_name)
+
def after_insert(self):
self.link_to_contact()
diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py
index 8984f1b..1924ffb 100644
--- a/erpnext/erpnext_integrations/utils.py
+++ b/erpnext/erpnext_integrations/utils.py
@@ -16,7 +16,7 @@
hmac.new(settings.get(secret_key).encode("utf8"), frappe.request.data, hashlib.sha256).digest()
)
- if frappe.request.data and not sig == bytes(frappe.get_request_header(hmac_key).encode()):
+ if frappe.request.data and sig != bytes(frappe.get_request_header(hmac_key).encode()):
frappe.throw(_("Unverified Webhook Data"))
frappe.set_user(settings.modified_by)
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 17ad155..f6b6802 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -637,6 +637,7 @@
extend_bootinfo = [
"erpnext.support.doctype.service_level_agreement.service_level_agreement.add_sla_doctypes",
+ "erpnext.startup.boot.bootinfo",
]
diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
index b5ab63e..6a72c4f 100644
--- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
+++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
@@ -89,7 +89,7 @@
def update_item(source, target, source_parent):
target_qty = source.get("qty") - source.get("ordered_qty")
- target.qty = target_qty if not flt(target_qty) < 0 else 0
+ target.qty = target_qty if flt(target_qty) >= 0 else 0
item = get_item_defaults(target.item_code, source_parent.company)
if item:
target.item_name = item.get("item_name")
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 71015a4..f0381d2 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -1381,7 +1381,7 @@
# check for deletions
for d in old_value:
- if not d.get(identifier) in new_row_by_identifier:
+ if d.get(identifier) not in new_row_by_identifier:
out.removed.append([df.fieldname, d.as_dict()])
return out
@@ -1397,13 +1397,18 @@
fields = ["name", "item_name", "item_group", "description"]
fields.extend(
- [field for field in searchfields if not field in ["name", "item_group", "description"]]
+ [field for field in searchfields if field not in ["name", "item_group", "description"]]
)
searchfields = searchfields + [
field
- for field in [searchfield or "name", "item_code", "item_group", "item_name"]
- if not field in searchfields
+ for field in [
+ searchfield or "name",
+ "item_code",
+ "item_group",
+ "item_name",
+ ]
+ if field not in searchfields
]
query_filters = {"disabled": 0, "ifnull(end_of_life, '3099-12-31')": (">", today())}
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 955821f..c201c4f 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -815,7 +815,7 @@
key = "{}:{}:{}".format(item.sales_order, material_request_type, item_doc.customer or "")
schedule_date = item.schedule_date or add_days(nowdate(), cint(item_doc.lead_time_days))
- if not key in material_request_map:
+ if key not in material_request_map:
# make a new MR for the combination
material_request_map[key] = frappe.new_doc("Material Request")
material_request = material_request_map[key]
@@ -1597,19 +1597,23 @@
)
locations = get_available_item_locations(
- item.get("item_code"), warehouses, item.get("quantity"), company, ignore_validation=True
+ item.get("item_code"),
+ warehouses,
+ item.get("quantity") * item.get("conversion_factor"),
+ company,
+ ignore_validation=True,
)
required_qty = item.get("quantity")
+ if item.get("conversion_factor") and item.get("purchase_uom") != item.get("stock_uom"):
+ # Convert qty to stock UOM
+ required_qty = required_qty * item.get("conversion_factor")
+
# get available material by transferring to production warehouse
for d in locations:
if required_qty <= 0:
return
- conversion_factor = 1.0
- if purchase_uom != stock_uom and purchase_uom == item["uom"]:
- conversion_factor = get_uom_conversion_factor(item["item_code"], item["uom"])
-
new_dict = copy.deepcopy(item)
quantity = required_qty if d.get("qty") > required_qty else d.get("qty")
@@ -1619,10 +1623,11 @@
"material_request_type": "Material Transfer",
"uom": new_dict.get("stock_uom"), # internal transfer should be in stock UOM
"from_warehouse": d.get("warehouse"),
+ "conversion_factor": 1.0,
}
)
- required_qty -= quantity / conversion_factor
+ required_qty -= quantity
new_mr_items.append(new_dict)
# raise purchase request for remaining qty
@@ -1634,7 +1639,7 @@
if frappe.db.get_value("UOM", purchase_uom, "must_be_whole_number"):
required_qty = ceil(required_qty)
- item["quantity"] = required_qty
+ item["quantity"] = required_qty / item.get("conversion_factor")
new_mr_items.append(item)
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index dd32c34..cc9d9a0 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -1283,12 +1283,14 @@
for row in items:
row = frappe._dict(row)
if row.material_request_type == "Material Transfer":
+ self.assertTrue(row.uom == row.stock_uom)
self.assertTrue(row.from_warehouse in [wh1, wh2])
self.assertEqual(row.quantity, 2)
if row.material_request_type == "Purchase":
+ self.assertTrue(row.uom != row.stock_uom)
self.assertTrue(row.warehouse == mrp_warhouse)
- self.assertEqual(row.quantity, 12)
+ self.assertEqual(row.quantity, 12.0)
def test_mr_qty_for_same_rm_with_different_sub_assemblies(self):
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
@@ -1404,6 +1406,58 @@
self.assertEqual(after_qty, before_qty)
+ def test_material_request_qty_purchase_and_material_transfer(self):
+ from erpnext.stock.doctype.item.test_item import make_item
+ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
+
+ fg_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name
+ bom_item = make_item(
+ properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1", "purchase_uom": "Nos"}
+ ).name
+
+ store_warehouse = create_warehouse("Store Warehouse", company="_Test Company")
+ rm_warehouse = create_warehouse("RM Warehouse", company="_Test Company")
+
+ make_stock_entry(
+ item_code=bom_item,
+ qty=60,
+ target=store_warehouse,
+ rate=99,
+ )
+
+ if not frappe.db.exists("UOM Conversion Detail", {"parent": bom_item, "uom": "Nos"}):
+ doc = frappe.get_doc("Item", bom_item)
+ doc.append("uoms", {"uom": "Nos", "conversion_factor": 10})
+ doc.save()
+
+ make_bom(item=fg_item, raw_materials=[bom_item], source_warehouse="_Test Warehouse - _TC")
+
+ pln = create_production_plan(
+ item_code=fg_item, planned_qty=10, stock_uom="_Test UOM 1", do_not_submit=1
+ )
+
+ pln.for_warehouse = rm_warehouse
+ items = get_items_for_material_requests(
+ pln.as_dict(), warehouses=[{"warehouse": store_warehouse}]
+ )
+
+ for row in items:
+ self.assertEqual(row.get("quantity"), 10.0)
+ self.assertEqual(row.get("material_request_type"), "Material Transfer")
+ self.assertEqual(row.get("uom"), "_Test UOM 1")
+ self.assertEqual(row.get("from_warehouse"), store_warehouse)
+ self.assertEqual(row.get("conversion_factor"), 1.0)
+
+ items = get_items_for_material_requests(
+ pln.as_dict(), warehouses=[{"warehouse": pln.for_warehouse}]
+ )
+
+ for row in items:
+ self.assertEqual(row.get("quantity"), 1.0)
+ self.assertEqual(row.get("material_request_type"), "Purchase")
+ self.assertEqual(row.get("uom"), "Nos")
+ self.assertEqual(row.get("conversion_factor"), 10.0)
+
def create_production_plan(**args):
"""
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 78bfc76..0acc2b1 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -930,7 +930,7 @@
validate_end_of_life(self.production_item)
def validate_qty(self):
- if not self.qty > 0:
+ if self.qty <= 0:
frappe.throw(_("Quantity to Manufacture must be greater than 0."))
if (
@@ -957,7 +957,7 @@
max_qty = qty_dict.get("planned_qty", 0) + allowance_qty - qty_dict.get("ordered_qty", 0)
- if not max_qty > 0:
+ if max_qty <= 0:
frappe.throw(
_("Cannot produce more item for {0}").format(self.production_item), OverProductionError
)
@@ -968,7 +968,7 @@
)
def validate_transfer_against(self):
- if not self.docstatus == 1:
+ if self.docstatus != 1:
# let user configure operations until they're ready to submit
return
if not self.operations:
@@ -981,7 +981,7 @@
def validate_operation_time(self):
for d in self.operations:
- if not d.time_in_mins > 0:
+ if d.time_in_mins <= 0:
frappe.throw(_("Operation Time must be greater than 0 for Operation {0}").format(d.operation))
def update_required_items(self):
diff --git a/erpnext/patches/v12_0/set_task_status.py b/erpnext/patches/v12_0/set_task_status.py
index 1c6654e..27810d7 100644
--- a/erpnext/patches/v12_0/set_task_status.py
+++ b/erpnext/patches/v12_0/set_task_status.py
@@ -10,7 +10,7 @@
)
if property_setter_name:
property_setter = frappe.get_doc("Property Setter", property_setter_name)
- if not "Completed" in property_setter.value:
+ if "Completed" not in property_setter.value:
property_setter.value = property_setter.value + "\nCompleted"
property_setter.save()
diff --git a/erpnext/patches/v13_0/update_sla_enhancements.py b/erpnext/patches/v13_0/update_sla_enhancements.py
index 84c683a..cf9e185 100644
--- a/erpnext/patches/v13_0/update_sla_enhancements.py
+++ b/erpnext/patches/v13_0/update_sla_enhancements.py
@@ -46,7 +46,7 @@
{"response_time": response_time, "resolution_time": resolution_time},
)
if priority.parenttype == "Service Level":
- if not priority.parent in priority_dict:
+ if priority.parent not in priority_dict:
priority_dict[priority.parent] = []
priority_dict[priority.parent].append(priority)
diff --git a/erpnext/portal/utils.py b/erpnext/portal/utils.py
index 903d4a6..86426b2 100644
--- a/erpnext/portal/utils.py
+++ b/erpnext/portal/utils.py
@@ -50,7 +50,7 @@
party = frappe.new_doc(doctype)
fullname = frappe.utils.get_fullname(user)
- if not doctype == "Customer":
+ if doctype != "Customer":
party.update(
{
"supplier_name": fullname,
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index 4f2e395..751dcbd 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -661,7 +661,7 @@
"""
set status for project and all related tasks
"""
- if not status in ("Completed", "Cancelled"):
+ if status not in ("Completed", "Cancelled"):
frappe.throw(_("Status must be Cancelled or Completed"))
project = frappe.get_doc("Project", project)
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index 0860d9c..3ed7fc7 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -36,14 +36,14 @@
// no idea where me is coming from
if(this.frm.get_field('shipping_address')) {
- this.frm.set_query("shipping_address", function() {
- if(me.frm.doc.customer) {
+ this.frm.set_query("shipping_address", () => {
+ if(this.frm.doc.customer) {
return {
query: 'frappe.contacts.doctype.address.address.address_query',
- filters: { link_doctype: 'Customer', link_name: me.frm.doc.customer }
+ filters: { link_doctype: 'Customer', link_name: this.frm.doc.customer }
};
} else
- return erpnext.queries.company_address_query(me.frm.doc)
+ return erpnext.queries.company_address_query(this.frm.doc)
});
}
}
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 8c0d84b..3935783 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -380,6 +380,7 @@
}
scan_barcode() {
+ frappe.flags.dialog_set = false;
const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
barcode_scanner.process_scan();
}
diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js
index 1b10d8a..17341d1 100644
--- a/erpnext/public/js/financial_statements.js
+++ b/erpnext/public/js/financial_statements.js
@@ -2,10 +2,16 @@
erpnext.financial_statements = {
"filters": get_filters(),
- "formatter": function(value, row, column, data, default_formatter) {
+ "formatter": function(value, row, column, data, default_formatter, filter) {
if (data && column.fieldname=="account") {
value = data.account_name || value;
+ if (filter && filter?.text && filter?.type == "contains") {
+ if (!value.toLowerCase().includes(filter.text)) {
+ return value;
+ }
+ }
+
if (data.account) {
column.link_onclick =
"erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")";
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index 25fc754..b0ea568 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -8,7 +8,7 @@
if(!company && cur_frm)
company = cur_frm.doc.company;
if(company)
- return frappe.get_doc(":Company", company).default_currency || frappe.boot.sysdefaults.currency;
+ return frappe.get_doc(":Company", company)?.default_currency || frappe.boot.sysdefaults.currency;
else
return frappe.boot.sysdefaults.currency;
},
@@ -1077,7 +1077,7 @@
}
function get_time_left(timestamp, agreement_status) {
- const diff = moment(timestamp).diff(moment());
+ const diff = moment(timestamp).diff(frappe.datetime.system_datetime(true));
const diff_display = diff >= 44500 ? moment.duration(diff).humanize() : 'Failed';
let indicator = (diff_display == 'Failed' && agreement_status != 'Fulfilled') ? 'red' : 'green';
return {'diff_display': diff_display, 'indicator': indicator};
diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js
index a4f74bd..a1ebfe9 100644
--- a/erpnext/public/js/utils/barcode_scanner.js
+++ b/erpnext/public/js/utils/barcode_scanner.js
@@ -114,13 +114,13 @@
frappe.run_serially([
() => this.set_selector_trigger_flag(data),
+ () => this.set_serial_no(row, serial_no),
+ () => this.set_batch_no(row, batch_no),
+ () => this.set_barcode(row, barcode),
() => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => {
this.show_scan_message(row.idx, row.item_code, qty);
}),
() => this.set_barcode_uom(row, uom),
- () => this.set_serial_no(row, serial_no),
- () => this.set_batch_no(row, batch_no),
- () => this.set_barcode(row, barcode),
() => this.clean_up(),
() => this.revert_selector_flag(),
() => resolve(row)
@@ -131,10 +131,10 @@
// batch and serial selector is reduandant when all info can be added by scan
// this flag on item row is used by transaction.js to avoid triggering selector
set_selector_trigger_flag(data) {
- const {batch_no, serial_no, has_batch_no, has_serial_no} = data;
+ const {has_batch_no, has_serial_no} = data;
- const require_selecting_batch = has_batch_no && !batch_no;
- const require_selecting_serial = has_serial_no && !serial_no;
+ const require_selecting_batch = has_batch_no;
+ const require_selecting_serial = has_serial_no;
if (!(require_selecting_batch || require_selecting_serial)) {
frappe.flags.hide_serial_batch_dialog = true;
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index 9267801..7b9cdfe 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -31,8 +31,23 @@
secondary_action: () => this.edit_full_form(),
});
- this.dialog.set_value("qty", this.item.qty);
this.dialog.show();
+
+ let qty = this.item.stock_qty || this.item.transfer_qty || this.item.qty;
+ this.dialog.set_value("qty", qty).then(() => {
+ if (this.item.serial_no) {
+ this.dialog.set_value("scan_serial_no", this.item.serial_no);
+ frappe.model.set_value(this.item.doctype, this.item.name, 'serial_no', '');
+ } else if (this.item.batch_no) {
+ this.dialog.set_value("scan_batch_no", this.item.batch_no);
+ frappe.model.set_value(this.item.doctype, this.item.name, 'batch_no', '');
+ }
+
+ this.dialog.fields_dict.entries.grid.refresh();
+ });
+
+ this.$scan_btn = this.dialog.$wrapper.find(".link-btn");
+ this.$scan_btn.css("display", "inline");
}
get_serial_no_filters() {
@@ -95,6 +110,7 @@
if (this.item.has_serial_no) {
fields.push({
fieldtype: 'Data',
+ options: 'Barcode',
fieldname: 'scan_serial_no',
label: __('Scan Serial No'),
get_query: () => {
@@ -106,15 +122,10 @@
});
}
- if (this.item.has_batch_no && this.item.has_serial_no) {
- fields.push({
- fieldtype: 'Column Break',
- });
- }
-
- if (this.item.has_batch_no) {
+ if (this.item.has_batch_no && !this.item.has_serial_no) {
fields.push({
fieldtype: 'Data',
+ options: 'Barcode',
fieldname: 'scan_batch_no',
label: __('Scan Batch No'),
onchange: () => this.update_serial_batch_no()
@@ -309,6 +320,14 @@
}
get_auto_data() {
+ if (this.item.serial_and_batch_bundle || this.item.rejected_serial_and_batch_bundle) {
+ return;
+ }
+
+ if (this.item.serial_no || this.item.batch_no) {
+ return;
+ }
+
let { qty, based_on } = this.dialog.get_values();
if (!based_on) {
@@ -340,16 +359,57 @@
const { scan_serial_no, scan_batch_no } = this.dialog.get_values();
if (scan_serial_no) {
- this.dialog.fields_dict.entries.df.data.push({
- serial_no: scan_serial_no
+ let existing_row = this.dialog.fields_dict.entries.df.data.filter(d => {
+ if (d.serial_no === scan_serial_no) {
+ return d
+ }
});
- this.dialog.fields_dict.scan_serial_no.set_value('');
+ if (existing_row?.length) {
+ frappe.throw(__('Serial No {0} already exists', [scan_serial_no]));
+ }
+
+ if (!this.item.has_batch_no) {
+ this.dialog.fields_dict.entries.df.data.push({
+ serial_no: scan_serial_no
+ });
+
+ this.dialog.fields_dict.scan_serial_no.set_value('');
+ } else {
+ frappe.call({
+ method: 'erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.get_batch_no_from_serial_no',
+ args: {
+ serial_no: scan_serial_no,
+ },
+ callback: (r) => {
+ if (r.message) {
+ this.dialog.fields_dict.entries.df.data.push({
+ serial_no: scan_serial_no,
+ batch_no: r.message
+ });
+
+ this.dialog.fields_dict.scan_serial_no.set_value('');
+ }
+ }
+
+ })
+ }
} else if (scan_batch_no) {
- this.dialog.fields_dict.entries.df.data.push({
- batch_no: scan_batch_no
+ let existing_row = this.dialog.fields_dict.entries.df.data.filter(d => {
+ if (d.batch_no === scan_batch_no) {
+ return d
+ }
});
+ if (existing_row?.length) {
+ existing_row[0].qty += 1;
+ } else {
+ this.dialog.fields_dict.entries.df.data.push({
+ batch_no: scan_batch_no,
+ qty: 1
+ });
+ }
+
this.dialog.fields_dict.scan_batch_no.set_value('');
}
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 96df0ed..efb9820 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -587,7 +587,8 @@
"""
select sum(debit) - sum(credit)
from `tabGL Entry` where party_type = 'Customer'
- and party = %s and company=%s {0}""".format(
+ and is_cancelled = 0 and party = %s
+ and company=%s {0}""".format(
cond
),
(customer, company),
diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
index 2624db3..cd45e7d 100644
--- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
+++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
@@ -240,7 +240,7 @@
for row in data:
item_key = row.get("item_code")
- if not item_key in item_wise_sales_map:
+ if item_key not in item_wise_sales_map:
item_wise_sales_map[item_key] = 0
item_wise_sales_map[item_key] = flt(item_wise_sales_map[item_key]) + flt(row.get("amount"))
diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
index 2969123..1e1d0c0 100644
--- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
+++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
@@ -167,7 +167,7 @@
if filters.get("group_by_so"):
so_name = row["sales_order"]
- if not so_name in sales_order_map:
+ if so_name not in sales_order_map:
# create an entry
row_copy = copy.deepcopy(row)
sales_order_map[so_name] = row_copy
diff --git a/erpnext/setup/doctype/authorization_control/authorization_control.py b/erpnext/setup/doctype/authorization_control/authorization_control.py
index feb14a8..9446fb4 100644
--- a/erpnext/setup/doctype/authorization_control/authorization_control.py
+++ b/erpnext/setup/doctype/authorization_control/authorization_control.py
@@ -185,7 +185,10 @@
# Remove user specific rules from global authorization rules
for r in based_on:
- if r in final_based_on and not r in ["Itemwise Discount", "Item Group wise Discount"]:
+ if r in final_based_on and r not in [
+ "Itemwise Discount",
+ "Item Group wise Discount",
+ ]:
final_based_on.remove(r)
# Check for authorization set on particular roles
@@ -213,7 +216,10 @@
# Remove role specific rules from global authorization rules
for r in based_on:
- if r in final_based_on and not r in ["Itemwise Discount", "Item Group wise Discount"]:
+ if r in final_based_on and r not in [
+ "Itemwise Discount",
+ "Item Group wise Discount",
+ ]:
final_based_on.remove(r)
# Check for global authorization
diff --git a/erpnext/setup/doctype/department/department.py b/erpnext/setup/doctype/department/department.py
index 6b090f8..16f6fbf 100644
--- a/erpnext/setup/doctype/department/department.py
+++ b/erpnext/setup/doctype/department/department.py
@@ -44,7 +44,7 @@
def before_rename(self, old, new, merge=False):
# renaming consistency with abbreviation
- if not frappe.get_cached_value("Company", self.company, "abbr") in new:
+ if frappe.get_cached_value("Company", self.company, "abbr") not in new:
new = get_abbreviated_name(new, self.company)
return new
diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py
index 22bdf50..4b07056 100644
--- a/erpnext/setup/doctype/email_digest/email_digest.py
+++ b/erpnext/setup/doctype/email_digest/email_digest.py
@@ -689,7 +689,7 @@
]
def get_root_type_accounts(self, root_type):
- if not root_type in self._accounts:
+ if root_type not in self._accounts:
self._accounts[root_type] = [
d.name
for d in frappe.db.get_all(
diff --git a/erpnext/setup/doctype/employee/employee.py b/erpnext/setup/doctype/employee/employee.py
index 6f9176c..4bb3539 100755
--- a/erpnext/setup/doctype/employee/employee.py
+++ b/erpnext/setup/doctype/employee/employee.py
@@ -187,7 +187,7 @@
throw(_("Please enter relieving date."))
def validate_for_enabled_user_id(self, enabled):
- if not self.status == "Active":
+ if self.status != "Active":
return
if enabled is None:
diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py
index 12cc62a..7a1d5e2 100644
--- a/erpnext/setup/install.py
+++ b/erpnext/setup/install.py
@@ -195,7 +195,7 @@
for item in erpnext_navbar_items:
current_labels = [item.get("item_label") for item in current_navbar_items]
- if not item.get("item_label") in current_labels:
+ if item.get("item_label") not in current_labels:
navbar_settings.append("help_dropdown", item)
for item in current_navbar_items:
diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py
index 49ba78c..32d92f6 100644
--- a/erpnext/setup/setup_wizard/operations/taxes_setup.py
+++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py
@@ -2,8 +2,8 @@
# License: GNU General Public License v3. See license.txt
-import os
import json
+import os
import frappe
from frappe import _
@@ -114,10 +114,11 @@
frappe.scrub(country)
)
frappe.get_attr(module_name)(country, company)
- except Exception as e:
+ except (ImportError, AttributeError):
+ pass
+ except Exception:
# Log error and ignore if failed to setup regional tax settings
frappe.log_error("Unable to setup regional tax settings")
- pass
def make_taxes_and_charges_template(company_name, doctype, template):
diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py
index bdbf8b4..4b4d14f 100644
--- a/erpnext/startup/boot.py
+++ b/erpnext/startup/boot.py
@@ -75,3 +75,11 @@
"Sales Person Tree": {"title": "Sales Person Tree", "route": "Tree/Sales Person"},
}
)
+
+
+def bootinfo(bootinfo):
+ if bootinfo.get("user") and bootinfo["user"].get("name"):
+ bootinfo["user"]["employee"] = ""
+ employee = frappe.db.get_value("Employee", {"user_id": bootinfo["user"]["name"]}, "name")
+ if employee:
+ bootinfo["user"]["employee"] = employee
diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py
index da7edbf..5a60d2f 100644
--- a/erpnext/startup/leaderboard.py
+++ b/erpnext/startup/leaderboard.py
@@ -1,5 +1,5 @@
import frappe
-from frappe.utils import cint
+from frappe.utils.deprecations import deprecated
def get_leaderboards():
@@ -54,12 +54,13 @@
@frappe.whitelist()
def get_all_customers(date_range, company, field, limit=None):
+ filters = [["docstatus", "=", "1"], ["company", "=", company]]
+ from_date, to_date = parse_date_range(date_range)
if field == "outstanding_amount":
- filters = [["docstatus", "=", "1"], ["company", "=", company]]
- if date_range:
- date_range = frappe.parse_json(date_range)
- filters.append(["posting_date", ">=", "between", [date_range[0], date_range[1]]])
- return frappe.db.get_all(
+ if from_date and to_date:
+ filters.append(["posting_date", "between", [from_date, to_date]])
+
+ return frappe.get_list(
"Sales Invoice",
fields=["customer as name", "sum(outstanding_amount) as value"],
filters=filters,
@@ -69,26 +70,20 @@
)
else:
if field == "total_sales_amount":
- select_field = "sum(so_item.base_net_amount)"
+ select_field = "base_net_total"
elif field == "total_qty_sold":
- select_field = "sum(so_item.stock_qty)"
+ select_field = "total_qty"
- date_condition = get_date_condition(date_range, "so.transaction_date")
+ if from_date and to_date:
+ filters.append(["transaction_date", "between", [from_date, to_date]])
- return frappe.db.sql(
- """
- select so.customer as name, {0} as value
- FROM `tabSales Order` as so JOIN `tabSales Order Item` as so_item
- ON so.name = so_item.parent
- where so.docstatus = 1 {1} and so.company = %s
- group by so.customer
- order by value DESC
- limit %s
- """.format(
- select_field, date_condition
- ),
- (company, cint(limit)),
- as_dict=1,
+ return frappe.get_list(
+ "Sales Order",
+ fields=["customer as name", f"sum({select_field}) as value"],
+ filters=filters,
+ group_by="customer",
+ order_by="value desc",
+ limit=limit,
)
@@ -96,55 +91,58 @@
def get_all_items(date_range, company, field, limit=None):
if field in ("available_stock_qty", "available_stock_value"):
select_field = "sum(actual_qty)" if field == "available_stock_qty" else "sum(stock_value)"
- return frappe.db.get_all(
+ results = frappe.db.get_all(
"Bin",
fields=["item_code as name", "{0} as value".format(select_field)],
group_by="item_code",
order_by="value desc",
limit=limit,
)
+ readable_active_items = set(frappe.get_list("Item", filters={"disabled": 0}, pluck="name"))
+ return [item for item in results if item["name"] in readable_active_items]
else:
if field == "total_sales_amount":
- select_field = "sum(order_item.base_net_amount)"
+ select_field = "base_net_amount"
select_doctype = "Sales Order"
elif field == "total_purchase_amount":
- select_field = "sum(order_item.base_net_amount)"
+ select_field = "base_net_amount"
select_doctype = "Purchase Order"
elif field == "total_qty_sold":
- select_field = "sum(order_item.stock_qty)"
+ select_field = "stock_qty"
select_doctype = "Sales Order"
elif field == "total_qty_purchased":
- select_field = "sum(order_item.stock_qty)"
+ select_field = "stock_qty"
select_doctype = "Purchase Order"
- date_condition = get_date_condition(date_range, "sales_order.transaction_date")
+ filters = [["docstatus", "=", "1"], ["company", "=", company]]
+ from_date, to_date = parse_date_range(date_range)
+ if from_date and to_date:
+ filters.append(["transaction_date", "between", [from_date, to_date]])
- return frappe.db.sql(
- """
- select order_item.item_code as name, {0} as value
- from `tab{1}` sales_order join `tab{1} Item` as order_item
- on sales_order.name = order_item.parent
- where sales_order.docstatus = 1
- and sales_order.company = %s {2}
- group by order_item.item_code
- order by value desc
- limit %s
- """.format(
- select_field, select_doctype, date_condition
- ),
- (company, cint(limit)),
- as_dict=1,
- ) # nosec
+ child_doctype = f"{select_doctype} Item"
+ return frappe.get_list(
+ select_doctype,
+ fields=[
+ f"`tab{child_doctype}`.item_code as name",
+ f"sum(`tab{child_doctype}`.{select_field}) as value",
+ ],
+ filters=filters,
+ order_by="value desc",
+ group_by=f"`tab{child_doctype}`.item_code",
+ limit=limit,
+ )
@frappe.whitelist()
def get_all_suppliers(date_range, company, field, limit=None):
+ filters = [["docstatus", "=", "1"], ["company", "=", company]]
+ from_date, to_date = parse_date_range(date_range)
+
if field == "outstanding_amount":
- filters = [["docstatus", "=", "1"], ["company", "=", company]]
- if date_range:
- date_range = frappe.parse_json(date_range)
- filters.append(["posting_date", "between", [date_range[0], date_range[1]]])
- return frappe.db.get_all(
+ if from_date and to_date:
+ filters.append(["posting_date", "between", [from_date, to_date]])
+
+ return frappe.get_list(
"Purchase Invoice",
fields=["supplier as name", "sum(outstanding_amount) as value"],
filters=filters,
@@ -154,48 +152,40 @@
)
else:
if field == "total_purchase_amount":
- select_field = "sum(purchase_order_item.base_net_amount)"
+ select_field = "base_net_total"
elif field == "total_qty_purchased":
- select_field = "sum(purchase_order_item.stock_qty)"
+ select_field = "total_qty"
- date_condition = get_date_condition(date_range, "purchase_order.modified")
+ if from_date and to_date:
+ filters.append(["transaction_date", "between", [from_date, to_date]])
- return frappe.db.sql(
- """
- select purchase_order.supplier as name, {0} as value
- FROM `tabPurchase Order` as purchase_order LEFT JOIN `tabPurchase Order Item`
- as purchase_order_item ON purchase_order.name = purchase_order_item.parent
- where
- purchase_order.docstatus = 1
- {1}
- and purchase_order.company = %s
- group by purchase_order.supplier
- order by value DESC
- limit %s""".format(
- select_field, date_condition
- ),
- (company, cint(limit)),
- as_dict=1,
- ) # nosec
+ return frappe.get_list(
+ "Purchase Order",
+ fields=["supplier as name", f"sum({select_field}) as value"],
+ filters=filters,
+ group_by="supplier",
+ order_by="value desc",
+ limit=limit,
+ )
@frappe.whitelist()
def get_all_sales_partner(date_range, company, field, limit=None):
if field == "total_sales_amount":
- select_field = "sum(`base_net_total`)"
+ select_field = "base_net_total"
elif field == "total_commission":
- select_field = "sum(`total_commission`)"
+ select_field = "total_commission"
- filters = {"sales_partner": ["!=", ""], "docstatus": 1, "company": company}
- if date_range:
- date_range = frappe.parse_json(date_range)
- filters["transaction_date"] = ["between", [date_range[0], date_range[1]]]
+ filters = [["docstatus", "=", "1"], ["company", "=", company], ["sales_partner", "is", "set"]]
+ from_date, to_date = parse_date_range(date_range)
+ if from_date and to_date:
+ filters.append(["transaction_date", "between", [from_date, to_date]])
return frappe.get_list(
"Sales Order",
fields=[
- "`sales_partner` as name",
- "{} as value".format(select_field),
+ "sales_partner as name",
+ f"sum({select_field}) as value",
],
filters=filters,
group_by="sales_partner",
@@ -206,27 +196,29 @@
@frappe.whitelist()
def get_all_sales_person(date_range, company, field=None, limit=0):
- date_condition = get_date_condition(date_range, "sales_order.transaction_date")
+ filters = [
+ ["docstatus", "=", "1"],
+ ["company", "=", company],
+ ["Sales Team", "sales_person", "is", "set"],
+ ]
+ from_date, to_date = parse_date_range(date_range)
+ if from_date and to_date:
+ filters.append(["transaction_date", "between", [from_date, to_date]])
- return frappe.db.sql(
- """
- select sales_team.sales_person as name, sum(sales_order.base_net_total) as value
- from `tabSales Order` as sales_order join `tabSales Team` as sales_team
- on sales_order.name = sales_team.parent and sales_team.parenttype = 'Sales Order'
- where sales_order.docstatus = 1
- and sales_order.company = %s
- {date_condition}
- group by sales_team.sales_person
- order by value DESC
- limit %s
- """.format(
- date_condition=date_condition
- ),
- (company, cint(limit)),
- as_dict=1,
+ return frappe.get_list(
+ "Sales Order",
+ fields=[
+ "`tabSales Team`.sales_person as name",
+ "sum(`tabSales Team`.allocated_amount) as value",
+ ],
+ filters=filters,
+ group_by="`tabSales Team`.sales_person",
+ order_by="value desc",
+ limit=limit,
)
+@deprecated
def get_date_condition(date_range, field):
date_condition = ""
if date_range:
@@ -236,3 +228,11 @@
field, frappe.db.escape(from_date), frappe.db.escape(to_date)
)
return date_condition
+
+
+def parse_date_range(date_range):
+ if date_range:
+ date_range = frappe.parse_json(date_range)
+ return date_range[0], date_range[1]
+
+ return None, None
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index 7df74f8..3e90ed5 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -79,10 +79,10 @@
so_items = {} # Format --> {'SO/00001': {'Item/001': 120, 'Item/002': 24}}
for d in self.get("items"):
if d.sales_order:
- if not d.sales_order in so_items:
+ if d.sales_order not in so_items:
so_items[d.sales_order] = {d.item_code: flt(d.qty)}
else:
- if not d.item_code in so_items[d.sales_order]:
+ if d.item_code not in so_items[d.sales_order]:
so_items[d.sales_order][d.item_code] = flt(d.qty)
else:
so_items[d.sales_order][d.item_code] += flt(d.qty)
diff --git a/erpnext/stock/doctype/putaway_rule/putaway_rule.py b/erpnext/stock/doctype/putaway_rule/putaway_rule.py
index 3fc4e01..7ed6923 100644
--- a/erpnext/stock/doctype/putaway_rule/putaway_rule.py
+++ b/erpnext/stock/doctype/putaway_rule/putaway_rule.py
@@ -170,7 +170,7 @@
pending_qty -= qty_to_allocate
rule["free_space"] -= stock_qty_to_allocate
- if not pending_stock_qty > 0:
+ if pending_stock_qty <= 0:
break
# if pending qty after applying all rules, add row without warehouse
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js
index cda4445..9f01ee9 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js
@@ -121,7 +121,7 @@
frappe.throw(__("Please attach CSV file"));
}
- if (frm.doc.has_serial_no && !prompt_data.using_csv_file && !prompt_data.serial_nos) {
+ if (frm.doc.has_serial_no && !prompt_data.csv_file && !prompt_data.serial_nos) {
frappe.throw(__("Please enter serial nos"));
}
},
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json
index d46b07a..7a58462 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json
@@ -1,7 +1,7 @@
{
"actions": [],
"autoname": "naming_series:",
- "creation": "2022-09-29 14:56:38.338267",
+ "creation": "2023-08-11 17:22:12.907518",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
@@ -250,7 +250,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2023-07-28 12:56:03.072224",
+ "modified": "2023-12-07 17:56:55.528563",
"modified_by": "Administrator",
"module": "Stock",
"name": "Serial and Batch Bundle",
@@ -270,6 +270,118 @@
"share": 1,
"submit": 1,
"write": 1
+ },
+ {
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Purchase User",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Purchase Manager",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Stock User",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Stock Manager",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Delivery User",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Delivery Manager",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Manufacturing User",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Manufacturing Manager",
+ "share": 1,
+ "submit": 1,
+ "write": 1
}
],
"sort_field": "modified",
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
index 88929ea..ecb9314 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
@@ -511,6 +511,22 @@
serial_batches = {}
for row in self.entries:
+ if self.has_serial_no and not row.serial_no:
+ frappe.throw(
+ _("At row {0}: Serial No is mandatory for Item {1}").format(
+ bold(row.idx), bold(self.item_code)
+ ),
+ title=_("Serial No is mandatory"),
+ )
+
+ if self.has_batch_no and not row.batch_no:
+ frappe.throw(
+ _("At row {0}: Batch No is mandatory for Item {1}").format(
+ bold(row.idx), bold(self.item_code)
+ ),
+ title=_("Batch No is mandatory"),
+ )
+
if row.serial_no:
serial_nos.append(row.serial_no)
@@ -693,6 +709,7 @@
"item_code": self.item_code,
"warehouse": self.warehouse,
"batch_no": batches,
+ "consider_negative_batches": True,
}
)
)
@@ -703,6 +720,9 @@
available_batches = get_available_batches_qty(available_batches)
for batch_no in batches:
if batch_no not in available_batches or available_batches[batch_no] < 0:
+ if flt(available_batches.get(batch_no)) < 0:
+ self.validate_negative_batch(batch_no, available_batches[batch_no])
+
self.throw_error_message(
f"Batch {bold(batch_no)} is not available in the selected warehouse {self.warehouse}"
)
@@ -794,6 +814,9 @@
if index == 0:
has_serial_no = row[0] == "Serial No"
has_batch_no = row[0] == "Batch No"
+ if not has_batch_no:
+ has_batch_no = row[1] == "Batch No"
+
continue
if not row[0]:
@@ -810,6 +833,13 @@
}
)
+ batch_nos.append(
+ {
+ "batch_no": row[1],
+ "qty": row[2],
+ }
+ )
+
serial_nos.append(_dict)
elif has_batch_no:
batch_nos.append(
@@ -845,6 +875,9 @@
serial_nos_details = []
user = frappe.session.user
for serial_no in serial_nos:
+ if frappe.db.exists("Serial No", serial_no):
+ continue
+
serial_nos_details.append(
(
serial_no,
@@ -875,7 +908,7 @@
frappe.db.bulk_insert("Serial No", fields=fields, values=set(serial_nos_details))
- frappe.msgprint(_("Serial Nos are created successfully"))
+ frappe.msgprint(_("Serial Nos are created successfully"), alert=True)
def make_batch_nos(item_code, batch_nos):
@@ -886,6 +919,9 @@
batch_nos_details = []
user = frappe.session.user
for batch_no in batch_nos:
+ if frappe.db.exists("Batch", batch_no):
+ continue
+
batch_nos_details.append(
(batch_no, batch_no, now(), now(), user, user, item.item_code, item.item_name, item.description)
)
@@ -904,7 +940,7 @@
frappe.db.bulk_insert("Batch", fields=fields, values=set(batch_nos_details))
- frappe.msgprint(_("Batch Nos are created successfully"))
+ frappe.msgprint(_("Batch Nos are created successfully"), alert=True)
def parse_serial_nos(data):
@@ -1459,7 +1495,8 @@
available_batches, stock_ledgers_batches, pos_invoice_batches, sre_reserved_batches
)
- available_batches = list(filter(lambda x: x.qty > 0, available_batches))
+ if not kwargs.consider_negative_batches:
+ available_batches = list(filter(lambda x: x.qty > 0, available_batches))
if not qty:
return available_batches
@@ -1745,3 +1782,8 @@
batches[key].qty += d.qty
return batches
+
+
+@frappe.whitelist()
+def get_batch_no_from_serial_no(serial_no):
+ return frappe.get_cached_value("Serial No", serial_no, "batch_no")
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py
index 0e01b20..d74d657 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py
@@ -368,6 +368,58 @@
# Batch does not belong to serial no
self.assertRaises(frappe.exceptions.ValidationError, doc.save)
+ def test_auto_delete_draft_serial_and_batch_bundle(self):
+ serial_and_batch_code = "New Serial No Auto Delete 1"
+ make_item(
+ serial_and_batch_code,
+ {
+ "has_serial_no": 1,
+ "serial_no_series": "TEST-SER-VALL-.#####",
+ "is_stock_item": 1,
+ },
+ )
+
+ ste = make_stock_entry(
+ item_code=serial_and_batch_code,
+ target="_Test Warehouse - _TC",
+ qty=1,
+ rate=500,
+ do_not_submit=True,
+ )
+
+ serial_no = "SN-TEST-AUTO-DEL"
+ if not frappe.db.exists("Serial No", serial_no):
+ frappe.get_doc(
+ {
+ "doctype": "Serial No",
+ "serial_no": serial_no,
+ "item_code": serial_and_batch_code,
+ "company": "_Test Company",
+ }
+ ).insert(ignore_permissions=True)
+
+ bundle_doc = make_serial_batch_bundle(
+ {
+ "item_code": serial_and_batch_code,
+ "warehouse": "_Test Warehouse - _TC",
+ "voucher_type": "Stock Entry",
+ "posting_date": ste.posting_date,
+ "posting_time": ste.posting_time,
+ "qty": 1,
+ "serial_nos": [serial_no],
+ "type_of_transaction": "Inward",
+ "do_not_submit": True,
+ }
+ )
+
+ bundle_doc.reload()
+ ste.items[0].serial_and_batch_bundle = bundle_doc.name
+ ste.save()
+ ste.reload()
+
+ ste.delete()
+ self.assertFalse(frappe.db.exists("Serial and Batch Bundle", bundle_doc.name))
+
def get_batch_from_bundle(bundle):
from erpnext.stock.serial_batch_bundle import get_batch_nos
diff --git a/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json b/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json
index 09565cb..5de2c2e 100644
--- a/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json
+++ b/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json
@@ -27,7 +27,6 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Serial No",
- "mandatory_depends_on": "eval:parent.has_serial_no == 1",
"options": "Serial No",
"search_index": 1
},
@@ -38,7 +37,6 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Batch No",
- "mandatory_depends_on": "eval:parent.has_batch_no == 1",
"options": "Batch",
"search_index": 1
},
@@ -122,7 +120,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2023-07-03 15:29:50.199075",
+ "modified": "2023-12-10 19:47:48.227772",
"modified_by": "Administrator",
"module": "Stock",
"name": "Serial and Batch Entry",
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 7334b35..7af5d1a 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -781,10 +781,9 @@
});
refresh_field("items");
- let no_batch_serial_number_value = !d.serial_no;
- if (d.has_batch_no && !d.has_serial_no) {
- // check only batch_no for batched item
- no_batch_serial_number_value = !d.batch_no;
+ let no_batch_serial_number_value = false;
+ if (d.has_serial_no || d.has_batch_no) {
+ no_batch_serial_number_value = true;
}
if (no_batch_serial_number_value && !frappe.flags.hide_serial_batch_dialog && !frappe.flags.dialog_set) {
@@ -941,6 +940,7 @@
}
scan_barcode() {
+ frappe.flags.dialog_set = false;
const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
barcode_scanner.process_scan();
}
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 3baafd7..84e99c5 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -227,7 +227,7 @@
self.calculate_rate_and_amount()
self.validate_putaway_capacity()
- if not self.get("purpose") == "Manufacture":
+ if self.get("purpose") != "Manufacture":
# ignore scrap item wh difference and empty source/target wh
# in Manufacture Entry
self.reset_default_field_value("from_warehouse", "items", "s_warehouse")
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index eb1c7a8..e0e364f 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -1733,6 +1733,45 @@
self.assertFalse(doc.is_enqueue_action())
frappe.flags.in_test = True
+ def test_negative_batch(self):
+ item_code = "Test Negative Batch Item - 001"
+ make_item(
+ item_code,
+ {"has_batch_no": 1, "create_new_batch": 1, "batch_naming_series": "Test-BCH-NNS.#####"},
+ )
+
+ se1 = make_stock_entry(
+ item_code=item_code,
+ purpose="Material Receipt",
+ qty=100,
+ target="_Test Warehouse - _TC",
+ )
+
+ se1.reload()
+
+ batch_no = get_batch_from_bundle(se1.items[0].serial_and_batch_bundle)
+
+ se2 = make_stock_entry(
+ item_code=item_code,
+ purpose="Material Issue",
+ batch_no=batch_no,
+ qty=10,
+ source="_Test Warehouse - _TC",
+ )
+
+ se2.reload()
+
+ se3 = make_stock_entry(
+ item_code=item_code,
+ purpose="Material Receipt",
+ qty=100,
+ target="_Test Warehouse - _TC",
+ )
+
+ se3.reload()
+
+ self.assertRaises(frappe.ValidationError, se1.cancel)
+
def make_serialized_item(**args):
args = frappe._dict(args)
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index e62f0b2..23788cf 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -214,7 +214,9 @@
if not self.serial_and_batch_bundle:
self.throw_error_message(f"Serial No / Batch No are mandatory for Item {self.item_code}")
- if self.serial_and_batch_bundle and not (item_detail.has_serial_no or item_detail.has_batch_no):
+ if (
+ self.serial_and_batch_bundle and not item_detail.has_serial_no and not item_detail.has_batch_no
+ ):
self.throw_error_message(f"Serial No and Batch No are not allowed for Item {self.item_code}")
def throw_error_message(self, message, exception=frappe.ValidationError):
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
index b3998b7..8e9dcb0 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
@@ -209,7 +209,7 @@
set_amount_quantity: function(doc, cdt, cdn) {
var d = frappe.model.get_doc(cdt, cdn);
- if (d.qty & d.valuation_rate) {
+ if (d.qty && d.valuation_rate) {
frappe.model.set_value(cdt, cdn, "amount", flt(d.qty) * flt(d.valuation_rate));
frappe.model.set_value(cdt, cdn, "quantity_difference", flt(d.qty) - flt(d.current_qty));
frappe.model.set_value(cdt, cdn, "amount_difference", flt(d.amount) - flt(d.current_amount));
diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
index 85550c2..24650fd 100644
--- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
+++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
@@ -931,7 +931,7 @@
continue
# Stock should be reserved from the Pick List if has Picked Qty.
- if not from_voucher_type == "Pick List" and flt(item.picked_qty) > 0:
+ if from_voucher_type != "Pick List" and flt(item.picked_qty) > 0:
frappe.throw(
_("Row #{0}: Item {1} has been picked, please reserve stock from the Pick List.").format(
item.idx, frappe.bold(item.item_code)
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index dfeb1ee..e746595 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -358,7 +358,6 @@
"net_amount": 0.0,
"discount_percentage": 0.0,
"discount_amount": flt(args.discount_amount) or 0.0,
- "supplier": get_default_supplier(args, item_defaults, item_group_defaults, brand_defaults),
"update_stock": args.get("update_stock")
if args.get("doctype") in ["Sales Invoice", "Purchase Invoice"]
else 0,
@@ -378,6 +377,10 @@
}
)
+ default_supplier = get_default_supplier(args, item_defaults, item_group_defaults, brand_defaults)
+ if default_supplier:
+ out.supplier = default_supplier
+
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
out.update(calculate_service_end_date(args, item))
@@ -572,8 +575,8 @@
item_tax_template = _get_item_tax_template(args, item_group_doc.taxes, out)
item_group = item_group_doc.parent_item_group
- if args.child_doctype and item_tax_template:
- out.update(get_fetch_values(args.child_doctype, "item_tax_template", item_tax_template))
+ if args.get("child_doctype") and item_tax_template:
+ out.update(get_fetch_values(args.get("child_doctype"), "item_tax_template", item_tax_template))
def _get_item_tax_template(args, taxes, out=None, for_validate=False):
diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
index ae12fbb..810dc46 100644
--- a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
+++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
@@ -1,9 +1,12 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
+import copy
+
import frappe
from frappe import _
+from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos as get_serial_nos_from_sle
from erpnext.stock.stock_ledger import get_stock_ledger_entries
@@ -15,8 +18,8 @@
def get_columns(filters):
columns = [
- {"label": _("Posting Date"), "fieldtype": "Date", "fieldname": "posting_date"},
- {"label": _("Posting Time"), "fieldtype": "Time", "fieldname": "posting_time"},
+ {"label": _("Posting Date"), "fieldtype": "Date", "fieldname": "posting_date", "width": 120},
+ {"label": _("Posting Time"), "fieldtype": "Time", "fieldname": "posting_time", "width": 90},
{
"label": _("Voucher Type"),
"fieldtype": "Link",
@@ -29,7 +32,7 @@
"fieldtype": "Dynamic Link",
"fieldname": "voucher_no",
"options": "voucher_type",
- "width": 180,
+ "width": 230,
},
{
"label": _("Company"),
@@ -49,7 +52,7 @@
"label": _("Status"),
"fieldtype": "Data",
"fieldname": "status",
- "width": 120,
+ "width": 90,
},
{
"label": _("Serial No"),
@@ -62,7 +65,7 @@
"label": _("Valuation Rate"),
"fieldtype": "Float",
"fieldname": "valuation_rate",
- "width": 150,
+ "width": 130,
},
{
"label": _("Qty"),
@@ -102,15 +105,29 @@
}
)
- serial_nos = [{"serial_no": row.serial_no, "valuation_rate": row.valuation_rate}]
+ serial_nos = []
+ if row.serial_no:
+ parsed_serial_nos = get_serial_nos_from_sle(row.serial_no)
+ for serial_no in parsed_serial_nos:
+ if filters.get("serial_no") and filters.get("serial_no") != serial_no:
+ continue
+
+ serial_nos.append(
+ {
+ "serial_no": serial_no,
+ "valuation_rate": abs(row.stock_value_difference / row.actual_qty),
+ }
+ )
+
if row.serial_and_batch_bundle:
- serial_nos = bundle_wise_serial_nos.get(row.serial_and_batch_bundle, [])
+ serial_nos.extend(bundle_wise_serial_nos.get(row.serial_and_batch_bundle, []))
for index, bundle_data in enumerate(serial_nos):
if index == 0:
- args.serial_no = bundle_data.get("serial_no")
- args.valuation_rate = bundle_data.get("valuation_rate")
- data.append(args)
+ new_args = copy.deepcopy(args)
+ new_args.serial_no = bundle_data.get("serial_no")
+ new_args.valuation_rate = bundle_data.get("valuation_rate")
+ data.append(new_args)
else:
data.append(
{
diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.js b/erpnext/stock/report/stock_analytics/stock_analytics.js
index ea7bf56..e033fd9 100644
--- a/erpnext/stock/report/stock_analytics/stock_analytics.js
+++ b/erpnext/stock/report/stock_analytics/stock_analytics.js
@@ -17,6 +17,7 @@
fieldtype: "Link",
options:"Item",
default: "",
+ get_query: () => ({filters: { 'is_stock_item': 1 }}),
},
{
fieldname: "value_quantity",
diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.py b/erpnext/stock/report/stock_analytics/stock_analytics.py
index 6c5b58c..ab48181 100644
--- a/erpnext/stock/report/stock_analytics/stock_analytics.py
+++ b/erpnext/stock/report/stock_analytics/stock_analytics.py
@@ -270,7 +270,7 @@
if item_code := filters.get("item_code"):
return [item_code]
else:
- item_filters = {}
+ item_filters = {"is_stock_item": 1}
if item_group := filters.get("item_group"):
children = get_descendants_of("Item Group", item_group, ignore_permissions=True)
item_filters["item_group"] = ("in", children + [item_group])
diff --git a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js
index b1e4a74..bf3a397 100644
--- a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js
+++ b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js
@@ -14,9 +14,17 @@
frappe.query_reports["Stock Ledger Variance"] = {
"filters": [
{
+ "fieldname": "company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "options": "Company",
+ "reqd": 1,
+ "default": frappe.defaults.get_user_default("Company")
+ },
+ {
"fieldname": "item_code",
"fieldtype": "Link",
- "label": "Item",
+ "label": __("Item"),
"options": "Item",
get_query: function() {
return {
@@ -27,7 +35,7 @@
{
"fieldname": "warehouse",
"fieldtype": "Link",
- "label": "Warehouse",
+ "label": __("Warehouse"),
"options": "Warehouse",
get_query: function() {
return {
@@ -38,7 +46,7 @@
{
"fieldname": "difference_in",
"fieldtype": "Select",
- "label": "Difference In",
+ "label": __("Difference In"),
"options": [
"",
"Qty",
@@ -49,7 +57,7 @@
{
"fieldname": "include_disabled",
"fieldtype": "Check",
- "label": "Include Disabled",
+ "label": __("Include Disabled"),
}
],
diff --git a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py
index 732f108..189a90a 100644
--- a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py
+++ b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py
@@ -56,6 +56,11 @@
"options": "Warehouse",
},
{
+ "fieldname": "valuation_method",
+ "fieldtype": "Data",
+ "label": _("Valuation Method"),
+ },
+ {
"fieldname": "voucher_type",
"fieldtype": "Link",
"label": _("Voucher Type"),
@@ -194,6 +199,7 @@
def get_data(filters=None):
filters = frappe._dict(filters or {})
item_warehouse_map = get_item_warehouse_combinations(filters)
+ valuation_method = frappe.db.get_single_value("Stock Settings", "valuation_method")
data = []
if item_warehouse_map:
@@ -206,8 +212,17 @@
continue
for row in report_data:
- if has_difference(row, precision, filters.difference_in):
- data.append(add_item_warehouse_details(row, item_warehouse))
+ if has_difference(
+ row, precision, filters.difference_in, item_warehouse.valuation_method or valuation_method
+ ):
+ row.update(
+ {
+ "item_code": item_warehouse.item_code,
+ "warehouse": item_warehouse.warehouse,
+ "valuation_method": item_warehouse.valuation_method or valuation_method,
+ }
+ )
+ data.append(row)
break
return data
@@ -229,8 +244,14 @@
.select(
bin.item_code,
bin.warehouse,
+ item.valuation_method,
)
- .where((item.is_stock_item == 1) & (item.has_serial_no == 0) & (warehouse.is_group == 0))
+ .where(
+ (item.is_stock_item == 1)
+ & (item.has_serial_no == 0)
+ & (warehouse.is_group == 0)
+ & (warehouse.company == filters.company)
+ )
)
if filters.item_code:
@@ -243,37 +264,27 @@
return query.run(as_dict=1)
-def has_difference(row, precision, difference_in):
- has_qty_difference = flt(row.difference_in_qty, precision) or flt(row.fifo_qty_diff, precision)
- has_value_difference = (
- flt(row.diff_value_diff, precision)
- or flt(row.fifo_value_diff, precision)
- or flt(row.fifo_difference_diff, precision)
- )
- has_valuation_difference = flt(row.valuation_diff, precision) or flt(
- row.fifo_valuation_diff, precision
- )
+def has_difference(row, precision, difference_in, valuation_method):
+ if valuation_method == "Moving Average":
+ qty_diff = flt(row.difference_in_qty, precision)
+ value_diff = flt(row.diff_value_diff, precision)
+ valuation_diff = flt(row.valuation_diff, precision)
+ else:
+ qty_diff = flt(row.difference_in_qty, precision) or flt(row.fifo_qty_diff, precision)
+ value_diff = (
+ flt(row.diff_value_diff, precision)
+ or flt(row.fifo_value_diff, precision)
+ or flt(row.fifo_difference_diff, precision)
+ )
+ valuation_diff = flt(row.valuation_diff, precision) or flt(row.fifo_valuation_diff, precision)
- if difference_in == "Qty" and has_qty_difference:
+ if difference_in == "Qty" and qty_diff:
return True
- elif difference_in == "Value" and has_value_difference:
+ elif difference_in == "Value" and value_diff:
return True
- elif difference_in == "Valuation" and has_valuation_difference:
+ elif difference_in == "Valuation" and valuation_diff:
return True
elif difference_in not in ["Qty", "Value", "Valuation"] and (
- has_qty_difference or has_value_difference or has_valuation_difference
+ qty_diff or value_diff or valuation_diff
):
return True
-
- return False
-
-
-def add_item_warehouse_details(row, item_warehouse):
- row.update(
- {
- "item_code": item_warehouse.item_code,
- "warehouse": item_warehouse.warehouse,
- }
- )
-
- return row
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 9142a27..9203f45 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -1711,7 +1711,7 @@
def validate_negative_qty_in_future_sle(args, allow_negative_stock=False):
if allow_negative_stock or is_negative_stock_allowed(item_code=args.item_code):
return
- if not (args.actual_qty < 0 or args.voucher_type == "Stock Reconciliation"):
+ if args.actual_qty >= 0 and args.voucher_type != "Stock Reconciliation":
return
neg_sle = get_future_sle_with_negative_qty(args)
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
index 6c187f8..0fe8c13 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
+++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
@@ -7,6 +7,7 @@
from frappe.utils import flt
from erpnext.buying.doctype.purchase_order.purchase_order import is_subcontracting_order_created
+from erpnext.buying.doctype.purchase_order.purchase_order import update_status as update_po_status
from erpnext.controllers.subcontracting_controller import SubcontractingController
from erpnext.stock.stock_balance import update_bin_qty
from erpnext.stock.utils import get_bin
@@ -308,6 +309,9 @@
"Subcontracting Order", self.name, "status", status, update_modified=update_modified
)
+ if status == "Closed":
+ update_po_status("Closed", self.purchase_order)
+
@frappe.whitelist()
def make_subcontracting_receipt(source_name, target_doc=None):
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
index 879381c..8b37c94 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
@@ -100,7 +100,7 @@
priorities.append(priority.priority)
# Check if repeated priority
- if not len(set(priorities)) == len(priorities):
+ if len(set(priorities)) != len(priorities):
repeated_priority = get_repeated(priorities)
frappe.throw(_("Priority {0} has been repeated.").format(repeated_priority))
@@ -128,7 +128,7 @@
)
# Check for repeated workday
- if not len(set(support_days)) == len(support_days):
+ if len(set(support_days)) != len(support_days):
repeated_days = get_repeated(support_days)
frappe.throw(_("Workday {0} has been repeated.").format(repeated_days))
@@ -748,13 +748,13 @@
and frappe.db.get_single_value("Support Settings", "track_service_level_agreement")
):
- if not self.priority == frappe.db.get_value("Issue", self.name, "priority"):
+ if self.priority != frappe.db.get_value("Issue", self.name, "priority"):
self.set_response_and_resolution_time(
priority=self.priority, service_level_agreement=self.service_level_agreement
)
frappe.msgprint(_("Priority has been changed to {0}.").format(self.priority))
- if not self.service_level_agreement == frappe.db.get_value(
+ if self.service_level_agreement != frappe.db.get_value(
"Issue", self.name, "service_level_agreement"
):
self.set_response_and_resolution_time(