Merge pull request #40433 from deepeshgarg007/client_side_tax_updates
fix: Taxes not getting updated on change
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 48ebe92..1c1c10c 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -13,7 +13,7 @@
if not user:
user = frappe.session.user
- companies = get_user_default_as_list(user, "company")
+ companies = get_user_default_as_list("company", user)
if companies:
default_company = companies[0]
else:
diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
index 9e6b51d..65158fc 100644
--- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
+++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
@@ -9,7 +9,6 @@
from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
from frappe.utils import cint, flt
-from pypika.terms import Parameter
from erpnext import get_default_cost_center
from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_total_allocated_amount
@@ -509,6 +508,18 @@
to_reference_date,
):
exact_match = True if "exact_match" in document_types else False
+
+ common_filters = frappe._dict(
+ {
+ "amount": transaction.unallocated_amount,
+ "payment_type": "Receive" if transaction.deposit > 0.0 else "Pay",
+ "reference_no": transaction.reference_number,
+ "party_type": transaction.party_type,
+ "party": transaction.party,
+ "bank_account": bank_account,
+ }
+ )
+
queries = get_queries(
bank_account,
company,
@@ -520,20 +531,12 @@
from_reference_date,
to_reference_date,
exact_match,
+ common_filters,
)
- filters = {
- "amount": transaction.unallocated_amount,
- "payment_type": "Receive" if transaction.deposit > 0.0 else "Pay",
- "reference_no": transaction.reference_number,
- "party_type": transaction.party_type,
- "party": transaction.party,
- "bank_account": bank_account,
- }
-
matching_vouchers = []
for query in queries:
- matching_vouchers.extend(frappe.db.sql(query, filters, as_dict=True))
+ matching_vouchers.extend(query.run(as_dict=True))
return (
sorted(matching_vouchers, key=lambda x: x["rank"], reverse=True) if matching_vouchers else []
@@ -551,6 +554,7 @@
from_reference_date,
to_reference_date,
exact_match,
+ common_filters,
):
# get queries to get matching vouchers
account_from_to = "paid_to" if transaction.deposit > 0.0 else "paid_from"
@@ -571,6 +575,7 @@
filter_by_reference_date,
from_reference_date,
to_reference_date,
+ common_filters,
)
or []
)
@@ -590,6 +595,7 @@
filter_by_reference_date,
from_reference_date,
to_reference_date,
+ common_filters,
):
queries = []
currency = get_account_currency(bank_account)
@@ -604,6 +610,7 @@
filter_by_reference_date,
from_reference_date,
to_reference_date,
+ common_filters,
)
queries.append(query)
@@ -616,16 +623,17 @@
filter_by_reference_date,
from_reference_date,
to_reference_date,
+ common_filters,
)
queries.append(query)
if transaction.deposit > 0.0 and "sales_invoice" in document_types:
- query = get_si_matching_query(exact_match, currency)
+ query = get_si_matching_query(exact_match, currency, common_filters)
queries.append(query)
if transaction.withdrawal > 0.0:
if "purchase_invoice" in document_types:
- query = get_pi_matching_query(exact_match, currency)
+ query = get_pi_matching_query(exact_match, currency, common_filters)
queries.append(query)
if "bank_transaction" in document_types:
@@ -680,7 +688,7 @@
.where(amount_condition)
.where(bt.docstatus == 1)
)
- return str(query)
+ return query
def get_pe_matching_query(
@@ -692,6 +700,7 @@
filter_by_reference_date,
from_reference_date,
to_reference_date,
+ common_filters,
):
# get matching payment entries query
to_from = "to" if transaction.deposit > 0.0 else "from"
@@ -734,7 +743,7 @@
.where(pe.docstatus == 1)
.where(pe.payment_type.isin([payment_type, "Internal Transfer"]))
.where(pe.clearance_date.isnull())
- .where(getattr(pe, account_from_to) == Parameter("%(bank_account)s"))
+ .where(getattr(pe, account_from_to) == common_filters.bank_account)
.where(amount_condition)
.where(filter_by_date)
.orderby(pe.reference_date if cint(filter_by_reference_date) else pe.posting_date)
@@ -743,7 +752,7 @@
if frappe.flags.auto_reconcile_vouchers == True:
query = query.where(ref_condition)
- return str(query)
+ return query
def get_je_matching_query(
@@ -754,6 +763,7 @@
filter_by_reference_date,
from_reference_date,
to_reference_date,
+ common_filters,
):
# get matching journal entry query
# We have mapping at the bank level
@@ -793,7 +803,7 @@
.where(je.docstatus == 1)
.where(je.voucher_type != "Opening Entry")
.where(je.clearance_date.isnull())
- .where(jea.account == Parameter("%(bank_account)s"))
+ .where(jea.account == common_filters.bank_account)
.where(amount_equality if exact_match else getattr(jea, amount_field) > 0.0)
.where(je.docstatus == 1)
.where(filter_by_date)
@@ -803,19 +813,19 @@
if frappe.flags.auto_reconcile_vouchers == True:
query = query.where(ref_condition)
- return str(query)
+ return query
-def get_si_matching_query(exact_match, currency):
+def get_si_matching_query(exact_match, currency, common_filters):
# get matching sales invoice query
si = frappe.qb.DocType("Sales Invoice")
sip = frappe.qb.DocType("Sales Invoice Payment")
- amount_equality = sip.amount == Parameter("%(amount)s")
+ amount_equality = sip.amount == common_filters.amount
amount_rank = frappe.qb.terms.Case().when(amount_equality, 1).else_(0)
amount_condition = amount_equality if exact_match else sip.amount > 0.0
- party_condition = si.customer == Parameter("%(party)s")
+ party_condition = si.customer == common_filters.party
party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0)
query = (
@@ -836,23 +846,23 @@
)
.where(si.docstatus == 1)
.where(sip.clearance_date.isnull())
- .where(sip.account == Parameter("%(bank_account)s"))
+ .where(sip.account == common_filters.bank_account)
.where(amount_condition)
.where(si.currency == currency)
)
- return str(query)
+ return query
-def get_pi_matching_query(exact_match, currency):
+def get_pi_matching_query(exact_match, currency, common_filters):
# get matching purchase invoice query when they are also used as payment entries (is_paid)
purchase_invoice = frappe.qb.DocType("Purchase Invoice")
- amount_equality = purchase_invoice.paid_amount == Parameter("%(amount)s")
+ amount_equality = purchase_invoice.paid_amount == common_filters.amount
amount_rank = frappe.qb.terms.Case().when(amount_equality, 1).else_(0)
amount_condition = amount_equality if exact_match else purchase_invoice.paid_amount > 0.0
- party_condition = purchase_invoice.supplier == Parameter("%(party)s")
+ party_condition = purchase_invoice.supplier == common_filters.party
party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0)
query = (
@@ -872,9 +882,9 @@
.where(purchase_invoice.docstatus == 1)
.where(purchase_invoice.is_paid == 1)
.where(purchase_invoice.clearance_date.isnull())
- .where(purchase_invoice.cash_bank_account == Parameter("%(bank_account)s"))
+ .where(purchase_invoice.cash_bank_account == common_filters.bank_account)
.where(amount_condition)
.where(purchase_invoice.currency == currency)
)
- return str(query)
+ return query
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 7970a3e..95bb188 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -30,7 +30,7 @@
make_reverse_gl_entries,
process_gl_map,
)
-from erpnext.accounts.party import get_party_account
+from erpnext.accounts.party import get_party_account, set_contact_details
from erpnext.accounts.utils import (
cancel_exchange_gain_loss_journal,
get_account_currency,
@@ -444,6 +444,8 @@
self.party_name = frappe.db.get_value(self.party_type, self.party, "name")
if self.party:
+ if not self.contact_person:
+ set_contact_details(self, party=frappe._dict({"name": self.party}), party_type=self.party_type)
if not self.party_balance:
self.party_balance = get_balance_on(
party_type=self.party_type, party=self.party, date=self.posting_date, company=self.company
diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
index 0bb8d3a..a9c1900 100644
--- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
+++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
@@ -60,6 +60,8 @@
"free_item_rate",
"same_item",
"is_recursive",
+ "recurse_for",
+ "apply_recursion_over",
"apply_multiple_pricing_rules",
]
diff --git a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py
index 9e576fb..9b40c98 100644
--- a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py
+++ b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py
@@ -107,6 +107,28 @@
price_rules = frappe.get_all("Pricing Rule", filters={"promotional_scheme": ps.name})
self.assertEqual(price_rules, [])
+ def test_pricing_rule_for_product_discount_slabs(self):
+ ps = make_promotional_scheme()
+ ps.set("price_discount_slabs", [])
+ ps.set(
+ "product_discount_slabs",
+ [
+ {
+ "rule_description": "12+1",
+ "min_qty": 12,
+ "free_item": "_Test Item 2",
+ "free_qty": 1,
+ "is_recursive": 1,
+ "recurse_for": 12,
+ }
+ ],
+ )
+ ps.save()
+ pr = frappe.get_doc("Pricing Rule", {"promotional_scheme_id": ps.product_discount_slabs[0].name})
+ self.assertSequenceEqual(
+ [pr.min_qty, pr.free_item, pr.free_qty, pr.recurse_for], [12, "_Test Item 2", 1, 12]
+ )
+
def make_promotional_scheme(**args):
args = frappe._dict(args)
diff --git a/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json b/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json
index 3eab515..4e61d04 100644
--- a/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json
+++ b/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json
@@ -27,7 +27,9 @@
"threshold_percentage",
"column_break_15",
"priority",
- "is_recursive"
+ "is_recursive",
+ "recurse_for",
+ "apply_recursion_over"
],
"fields": [
{
@@ -161,17 +163,36 @@
"fieldname": "is_recursive",
"fieldtype": "Check",
"label": "Is Recursive"
+ },
+ {
+ "default": "0",
+ "depends_on": "is_recursive",
+ "description": "Give free item for every N quantity",
+ "fieldname": "recurse_for",
+ "fieldtype": "Float",
+ "label": "Recurse Every (As Per Transaction UOM)",
+ "mandatory_depends_on": "is_recursive"
+ },
+ {
+ "default": "0",
+ "depends_on": "is_recursive",
+ "description": "Qty for which recursion isn't applicable.",
+ "fieldname": "apply_recursion_over",
+ "fieldtype": "Float",
+ "label": "Apply Recursion Over (As Per Transaction UOM)",
+ "mandatory_depends_on": "is_recursive"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2021-03-06 21:58:18.162346",
+ "modified": "2024-03-12 12:53:58.199108",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Promotional Scheme Product Discount",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.py b/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.py
index 7dd5fea..1463a7b 100644
--- a/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.py
+++ b/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.py
@@ -15,6 +15,7 @@
from frappe.types import DF
apply_multiple_pricing_rules: DF.Check
+ apply_recursion_over: DF.Float
disable: DF.Check
free_item: DF.Link | None
free_item_rate: DF.Currency
@@ -51,6 +52,7 @@
"19",
"20",
]
+ recurse_for: DF.Float
rule_description: DF.SmallText
same_item: DF.Check
threshold_percentage: DF.Percent
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index d12a43c..22f2d13 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -22,6 +22,7 @@
"is_paid",
"is_return",
"return_against",
+ "update_outstanding_for_self",
"update_billed_amount_in_purchase_order",
"update_billed_amount_in_purchase_receipt",
"apply_tds",
@@ -1623,13 +1624,21 @@
"fieldtype": "Link",
"label": "Supplier Group",
"options": "Supplier Group"
+ },
+ {
+ "default": "1",
+ "depends_on": "eval: doc.is_return && doc.return_against",
+ "description": "Debit Note will update it's own outstanding amount, even if \"Return Against\" is specified.",
+ "fieldname": "update_outstanding_for_self",
+ "fieldtype": "Check",
+ "label": "Update Outstanding for Self"
}
],
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
- "modified": "2024-02-25 11:20:28.366808",
+ "modified": "2024-03-11 14:46:30.298184",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 8dfd69f..28d4a5e 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -217,6 +217,7 @@
unrealized_profit_loss_account: DF.Link | None
update_billed_amount_in_purchase_order: DF.Check
update_billed_amount_in_purchase_receipt: DF.Check
+ update_outstanding_for_self: DF.Check
update_stock: DF.Check
use_company_roundoff_cost_center: DF.Check
use_transaction_date_exchange_rate: DF.Check
@@ -829,6 +830,10 @@
)
if grand_total and not self.is_internal_transfer():
+ against_voucher = self.name
+ if self.is_return and self.return_against and not self.update_outstanding_for_self:
+ against_voucher = self.return_against
+
# Did not use base_grand_total to book rounding loss gle
gl_entries.append(
self.get_gl_dict(
@@ -842,7 +847,7 @@
"credit_in_account_currency": base_grand_total
if self.party_account_currency == self.company_currency
else grand_total,
- "against_voucher": self.name,
+ "against_voucher": against_voucher,
"against_voucher_type": self.doctype,
"project": self.project,
"cost_center": self.cost_center,
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 88b28ad..ac14d98 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -25,6 +25,7 @@
"is_consolidated",
"is_return",
"return_against",
+ "update_outstanding_for_self",
"update_billed_amount_in_sales_order",
"update_billed_amount_in_delivery_note",
"is_debit_note",
@@ -2171,6 +2172,14 @@
"fieldtype": "Check",
"label": "Don't Create Loyalty Points",
"no_copy": 1
+ },
+ {
+ "default": "1",
+ "depends_on": "eval: doc.is_return && doc.return_against",
+ "description": "Credit Note will update it's own outstanding amount, even if \"Return Against\" is specified.",
+ "fieldname": "update_outstanding_for_self",
+ "fieldtype": "Check",
+ "label": "Update Outstanding for Self"
}
],
"icon": "fa fa-file-text",
@@ -2183,7 +2192,7 @@
"link_fieldname": "consolidated_invoice"
}
],
- "modified": "2024-03-01 09:21:54.201289",
+ "modified": "2024-03-11 14:20:34.874192",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
@@ -2238,4 +2247,4 @@
"title_field": "title",
"track_changes": 1,
"track_seen": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index e2cbf5e..bf50e77 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -220,6 +220,7 @@
unrealized_profit_loss_account: DF.Link | None
update_billed_amount_in_delivery_note: DF.Check
update_billed_amount_in_sales_order: DF.Check
+ update_outstanding_for_self: DF.Check
update_stock: DF.Check
use_company_roundoff_cost_center: DF.Check
write_off_account: DF.Link | None
@@ -1219,6 +1220,10 @@
)
if grand_total and not self.is_internal_transfer():
+ against_voucher = self.name
+ if self.is_return and self.return_against and not self.update_outstanding_for_self:
+ against_voucher = self.return_against
+
# Did not use base_grand_total to book rounding loss gle
gl_entries.append(
self.get_gl_dict(
@@ -1232,7 +1237,7 @@
"debit_in_account_currency": base_grand_total
if self.party_account_currency == self.company_currency
else grand_total,
- "against_voucher": self.name,
+ "against_voucher": against_voucher,
"against_voucher_type": self.doctype,
"cost_center": self.cost_center,
"project": self.project,
diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py
index 9f19366..1a79103 100644
--- a/erpnext/accounts/doctype/subscription/subscription.py
+++ b/erpnext/accounts/doctype/subscription/subscription.py
@@ -408,11 +408,11 @@
# Earlier subscription didn't had any company field
company = self.get("company") or get_default_company()
if not company:
- # fmt: off
frappe.throw(
- _("Company is mandatory was generating invoice. Please set default company in Global Defaults.")
+ _(
+ "Company is mandatory for generating an invoice. Please set a default company in Global Defaults."
+ )
)
- # fmt: on
invoice = frappe.new_doc(self.invoice_document_type)
invoice.company = company
diff --git a/erpnext/accounts/doctype/tax_rule/tax_rule.json b/erpnext/accounts/doctype/tax_rule/tax_rule.json
index 2746748..5a6911c 100644
--- a/erpnext/accounts/doctype/tax_rule/tax_rule.json
+++ b/erpnext/accounts/doctype/tax_rule/tax_rule.json
@@ -1,6 +1,7 @@
{
"actions": [],
"allow_import": 1,
+ "allow_rename": 1,
"autoname": "ACC-TAX-RULE-.YYYY.-.#####",
"creation": "2015-08-07 02:33:52.670866",
"doctype": "DocType",
@@ -225,7 +226,7 @@
}
],
"links": [],
- "modified": "2021-06-04 23:14:27.186879",
+ "modified": "2024-03-09 08:08:27.186879",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Tax Rule",
@@ -247,4 +248,4 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC"
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 6d77ef5..38723e9 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -690,7 +690,12 @@
def get_return_entries(self):
doctype = "Sales Invoice" if self.account_type == "Receivable" else "Purchase Invoice"
- filters = {"is_return": 1, "docstatus": 1, "company": self.filters.company}
+ filters = {
+ "is_return": 1,
+ "docstatus": 1,
+ "company": self.filters.company,
+ "update_outstanding_for_self": 0,
+ }
or_filters = {}
for party_type in self.party_type:
party_field = scrub(party_type)
diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
index 6ff81be..a0f8af5 100644
--- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
@@ -62,7 +62,7 @@
pe.insert()
pe.submit()
- def create_credit_note(self, docname):
+ def create_credit_note(self, docname, do_not_submit=False):
credit_note = create_sales_invoice(
company=self.company,
customer=self.customer,
@@ -72,6 +72,7 @@
cost_center=self.cost_center,
is_return=1,
return_against=docname,
+ do_not_submit=do_not_submit,
)
return credit_note
@@ -149,7 +150,9 @@
)
# check invoice grand total, invoiced, paid and outstanding column's value after credit note
- self.create_credit_note(si.name)
+ cr_note = self.create_credit_note(si.name, do_not_submit=True)
+ cr_note.update_outstanding_for_self = False
+ cr_note.save().submit()
report = execute(filters)
expected_data_after_credit_note = [100, 0, 0, 40, -40, self.debit_to]
@@ -167,6 +170,68 @@
],
)
+ def test_cr_note_flag_to_update_self(self):
+ filters = {
+ "company": self.company,
+ "report_date": today(),
+ "range1": 30,
+ "range2": 60,
+ "range3": 90,
+ "range4": 120,
+ "show_remarks": True,
+ }
+
+ # check invoice grand total and invoiced column's value for 3 payment terms
+ si = self.create_sales_invoice(no_payment_schedule=True)
+ name = si.name
+
+ report = execute(filters)
+
+ expected_data = [100, 100, "No Remarks"]
+
+ self.assertEqual(len(report[1]), 1)
+ row = report[1][0]
+ self.assertEqual(expected_data, [row.invoice_grand_total, row.invoiced, row.remarks])
+
+ # check invoice grand total, invoiced, paid and outstanding column's value after payment
+ self.create_payment_entry(si.name)
+ report = execute(filters)
+
+ expected_data_after_payment = [100, 100, 40, 60]
+ self.assertEqual(len(report[1]), 1)
+ row = report[1][0]
+ self.assertEqual(
+ expected_data_after_payment,
+ [row.invoice_grand_total, row.invoiced, row.paid, row.outstanding],
+ )
+
+ # check invoice grand total, invoiced, paid and outstanding column's value after credit note
+ cr_note = self.create_credit_note(si.name, do_not_submit=True)
+ cr_note.posting_date = add_days(today(), 1)
+ cr_note.update_outstanding_for_self = True
+ cr_note.save().submit()
+ report = execute(filters)
+
+ expected_data_after_credit_note = [
+ [100.0, 100.0, 40.0, 0.0, 60.0, self.debit_to],
+ [0, 0, 100.0, 0.0, -100.0, self.debit_to],
+ ]
+ self.assertEqual(len(report[1]), 2)
+ for i in range(2):
+ row = report[1][i - 1]
+ # row = report[1][0]
+ self.assertEqual(
+ expected_data_after_credit_note[i - 1],
+ [
+ row.invoice_grand_total,
+ row.invoiced,
+ row.paid,
+ row.credit_note,
+ row.outstanding,
+ row.party_account,
+ ],
+ )
+
def test_payment_againt_po_in_receivable_report(self):
"""
Payments made against Purchase Order will show up as outstanding amount
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 166e8c4..385797f 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -152,6 +152,7 @@
def on_submit(self):
self.validate_in_use_date()
self.make_asset_movement()
+ self.reload()
if not self.booked_fixed_asset and self.validate_make_gl_entry():
self.make_gl_entries()
if self.calculate_depreciation and not self.split_from:
@@ -163,6 +164,7 @@
self.validate_cancellation()
self.cancel_movement_entries()
self.cancel_capitalization()
+ self.reload()
self.delete_depreciation_entries()
cancel_asset_depr_schedules(self)
self.set_status()
@@ -698,7 +700,9 @@
fixed_asset_account, cwip_account = self.get_fixed_asset_account(), self.get_cwip_account()
if (
- purchase_document and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()
+ purchase_document
+ and self.purchase_receipt_amount
+ and getdate(self.available_for_use_date) <= getdate()
):
gl_entries.append(
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index 191675c..205f4b9 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -242,9 +242,7 @@
debit_account,
accounting_dimensions,
)
- frappe.db.commit()
except Exception as e:
- frappe.db.rollback()
depreciation_posting_error = e
asset.set_status()
@@ -523,6 +521,7 @@
make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date)
+ asset_doc.reload()
cancel_depreciation_entries(asset_doc, date)
diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
index e27a492..2f4d710 100644
--- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
+++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
@@ -145,6 +145,10 @@
self.make_gl_entries()
self.restore_consumed_asset_items()
+ def on_trash(self):
+ frappe.db.set_value("Asset", self.target_asset, "capitalized_in", None)
+ super(AssetCapitalization, self).on_trash()
+
def cancel_target_asset(self):
if self.entry_type == "Capitalization" and self.target_asset:
asset_doc = frappe.get_doc("Asset", self.target_asset)
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 77469df..6e16508 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
@@ -327,7 +327,7 @@
schedule_date = get_last_day(schedule_date)
# if asset is being sold or scrapped
- if date_of_disposal:
+ if date_of_disposal and getdate(schedule_date) >= getdate(date_of_disposal):
from_date = add_months(
getdate(asset_doc.available_for_use_date),
(asset_doc.number_of_depreciations_booked * row.frequency_of_depreciation),
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index c543dfc..b7e687d 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -89,6 +89,7 @@
"weight_per_unit",
"weight_uom",
"total_weight",
+ "valuation_rate",
)
@@ -218,17 +219,18 @@
)
if self.get("is_return") and self.get("return_against") and not self.get("is_pos"):
- # if self.get("is_return") and self.get("return_against"):
- document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note"
- frappe.msgprint(
- _(
- "{0} will be treated as a standalone {0}. Post creation use {1} tool to reconcile against {2}."
- ).format(
- document_type,
- get_link_to_form("Payment Reconciliation"),
- get_link_to_form(self.doctype, self.get("return_against")),
+ if self.get("update_outstanding_for_self"):
+ document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note"
+ frappe.msgprint(
+ _(
+ "We can see {0} is made against {1}. If you want {1}'s outstanding to be updated, uncheck '{2}' checkbox. <br><br> Or you can use {3} tool to reconcile against {1} later."
+ ).format(
+ frappe.bold(document_type),
+ get_link_to_form(self.doctype, self.get("return_against")),
+ frappe.bold("Update Outstanding for Self"),
+ get_link_to_form("Payment Reconciliation"),
+ )
)
- )
pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid"
if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 0d64188..bb1ed35 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -670,7 +670,7 @@
searchfields = frappe.get_meta(doctype).get_search_fields()
meta = frappe.get_meta(doctype)
- if meta.is_tree:
+ if meta.is_tree and meta.has_field("is_group"):
query_filters.append(["is_group", "=", 0])
if meta.has_field("disabled"):
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 800e756..1ddcaa7 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -576,8 +576,52 @@
return
for qty_field in ["stock_qty", "rejected_qty"]:
- if target_doc.get(qty_field):
+ if target_doc.get(qty_field) and not target_doc.get("use_serial_batch_fields"):
update_serial_batch_no(source_doc, target_doc, source_parent, item_details, qty_field)
+ elif target_doc.get(qty_field) and target_doc.get("use_serial_batch_fields"):
+ update_non_bundled_serial_nos(source_doc, target_doc, source_parent)
+
+ def update_non_bundled_serial_nos(source_doc, target_doc, source_parent):
+ from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+ if source_doc.serial_no:
+ returned_serial_nos = get_returned_non_bundled_serial_nos(source_doc, source_parent)
+ serial_nos = list(set(get_serial_nos(source_doc.serial_no)) - set(returned_serial_nos))
+ if serial_nos:
+ target_doc.serial_no = "\n".join(serial_nos)
+
+ if source_doc.get("rejected_serial_no"):
+ returned_serial_nos = get_returned_non_bundled_serial_nos(
+ source_doc, source_parent, serial_no_field="rejected_serial_no"
+ )
+ rejected_serial_nos = list(
+ set(get_serial_nos(source_doc.rejected_serial_no)) - set(returned_serial_nos)
+ )
+ if rejected_serial_nos:
+ target_doc.rejected_serial_no = "\n".join(rejected_serial_nos)
+
+ def get_returned_non_bundled_serial_nos(child_doc, parent_doc, serial_no_field="serial_no"):
+ from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+ return_ref_field = frappe.scrub(child_doc.doctype)
+ if child_doc.doctype == "Delivery Note Item":
+ return_ref_field = "dn_detail"
+
+ serial_nos = []
+
+ fields = [f"`{'tab' + child_doc.doctype}`.`{serial_no_field}`"]
+
+ filters = [
+ [parent_doc.doctype, "return_against", "=", parent_doc.name],
+ [parent_doc.doctype, "is_return", "=", 1],
+ [child_doc.doctype, return_ref_field, "=", child_doc.name],
+ [parent_doc.doctype, "docstatus", "=", 1],
+ ]
+
+ for row in frappe.get_all(parent_doc.doctype, fields=fields, filters=filters):
+ serial_nos.extend(get_serial_nos(row.get(serial_no_field)))
+
+ return serial_nos
def update_terms(source_doc, target_doc, source_parent):
target_doc.payment_amount = -source_doc.payment_amount
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 35aebb9..e889c5d 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -264,7 +264,7 @@
if not self.has_overlap(production_capacity, time_logs):
return {}
- if self.workstation_type and time_logs:
+ if not self.workstation and self.workstation_type and time_logs:
if workstation_time := self.get_workstation_based_on_available_slot(time_logs):
self.workstation = workstation_time.get("workstation")
return workstation_time
@@ -420,7 +420,7 @@
if not workstation_doc.working_hours or cint(
frappe.db.get_single_value("Manufacturing Settings", "allow_overtime")
):
- if get_datetime(row.planned_end_time) < get_datetime(row.planned_start_time):
+ if get_datetime(row.planned_end_time) <= get_datetime(row.planned_start_time):
row.planned_end_time = add_to_date(row.planned_start_time, minutes=row.time_in_mins)
row.remaining_time_in_mins = 0.0
else:
diff --git a/erpnext/manufacturing/doctype/plant_floor/stock_summary_template.html b/erpnext/manufacturing/doctype/plant_floor/stock_summary_template.html
index 8824c98..69c8f44 100644
--- a/erpnext/manufacturing/doctype/plant_floor/stock_summary_template.html
+++ b/erpnext/manufacturing/doctype/plant_floor/stock_summary_template.html
@@ -52,10 +52,10 @@
</span>
</div>
<div class="col-sm-1">
- <button style="margin-left: 7px;" class="btn btn-default btn-xs btn-add" data-item-code="{{ escape(row.item_code) }}">Add</button>
+ <button style="margin-left: 7px;" class="btn btn-default btn-xs btn-add" data-item-code="{{ escape(row.item_code) }}">{{ __("Add") }}</button>
</div>
<div class="col-sm-1">
- <button style="margin-left: 7px;" class="btn btn-default btn-xs btn-move" data-item-code="{{ escape(row.item_code) }}">Move</button>
+ <button style="margin-left: 7px;" class="btn btn-default btn-xs btn-move" data-item-code="{{ escape(row.item_code) }}">{{ __("Move") }}</button>
</div>
</div>
-{% }); %}
\ No newline at end of file
+{% }); %}
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 5e22707..233ca19 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -330,6 +330,13 @@
else:
status = "Cancelled"
+ if (
+ self.skip_transfer
+ and self.produced_qty
+ and self.qty > (flt(self.produced_qty) + flt(self.process_loss_qty))
+ ):
+ status = "In Process"
+
return status
def update_work_order_qty(self):
@@ -784,7 +791,7 @@
)
def update_completed_qty_in_material_request(self):
- if self.material_request:
+ if self.material_request and self.material_request_item:
frappe.get_doc("Material Request", self.material_request).update_completed_qty(
[self.material_request_item]
)
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 815b01d..15dfc36 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -356,8 +356,10 @@
erpnext.patches.v15_0.create_advance_payment_status
erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes
erpnext.patches.v14_0.create_accounting_dimensions_in_reconciliation_tool
+erpnext.patches.v14_0.update_flag_for_return_invoices
# below migration patch should always run last
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20
erpnext.patches.v14_0.set_maintain_stock_for_bom_item
-erpnext.patches.v15_0.delete_orphaned_asset_movement_item_records
\ No newline at end of file
+erpnext.patches.v15_0.delete_orphaned_asset_movement_item_records
+erpnext.patches.v15_0.remove_cancelled_asset_capitalization_from_asset
\ No newline at end of file
diff --git a/erpnext/patches/v14_0/update_flag_for_return_invoices.py b/erpnext/patches/v14_0/update_flag_for_return_invoices.py
new file mode 100644
index 0000000..feb43be
--- /dev/null
+++ b/erpnext/patches/v14_0/update_flag_for_return_invoices.py
@@ -0,0 +1,62 @@
+from frappe import qb
+
+
+def execute():
+ # Set "update_outstanding_for_self" flag in Credit/Debit Notes
+ # Fetch Credit/Debit notes that does have 'return_against' but still post ledger entries against themselves.
+
+ gle = qb.DocType("GL Entry")
+
+ # Use hardcoded 'creation' date to isolate Credit/Debit notes created post v14 backport
+ # https://github.com/frappe/erpnext/pull/39497
+ creation_date = "2024-01-25"
+
+ si = qb.DocType("Sales Invoice")
+ if cr_notes := (
+ qb.from_(si)
+ .select(si.name)
+ .where(
+ (si.creation.gte(creation_date))
+ & (si.docstatus == 1)
+ & (si.is_return == True)
+ & (si.return_against.notnull())
+ )
+ .run()
+ ):
+ cr_notes = [x[0] for x in cr_notes]
+ if docs_that_require_update := (
+ qb.from_(gle)
+ .select(gle.voucher_no)
+ .distinct()
+ .where((gle.voucher_no.isin(cr_notes)) & (gle.voucher_no == gle.against_voucher))
+ .run()
+ ):
+ docs_that_require_update = [x[0] for x in docs_that_require_update]
+ qb.update(si).set(si.update_outstanding_for_self, True).where(
+ si.name.isin(docs_that_require_update)
+ ).run()
+
+ pi = qb.DocType("Purchase Invoice")
+ if dr_notes := (
+ qb.from_(pi)
+ .select(pi.name)
+ .where(
+ (pi.creation.gte(creation_date))
+ & (pi.docstatus == 1)
+ & (pi.is_return == True)
+ & (pi.return_against.notnull())
+ )
+ .run()
+ ):
+ dr_notes = [x[0] for x in dr_notes]
+ if docs_that_require_update := (
+ qb.from_(gle)
+ .select(gle.voucher_no)
+ .distinct()
+ .where((gle.voucher_no.isin(dr_notes)) & (gle.voucher_no == gle.against_voucher))
+ .run()
+ ):
+ docs_that_require_update = [x[0] for x in docs_that_require_update]
+ qb.update(pi).set(pi.update_outstanding_for_self, True).where(
+ pi.name.isin(docs_that_require_update)
+ ).run()
diff --git a/erpnext/patches/v15_0/remove_cancelled_asset_capitalization_from_asset.py b/erpnext/patches/v15_0/remove_cancelled_asset_capitalization_from_asset.py
new file mode 100644
index 0000000..cb39a92
--- /dev/null
+++ b/erpnext/patches/v15_0/remove_cancelled_asset_capitalization_from_asset.py
@@ -0,0 +1,11 @@
+import frappe
+
+
+def execute():
+ cancelled_asset_capitalizations = frappe.get_all(
+ "Asset Capitalization",
+ filters={"docstatus": 2},
+ fields=["name", "target_asset"],
+ )
+ for asset_capitalization in cancelled_asset_capitalizations:
+ frappe.db.set_value("Asset", asset_capitalization.target_asset, "capitalized_in", None)
diff --git a/erpnext/public/js/controllers/stock_controller.js b/erpnext/public/js/controllers/stock_controller.js
index e9c409e..3181d76 100644
--- a/erpnext/public/js/controllers/stock_controller.js
+++ b/erpnext/public/js/controllers/stock_controller.js
@@ -11,6 +11,18 @@
}
}
+ barcode(doc, cdt, cdn) {
+ let row = locals[cdt][cdn];
+ if (row.barcode) {
+ erpnext.stock.utils.set_item_details_using_barcode(this.frm, row, (r) => {
+ frappe.model.set_value(cdt, cdn, {
+ "item_code": r.message.item_code,
+ "qty": 1,
+ });
+ });
+ }
+ }
+
setup_warehouse_query() {
var me = this;
erpnext.queries.setup_queries(this.frm, "Warehouse", function() {
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index f43e3e7..8135bb2 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -411,6 +411,19 @@
barcode_scanner.process_scan();
}
+ barcode(doc, cdt, cdn) {
+ let row = locals[cdt][cdn];
+ if (row.barcode) {
+ erpnext.stock.utils.set_item_details_using_barcode(this.frm, row, (r) => {
+ debugger
+ frappe.model.set_value(cdt, cdn, {
+ "item_code": r.message.item_code,
+ "qty": 1,
+ });
+ });
+ }
+ }
+
validate_has_items () {
let table = this.frm.doc.items;
this.frm.has_items = (table && table.length
@@ -1813,8 +1826,8 @@
let items = [];
me.frm.doc.items.forEach(d => {
- // if same item was added a free item through a different pricing rule, keep it
- if(d.item_code != item.remove_free_item || !d.is_free_item || removed_pricing_rule?.includes(d.pricing_rules)) {
+ // if same item was added as free item through a different pricing rule, keep it
+ if(d.item_code != item.remove_free_item || !d.is_free_item || !removed_pricing_rule?.includes(d.pricing_rules)) {
items.push(d);
}
});
@@ -2216,7 +2229,7 @@
});
this.frm.doc.items.forEach(item => {
- if (!item.quality_inspection) {
+ if (this.has_inspection_required(item)) {
let dialog_items = dialog.fields_dict.items;
dialog_items.df.data.push({
"docname": item.name,
@@ -2240,6 +2253,16 @@
}
}
+ has_inspection_required(item) {
+ if (this.frm.doc.doctype === "Stock Entry" && this.frm.doc.purpose == "Manufacture" ) {
+ if (item.is_finished_item && !item.quality_inspection) {
+ return true;
+ }
+ } else if (!item.quality_inspection) {
+ return true;
+ }
+ }
+
get_method_for_payment() {
var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry";
if(cur_frm.doc.__onload && cur_frm.doc.__onload.make_payment_via_journal_entry){
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index f17f60a..7655ad9 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -2,6 +2,7 @@
// License: GNU General Public License v3. See license.txt
frappe.provide("erpnext");
frappe.provide("erpnext.utils");
+frappe.provide("erpnext.stock.utils");
$.extend(erpnext, {
get_currency: function (company) {
@@ -1201,3 +1202,10 @@
context.show_serial_batch_selector(grid_row.frm, grid_row.doc, "", "", true);
});
}
+
+$.extend(erpnext.stock.utils, {
+ set_item_details_using_barcode(frm, child_row, callback) {
+ const barcode_scanner = new erpnext.utils.BarcodeScanner({ frm: frm });
+ barcode_scanner.scan_api_call(child_row.barcode, callback);
+ },
+});
diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py
index 0b0d6e7..a525942 100644
--- a/erpnext/selling/doctype/quotation/test_quotation.py
+++ b/erpnext/selling/doctype/quotation/test_quotation.py
@@ -42,6 +42,39 @@
self.assertTrue(sales_order.get("payment_schedule"))
+ def test_gross_profit(self):
+ from erpnext.stock.doctype.item.test_item import make_item
+ from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+ from erpnext.stock.get_item_details import insert_item_price
+
+ item_doc = make_item("_Test Item for Gross Profit", {"is_stock_item": 1})
+ item_code = item_doc.name
+ make_stock_entry(item_code=item_code, qty=10, rate=100, target="_Test Warehouse - _TC")
+
+ selling_price_list = frappe.get_all("Price List", filters={"selling": 1}, limit=1)[0].name
+ frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 1)
+ insert_item_price(
+ frappe._dict(
+ {
+ "item_code": item_code,
+ "price_list": selling_price_list,
+ "price_list_rate": 300,
+ "rate": 300,
+ "conversion_factor": 1,
+ "discount_amount": 0.0,
+ "currency": frappe.db.get_value("Price List", selling_price_list, "currency"),
+ "uom": item_doc.stock_uom,
+ }
+ )
+ )
+
+ quotation = make_quotation(
+ item_code=item_code, qty=1, rate=300, selling_price_list=selling_price_list
+ )
+ self.assertEqual(quotation.items[0].valuation_rate, 100)
+ self.assertEqual(quotation.items[0].gross_profit, 200)
+ frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 0)
+
def test_maintain_rate_in_sales_cycle_is_enforced(self):
from erpnext.selling.doctype.quotation.quotation import make_sales_order
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index db16982..715d4d1 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -739,14 +739,6 @@
status: ["!=", "Lost"],
},
});
-
- setTimeout(() => {
- d.$parent.append(`
- <span class='small text-muted'>
- ${__("Note: Please create Sales Orders from individual Quotations to select from among Alternative Items.")}
- </span>
- `);
- }, 200);
},
__("Get Items From")
);
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index b5189b8..9fcda0d 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -2101,6 +2101,46 @@
self.assertFalse(row.warehouse == rejected_warehouse)
self.assertTrue(row.warehouse == warehouse)
+ def test_pick_list_for_batch(self):
+ from erpnext.stock.doctype.pick_list.pick_list import create_delivery_note
+
+ batch_item = make_item(
+ "_Test Batch Item for Pick LIST",
+ properties={
+ "has_batch_no": 1,
+ "create_new_batch": 1,
+ "batch_number_series": "BATCH-SDDTBIFRM-.#####",
+ },
+ ).name
+
+ warehouse = "_Test Warehouse - _TC"
+ se = make_stock_entry(item_code=batch_item, qty=10, target=warehouse, use_serial_batch_fields=1)
+ so = make_sales_order(item_code=batch_item, qty=10, warehouse=warehouse)
+ pick_list = create_pick_list(so.name)
+
+ pick_list.save()
+ batch_no = frappe.get_all(
+ "Serial and Batch Entry",
+ filters={"parent": se.items[0].serial_and_batch_bundle},
+ fields=["batch_no"],
+ )[0].batch_no
+
+ for row in pick_list.locations:
+ self.assertEqual(row.qty, 10.0)
+ self.assertTrue(row.warehouse == warehouse)
+ self.assertTrue(row.batch_no == batch_no)
+
+ pick_list.submit()
+
+ dn = create_delivery_note(pick_list.name)
+ for row in dn.items:
+ self.assertEqual(row.qty, 10.0)
+ self.assertTrue(row.warehouse == warehouse)
+ self.assertTrue(row.batch_no == batch_no)
+
+ dn.submit()
+ dn.reload()
+
def automatically_fetch_payment_terms(enable=1):
accounts_settings = frappe.get_doc("Accounts Settings")
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index f79803c..b206406 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -2069,6 +2069,7 @@
as_dict=1,
)
+ precision = frappe.get_precision("Stock Entry Detail", "qty")
for key, row in available_materials.items():
remaining_qty_to_produce = flt(wo_data.trans_qty) - flt(wo_data.produced_qty)
if remaining_qty_to_produce <= 0 and not self.is_return:
@@ -2091,7 +2092,8 @@
serial_nos = row.serial_nos[0 : cint(qty)]
row.serial_nos = serial_nos
- self.update_item_in_stock_entry_detail(row, item, qty)
+ if flt(qty, precision) != 0.0:
+ self.update_item_in_stock_entry_detail(row, item, qty)
def update_batches_to_be_consume(self, batches, row, qty):
qty_to_be_consumed = qty
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json
index 3a094f1..e8e82af 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json
@@ -230,7 +230,7 @@
},
{
"fieldname": "stock_queue",
- "fieldtype": "Text",
+ "fieldtype": "Long Text",
"label": "FIFO Stock Queue (qty, rate)",
"oldfieldname": "fcfs_stack",
"oldfieldtype": "Text",
@@ -360,7 +360,7 @@
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2024-02-07 09:18:13.999231",
+ "modified": "2024-03-13 09:56:13.021696",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Ledger Entry",
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 a3e51ca..b49fe22 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -58,7 +58,7 @@
recalculate_rate: DF.Check
serial_and_batch_bundle: DF.Link | None
serial_no: DF.LongText | None
- stock_queue: DF.Text | None
+ stock_queue: DF.LongText | None
stock_uom: DF.Link | None
stock_value: DF.Currency
stock_value_difference: DF.Currency
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 06fd5f9..3356ad5 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -138,12 +138,12 @@
"voucher_type": self.doctype,
"voucher_no": self.name,
"voucher_detail_no": row.name,
- "qty": row.qty,
+ "qty": row.current_qty,
"type_of_transaction": "Outward",
"company": self.company,
"is_rejected": 0,
"serial_nos": get_serial_nos(row.current_serial_no) if row.current_serial_no else None,
- "batches": frappe._dict({row.batch_no: row.qty}) if row.batch_no else None,
+ "batches": frappe._dict({row.batch_no: row.current_qty}) if row.batch_no else None,
"batch_no": row.batch_no,
"do_not_submit": True,
}
diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py
index 12df0fab..4e87fa0 100644
--- a/erpnext/stock/serial_batch_bundle.py
+++ b/erpnext/stock/serial_batch_bundle.py
@@ -599,6 +599,7 @@
elif self.sle.voucher_no:
query = query.where(parent.voucher_no != self.sle.voucher_no)
+ query = query.where(parent.voucher_type != "Pick List")
if timestamp_condition:
query = query.where(timestamp_condition)
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 60053aa..aef1a82 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -1697,7 +1697,7 @@
solutions += (
"<li>"
+ _("If not, you can Cancel / Submit this entry")
- + " {0} ".format(frappe.bold("after"))
+ + " {0} ".format(frappe.bold(_("after")))
+ _("performing either one below:")
+ "</li>"
)