diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 3ab9d2b60d5..0370fa2ce87 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -68,7 +68,12 @@ "enable_party_matching", "enable_fuzzy_matching", "tab_break_dpet", - "show_balance_in_coa" + "show_balance_in_coa", + "reports_tab", + "remarks_section", + "general_ledger_remarks_length", + "column_break_lvjk", + "receivable_payable_remarks_length" ], "fields": [ { @@ -429,6 +434,34 @@ "fieldname": "show_balance_in_coa", "fieldtype": "Check", "label": "Show Balances in Chart Of Accounts" + }, + { + "fieldname": "reports_tab", + "fieldtype": "Tab Break", + "label": "Reports" + }, + { + "default": "0", + "description": "Truncates 'Remarks' column to set character length", + "fieldname": "general_ledger_remarks_length", + "fieldtype": "Int", + "label": "General Ledger" + }, + { + "default": "0", + "description": "Truncates 'Remarks' column to set character length", + "fieldname": "receivable_payable_remarks_length", + "fieldtype": "Int", + "label": "Accounts Receivable/Payable" + }, + { + "fieldname": "column_break_lvjk", + "fieldtype": "Column Break" + }, + { + "fieldname": "remarks_section", + "fieldtype": "Section Break", + "label": "Remarks Column Length" } ], "icon": "icon-cog", @@ -436,7 +469,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-07-27 15:05:34.000264", + "modified": "2023-11-20 09:37:47.650347", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index d23ae877827..f6280307e9c 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -829,7 +829,6 @@ frappe.ui.form.on('Payment Entry', { else total_negative_outstanding += Math.abs(flt(row.outstanding_amount)); }) - var allocated_negative_outstanding = 0; if ( (frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") || @@ -844,6 +843,7 @@ frappe.ui.form.on('Payment Entry', { var allocated_positive_outstanding = paid_amount + allocated_negative_outstanding; } else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) { + total_negative_outstanding = flt(total_negative_outstanding, precision("outstanding_amount")) if(paid_amount > total_negative_outstanding) { if(total_negative_outstanding == 0) { frappe.msgprint( diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 35207eae0e5..03c939cf079 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -913,8 +913,11 @@ class PaymentEntry(AccountsController): ): return - total_negative_outstanding = sum( - abs(flt(d.outstanding_amount)) for d in self.get("references") if flt(d.outstanding_amount) < 0 + total_negative_outstanding = flt( + sum( + abs(flt(d.outstanding_amount)) for d in self.get("references") if flt(d.outstanding_amount) < 0 + ), + self.references[0].precision("outstanding_amount") if self.references else None, ) paid_amount = self.paid_amount if self.payment_type == "Receive" else self.received_amount diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index 71bc498b494..d7a73f0ce71 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -1137,6 +1137,40 @@ class TestPaymentReconciliation(FrappeTestCase): self.assertEqual(pay.unallocated_amount, 1000) self.assertEqual(pay.difference_amount, 0) + def test_rounding_of_unallocated_amount(self): + self.supplier = "_Test Supplier USD" + pi = self.create_purchase_invoice(qty=1, rate=10, do_not_submit=True) + pi.supplier = self.supplier + pi.currency = "USD" + pi.conversion_rate = 80 + pi.credit_to = self.creditors_usd + pi.save().submit() + + pe = get_payment_entry(pi.doctype, pi.name) + pe.target_exchange_rate = 78.726500000 + pe.received_amount = 26.75 + pe.paid_amount = 2105.93 + pe.references = [] + pe.save().submit() + + # unallocated_amount will have some rounding loss - 26.749950 + self.assertNotEqual(pe.unallocated_amount, 26.75) + + pr = frappe.get_doc("Payment Reconciliation") + pr.company = self.company + pr.party_type = "Supplier" + pr.party = self.supplier + pr.receivable_payable_account = self.creditors_usd + pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate() + pr.get_unreconciled_entries() + + invoices = [invoice.as_dict() for invoice in pr.invoices] + payments = [payment.as_dict() for payment in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + + # Should not raise frappe.exceptions.ValidationError: Payment Entry has been modified after you pulled it. Please pull it again. + pr.reconcile() + def make_customer(customer_name, currency=None): if not frappe.db.exists("Customer", customer_name): diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json index eedaaaf338b..de333cb9e8d 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json @@ -18,6 +18,7 @@ "is_pos", "is_return", "update_billed_amount_in_sales_order", + "update_billed_amount_in_delivery_note", "column_break1", "company", "posting_date", @@ -1549,12 +1550,19 @@ "fieldtype": "Currency", "label": "Amount Eligible for Commission", "read_only": 1 + }, + { + "default": "1", + "depends_on": "eval: doc.is_return && doc.return_against", + "fieldname": "update_billed_amount_in_delivery_note", + "fieldtype": "Check", + "label": "Update Billed Amount in Delivery Note" } ], "icon": "fa fa-file-text", "is_submittable": 1, "links": [], - "modified": "2022-09-30 03:49:50.455199", + "modified": "2023-11-20 12:27:12.848149", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 5ec612f6b0f..6b26d40303b 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -2147,7 +2147,7 @@ "label": "Use Company default Cost Center for Round off" }, { - "default": "0", + "default": "1", "depends_on": "eval: doc.is_return", "fieldname": "update_billed_amount_in_delivery_note", "fieldtype": "Check", @@ -2164,7 +2164,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2023-11-03 14:39:38.012346", + "modified": "2023-11-20 11:51:43.555197", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 79363dbeb1b..584f297c904 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -782,6 +782,28 @@ class TestSalesInvoice(FrappeTestCase): w = self.make() self.assertEqual(w.outstanding_amount, w.base_rounded_total) + def test_rounded_total_with_cash_discount(self): + si = frappe.copy_doc(test_records[2]) + + item = copy.deepcopy(si.get("items")[0]) + item.update( + { + "qty": 1, + "rate": 14960.66, + } + ) + + si.set("items", [item]) + si.set("taxes", []) + si.apply_discount_on = "Grand Total" + si.is_cash_or_non_trade_discount = 1 + si.discount_amount = 1 + si.insert() + + self.assertEqual(si.grand_total, 14959.66) + self.assertEqual(si.rounded_total, 14960) + self.assertEqual(si.rounding_adjustment, 0.34) + def test_payment(self): w = self.make() diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 7903beec7cf..037f0ec4a15 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -31,7 +31,12 @@ from erpnext.accounts.utils import get_fiscal_year from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen from erpnext.utilities.regional import temporary_flag -PURCHASE_TRANSACTION_TYPES = {"Purchase Order", "Purchase Receipt", "Purchase Invoice"} +PURCHASE_TRANSACTION_TYPES = { + "Supplier Quotation", + "Purchase Order", + "Purchase Receipt", + "Purchase Invoice", +} SALES_TRANSACTION_TYPES = { "Quotation", "Sales Order", @@ -231,7 +236,9 @@ def set_address_details( if shipping_address: party_details.update( shipping_address=shipping_address, - shipping_address_display=render_address(shipping_address), + shipping_address_display=render_address( + shipping_address, check_permissions=not ignore_permissions + ), **get_fetch_values(doctype, "shipping_address", shipping_address) ) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index c077d829939..18217f8d3be 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -7,7 +7,7 @@ from collections import OrderedDict import frappe from frappe import _, qb, scrub from frappe.query_builder import Criterion -from frappe.query_builder.functions import Date, Sum +from frappe.query_builder.functions import Date, Substring, Sum from frappe.utils import cint, cstr, flt, getdate, nowdate from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( @@ -762,7 +762,12 @@ class ReceivablePayableReport(object): ) if self.filters.get("show_remarks"): - query = query.select(ple.remarks) + if remarks_length := frappe.db.get_single_value( + "Accounts Settings", "receivable_payable_remarks_length" + ): + query = query.select(Substring(ple.remarks, 1, remarks_length).as_("remarks")) + else: + query = query.select(ple.remarks) if self.filters.get("group_by_party"): query = query.orderby(self.ple.party, self.ple.posting_date) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 754f17c118e..2e0a9c5f738 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -164,7 +164,12 @@ def get_gl_entries(filters, accounting_dimensions): credit_in_account_currency """ if filters.get("show_remarks"): - select_fields += """,remarks""" + if remarks_length := frappe.db.get_single_value( + "Accounts Settings", "general_ledger_remarks_length" + ): + select_fields += f",substr(remarks, 1, {remarks_length}) as 'remarks'" + else: + select_fields += """,remarks""" order_by_statement = "order by posting_date, account, creation" diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py index 06c9e44b455..f6c7bd3db70 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py @@ -184,6 +184,16 @@ def get_columns(filters): "width": 180, } ) + else: + columns.append( + { + "label": _(filters.get("party_type")), + "fieldname": "party", + "fieldtype": "Dynamic Link", + "options": "party_type", + "width": 180, + } + ) columns.extend( [ diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 01724ff55ca..966e7e4bf1e 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -533,7 +533,7 @@ def check_if_advance_entry_modified(args): where name = %(voucher_no)s and docstatus = 1 and party_type = %(party_type)s and party = %(party)s and {0} = %(account)s - and round(unallocated_amount, {1}) = %(unreconciled_amount)s + and round(unallocated_amount, {1}) = round(%(unreconciled_amount)s, {1}) """.format( party_account_field, precision ), diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index b092d193cc4..3b0d5d5e7fb 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -495,6 +495,7 @@ "read_only": 1 }, { + "default": "1", "fieldname": "asset_quantity", "fieldtype": "Int", "label": "Asset Quantity", @@ -564,7 +565,7 @@ "link_fieldname": "target_asset" } ], - "modified": "2023-11-15 17:40:17.315203", + "modified": "2023-11-20 21:05:45.216899", "modified_by": "Administrator", "module": "Assets", "name": "Asset", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 902a28b286c..9708c77f824 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -1221,6 +1221,7 @@ def get_item_details(item_code, asset_category, gross_purchase_amount): "expected_value_after_useful_life": flt(gross_purchase_amount) * flt(d.salvage_value_percentage / 100), "depreciation_start_date": d.depreciation_start_date or nowdate(), + "rate_of_depreciation": d.rate_of_depreciation, } ) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js index 0073170a855..dc54d606e7a 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js @@ -1,30 +1,21 @@ -// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -frappe.ui.form.on('Bulk Transaction Log', { - - refresh: function(frm) { - frm.disable_save(); - frm.add_custom_button(__('Retry Failed Transactions'), ()=>{ - frappe.confirm(__("Retry Failing Transactions ?"), ()=>{ - query(frm, 1); - } - ); - }); - } +frappe.ui.form.on("Bulk Transaction Log", { + refresh(frm) { + frm.add_custom_button(__('Succeeded Entries'), function() { + frappe.set_route('List', 'Bulk Transaction Log Detail', {'date': frm.doc.date, 'transaction_status': "Success"}); + }, __("View")); + frm.add_custom_button(__('Failed Entries'), function() { + frappe.set_route('List', 'Bulk Transaction Log Detail', {'date': frm.doc.date, 'transaction_status': "Failed"}); + }, __("View")); + if (frm.doc.failed) { + frm.add_custom_button(__('Retry Failed Transactions'), function() { + frappe.call({ + method: "erpnext.utilities.bulk_transaction.retry", + args: {date: frm.doc.date} + }); + }); + } + }, }); - -function query(frm) { - frappe.call({ - method: "erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction", - args: { - log_date: frm.doc.log_date - } - }).then((r) => { - if (r.message === "No Failed Records") { - frappe.show_alert(__(r.message), 5); - } else { - frappe.show_alert(__("Retrying Failed Transactions"), 5); - } - }); -} \ No newline at end of file diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json index da42cf1bd4b..75cb358ff2f 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json @@ -1,31 +1,64 @@ { "actions": [], - "allow_rename": 1, - "creation": "2021-11-30 13:41:16.343827", + "allow_copy": 1, + "creation": "2023-11-09 20:14:45.139593", + "default_view": "List", "doctype": "DocType", - "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "log_date", - "logger_data" + "date", + "column_break_bsan", + "log_entries", + "section_break_mdmv", + "succeeded", + "column_break_qryp", + "failed" ], "fields": [ { - "fieldname": "log_date", + "fieldname": "date", "fieldtype": "Date", - "label": "Log Date", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Date", "read_only": 1 }, { - "fieldname": "logger_data", - "fieldtype": "Table", - "label": "Logger Data", - "options": "Bulk Transaction Log Detail" + "fieldname": "log_entries", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Log Entries", + "read_only": 1 + }, + { + "fieldname": "column_break_bsan", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_mdmv", + "fieldtype": "Section Break" + }, + { + "fieldname": "succeeded", + "fieldtype": "Int", + "label": "Succeeded", + "read_only": 1 + }, + { + "fieldname": "column_break_qryp", + "fieldtype": "Column Break" + }, + { + "fieldname": "failed", + "fieldtype": "Int", + "label": "Failed", + "read_only": 1 } ], - "index_web_pages_for_search": 1, + "in_create": 1, + "is_virtual": 1, "links": [], - "modified": "2022-02-03 17:23:02.935325", + "modified": "2023-11-11 04:52:49.347376", "modified_by": "Administrator", "module": "Bulk Transaction", "name": "Bulk Transaction Log", @@ -47,5 +80,5 @@ "sort_field": "modified", "sort_order": "DESC", "states": [], - "track_changes": 1 + "title_field": "date" } \ No newline at end of file diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py index 0596be4462a..712caf1f91f 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py @@ -1,67 +1,112 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from datetime import date - import frappe +from frappe import qb from frappe.model.document import Document - -from erpnext.utilities.bulk_transaction import task, update_logger +from frappe.query_builder.functions import Count +from frappe.utils import cint +from pypika import Order class BulkTransactionLog(Document): - pass + def db_insert(self, *args, **kwargs): + pass + def load_from_db(self): + log_detail = qb.DocType("Bulk Transaction Log Detail") -@frappe.whitelist() -def retry_failing_transaction(log_date=None): - if not log_date: - log_date = str(date.today()) - btp = frappe.qb.DocType("Bulk Transaction Log Detail") - data = ( - frappe.qb.from_(btp) - .select(btp.transaction_name, btp.from_doctype, btp.to_doctype) - .distinct() - .where(btp.retried != 1) - .where(btp.transaction_status == "Failed") - .where(btp.date == log_date) - ).run(as_dict=True) + has_records = frappe.db.sql( + f"select exists (select * from `tabBulk Transaction Log Detail` where date = '{self.name}');" + )[0][0] + if not has_records: + raise frappe.DoesNotExistError - if data: - if len(data) > 10: - frappe.enqueue(job, queue="long", job_name="bulk_retry", data=data, log_date=log_date) - else: - job(data, log_date) - else: - return "No Failed Records" + succeeded_logs = ( + qb.from_(log_detail) + .select(Count(log_detail.date).as_("count")) + .where((log_detail.date == self.name) & (log_detail.transaction_status == "Success")) + .run() + )[0][0] or 0 + failed_logs = ( + qb.from_(log_detail) + .select(Count(log_detail.date).as_("count")) + .where((log_detail.date == self.name) & (log_detail.transaction_status == "Failed")) + .run() + )[0][0] or 0 + total_logs = succeeded_logs + failed_logs + transaction_log = frappe._dict( + { + "date": self.name, + "count": total_logs, + "succeeded": succeeded_logs, + "failed": failed_logs, + } + ) + super(Document, self).__init__(serialize_transaction_log(transaction_log)) + @staticmethod + def get_list(args): + filter_date = parse_list_filters(args) + limit = cint(args.get("page_length")) or 20 + log_detail = qb.DocType("Bulk Transaction Log Detail") -def job(data, log_date): - for d in data: - failed = [] - try: - frappe.db.savepoint("before_creation_of_record") - task(d.transaction_name, d.from_doctype, d.to_doctype) - except Exception as e: - frappe.db.rollback(save_point="before_creation_of_record") - failed.append(e) - update_logger( - d.transaction_name, - e, - d.from_doctype, - d.to_doctype, - status="Failed", - log_date=log_date, - restarted=1, + dates_query = ( + qb.from_(log_detail) + .select(log_detail.date) + .distinct() + .orderby(log_detail.date, order=Order.desc) + .limit(limit) + ) + if filter_date: + dates_query = dates_query.where(log_detail.date == filter_date) + dates = dates_query.run() + + transaction_logs = [] + if dates: + transaction_logs_query = ( + qb.from_(log_detail) + .select(log_detail.date.as_("date"), Count(log_detail.date).as_("count")) + .where(log_detail.date.isin(dates)) + .orderby(log_detail.date, order=Order.desc) + .groupby(log_detail.date) + .limit(limit) ) + transaction_logs = transaction_logs_query.run(as_dict=True) - if not failed: - update_logger( - d.transaction_name, - None, - d.from_doctype, - d.to_doctype, - status="Success", - log_date=log_date, - restarted=1, - ) + return [serialize_transaction_log(x) for x in transaction_logs] + + @staticmethod + def get_count(args): + pass + + @staticmethod + def get_stats(args): + pass + + def db_update(self, *args, **kwargs): + pass + + def delete(self): + pass + + +def serialize_transaction_log(data): + return frappe._dict( + name=data.date, + date=data.date, + log_entries=data.count, + succeeded=data.succeeded, + failed=data.failed, + ) + + +def parse_list_filters(args): + # parse date filter + filter_date = None + for fil in args.get("filters"): + if isinstance(fil, list): + for elem in fil: + if elem == "date": + filter_date = fil[3] + return filter_date diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py index c673be89b3f..01bb615a3e1 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py @@ -1,79 +1,9 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest -from datetime import date - -import frappe - -from erpnext.utilities.bulk_transaction import transaction_processing +# import frappe +from frappe.tests.utils import FrappeTestCase -class TestBulkTransactionLog(unittest.TestCase): - def setUp(self): - create_company() - create_customer() - create_item() - - def test_entry_in_log(self): - so_name = create_so() - transaction_processing([{"name": so_name}], "Sales Order", "Sales Invoice") - doc = frappe.get_doc("Bulk Transaction Log", str(date.today())) - for d in doc.get("logger_data"): - if d.transaction_name == so_name: - self.assertEqual(d.transaction_name, so_name) - self.assertEqual(d.transaction_status, "Success") - self.assertEqual(d.from_doctype, "Sales Order") - self.assertEqual(d.to_doctype, "Sales Invoice") - self.assertEqual(d.retried, 0) - - -def create_company(): - if not frappe.db.exists("Company", "_Test Company"): - frappe.get_doc( - { - "doctype": "Company", - "company_name": "_Test Company", - "country": "India", - "default_currency": "INR", - } - ).insert() - - -def create_customer(): - if not frappe.db.exists("Customer", "Bulk Customer"): - frappe.get_doc({"doctype": "Customer", "customer_name": "Bulk Customer"}).insert() - - -def create_item(): - if not frappe.db.exists("Item", "MK"): - frappe.get_doc( - { - "doctype": "Item", - "item_code": "MK", - "item_name": "Milk", - "description": "Milk", - "item_group": "Products", - } - ).insert() - - -def create_so(intent=None): - so = frappe.new_doc("Sales Order") - so.customer = "Bulk Customer" - so.company = "_Test Company" - so.transaction_date = date.today() - - so.set_warehouse = "Finished Goods - _TC" - so.append( - "items", - { - "item_code": "MK", - "delivery_date": date.today(), - "qty": 10, - "rate": 80, - }, - ) - so.insert() - so.submit() - return so.name +class TestBulkTransactionLog(FrappeTestCase): + pass diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.js b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.js new file mode 100644 index 00000000000..5669601d116 --- /dev/null +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.js @@ -0,0 +1,8 @@ +// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Bulk Transaction Log Detail", { +// refresh(frm) { + +// }, +// }); diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json index 8262caa0209..9590325a06c 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json @@ -6,12 +6,12 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "from_doctype", "transaction_name", "date", "time", "transaction_status", "error_description", - "from_doctype", "to_doctype", "retried" ], @@ -20,8 +20,11 @@ "fieldname": "transaction_name", "fieldtype": "Dynamic Link", "in_list_view": 1, + "in_standard_filter": 1, "label": "Name", - "options": "from_doctype" + "options": "from_doctype", + "read_only": 1, + "search_index": 1 }, { "fieldname": "transaction_status", @@ -39,9 +42,11 @@ { "fieldname": "from_doctype", "fieldtype": "Link", + "in_standard_filter": 1, "label": "From Doctype", "options": "DocType", - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "to_doctype", @@ -54,8 +59,10 @@ "fieldname": "date", "fieldtype": "Date", "in_list_view": 1, + "in_standard_filter": 1, "label": "Date ", - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "time", @@ -66,19 +73,33 @@ { "fieldname": "retried", "fieldtype": "Int", + "in_list_view": 1, "label": "Retried", "read_only": 1 } ], + "in_create": 1, "index_web_pages_for_search": 1, - "istable": 1, "links": [], - "modified": "2022-02-03 19:57:31.650359", + "modified": "2023-11-10 11:44:10.758342", "modified_by": "Administrator", "module": "Bulk Transaction", "name": "Bulk Transaction Log Detail", "owner": "Administrator", - "permissions": [], + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], "sort_field": "modified", "sort_order": "DESC", "states": [], diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/test_bulk_transaction_log_detail.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/test_bulk_transaction_log_detail.py new file mode 100644 index 00000000000..5217b601f87 --- /dev/null +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/test_bulk_transaction_log_detail.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestBulkTransactionLogDetail(FrappeTestCase): + pass diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json index 06dbd86ba12..fd73f77ff8f 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json @@ -9,6 +9,8 @@ "field_order": [ "naming_series", "company", + "billing_address", + "billing_address_display", "vendor", "column_break1", "transaction_date", @@ -292,13 +294,25 @@ "fieldtype": "Check", "label": "Send Document Print", "print_hide": 1 + }, + { + "fieldname": "billing_address", + "fieldtype": "Link", + "label": "Company Billing Address", + "options": "Address" + }, + { + "fieldname": "billing_address_display", + "fieldtype": "Small Text", + "label": "Billing Address Details", + "read_only": 1 } ], "icon": "fa fa-shopping-cart", "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-08-09 12:20:26.850623", + "modified": "2023-11-06 12:45:28.898706", "modified_by": "Administrator", "module": "Buying", "name": "Request for Quotation", diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index 11ff91af94d..aa89b81b5ed 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -20,6 +20,10 @@ "valid_till", "quotation_number", "amended_from", + "accounting_dimensions_section", + "cost_center", + "dimension_col_break", + "project", "currency_and_price_list", "currency", "conversion_rate", @@ -79,6 +83,7 @@ "pricing_rule_details", "pricing_rules", "address_and_contact_tab", + "supplier_address_section", "supplier_address", "address_display", "column_break_72", @@ -86,6 +91,14 @@ "contact_display", "contact_mobile", "contact_email", + "shipping_address_section", + "shipping_address", + "column_break_zjaq", + "shipping_address_display", + "company_billing_address_section", + "billing_address", + "column_break_gcth", + "billing_address_display", "terms_tab", "tc_name", "terms", @@ -837,6 +850,76 @@ "fieldname": "named_place", "fieldtype": "Data", "label": "Named Place" + }, + { + "fieldname": "shipping_address", + "fieldtype": "Link", + "label": "Shipping Address", + "options": "Address", + "print_hide": 1 + }, + { + "fieldname": "column_break_zjaq", + "fieldtype": "Column Break" + }, + { + "fieldname": "shipping_address_display", + "fieldtype": "Small Text", + "label": "Shipping Address Details", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "shipping_address_section", + "fieldtype": "Section Break", + "label": "Shipping Address" + }, + { + "fieldname": "supplier_address_section", + "fieldtype": "Section Break", + "label": "Supplier Address" + }, + { + "fieldname": "company_billing_address_section", + "fieldtype": "Section Break", + "label": "Company Billing Address" + }, + { + "fieldname": "billing_address", + "fieldtype": "Link", + "label": "Company Billing Address", + "options": "Address" + }, + { + "fieldname": "column_break_gcth", + "fieldtype": "Column Break" + }, + { + "fieldname": "billing_address_display", + "fieldtype": "Small Text", + "label": "Billing Address Details", + "read_only": 1 + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" + }, + { + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" } ], "icon": "fa fa-shopping-cart", @@ -844,7 +927,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-04-14 16:43:41.714832", + "modified": "2023-11-20 11:15:30.083077", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation", diff --git a/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json b/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json index a4e3c4f32e2..52da29c7e2a 100644 --- a/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json +++ b/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json @@ -68,6 +68,8 @@ "column_break_15", "manufacturer_part_no", "ad_sec_break", + "cost_center", + "dimension_col_break", "project", "section_break_44", "page_break" @@ -555,13 +557,23 @@ "fieldname": "expected_delivery_date", "fieldtype": "Date", "label": "Expected Delivery Date" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-11-14 18:35:03.435817", + "modified": "2023-11-17 12:25:26.235367", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation Item", diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 913e2274b1a..81736915f55 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -87,7 +87,8 @@ class BuyingController(SubcontractingController): "posting_date": self.get("posting_date"), "posting_time": self.get("posting_time"), "qty": row.qty, - "serial_and_batch_bundle": row.get("serial_and_batch_bundle"), + "serial_no": row.serial_no, + "batch_no": row.batch_no, "company": self.company, "voucher_type": self.doctype, "voucher_no": self.name, @@ -745,7 +746,7 @@ class BuyingController(SubcontractingController): "calculate_depreciation": 1, "purchase_receipt_amount": purchase_amount, "gross_purchase_amount": purchase_amount, - "asset_quantity": row.qty if is_grouped_asset else 0, + "asset_quantity": row.qty if is_grouped_asset else 1, "purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None, "purchase_invoice": self.name if self.doctype == "Purchase Invoice" else None, "cost_center": row.cost_center, diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index a69b21c7c1d..5a6c87c2169 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -356,6 +356,7 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None): if doc.doctype == "Sales Invoice" or doc.doctype == "POS Invoice": doc.consolidated_invoice = "" doc.set("payments", []) + doc.update_billed_amount_in_delivery_note = True for data in source.payments: paid_amount = 0.00 base_paid_amount = 0.00 diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 95bf0e4688e..e24618af103 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -51,6 +51,7 @@ class calculate_taxes_and_totals(object): if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"): self.doc.grand_total -= self.doc.discount_amount self.doc.base_grand_total -= self.doc.base_discount_amount + self.doc.rounding_adjustment = self.doc.base_rounding_adjustment = 0.0 self.set_rounded_total() self.calculate_shipping_charges() diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 6d5dddd9e97..dad12c4d591 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -424,7 +424,7 @@ scheduler_events = { "hourly_long": [ "erpnext.accounts.doctype.subscription.subscription.process_all", "erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries", - "erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction", + "erpnext.utilities.bulk_transaction.retry", ], "daily": [ "erpnext.support.doctype.issue.issue.auto_close_tickets", @@ -548,6 +548,8 @@ accounting_dimension_doctypes = [ "Subcontracting Receipt", "Subcontracting Receipt Item", "Account Closing Balance", + "Supplier Quotation", + "Supplier Quotation Item", ] # get matching queries for Bank Reconciliation diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 86984819816..de663133a3f 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -349,5 +349,7 @@ execute:frappe.db.set_single_value("Payment Reconciliation", "payment_limit", 50 erpnext.patches.v14_0.rename_daily_depreciation_to_depreciation_amount_based_on_num_days_in_month erpnext.patches.v14_0.rename_depreciation_amount_based_on_num_days_in_month_to_daily_prorata_based erpnext.patches.v14_0.add_default_for_repost_settings +erpnext.patches.v14_0.create_accounting_dimensions_in_supplier_quotation +erpnext.patches.v14_0.update_zero_asset_quantity_field # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger diff --git a/erpnext/patches/v14_0/create_accounting_dimensions_in_supplier_quotation.py b/erpnext/patches/v14_0/create_accounting_dimensions_in_supplier_quotation.py new file mode 100644 index 00000000000..6966db1fd73 --- /dev/null +++ b/erpnext/patches/v14_0/create_accounting_dimensions_in_supplier_quotation.py @@ -0,0 +1,8 @@ +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( + create_accounting_dimensions_for_doctype, +) + + +def execute(): + create_accounting_dimensions_for_doctype(doctype="Supplier Quotation") + create_accounting_dimensions_for_doctype(doctype="Supplier Quotation Item") diff --git a/erpnext/patches/v14_0/update_zero_asset_quantity_field.py b/erpnext/patches/v14_0/update_zero_asset_quantity_field.py new file mode 100644 index 00000000000..0480f9b7aad --- /dev/null +++ b/erpnext/patches/v14_0/update_zero_asset_quantity_field.py @@ -0,0 +1,6 @@ +import frappe + + +def execute(): + asset = frappe.qb.DocType("Asset") + frappe.qb.update(asset).set(asset.asset_quantity, 1).where(asset.asset_quantity == 0).run() diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index 4a27807e4ec..e63ac144f64 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -65,6 +65,12 @@ class Timesheet(Document): if args.is_billable: if flt(args.billing_hours) == 0.0: args.billing_hours = args.hours + elif flt(args.billing_hours) > flt(args.hours): + frappe.msgprint( + _("Warning - Row {0}: Billing Hours are more than Actual Hours").format(args.idx), + indicator="orange", + alert=True, + ) else: args.billing_hours = 0 diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index b7ed22346b4..c179928b511 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -43,6 +43,9 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { if (this.frm.doc.apply_discount_on == "Grand Total" && this.frm.doc.is_cash_or_non_trade_discount) { this.frm.doc.grand_total -= this.frm.doc.discount_amount; this.frm.doc.base_grand_total -= this.frm.doc.base_discount_amount; + this.frm.doc.rounding_adjustment = 0; + this.frm.doc.base_rounding_adjustment = 0; + this.set_rounded_total(); } await this.calculate_shipping_charges(); diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js index 5c41aa06804..cba615c0d22 100644 --- a/erpnext/public/js/utils/party.js +++ b/erpnext/public/js/utils/party.js @@ -4,7 +4,7 @@ frappe.provide("erpnext.utils"); const SALES_DOCTYPES = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice']; -const PURCHASE_DOCTYPES = ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice']; +const PURCHASE_DOCTYPES = ['Supplier Quotation','Purchase Order', 'Purchase Receipt', 'Purchase Invoice']; erpnext.utils.get_party_details = function(frm, method, args, callback) { if (!method) { diff --git a/erpnext/setup/doctype/employee/employee.js b/erpnext/setup/doctype/employee/employee.js index 39a215f3831..efc3fd1d33d 100755 --- a/erpnext/setup/doctype/employee/employee.js +++ b/erpnext/setup/doctype/employee/employee.js @@ -81,8 +81,10 @@ frappe.ui.form.on("Employee", { employee: frm.doc.name, email: frm.doc.prefered_email }, + freeze: true, + freeze_message: __("Creating User..."), callback: function (r) { - frm.set_value("user_id", r.message); + frm.reload_doc(); } }); } diff --git a/erpnext/setup/doctype/employee/employee.py b/erpnext/setup/doctype/employee/employee.py index 78f2e4935e7..a632c90f51e 100755 --- a/erpnext/setup/doctype/employee/employee.py +++ b/erpnext/setup/doctype/employee/employee.py @@ -49,6 +49,9 @@ class Employee(NestedSet): else: existing_user_id = frappe.db.get_value("Employee", self.name, "user_id") if existing_user_id: + user = frappe.get_doc("User", existing_user_id) + validate_employee_role(user, ignore_emp_check=True) + user.save(ignore_permissions=True) remove_user_permission("Employee", self.name, existing_user_id) def after_rename(self, old, new, merge): @@ -254,12 +257,26 @@ class Employee(NestedSet): frappe.cache().hdel("employees_with_number", prev_number) -def validate_employee_role(doc, method): +def validate_employee_role(doc, method=None, ignore_emp_check=False): # called via User hook - if "Employee" in [d.role for d in doc.get("roles")]: - if not frappe.db.get_value("Employee", {"user_id": doc.name}): - frappe.msgprint(_("Please set User ID field in an Employee record to set Employee Role")) - doc.get("roles").remove(doc.get("roles", {"role": "Employee"})[0]) + if not ignore_emp_check: + if frappe.db.get_value("Employee", {"user_id": doc.name}): + return + + user_roles = [d.role for d in doc.get("roles")] + if "Employee" in user_roles: + frappe.msgprint( + _("User {0}: Removed Employee role as there is no mapped employee.").format(doc.name) + ) + doc.get("roles").remove(doc.get("roles", {"role": "Employee"})[0]) + + if "Employee Self Service" in user_roles: + frappe.msgprint( + _("User {0}: Removed Employee Self Service role as there is no mapped employee.").format( + doc.name + ) + ) + doc.get("roles").remove(doc.get("roles", {"role": "Employee Self Service"})[0]) def update_user_permissions(doc, method): @@ -371,6 +388,8 @@ def create_user(employee, user=None, email=None): } ) user.insert() + emp.user_id = user.name + emp.save() return user.name diff --git a/erpnext/setup/doctype/employee/test_employee.py b/erpnext/setup/doctype/employee/test_employee.py index 5a693c5effb..9b706836269 100644 --- a/erpnext/setup/doctype/employee/test_employee.py +++ b/erpnext/setup/doctype/employee/test_employee.py @@ -25,6 +25,15 @@ class TestEmployee(unittest.TestCase): employee1_doc.status = "Left" self.assertRaises(InactiveEmployeeStatusError, employee1_doc.save) + def test_user_has_employee(self): + employee = make_employee("test_emp_user_creation@company.com") + employee_doc = frappe.get_doc("Employee", employee) + user = employee_doc.user_id + self.assertTrue("Employee" in frappe.get_roles(user)) + employee_doc.user_id = "" + employee_doc.save() + self.assertTrue("Employee" not in frappe.get_roles(user)) + def tearDown(self): frappe.db.rollback() diff --git a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.json b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.json index 225da6d15ec..0c4757ffadb 100644 --- a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.json +++ b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.json @@ -103,15 +103,6 @@ "print_hide": 1, "read_only": 1 }, - { - "fieldname": "amended_from", - "fieldtype": "Link", - "label": "Amended From", - "no_copy": 1, - "options": "Closing Stock Balance", - "print_hide": 1, - "read_only": 1 - }, { "fieldname": "include_uom", "fieldtype": "Link", @@ -145,4 +136,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 7d0a387f43e..7afc031d87d 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -505,7 +505,7 @@ "fieldtype": "Table", "hidden": 1, "label": "Variant Attributes", - "mandatory_depends_on": "has_variants", + "mandatory_depends_on": "eval:(doc.has_variants || doc.variant_of) && doc.variant_based_on==='Item Attribute'", "options": "Item Variant Attribute" }, { @@ -897,7 +897,7 @@ "index_web_pages_for_search": 1, "links": [], "make_attachments_public": 1, - "modified": "2023-09-11 13:46:32.688051", + "modified": "2023-09-18 15:41:32.688051", "modified_by": "Administrator", "module": "Stock", "name": "Item", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 10bd335468d..d8c678737ea 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -732,12 +732,18 @@ class PurchaseReceipt(BuyingController): def update_assets(self, item, valuation_rate): assets = frappe.db.get_all( - "Asset", filters={"purchase_receipt": self.name, "item_code": item.item_code} + "Asset", + filters={"purchase_receipt": self.name, "item_code": item.item_code}, + fields=["name", "asset_quantity"], ) for asset in assets: - frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(valuation_rate)) - frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(valuation_rate)) + frappe.db.set_value( + "Asset", asset.name, "gross_purchase_amount", flt(valuation_rate) * asset.asset_quantity + ) + frappe.db.set_value( + "Asset", asset.name, "purchase_receipt_amount", flt(valuation_rate) * asset.asset_quantity + ) def update_status(self, status): self.set_status(update=True, status=status) diff --git a/erpnext/stock/report/item_prices/item_prices.py b/erpnext/stock/report/item_prices/item_prices.py index ab47b4a8b99..a53a9f24f54 100644 --- a/erpnext/stock/report/item_prices/item_prices.py +++ b/erpnext/stock/report/item_prices/item_prices.py @@ -202,7 +202,7 @@ def get_valuation_rate(): bin_data = ( frappe.qb.from_(bin) .select( - bin.item_code, Sum(bin.actual_qty * bin.valuation_rate) / Sum(bin.actual_qty).as_("val_rate") + bin.item_code, (Sum(bin.actual_qty * bin.valuation_rate) / Sum(bin.actual_qty)).as_("val_rate") ) .where(bin.actual_qty > 0) .groupby(bin.item_code) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json index d572ae3d4be..4ed169717fb 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json @@ -11,6 +11,7 @@ "naming_series", "supplier", "supplier_name", + "supplier_delivery_note", "column_break1", "company", "posting_date", @@ -625,12 +626,17 @@ "fieldtype": "Check", "label": "Edit Posting Date and Time", "print_hide": 1 + }, + { + "fieldname": "supplier_delivery_note", + "fieldtype": "Data", + "label": "Supplier Delivery Note" } ], "in_create": 1, "is_submittable": 1, "links": [], - "modified": "2023-07-06 18:44:16.171842", + "modified": "2023-11-16 13:04:00.710534", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt", diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py index fcee2656445..5c43bbe41da 100644 --- a/erpnext/utilities/bulk_transaction.py +++ b/erpnext/utilities/bulk_transaction.py @@ -3,6 +3,7 @@ from datetime import date, datetime import frappe from frappe import _ +from frappe.utils import get_link_to_form, today @frappe.whitelist() @@ -28,6 +29,51 @@ def transaction_processing(data, from_doctype, to_doctype): job(deserialized_data, from_doctype, to_doctype) +@frappe.whitelist() +def retry(date: str | None = None): + if not date: + date = today() + + if date: + failed_docs = frappe.db.get_all( + "Bulk Transaction Log Detail", + filters={"date": date, "transaction_status": "Failed", "retried": 0}, + fields=["name", "transaction_name", "from_doctype", "to_doctype"], + ) + if not failed_docs: + frappe.msgprint(_("There are no Failed transactions")) + else: + job = frappe.enqueue( + retry_failed_transactions, + failed_docs=failed_docs, + ) + frappe.msgprint( + _("Job: {0} has been triggered for processing failed transactions").format( + get_link_to_form("RQ Job", job.id) + ) + ) + + +def retry_failed_transactions(failed_docs: list | None): + if failed_docs: + for log in failed_docs: + try: + frappe.db.savepoint("before_creation_state") + task(log.transaction_name, log.from_doctype, log.to_doctype) + except Exception as e: + frappe.db.rollback(save_point="before_creation_state") + update_log(log.name, "Failed", 1, str(frappe.get_traceback())) + else: + update_log(log.name, "Success", 1) + + +def update_log(log_name, status, retried, err=None): + frappe.db.set_value("Bulk Transaction Log Detail", log_name, "transaction_status", status) + frappe.db.set_value("Bulk Transaction Log Detail", log_name, "retried", retried) + if err: + frappe.db.set_value("Bulk Transaction Log Detail", log_name, "error_description", err) + + def job(deserialized_data, from_doctype, to_doctype): fail_count = 0 for d in deserialized_data: @@ -38,7 +84,7 @@ def job(deserialized_data, from_doctype, to_doctype): except Exception as e: frappe.db.rollback(save_point="before_creation_state") fail_count += 1 - update_logger( + create_log( doc_name, str(frappe.get_traceback()), from_doctype, @@ -47,7 +93,7 @@ def job(deserialized_data, from_doctype, to_doctype): log_date=str(date.today()), ) else: - update_logger( + create_log( doc_name, None, from_doctype, to_doctype, status="Success", log_date=str(date.today()) ) @@ -108,45 +154,18 @@ def task(doc_name, from_doctype, to_doctype): obj.insert(ignore_mandatory=True) -def check_logger_doc_exists(log_date): - return frappe.db.exists("Bulk Transaction Log", log_date) - - -def get_logger_doc(log_date): - return frappe.get_doc("Bulk Transaction Log", log_date) - - -def create_logger_doc(): - log_doc = frappe.new_doc("Bulk Transaction Log") - log_doc.set_new_name(set_name=str(date.today())) - log_doc.log_date = date.today() - - return log_doc - - -def append_data_to_logger(log_doc, doc_name, error, from_doctype, to_doctype, status, restarted): - row = log_doc.append("logger_data", {}) - row.transaction_name = doc_name - row.date = date.today() +def create_log(doc_name, e, from_doctype, to_doctype, status, log_date=None, restarted=0): + transaction_log = frappe.new_doc("Bulk Transaction Log Detail") + transaction_log.transaction_name = doc_name + transaction_log.date = today() now = datetime.now() - row.time = now.strftime("%H:%M:%S") - row.transaction_status = status - row.error_description = str(error) - row.from_doctype = from_doctype - row.to_doctype = to_doctype - row.retried = restarted - - -def update_logger(doc_name, e, from_doctype, to_doctype, status, log_date=None, restarted=0): - if not check_logger_doc_exists(log_date): - log_doc = create_logger_doc() - append_data_to_logger(log_doc, doc_name, e, from_doctype, to_doctype, status, restarted) - log_doc.insert() - else: - log_doc = get_logger_doc(log_date) - if record_exists(log_doc, doc_name, status): - append_data_to_logger(log_doc, doc_name, e, from_doctype, to_doctype, status, restarted) - log_doc.save() + transaction_log.time = now.strftime("%H:%M:%S") + transaction_log.transaction_status = status + transaction_log.error_description = str(e) + transaction_log.from_doctype = from_doctype + transaction_log.to_doctype = to_doctype + transaction_log.retried = restarted + transaction_log.save() def show_job_status(fail_count, deserialized_data_count, to_doctype): @@ -176,23 +195,3 @@ def show_job_status(fail_count, deserialized_data_count, to_doctype): title="Failed", indicator="red", ) - - -def record_exists(log_doc, doc_name, status): - record = mark_retrired_transaction(log_doc, doc_name) - if record and status == "Failed": - return False - elif record and status == "Success": - return True - else: - return True - - -def mark_retrired_transaction(log_doc, doc_name): - record = 0 - for d in log_doc.get("logger_data"): - if d.transaction_name == doc_name and d.transaction_status == "Failed": - frappe.db.set_value("Bulk Transaction Log Detail", d.name, "retried", 1) - record = record + 1 - - return record