Merge pull request #38258 from ruthra-kumar/rename_unreconcile
refactor: rename 'Unreconcile Payments' doctype to singular 'Unreconcile Payment'
diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
index 71bc498..d7a73f0 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 @@
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/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index e1f0f19..d7c2361 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -13,6 +13,7 @@
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
validate_docs_for_deferred_accounting,
+ validate_docs_for_voucher_types,
)
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
check_if_return_invoice_linked_with_payment_entry,
@@ -491,6 +492,7 @@
def validate_for_repost(self):
self.validate_write_off_account()
self.validate_expense_account()
+ validate_docs_for_voucher_types(["Purchase Invoice"])
validate_docs_for_deferred_accounting([], [self.name])
def on_submit(self):
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index bcedb7c..71796c9 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -498,6 +498,7 @@
"fieldtype": "Column Break"
},
{
+ "allow_on_submit": 1,
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
@@ -505,6 +506,7 @@
"print_hide": 1
},
{
+ "allow_on_submit": 1,
"default": ":Company",
"depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "cost_center",
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
index 69cfe9f..1d72a46 100644
--- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
+++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
@@ -10,12 +10,7 @@
class RepostAccountingLedger(Document):
def __init__(self, *args, **kwargs):
super(RepostAccountingLedger, self).__init__(*args, **kwargs)
- self._allowed_types = [
- x.document_type
- for x in frappe.db.get_all(
- "Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"]
- )
- ]
+ self._allowed_types = get_allowed_types_from_settings()
def validate(self):
self.validate_vouchers()
@@ -56,15 +51,7 @@
def validate_vouchers(self):
if self.vouchers:
- # Validate voucher types
- voucher_types = set([x.voucher_type for x in self.vouchers])
- if disallowed_types := voucher_types.difference(self._allowed_types):
- frappe.throw(
- _("{0} types are not allowed. Only {1} are.").format(
- frappe.bold(comma_and(list(disallowed_types))),
- frappe.bold(comma_and(list(self._allowed_types))),
- )
- )
+ validate_docs_for_voucher_types([x.voucher_type for x in self.vouchers])
def get_existing_ledger_entries(self):
vouchers = [x.voucher_no for x in self.vouchers]
@@ -168,6 +155,15 @@
frappe.db.commit()
+def get_allowed_types_from_settings():
+ return [
+ x.document_type
+ for x in frappe.db.get_all(
+ "Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"]
+ )
+ ]
+
+
def validate_docs_for_deferred_accounting(sales_docs, purchase_docs):
docs_with_deferred_revenue = frappe.db.get_all(
"Sales Invoice Item",
@@ -191,6 +187,25 @@
)
+def validate_docs_for_voucher_types(doc_voucher_types):
+ allowed_types = get_allowed_types_from_settings()
+ # Validate voucher types
+ voucher_types = set(doc_voucher_types)
+ if disallowed_types := voucher_types.difference(allowed_types):
+ message = "are" if len(disallowed_types) > 1 else "is"
+ frappe.throw(
+ _("{0} {1} not allowed to be reposted. Modify {2} to enable reposting.").format(
+ frappe.bold(comma_and(list(disallowed_types))),
+ message,
+ frappe.bold(
+ frappe.utils.get_link_to_form(
+ "Repost Accounting Ledger Settings", "Repost Accounting Ledger Settings"
+ )
+ ),
+ )
+ )
+
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_repost_allowed_types(doctype, txt, searchfield, start, page_len, filters):
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 20b7382..85cb367 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -17,6 +17,7 @@
)
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
validate_docs_for_deferred_accounting,
+ validate_docs_for_voucher_types,
)
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
get_party_tax_withholding_details,
@@ -172,6 +173,7 @@
self.validate_write_off_account()
self.validate_account_for_change_amount()
self.validate_income_account()
+ validate_docs_for_voucher_types(["Sales Invoice"])
validate_docs_for_deferred_accounting([self.name], [])
def validate_fixed_asset(self):
diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json
index d6b9c46..540a4f5 100644
--- a/erpnext/assets/doctype/asset/asset.json
+++ b/erpnext/assets/doctype/asset/asset.json
@@ -481,6 +481,7 @@
"read_only": 1
},
{
+ "default": "1",
"fieldname": "asset_quantity",
"fieldtype": "Int",
"label": "Asset Quantity",
@@ -571,7 +572,7 @@
"link_fieldname": "target_asset"
}
],
- "modified": "2023-11-15 17:40:17.315203",
+ "modified": "2023-11-20 20:57:37.010467",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 3c570d1..7b7953b 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -46,12 +46,28 @@
self.validate_item()
self.validate_cost_center()
self.set_missing_values()
- self.validate_finance_books()
- if not self.split_from:
- self.prepare_depreciation_data()
- update_draft_asset_depr_schedules(self)
self.validate_gross_and_purchase_amount()
self.validate_expected_value_after_useful_life()
+ self.validate_finance_books()
+
+ if not self.split_from:
+ self.prepare_depreciation_data()
+
+ if self.calculate_depreciation:
+ update_draft_asset_depr_schedules(self)
+
+ if frappe.db.exists("Asset", self.name):
+ asset_depr_schedules_names = make_draft_asset_depr_schedules_if_not_present(self)
+
+ if asset_depr_schedules_names:
+ asset_depr_schedules_links = get_comma_separated_links(
+ asset_depr_schedules_names, "Asset Depreciation Schedule"
+ )
+ frappe.msgprint(
+ _(
+ "Asset Depreciation Schedules created:<br>{0}<br><br>Please check, edit if needed, and submit the Asset."
+ ).format(asset_depr_schedules_links)
+ )
self.status = self.get_status()
@@ -61,17 +77,7 @@
if not self.booked_fixed_asset and self.validate_make_gl_entry():
self.make_gl_entries()
if self.calculate_depreciation and not self.split_from:
- asset_depr_schedules_names = make_draft_asset_depr_schedules_if_not_present(self)
convert_draft_asset_depr_schedules_into_active(self)
- if asset_depr_schedules_names:
- asset_depr_schedules_links = get_comma_separated_links(
- asset_depr_schedules_names, "Asset Depreciation Schedule"
- )
- frappe.msgprint(
- _(
- "Asset Depreciation Schedules created:<br>{0}<br><br>Please check, edit if needed, and submit the Asset."
- ).format(asset_depr_schedules_links)
- )
self.set_status()
add_asset_activity(self.name, _("Asset submitted"))
@@ -823,6 +829,7 @@
"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/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index a470b47..68ad97d 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -758,7 +758,7 @@
"calculate_depreciation": 0,
"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,
}
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 6c10dd9..0badab5 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -351,5 +351,6 @@
erpnext.patches.v15_0.rename_depreciation_amount_based_on_num_days_in_month_to_daily_prorata_based
erpnext.patches.v15_0.set_reserved_stock_in_bin
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/update_zero_asset_quantity_field.py b/erpnext/patches/v14_0/update_zero_asset_quantity_field.py
new file mode 100644
index 0000000..0480f9b
--- /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 8e464b5..b9d801c 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -69,8 +69,14 @@
def update_billing_hours(self, args):
if args.is_billable:
- if flt(args.billing_hours) == 0.0 or flt(args.billing_hours) > flt(args.hours):
+ 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/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index d905fe5..a5940f0 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -731,12 +731,18 @@
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/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
index 4b8d965..abdbeb4 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
@@ -246,7 +246,7 @@
valuation_field = "rate"
child_table = "Subcontracting Receipt Supplied Item"
else:
- valuation_field = "rm_supp_cost"
+ valuation_field = "rate"
child_table = "Subcontracting Receipt Item"
precision = frappe.get_precision(child_table, valuation_field) or 2
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
index 6191a8c..f0e4e00 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
@@ -667,6 +667,104 @@
"Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 0
)
+ def test_subcontracting_receipt_valuation_for_fg_with_auto_created_serial_batch_bundle(self):
+ set_backflush_based_on("BOM")
+
+ fg_item = make_item(
+ properties={
+ "is_stock_item": 1,
+ "is_sub_contracted_item": 1,
+ "has_batch_no": 1,
+ "create_new_batch": 1,
+ "batch_number_series": "BSSNGS-.####",
+ }
+ ).name
+
+ rm_item1 = make_item(
+ properties={
+ "is_stock_item": 1,
+ "has_batch_no": 1,
+ "create_new_batch": 1,
+ "batch_number_series": "BNGS-.####",
+ }
+ ).name
+
+ rm_item2 = make_item(
+ properties={
+ "is_stock_item": 1,
+ "has_batch_no": 1,
+ "has_serial_no": 1,
+ "create_new_batch": 1,
+ "batch_number_series": "BNGS-.####",
+ "serial_no_series": "BNSS-.####",
+ }
+ ).name
+
+ rm_item3 = make_item(
+ properties={
+ "is_stock_item": 1,
+ "has_serial_no": 1,
+ "serial_no_series": "BSSSS-.####",
+ }
+ ).name
+
+ bom = make_bom(item=fg_item, raw_materials=[rm_item1, rm_item2, rm_item3])
+
+ rm_batch_no = None
+ for row in bom.items:
+ make_stock_entry(
+ item_code=row.item_code,
+ qty=1,
+ target="_Test Warehouse 1 - _TC",
+ rate=300,
+ )
+
+ service_items = [
+ {
+ "warehouse": "_Test Warehouse - _TC",
+ "item_code": "Subcontracted Service Item 1",
+ "qty": 1,
+ "rate": 100,
+ "fg_item": fg_item,
+ "fg_item_qty": 1,
+ },
+ ]
+ sco = get_subcontracting_order(service_items=service_items)
+
+ frappe.db.set_single_value(
+ "Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 1
+ )
+ scr = make_subcontracting_receipt(sco.name)
+ scr.save()
+ scr.submit()
+ scr.reload()
+
+ for row in scr.supplied_items:
+ self.assertEqual(row.rate, 300.00)
+ self.assertTrue(row.serial_and_batch_bundle)
+ auto_created_serial_batch = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_no": scr.name, "voucher_detail_no": row.name},
+ "auto_created_serial_and_batch_bundle",
+ )
+
+ self.assertTrue(auto_created_serial_batch)
+
+ self.assertEqual(scr.items[0].rm_cost_per_qty, 900)
+ self.assertEqual(scr.items[0].service_cost_per_qty, 100)
+ self.assertEqual(scr.items[0].rate, 1000)
+ valuation_rate = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_no": scr.name, "voucher_detail_no": scr.items[0].name},
+ "valuation_rate",
+ )
+
+ self.assertEqual(flt(valuation_rate), flt(1000))
+
+ frappe.db.set_single_value(
+ "Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 0
+ )
+
def test_subcontracting_receipt_raw_material_rate(self):
# Step - 1: Set Backflush Based On as "BOM"
set_backflush_based_on("BOM")