diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 0370fa2ce87..5581ab038c5 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -469,7 +469,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-11-20 09:37:47.650347", + "modified": "2024-01-22 12:10:10.151819", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py index 6445deba6c3..b361a1ebb82 100644 --- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py +++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py @@ -6,7 +6,7 @@ import frappe from frappe import _, msgprint from frappe.model.document import Document from frappe.query_builder.custom import ConstantColumn -from frappe.utils import flt, fmt_money, getdate +from frappe.utils import flt, fmt_money, get_link_to_form, getdate import erpnext @@ -210,8 +210,11 @@ class BankClearance(Document): if d.cheque_date and getdate(d.clearance_date) < getdate(d.cheque_date): frappe.throw( - _("Row #{0}: Clearance date {1} cannot be before Cheque Date {2}").format( - d.idx, d.clearance_date, d.cheque_date + _("Row #{0}: For {1} Clearance date {2} cannot be before Cheque Date {3}").format( + d.idx, + get_link_to_form(d.payment_document, d.payment_entry), + d.clearance_date, + d.cheque_date, ) ) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 3d5d030a9ee..137dedf80ec 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -23,6 +23,11 @@ frappe.ui.form.on('Payment Entry', { var account_types = ["Pay", "Internal Transfer"].includes(frm.doc.payment_type) ? ["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]]; + + if (frm.doc.party_type == "Shareholder") { + account_types.push("Equity"); + } + return { filters: { "account_type": ["in", account_types], @@ -77,6 +82,9 @@ frappe.ui.form.on('Payment Entry', { var account_types = ["Receive", "Internal Transfer"].includes(frm.doc.payment_type) ? ["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]]; + if (frm.doc.party_type == "Shareholder") { + account_types.push("Equity"); + } return { filters: { "account_type": ["in", account_types], @@ -326,6 +334,12 @@ frappe.ui.form.on('Payment Entry', { return { query: "erpnext.controllers.queries.customer_query" } + } else if (frm.doc.party_type == "Shareholder") { + return { + filters: { + company: frm.doc.company, + }, + }; } }); diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 9384ee180d2..6d9565fb21c 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1563,7 +1563,7 @@ def get_outstanding_reference_documents(args): d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no") # Get negative outstanding sales /purchase invoices - if args.get("party_type") != "Employee" and not args.get("voucher_no"): + if args.get("party_type") != "Employee": negative_outstanding_invoices = get_negative_outstanding_invoices( args.get("party_type"), args.get("party"), diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 106000a78f3..78567c37cb5 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -19,7 +19,7 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import ( ) from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate from erpnext.accounts.party import get_party_account, get_party_bank_account -from erpnext.accounts.utils import get_account_currency +from erpnext.accounts.utils import get_account_currency, get_currency_precision from erpnext.erpnext_integrations.stripe_integration import create_stripe_subscription from erpnext.utilities import payment_app_import_guard @@ -520,7 +520,7 @@ def get_amount(ref_doc, payment_account=None): grand_total = ref_doc.outstanding_amount if grand_total > 0: - return grand_total + return flt(grand_total, get_currency_precision()) else: frappe.throw(_("Payment Entry is already created")) diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index 186c0ea7fd4..b44262cc280 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -87,7 +87,7 @@ class TestPOSInvoice(unittest.TestCase): inv.save() - self.assertEqual(inv.net_total, 4298.25) + self.assertEqual(inv.net_total, 4298.24) self.assertEqual(inv.grand_total, 4900.00) def test_tax_calculation_with_multiple_items(self): diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py index 144523624e8..5f5427c0764 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py @@ -340,7 +340,7 @@ class TestPOSInvoiceMergeLog(unittest.TestCase): inv.load_from_db() consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice) self.assertEqual(consolidated_invoice.status, "Return") - self.assertEqual(consolidated_invoice.rounding_adjustment, -0.001) + self.assertEqual(consolidated_invoice.rounding_adjustment, -0.002) finally: frappe.set_user("Administrator") diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index ab9894a52a8..b5d4af1ab90 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -485,7 +485,7 @@ def get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules, row_item): continue stock_qty = row.get("qty") * (row.get("conversion_factor") or 1.0) - amount = stock_qty * (row.get("price_list_rate") or row.get("rate")) + amount = stock_qty * (flt(row.get("price_list_rate")) or flt(row.get("rate"))) pricing_rules = filter_pricing_rules_for_qty_amount(stock_qty, amount, pricing_rules, row) if pricing_rules and pricing_rules[0]: diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index bb51223db16..7b471ed418e 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -307,7 +307,8 @@ class TestSalesInvoice(FrappeTestCase): si.insert() # with inclusive tax - self.assertEqual(si.items[0].net_amount, 3947.368421052631) + self.assertEqual(si.items[0].net_amount, 3947.37) + self.assertEqual(si.net_total, si.base_net_total) self.assertEqual(si.net_total, 3947.37) self.assertEqual(si.grand_total, 5000) @@ -414,8 +415,8 @@ class TestSalesInvoice(FrappeTestCase): for i, k in enumerate(expected_values["keys"]): self.assertEqual(d.get(k), expected_values[d.account_head][i]) - self.assertEqual(si.base_grand_total, 1500) - self.assertEqual(si.grand_total, 1500) + self.assertEqual(si.base_grand_total, 1500.01) + self.assertEqual(si.grand_total, 1500.01) self.assertEqual(si.rounding_adjustment, -0.01) def test_discount_amount_gl_entry(self): @@ -651,7 +652,7 @@ class TestSalesInvoice(FrappeTestCase): 62.5, 625.0, 50, - 499.97600115194473, + 499.98, ], "_Test Item Home Desktop 200": [ 190.66, @@ -662,7 +663,7 @@ class TestSalesInvoice(FrappeTestCase): 190.66, 953.3, 150, - 749.9968530500239, + 750, ], } @@ -675,20 +676,21 @@ class TestSalesInvoice(FrappeTestCase): self.assertEqual(d.get(k), expected_values[d.item_code][i]) # check net total - self.assertEqual(si.net_total, 1249.97) + self.assertEqual(si.base_net_total, si.net_total) + self.assertEqual(si.net_total, 1249.98) self.assertEqual(si.total, 1578.3) # check tax calculation expected_values = { "keys": ["tax_amount", "total"], - "_Test Account Excise Duty - _TC": [140, 1389.97], - "_Test Account Education Cess - _TC": [2.8, 1392.77], - "_Test Account S&H Education Cess - _TC": [1.4, 1394.17], - "_Test Account CST - _TC": [27.88, 1422.05], - "_Test Account VAT - _TC": [156.25, 1578.30], - "_Test Account Customs Duty - _TC": [125, 1703.30], - "_Test Account Shipping Charges - _TC": [100, 1803.30], - "_Test Account Discount - _TC": [-180.33, 1622.97], + "_Test Account Excise Duty - _TC": [140, 1389.98], + "_Test Account Education Cess - _TC": [2.8, 1392.78], + "_Test Account S&H Education Cess - _TC": [1.4, 1394.18], + "_Test Account CST - _TC": [27.88, 1422.06], + "_Test Account VAT - _TC": [156.25, 1578.31], + "_Test Account Customs Duty - _TC": [125, 1703.31], + "_Test Account Shipping Charges - _TC": [100, 1803.31], + "_Test Account Discount - _TC": [-180.33, 1622.98], } for d in si.get("taxes"): @@ -724,7 +726,7 @@ class TestSalesInvoice(FrappeTestCase): "base_rate": 2500, "base_amount": 25000, "net_rate": 40, - "net_amount": 399.9808009215558, + "net_amount": 399.98, "base_net_rate": 2000, "base_net_amount": 19999, }, @@ -738,7 +740,7 @@ class TestSalesInvoice(FrappeTestCase): "base_rate": 7500, "base_amount": 37500, "net_rate": 118.01, - "net_amount": 590.0531205155963, + "net_amount": 590.05, "base_net_rate": 5900.5, "base_net_amount": 29502.5, }, @@ -776,8 +778,13 @@ class TestSalesInvoice(FrappeTestCase): self.assertEqual(si.base_grand_total, 60795) self.assertEqual(si.grand_total, 1215.90) - self.assertEqual(si.rounding_adjustment, 0.01) - self.assertEqual(si.base_rounding_adjustment, 0.50) + # no rounding adjustment as the Smallest Currency Fraction Value of USD is 0.01 + if frappe.db.get_value("Currency", "USD", "smallest_currency_fraction_value") < 0.01: + self.assertEqual(si.rounding_adjustment, 0.10) + self.assertEqual(si.base_rounding_adjustment, 5.0) + else: + self.assertEqual(si.rounding_adjustment, 0.0) + self.assertEqual(si.base_rounding_adjustment, 0.0) def test_outstanding(self): w = self.make() @@ -2099,7 +2106,7 @@ class TestSalesInvoice(FrappeTestCase): self.assertEqual(si.net_total, 19453.13) self.assertEqual(si.grand_total, 24900) self.assertEqual(si.total_taxes_and_charges, 5446.88) - self.assertEqual(si.rounding_adjustment, -0.01) + self.assertEqual(si.rounding_adjustment, 0.00) expected_values = dict( (d[0], d) @@ -2126,7 +2133,7 @@ class TestSalesInvoice(FrappeTestCase): def test_rounding_adjustment_2(self): si = create_sales_invoice(rate=400, do_not_save=True) - for rate in [400, 600, 100]: + for rate in [400.25, 600.30, 100.65]: si.append( "items", { @@ -2152,17 +2159,18 @@ class TestSalesInvoice(FrappeTestCase): ) si.save() si.submit() - self.assertEqual(si.net_total, 1271.19) - self.assertEqual(si.grand_total, 1500) - self.assertEqual(si.total_taxes_and_charges, 228.82) - self.assertEqual(si.rounding_adjustment, -0.01) + self.assertEqual(si.net_total, si.base_net_total) + self.assertEqual(si.net_total, 1272.20) + self.assertEqual(si.grand_total, 1501.20) + self.assertEqual(si.total_taxes_and_charges, 229) + self.assertEqual(si.rounding_adjustment, -0.20) expected_values = [ - ["_Test Account Service Tax - _TC", 0.0, 114.41], - ["_Test Account VAT - _TC", 0.0, 114.41], - [si.debit_to, 1500, 0.0], - ["Round Off - _TC", 0.01, 0.01], - ["Sales - _TC", 0.0, 1271.18], + ["_Test Account Service Tax - _TC", 0.0, 114.50], + ["_Test Account VAT - _TC", 0.0, 114.50], + [si.debit_to, 1501, 0.0], + ["Round Off - _TC", 0.20, 0.0], + ["Sales - _TC", 0.0, 1272.20], ] gl_entries = frappe.db.sql( @@ -2220,7 +2228,8 @@ class TestSalesInvoice(FrappeTestCase): si.save() si.submit() - self.assertEqual(si.net_total, 4007.16) + self.assertEqual(si.net_total, si.base_net_total) + self.assertEqual(si.net_total, 4007.15) self.assertEqual(si.grand_total, 4488.02) self.assertEqual(si.total_taxes_and_charges, 480.86) self.assertEqual(si.rounding_adjustment, -0.02) @@ -2232,7 +2241,7 @@ class TestSalesInvoice(FrappeTestCase): ["_Test Account Service Tax - _TC", 0.0, 240.43], ["_Test Account VAT - _TC", 0.0, 240.43], ["Sales - _TC", 0.0, 4007.15], - ["Round Off - _TC", 0.02, 0.01], + ["Round Off - _TC", 0.01, 0.0], ] ) diff --git a/erpnext/accounts/doctype/share_transfer/share_transfer.json b/erpnext/accounts/doctype/share_transfer/share_transfer.json index 59a305317d8..c0b1e91977c 100644 --- a/erpnext/accounts/doctype/share_transfer/share_transfer.json +++ b/erpnext/accounts/doctype/share_transfer/share_transfer.json @@ -147,6 +147,7 @@ "fieldname": "amount", "fieldtype": "Currency", "label": "Amount", + "options": "Company:company:default_currency", "read_only": 1 }, { diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 96c97659ad8..a08eb4dda7d 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -694,10 +694,7 @@ class Subscription(Document): elif self.generate_invoice_at == "Days before the current subscription period": processing_date = add_days(self.current_invoice_start, -self.number_of_days) - process_subscription = frappe.new_doc("Process Subscription") - process_subscription.posting_date = processing_date - process_subscription.subscription = self.name - process_subscription.save().submit() + self.process(posting_date=processing_date) def get_calendar_months(billing_interval): diff --git a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py index 7c2389d8dd5..105b592a529 100644 --- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py +++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py @@ -150,8 +150,8 @@ def get_payment_entries(filters): select "Payment Entry" as payment_document, name as payment_entry, reference_no, reference_date as ref_date, - if(paid_to=%(account)s, received_amount, 0) as debit, - if(paid_from=%(account)s, paid_amount, 0) as credit, + if(paid_to=%(account)s, received_amount_after_tax, 0) as debit, + if(paid_from=%(account)s, paid_amount_after_tax, 0) as credit, posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date, if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency from `tabPayment Entry` diff --git a/erpnext/accounts/report/cheques_and_deposits_incorrectly_cleared/__init__.py b/erpnext/accounts/report/cheques_and_deposits_incorrectly_cleared/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/report/cheques_and_deposits_incorrectly_cleared/cheques_and_deposits_incorrectly_cleared.js b/erpnext/accounts/report/cheques_and_deposits_incorrectly_cleared/cheques_and_deposits_incorrectly_cleared.js new file mode 100644 index 00000000000..e83fc6f5b57 --- /dev/null +++ b/erpnext/accounts/report/cheques_and_deposits_incorrectly_cleared/cheques_and_deposits_incorrectly_cleared.js @@ -0,0 +1,44 @@ +// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.query_reports["Cheques and Deposits Incorrectly cleared"] = { + filters: [ + { + fieldname: "company", + label: __("Company"), + fieldtype: "Link", + options: "Company", + reqd: 1, + default: frappe.defaults.get_user_default("Company"), + }, + { + fieldname: "account", + label: __("Bank Account"), + fieldtype: "Link", + options: "Account", + default: frappe.defaults.get_user_default("Company") + ? locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"] + : "", + reqd: 1, + get_query: function () { + var company = frappe.query_report.get_filter_value("company"); + return { + query: "erpnext.controllers.queries.get_account_list", + filters: [ + ["Account", "account_type", "in", "Bank, Cash"], + ["Account", "is_group", "=", 0], + ["Account", "disabled", "=", 0], + ["Account", "company", "=", company], + ], + }; + }, + }, + { + fieldname: "report_date", + label: __("Date"), + fieldtype: "Date", + default: frappe.datetime.get_today(), + reqd: 1, + }, + ], +}; diff --git a/erpnext/accounts/report/cheques_and_deposits_incorrectly_cleared/cheques_and_deposits_incorrectly_cleared.json b/erpnext/accounts/report/cheques_and_deposits_incorrectly_cleared/cheques_and_deposits_incorrectly_cleared.json new file mode 100644 index 00000000000..50cf765ca3d --- /dev/null +++ b/erpnext/accounts/report/cheques_and_deposits_incorrectly_cleared/cheques_and_deposits_incorrectly_cleared.json @@ -0,0 +1,29 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2024-07-30 17:20:07.570971", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "letterhead": null, + "modified": "2024-07-30 17:20:07.570971", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Cheques and Deposits Incorrectly cleared", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Payment Entry", + "report_name": "Cheques and Deposits Incorrectly cleared", + "report_type": "Script Report", + "roles": [ + { + "role": "Accounts User" + }, + { + "role": "Accounts Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/accounts/report/cheques_and_deposits_incorrectly_cleared/cheques_and_deposits_incorrectly_cleared.py b/erpnext/accounts/report/cheques_and_deposits_incorrectly_cleared/cheques_and_deposits_incorrectly_cleared.py new file mode 100644 index 00000000000..891dc2c4bb1 --- /dev/null +++ b/erpnext/accounts/report/cheques_and_deposits_incorrectly_cleared/cheques_and_deposits_incorrectly_cleared.py @@ -0,0 +1,153 @@ +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _, qb +from frappe.query_builder import CustomFunction +from frappe.query_builder.custom import ConstantColumn + + +def execute(filters=None): + columns = get_columns() + data = build_data(filters) + return columns, data + + +def build_payment_entry_dict(row: dict) -> dict: + row_dict = frappe._dict() + row_dict.update( + { + "payment_document": row.get("doctype"), + "payment_entry": row.get("name"), + "posting_date": row.get("posting_date"), + "clearance_date": row.get("clearance_date"), + } + ) + if row.get("payment_type") == "Receive" and row.get("party_type") in ["Customer", "Supplier"]: + row_dict.update( + { + "debit": row.get("amount"), + "credit": 0, + } + ) + else: + row_dict.update( + { + "debit": 0, + "credit": row.get("amount"), + } + ) + return row_dict + + +def build_journal_entry_dict(row: dict) -> dict: + row_dict = frappe._dict() + row_dict.update( + { + "payment_document": row.get("doctype"), + "payment_entry": row.get("name"), + "posting_date": row.get("posting_date"), + "clearance_date": row.get("clearance_date"), + "debit": row.get("debit_in_account_currency"), + "credit": row.get("credit_in_account_currency"), + } + ) + return row_dict + + +def build_data(filters): + vouchers = get_amounts_not_reflected_in_system_for_bank_reconciliation_statement(filters) + data = [] + for x in vouchers: + if x.doctype == "Payment Entry": + data.append(build_payment_entry_dict(x)) + elif x.doctype == "Journal Entry": + data.append(build_journal_entry_dict(x)) + return data + + +def get_amounts_not_reflected_in_system_for_bank_reconciliation_statement(filters): + je = qb.DocType("Journal Entry") + jea = qb.DocType("Journal Entry Account") + doctype_name = ConstantColumn("Journal Entry") + + journals = ( + qb.from_(je) + .inner_join(jea) + .on(je.name == jea.parent) + .select( + doctype_name.as_("doctype"), + je.name, + jea.debit_in_account_currency, + jea.credit_in_account_currency, + je.posting_date, + je.clearance_date, + ) + .where( + je.docstatus.eq(1) + & jea.account.eq(filters.account) + & je.posting_date.gt(filters.report_date) + & je.clearance_date.lte(filters.report_date) + & (je.is_opening.isnull() | je.is_opening.eq("No")) + ) + .run(as_dict=1) + ) + + ifelse = CustomFunction("IF", ["condition", "then", "else"]) + pe = qb.DocType("Payment Entry") + doctype_name = ConstantColumn("Payment Entry") + payments = ( + qb.from_(pe) + .select( + doctype_name.as_("doctype"), + pe.name, + ifelse(pe.paid_from.eq(filters.account), pe.paid_amount, pe.received_amount).as_("amount"), + pe.payment_type, + pe.party_type, + pe.posting_date, + pe.clearance_date, + ) + .where( + pe.docstatus.eq(1) + & (pe.paid_from.eq(filters.account) | pe.paid_to.eq(filters.account)) + & pe.posting_date.gt(filters.report_date) + & pe.clearance_date.lte(filters.report_date) + ) + .run(as_dict=1) + ) + + return journals + payments + + +def get_columns(): + return [ + { + "fieldname": "payment_document", + "label": _("Payment Document Type"), + "fieldtype": "Data", + "width": 220, + }, + { + "fieldname": "payment_entry", + "label": _("Payment Document"), + "fieldtype": "Dynamic Link", + "options": "payment_document", + "width": 220, + }, + { + "fieldname": "debit", + "label": _("Debit"), + "fieldtype": "Currency", + "options": "account_currency", + "width": 120, + }, + { + "fieldname": "credit", + "label": _("Credit"), + "fieldtype": "Currency", + "options": "account_currency", + "width": 120, + }, + {"fieldname": "posting_date", "label": _("Posting Date"), "fieldtype": "Date", "width": 110}, + {"fieldname": "clearance_date", "label": _("Clearance Date"), "fieldtype": "Date", "width": 110}, + ] diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index ed4a4f52b8f..cd95a2d46da 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1192,6 +1192,12 @@ class AccountsController(TransactionBase): # Cancelling existing exchange gain/loss journals is handled during the `on_cancel` event. # see accounts/utils.py:cancel_exchange_gain_loss_journal() if self.docstatus == 1: + if dimensions_dict is None: + dimensions_dict = frappe._dict() + active_dimensions = get_dimensions()[0] + for dim in active_dimensions: + dimensions_dict[dim.fieldname] = self.get(dim.fieldname) + if self.get("doctype") == "Journal Entry": # 'args' is populated with exchange gain/loss account and the amount to be booked. # These are generated by Sales/Purchase Invoice during reconciliation and advance allocation. diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index e92356e30a8..7d371bdf9a6 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -8,6 +8,7 @@ import frappe from frappe import _, scrub from frappe.model.document import Document from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction +from frappe.utils.deprecations import deprecated import erpnext from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_rate @@ -71,7 +72,7 @@ class calculate_taxes_and_totals: self.calculate_net_total() self.calculate_tax_withholding_net_total() self.calculate_taxes() - self.manipulate_grand_total_for_inclusive_tax() + self.adjust_grand_total_for_inclusive_tax() self.calculate_totals() self._cleanup() self.calculate_total_net_weight() @@ -280,7 +281,7 @@ class calculate_taxes_and_totals: ): amount = flt(item.amount) - total_inclusive_tax_amount_per_qty - item.net_amount = flt(amount / (1 + cumulated_tax_fraction)) + item.net_amount = flt(amount / (1 + cumulated_tax_fraction), item.precision("net_amount")) item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate")) item.discount_percentage = flt( item.discount_percentage, item.precision("discount_percentage") @@ -505,7 +506,12 @@ class calculate_taxes_and_totals: tax.base_tax_amount = round(tax.base_tax_amount, 0) tax.base_tax_amount_after_discount_amount = round(tax.base_tax_amount_after_discount_amount, 0) + @deprecated def manipulate_grand_total_for_inclusive_tax(self): + # for backward compatablility - if in case used by an external application + return self.adjust_grand_total_for_inclusive_tax() + + def adjust_grand_total_for_inclusive_tax(self): # if fully inclusive taxes and diff if self.doc.get("taxes") and any(cint(t.included_in_print_rate) for t in self.doc.get("taxes")): last_tax = self.doc.get("taxes")[-1] @@ -527,17 +533,21 @@ class calculate_taxes_and_totals: diff = flt(diff, self.doc.precision("rounding_adjustment")) if diff and abs(diff) <= (5.0 / 10 ** last_tax.precision("tax_amount")): - self.doc.rounding_adjustment = diff + self.doc.grand_total_diff = diff + else: + self.doc.grand_total_diff = 0 def calculate_totals(self): if self.doc.get("taxes"): - self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment) + self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt( + self.doc.get("grand_total_diff") + ) else: self.doc.grand_total = flt(self.doc.net_total) if self.doc.get("taxes"): self.doc.total_taxes_and_charges = flt( - self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment), + self.doc.grand_total - self.doc.net_total - flt(self.doc.get("grand_total_diff")), self.doc.precision("total_taxes_and_charges"), ) else: @@ -600,8 +610,8 @@ class calculate_taxes_and_totals: self.doc.grand_total, self.doc.currency, self.doc.precision("rounded_total") ) - # if print_in_rate is set, we would have already calculated rounding adjustment - self.doc.rounding_adjustment += flt( + # rounding adjustment should always be the difference vetween grand and rounded total + self.doc.rounding_adjustment = flt( self.doc.rounded_total - self.doc.grand_total, self.doc.precision("rounding_adjustment") ) diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json index 07641d20c33..75373398b09 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.json +++ b/erpnext/crm/doctype/opportunity/opportunity.json @@ -185,7 +185,8 @@ { "fieldname": "expected_closing", "fieldtype": "Date", - "label": "Expected Closing Date" + "label": "Expected Closing Date", + "no_copy": 1 }, { "fieldname": "section_break_14", @@ -357,6 +358,7 @@ "fieldname": "transaction_date", "fieldtype": "Date", "label": "Opportunity Date", + "no_copy": 1, "oldfieldname": "transaction_date", "oldfieldtype": "Date", "reqd": 1, @@ -388,6 +390,7 @@ "fieldname": "first_response_time", "fieldtype": "Duration", "label": "First Response Time", + "no_copy": 1, "read_only": 1 }, { @@ -622,7 +625,7 @@ "icon": "fa fa-info-sign", "idx": 195, "links": [], - "modified": "2022-10-13 12:42:21.545636", + "modified": "2024-08-20 04:12:29.095761", "modified_by": "Administrator", "module": "CRM", "name": "Opportunity", diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js index 1ba941ab6f7..304b71e33d4 100644 --- a/erpnext/public/js/controllers/accounts.js +++ b/erpnext/public/js/controllers/accounts.js @@ -230,7 +230,8 @@ cur_frm.cscript.validate_taxes_and_charges = function(cdt, cdn) { } -cur_frm.cscript.validate_inclusive_tax = function(tax) { +cur_frm.cscript.validate_inclusive_tax = function(tax, frm) { + this.frm = this.frm || frm; var actual_type_error = function() { var msg = __("Actual type tax cannot be included in Item rate in row {0}", [tax.idx]) frappe.throw(msg); @@ -246,12 +247,12 @@ cur_frm.cscript.validate_inclusive_tax = function(tax) { if(tax.charge_type == "Actual") { // inclusive tax cannot be of type Actual actual_type_error(); - } else if(tax.charge_type == "On Previous Row Amount" && + } else if(tax.charge_type == "On Previous Row Amount" && this.frm && !cint(this.frm.doc["taxes"][tax.row_id - 1].included_in_print_rate) ) { // referred row should also be an inclusive tax on_previous_row_error(tax.row_id); - } else if(tax.charge_type == "On Previous Row Total") { + } else if(tax.charge_type == "On Previous Row Total" && this.frm) { var taxes_not_included = $.map(this.frm.doc["taxes"].slice(0, tax.row_id), function(t) { return cint(t.included_in_print_rate) ? null : t; }); if(taxes_not_included.length > 0) { @@ -294,7 +295,7 @@ if(!erpnext.taxes.flags[cur_frm.cscript.tax_table]) { var tax = frappe.get_doc(cdt, cdn); try { cur_frm.cscript.validate_taxes_and_charges(cdt, cdn); - cur_frm.cscript.validate_inclusive_tax(tax); + cur_frm.cscript.validate_inclusive_tax(tax, frm); } catch(e) { tax.included_in_print_rate = 0; refresh_field("included_in_print_rate", tax.name, tax.parentfield); diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 670cf35bb11..82e1d518362 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -103,7 +103,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { this.determine_exclusive_rate(); this.calculate_net_total(); this.calculate_taxes(); - this.manipulate_grand_total_for_inclusive_tax(); + this.adjust_grand_total_for_inclusive_tax(); this.calculate_totals(); this._cleanup(); } @@ -243,7 +243,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { if(!me.discount_amount_applied && item.qty && (total_inclusive_tax_amount_per_qty || cumulated_tax_fraction)) { var amount = flt(item.amount) - total_inclusive_tax_amount_per_qty; - item.net_amount = flt(amount / (1 + cumulated_tax_fraction)); + item.net_amount = flt(amount / (1 + cumulated_tax_fraction), precision("net_amount", item)); item.net_rate = item.qty ? flt(item.net_amount / item.qty, precision("net_rate", item)) : 0; me.set_in_company_currency(item, ["net_rate", "net_amount"]); @@ -298,6 +298,8 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { me.frm.doc.net_total += item.net_amount; me.frm.doc.base_net_total += item.base_net_amount; }); + + frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]); } calculate_shipping_charges() { @@ -506,8 +508,17 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { } } + /** + * @deprecated Use adjust_grand_total_for_inclusive_tax instead. + */ manipulate_grand_total_for_inclusive_tax() { + // for backward compatablility - if in case used by an external application + this.adjust_grand_total_for_inclusive_tax() + } + + adjust_grand_total_for_inclusive_tax() { var me = this; + // if fully inclusive taxes and diff if (this.frm.doc["taxes"] && this.frm.doc["taxes"].length) { var any_inclusive_tax = false; @@ -533,7 +544,9 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { diff = flt(diff, precision("rounding_adjustment")); if ( diff && Math.abs(diff) <= (5.0 / Math.pow(10, precision("tax_amount", last_tax))) ) { - me.frm.doc.rounding_adjustment = diff; + me.frm.doc.grand_total_diff = diff; + } else { + me.frm.doc.grand_total_diff = 0; } } } @@ -544,7 +557,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { var me = this; var tax_count = this.frm.doc["taxes"] ? this.frm.doc["taxes"].length : 0; this.frm.doc.grand_total = flt(tax_count - ? this.frm.doc["taxes"][tax_count - 1].total + flt(this.frm.doc.rounding_adjustment) + ? this.frm.doc["taxes"][tax_count - 1].total + flt(this.frm.doc.grand_total_diff) : this.frm.doc.net_total); if(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)) { @@ -604,7 +617,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { if(frappe.meta.get_docfield(this.frm.doc.doctype, "rounded_total", this.frm.doc.name)) { this.frm.doc.rounded_total = round_based_on_smallest_currency_fraction(this.frm.doc.grand_total, this.frm.doc.currency, precision("rounded_total")); - this.frm.doc.rounding_adjustment += flt(this.frm.doc.rounded_total - this.frm.doc.grand_total, + this.frm.doc.rounding_adjustment = flt(this.frm.doc.rounded_total - this.frm.doc.grand_total, precision("rounding_adjustment")); this.set_in_company_currency(this.frm.doc, ["rounding_adjustment", "rounded_total"]); @@ -672,8 +685,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { if (total_for_discount_amount) { $.each(this.frm._items || [], function(i, item) { distributed_amount = flt(me.frm.doc.discount_amount) * item.net_amount / total_for_discount_amount; - item.net_amount = flt(item.net_amount - distributed_amount, - precision("base_amount", item)); + item.net_amount = flt(item.net_amount - distributed_amount, precision("net_amount", item)); net_total += item.net_amount; // discount amount rounding loss adjustment if no taxes diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index c0cf79c63da..c87699f5f8c 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -3,7 +3,7 @@ import frappe from frappe.tests.utils import FrappeTestCase, change_settings -from frappe.utils import add_days, cint, cstr, flt, nowtime, today +from frappe.utils import add_days, cint, cstr, flt, get_datetime, getdate, nowtime, today from pypika import functions as fn import erpnext @@ -2603,6 +2603,71 @@ class TestPurchaseReceipt(FrappeTestCase): company_doc.default_inventory_account = None company_doc.save() + def test_sles_with_same_posting_datetime_and_creation(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + from erpnext.stock.report.stock_balance.stock_balance import execute + + item_code = "Test Item for SLE with same posting datetime and creation" + create_item(item_code) + + pr = make_purchase_receipt( + item_code=item_code, + qty=10, + rate=100, + posting_date="2023-11-06", + posting_time="00:00:00", + ) + + sr = make_stock_entry( + item_code=item_code, + source=pr.items[0].warehouse, + qty=10, + posting_date="2023-11-07", + posting_time="14:28:0.330404", + ) + + sle = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": sr.doctype, "voucher_no": sr.name, "item_code": sr.items[0].item_code}, + "name", + ) + + sle_doc = frappe.get_doc("Stock Ledger Entry", sle) + sle_doc.db_set("creation", "2023-11-07 14:28:01.208930") + + sle_doc.reload() + self.assertEqual(get_datetime(sle_doc.creation), get_datetime("2023-11-07 14:28:01.208930")) + + sr = make_stock_entry( + item_code=item_code, + target=pr.items[0].warehouse, + qty=50, + posting_date="2023-11-07", + posting_time="14:28:0.920825", + ) + + sle = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": sr.doctype, "voucher_no": sr.name, "item_code": sr.items[0].item_code}, + "name", + ) + + sle_doc = frappe.get_doc("Stock Ledger Entry", sle) + sle_doc.db_set("creation", "2023-11-07 14:28:01.044561") + + sle_doc.reload() + self.assertEqual(get_datetime(sle_doc.creation), get_datetime("2023-11-07 14:28:01.044561")) + + pr.repost_future_sle_and_gle(force=True) + + columns, data = execute( + filters=frappe._dict( + {"item_code": item_code, "warehouse": pr.items[0].warehouse, "company": pr.company} + ) + ) + + self.assertEqual(data[0].get("bal_qty"), 50.0) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 085f6b36f29..ff3a8be0419 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -4,7 +4,7 @@ from frappe.permissions import add_user_permission, remove_user_permission from frappe.tests.utils import FrappeTestCase, change_settings -from frappe.utils import add_days, flt, nowtime, today +from frappe.utils import add_days, cstr, flt, get_time, getdate, nowtime, today from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.stock.doctype.item.test_item import ( @@ -1727,6 +1727,74 @@ class TestStockEntry(FrappeTestCase): mr.cancel() mr.delete() + def test_stock_entry_for_same_posting_date_and_time(self): + warehouse = "_Test Warehouse - _TC" + item_code = "Test Stock Entry For Same Posting Datetime 1" + make_item(item_code, {"is_stock_item": 1}) + posting_date = nowdate() + posting_time = nowtime() + + for index in range(25): + se = make_stock_entry( + item_code=item_code, + qty=1, + to_warehouse=warehouse, + posting_date=posting_date, + posting_time=posting_time, + do_not_submit=True, + purpose="Material Receipt", + basic_rate=100, + ) + + se.append( + "items", + { + "item_code": item_code, + "item_name": se.items[0].item_name, + "description": se.items[0].description, + "t_warehouse": se.items[0].t_warehouse, + "basic_rate": 100, + "qty": 1, + "stock_qty": 1, + "conversion_factor": 1, + "expense_account": se.items[0].expense_account, + "cost_center": se.items[0].cost_center, + "uom": se.items[0].uom, + "stock_uom": se.items[0].stock_uom, + }, + ) + + se.remarks = f"The current number is {cstr(index)}" + + se.submit() + + sles = frappe.get_all( + "Stock Ledger Entry", + fields=[ + "posting_date", + "posting_time", + "actual_qty", + "qty_after_transaction", + "incoming_rate", + "stock_value_difference", + "stock_value", + ], + filters={"item_code": item_code, "warehouse": warehouse}, + order_by="creation", + ) + + self.assertEqual(len(sles), 50) + i = 0 + for sle in sles: + i += 1 + self.assertEqual(getdate(sle.posting_date), getdate(posting_date)) + self.assertEqual(get_time(sle.posting_time), get_time(posting_time)) + self.assertEqual(sle.actual_qty, 1) + self.assertEqual(sle.qty_after_transaction, i) + self.assertEqual(sle.incoming_rate, 100) + self.assertEqual(sle.stock_value_difference, 100) + self.assertEqual(sle.stock_value, 100 * i) + def make_serialized_item(**args): args = frappe._dict(args) 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 835002f0e16..94571b8ab87 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json @@ -319,7 +319,8 @@ { "fieldname": "posting_datetime", "fieldtype": "Datetime", - "label": "Posting Datetime" + "label": "Posting Datetime", + "search_index": 1 } ], "hide_toolbar": 1, @@ -328,7 +329,7 @@ "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2024-02-07 09:18:13.999231", + "modified": "2024-08-27 09:29:03.961443", "modified_by": "Administrator", "module": "Stock", "name": "Stock Ledger Entry", diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index 2b01ada1092..b85453d2572 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -1028,6 +1028,8 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin): self.assertEqual(50, _get_stock_credit(final_consumption)) def test_tie_breaking(self): + from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import repost_entries + frappe.flags.dont_execute_stock_reposts = True self.addCleanup(frappe.flags.pop, "dont_execute_stock_reposts") @@ -1070,6 +1072,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin): self.assertEqual([10, 11], ordered_qty_after_transaction()) first.cancel() + repost_entries() self.assertEqual([1], ordered_qty_after_transaction()) backdated = make_stock_entry( @@ -1169,7 +1172,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin): qty=5, posting_date="2021-01-01", rate=10, - posting_time="02:00:00.1234", + posting_time="02:00:00", ) time.sleep(3) @@ -1181,7 +1184,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin): qty=100, rate=10, posting_date="2021-01-01", - posting_time="02:00:00", + posting_time="02:00:00.1234", ) sle = frappe.get_all( diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index d78e3c14048..0201321785f 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -5,7 +5,7 @@ import frappe from frappe import _, bold, msgprint from frappe.query_builder.functions import CombineDatetime, Sum -from frappe.utils import add_to_date, cint, cstr, flt +from frappe.utils import add_to_date, cint, cstr, flt, get_link_to_form import erpnext from erpnext.accounts.utils import get_company_default @@ -357,7 +357,6 @@ class StockReconciliation(StockController): sl_entries.append(args) - qty_after_transaction = 0 for serial_no in serial_nos: args = self.get_sle_for_items(row, [serial_no]) @@ -373,27 +372,16 @@ class StockReconciliation(StockController): if previous_sle and row.warehouse != previous_sle.get("warehouse"): # If serial no exists in different warehouse - warehouse = previous_sle.get("warehouse", "") or row.warehouse - - if not qty_after_transaction: - qty_after_transaction = get_stock_balance( - row.item_code, warehouse, self.posting_date, self.posting_time + frappe.throw( + _( + "The Serial No {0} already exists in the warehouse {1}. It cannot be transferred to the warehouse {2}" + ).format( + get_link_to_form("Serial No", serial_no), + bold(previous_sle.get("warehouse")), + row.warehouse, ) - - qty_after_transaction -= 1 - - new_args = args.copy() - new_args.update( - { - "actual_qty": -1, - "qty_after_transaction": qty_after_transaction, - "warehouse": warehouse, - "valuation_rate": previous_sle.get("valuation_rate"), - } ) - sl_entries.append(new_args) - if row.qty: args = self.get_sle_for_items(row) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 8f3c9891334..2f4755cf511 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -1119,6 +1119,33 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): active_serial_no = frappe.get_all("Serial No", filters={"status": "Active", "item_code": item_code}) self.assertEqual(len(active_serial_no), 5) + def test_stock_reco_for_serialized_item_with_different_warehouse(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + # Add new serial nos + serial_item_code = "Stock-Reco-Serial-Item-11" + warehouse = "_Test Warehouse - _TC" + serial_warehouse = "_Test Warehouse for Stock Reco1 - _TC" + + self.make_item( + serial_item_code, {"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "SNT-SRS11.####"} + ) + + make_stock_entry(item_code=serial_item_code, target=warehouse, qty=10, basic_rate=100) + stock_entry = make_stock_entry( + item_code=serial_item_code, target=serial_warehouse, qty=10, basic_rate=200 + ) + + sr = create_stock_reconciliation( + item_code=serial_item_code, warehouse=warehouse, qty=11, rate=200, do_not_submit=True + ) + + serial_nos = get_serial_nos(stock_entry.items[0].serial_no) + + sr.items[0].serial_no += f"\n{serial_nos[0]}" + sr.save() + self.assertRaises(frappe.ValidationError, sr.submit) + def create_batch_item_with_batch(item_name, batch_id): batch_item_doc = create_item(item_name, is_stock_item=1) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 8df785a0c1f..826d8eb0060 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -445,6 +445,7 @@ class update_entries_after: and ( posting_datetime = %(posting_datetime)s ) + and creation = %(creation)s order by creation ASC for update @@ -1236,6 +1237,11 @@ def get_previous_sle_of_current_voucher(args, operator="<", exclude_current_vouc voucher_no = args.get("voucher_no") voucher_condition = f"and voucher_no != '{voucher_no}'" + elif args.get("creation"): + creation = args.get("creation") + operator = "<=" + voucher_condition = f"and creation < '{creation}'" + sle = frappe.db.sql( f""" select *, posting_datetime as "timestamp" @@ -1247,7 +1253,7 @@ def get_previous_sle_of_current_voucher(args, operator="<", exclude_current_vouc and ( posting_datetime {operator} %(posting_datetime)s ) - order by posting_datetime desc, creation desc + order by posting_date desc, posting_time desc, creation desc limit 1 for update""", { @@ -1341,7 +1347,7 @@ def get_stock_ledger_entries( where item_code = %(item_code)s and is_cancelled = 0 {conditions} - order by posting_datetime {order}, creation {order} + order by posting_date {order}, posting_time {order}, creation {order} {limit} {for_update}""".format( conditions=conditions, limit=limit or "", @@ -1446,7 +1452,7 @@ def get_valuation_rate( AND valuation_rate >= 0 AND is_cancelled = 0 AND NOT (voucher_no = %s AND voucher_type = %s) - order by posting_datetime desc, name desc limit 1""", + order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, warehouse, voucher_no, voucher_type), ) @@ -1695,7 +1701,8 @@ def get_future_sle_with_negative_qty(sle): & (SLE.is_cancelled == 0) & (SLE.qty_after_transaction < 0) ) - .orderby(SLE.posting_datetime) + .orderby(SLE.posting_date) + .orderby(SLE.posting_time) .limit(1) ) @@ -1711,14 +1718,14 @@ def get_future_sle_with_negative_batch_qty(args): with batch_ledger as ( select posting_date, posting_time, posting_datetime, voucher_type, voucher_no, - sum(actual_qty) over (order by posting_datetime, creation) as cumulative_total + sum(actual_qty) over (order by posting_date, posting_time, creation) as cumulative_total from `tabStock Ledger Entry` where item_code = %(item_code)s and warehouse = %(warehouse)s and batch_no=%(batch_no)s and is_cancelled = 0 - order by posting_datetime, creation + order by posting_date, posting_time, creation ) select * from batch_ledger where diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index e93fd851fa0..ebe94a9ec47 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -9108,13 +9108,13 @@ Warehouse wise Stock Value,Warenwert nach Lager, Ex Works,Ab Werk, Free Carrier,Frei Frachtführer, Free Alongside Ship,Frei Längsseite Schiff, -Free on Board,Frei an Bord, +Free On Board,Frei an Bord, Carriage Paid To,Frachtfrei, Carriage and Insurance Paid to,Frachtfrei versichert, Cost and Freight,Kosten und Fracht, "Cost, Insurance and Freight","Kosten, Versicherung und Fracht", -Delivered at Place,Geliefert benannter Ort, -Delivered at Place Unloaded,Geliefert benannter Ort entladen, +Delivered At Place,Geliefert benannter Ort, +Delivered At Place Unloaded,Geliefert benannter Ort entladen, Delivered Duty Paid,Geliefert verzollt, Discount Validity,Frist für den Rabatt, Discount Validity Based On,Frist für den Rabatt berechnet sich nach,