Merge pull request #39196 from ruthra-kumar/incorrect_status_in_bank_transaction
fix: bank transaction status upon reconciliation
diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json
index ac712d4..d0c9350 100644
--- a/erpnext/assets/doctype/asset/asset.json
+++ b/erpnext/assets/doctype/asset/asset.json
@@ -202,9 +202,9 @@
"fieldname": "purchase_date",
"fieldtype": "Date",
"label": "Purchase Date",
+ "mandatory_depends_on": "eval:!doc.is_existing_asset",
"read_only": 1,
- "read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset",
- "reqd": 1
+ "read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset"
},
{
"fieldname": "disposal_date",
@@ -227,15 +227,15 @@
"fieldname": "gross_purchase_amount",
"fieldtype": "Currency",
"label": "Gross Purchase Amount",
+ "mandatory_depends_on": "eval:(!doc.is_composite_asset || doc.docstatus==1)",
"options": "Company:company:default_currency",
- "read_only_depends_on": "eval:!doc.is_existing_asset",
- "reqd": 1
+ "read_only_depends_on": "eval:!doc.is_existing_asset"
},
{
"fieldname": "available_for_use_date",
"fieldtype": "Date",
"label": "Available-for-use Date",
- "reqd": 1
+ "mandatory_depends_on": "eval:(!doc.is_composite_asset || doc.docstatus==1)"
},
{
"default": "0",
@@ -590,7 +590,7 @@
"link_fieldname": "target_asset"
}
],
- "modified": "2023-12-21 16:46:20.732869",
+ "modified": "2024-01-05 17:36:53.131512",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index dd34189..5f44898 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -57,7 +57,7 @@
asset_owner: DF.Literal["", "Company", "Supplier", "Customer"]
asset_owner_company: DF.Link | None
asset_quantity: DF.Int
- available_for_use_date: DF.Date
+ available_for_use_date: DF.Date | None
booked_fixed_asset: DF.Check
calculate_depreciation: DF.Check
capitalized_in: DF.Link | None
@@ -92,7 +92,7 @@
number_of_depreciations_booked: DF.Int
opening_accumulated_depreciation: DF.Currency
policy_number: DF.Data | None
- purchase_date: DF.Date
+ purchase_date: DF.Date | None
purchase_invoice: DF.Link | None
purchase_receipt: DF.Link | None
purchase_receipt_amount: DF.Currency
@@ -316,7 +316,12 @@
frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError)
if is_cwip_accounting_enabled(self.asset_category):
- if not self.is_existing_asset and not self.purchase_receipt and not self.purchase_invoice:
+ if (
+ not self.is_existing_asset
+ and not self.is_composite_asset
+ and not self.purchase_receipt
+ and not self.purchase_invoice
+ ):
frappe.throw(
_("Please create purchase receipt or purchase invoice for the item {0}").format(
self.item_code
@@ -329,7 +334,7 @@
and not frappe.db.get_value("Purchase Invoice", self.purchase_invoice, "update_stock")
):
frappe.throw(
- _("Update stock must be enable for the purchase invoice {0}").format(self.purchase_invoice)
+ _("Update stock must be enabled for the purchase invoice {0}").format(self.purchase_invoice)
)
if not self.calculate_depreciation:
diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py
index 034ec55..d401b81 100644
--- a/erpnext/assets/doctype/asset_category/asset_category.py
+++ b/erpnext/assets/doctype/asset_category/asset_category.py
@@ -86,12 +86,12 @@
if selected_key_type not in expected_key_types:
frappe.throw(
_(
- "Row #{}: {} of {} should be {}. Please modify the account or select a different account."
+ "Row #{0}: {1} of {2} should be {3}. Please update the {1} or select a different account."
).format(
d.idx,
frappe.unscrub(key_to_match),
frappe.bold(selected_account),
- frappe.bold(expected_key_types),
+ frappe.bold(" or ".join(expected_key_types)),
),
title=_("Invalid Account"),
)
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 2bfd4be..f64af50 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -646,6 +646,10 @@
"project": self.project,
}
+ key = (d.item_code, d.sales_order, d.warehouse)
+ if not d.sales_order:
+ key = (d.name, d.item_code, d.warehouse)
+
if not item_details["project"] and d.sales_order:
item_details["project"] = frappe.get_cached_value("Sales Order", d.sales_order, "project")
@@ -654,12 +658,9 @@
item_dict[(d.item_code, d.material_request_item, d.warehouse)] = item_details
else:
item_details.update(
- {
- "qty": flt(item_dict.get((d.item_code, d.sales_order, d.warehouse), {}).get("qty"))
- + (flt(d.planned_qty) - flt(d.ordered_qty))
- }
+ {"qty": flt(item_dict.get(key, {}).get("qty")) + (flt(d.planned_qty) - flt(d.ordered_qty))}
)
- item_dict[(d.item_code, d.sales_order, d.warehouse)] = item_details
+ item_dict[key] = item_details
return item_dict
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index f6dfaa5..fedeb7a 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -672,7 +672,7 @@
items_data = pln.get_production_items()
# Update qty
- items_data[(item, None, None)]["qty"] = qty
+ items_data[(pln.po_items[0].name, item, None)]["qty"] = qty
# Create and Submit Work Order for each item in items_data
for key, item in items_data.items():
@@ -1522,6 +1522,45 @@
for d in mr_items:
self.assertEqual(d.get("quantity"), 1000.0)
+ def test_fg_item_quantity(self):
+ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
+ from erpnext.stock.utils import get_or_make_bin
+
+ fg_item = make_item(properties={"is_stock_item": 1}).name
+ rm_item = make_item(properties={"is_stock_item": 1}).name
+
+ make_bom(item=fg_item, raw_materials=[rm_item], source_warehouse="_Test Warehouse - _TC")
+
+ pln = create_production_plan(item_code=fg_item, planned_qty=10, do_not_submit=1)
+
+ pln.append(
+ "po_items",
+ {
+ "item_code": rm_item,
+ "planned_qty": 20,
+ "bom_no": pln.po_items[0].bom_no,
+ "warehouse": pln.po_items[0].warehouse,
+ "planned_start_date": add_to_date(nowdate(), days=1),
+ },
+ )
+ pln.submit()
+ wo_qty = {}
+
+ for row in pln.po_items:
+ wo_qty[row.name] = row.planned_qty
+
+ pln.make_work_order()
+
+ work_orders = frappe.get_all(
+ "Work Order",
+ fields=["qty", "production_plan_item as name"],
+ filters={"production_plan": pln.name},
+ )
+ self.assertEqual(len(work_orders), 2)
+
+ for row in work_orders:
+ self.assertEqual(row.qty, wo_qty[row.name])
+
def create_production_plan(**args):
"""
diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json
index 715b09c..5917e9b 100644
--- a/erpnext/projects/doctype/project/project.json
+++ b/erpnext/projects/doctype/project/project.json
@@ -131,6 +131,7 @@
"set_only_once": 1
},
{
+ "bold": 1,
"fieldname": "expected_start_date",
"fieldtype": "Date",
"label": "Expected Start Date",
@@ -453,7 +454,7 @@
"index_web_pages_for_search": 1,
"links": [],
"max_attachments": 4,
- "modified": "2023-08-28 22:27:28.370849",
+ "modified": "2024-01-08 16:01:34.598258",
"modified_by": "Administrator",
"module": "Projects",
"name": "Project",
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index 751dcbd..d17d21c 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -19,6 +19,62 @@
class Project(Document):
+ # begin: auto-generated types
+ # This code is auto-generated. Do not modify anything in this block.
+
+ from typing import TYPE_CHECKING
+
+ if TYPE_CHECKING:
+ from frappe.types import DF
+
+ from erpnext.projects.doctype.project_user.project_user import ProjectUser
+
+ actual_end_date: DF.Date | None
+ actual_start_date: DF.Date | None
+ actual_time: DF.Float
+ collect_progress: DF.Check
+ company: DF.Link
+ copied_from: DF.Data | None
+ cost_center: DF.Link | None
+ customer: DF.Link | None
+ daily_time_to_send: DF.Time | None
+ day_to_send: DF.Literal[
+ "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"
+ ]
+ department: DF.Link | None
+ estimated_costing: DF.Currency
+ expected_end_date: DF.Date | None
+ expected_start_date: DF.Date | None
+ first_email: DF.Time | None
+ frequency: DF.Literal["Hourly", "Twice Daily", "Daily", "Weekly"]
+ from_time: DF.Time | None
+ gross_margin: DF.Currency
+ holiday_list: DF.Link | None
+ is_active: DF.Literal["Yes", "No"]
+ message: DF.Text | None
+ naming_series: DF.Literal["PROJ-.####"]
+ notes: DF.TextEditor | None
+ per_gross_margin: DF.Percent
+ percent_complete: DF.Percent
+ percent_complete_method: DF.Literal["Manual", "Task Completion", "Task Progress", "Task Weight"]
+ priority: DF.Literal["Medium", "Low", "High"]
+ project_name: DF.Data
+ project_template: DF.Link | None
+ project_type: DF.Link | None
+ sales_order: DF.Link | None
+ second_email: DF.Time | None
+ status: DF.Literal["Open", "Completed", "Cancelled"]
+ to_time: DF.Time | None
+ total_billable_amount: DF.Currency
+ total_billed_amount: DF.Currency
+ total_consumed_material_cost: DF.Currency
+ total_costing_amount: DF.Currency
+ total_purchase_cost: DF.Currency
+ total_sales_amount: DF.Currency
+ users: DF.Table[ProjectUser]
+ weekly_time_to_send: DF.Time | None
+ # end: auto-generated types
+
def onload(self):
self.set_onload(
"activity_summary",
diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json
index 4d2d225..cc9832b 100644
--- a/erpnext/projects/doctype/task/task.json
+++ b/erpnext/projects/doctype/task/task.json
@@ -153,6 +153,7 @@
"label": "Timeline"
},
{
+ "bold": 1,
"fieldname": "exp_start_date",
"fieldtype": "Date",
"label": "Expected Start Date",
@@ -398,7 +399,7 @@
"is_tree": 1,
"links": [],
"max_attachments": 5,
- "modified": "2023-11-20 11:42:41.884069",
+ "modified": "2024-01-08 16:00:41.296203",
"modified_by": "Administrator",
"module": "Projects",
"name": "Task",
diff --git a/erpnext/public/js/utils/dimension_tree_filter.js b/erpnext/public/js/utils/dimension_tree_filter.js
index bb23f15..3f70c09 100644
--- a/erpnext/public/js/utils/dimension_tree_filter.js
+++ b/erpnext/public/js/utils/dimension_tree_filter.js
@@ -16,6 +16,8 @@
},
callback: function(r) {
me.accounting_dimensions = r.message[0];
+ // Ignoring "Project" as it is already handled specifically in Sales Order and Delivery Note
+ me.accounting_dimensions = me.accounting_dimensions.filter(x=>{return x.document_type != "Project"});
me.default_dimensions = r.message[1];
me.setup_filters(frm, doctype);
}
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 eede928..620b960 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
@@ -1004,13 +1004,17 @@
item = frappe.get_cached_value("Item", item_code, ["description", "item_code"], as_dict=1)
serial_nos = [d.get("serial_no") for d in serial_nos if d.get("serial_no")]
+ existing_serial_nos = frappe.get_all("Serial No", filters={"name": ("in", serial_nos)})
+
+ existing_serial_nos = [d.get("name") for d in existing_serial_nos if d.get("name")]
+ serial_nos = list(set(serial_nos) - set(existing_serial_nos))
+
+ if not serial_nos:
+ return
serial_nos_details = []
user = frappe.session.user
for serial_no in serial_nos:
- if frappe.db.exists("Serial No", serial_no):
- continue
-
serial_nos_details.append(
(
serial_no,
@@ -1046,9 +1050,16 @@
def make_batch_nos(item_code, batch_nos):
item = frappe.get_cached_value("Item", item_code, ["description", "item_code"], as_dict=1)
-
batch_nos = [d.get("batch_no") for d in batch_nos if d.get("batch_no")]
+ existing_batches = frappe.get_all("Batch", filters={"name": ("in", batch_nos)})
+
+ existing_batches = [d.get("name") for d in existing_batches if d.get("name")]
+
+ batch_nos = list(set(batch_nos) - set(existing_batches))
+ if not batch_nos:
+ return
+
batch_nos_details = []
user = frappe.session.user
for batch_no in batch_nos:
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py
index 1975747..0d453fb 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py
@@ -10,6 +10,8 @@
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
add_serial_batch_ledgers,
+ make_batch_nos,
+ make_serial_nos,
)
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
@@ -481,6 +483,38 @@
docstatus = frappe.db.get_value("Serial and Batch Bundle", bundle, "docstatus")
self.assertEqual(docstatus, 2)
+ def test_batch_duplicate_entry(self):
+ item_code = make_item(properties={"has_batch_no": 1}).name
+
+ batch_id = "TEST-BATTCCH-VAL-00001"
+ batch_nos = [{"batch_no": batch_id, "qty": 1}]
+
+ make_batch_nos(item_code, batch_nos)
+ self.assertTrue(frappe.db.exists("Batch", batch_id))
+
+ batch_id = "TEST-BATTCCH-VAL-00001"
+ batch_nos = [{"batch_no": batch_id, "qty": 1}]
+
+ # Shouldn't throw duplicate entry error
+ make_batch_nos(item_code, batch_nos)
+ self.assertTrue(frappe.db.exists("Batch", batch_id))
+
+ def test_serial_no_duplicate_entry(self):
+ item_code = make_item(properties={"has_serial_no": 1}).name
+
+ serial_no_id = "TEST-SNID-VAL-00001"
+ serial_nos = [{"serial_no": serial_no_id, "qty": 1}]
+
+ make_serial_nos(item_code, serial_nos)
+ self.assertTrue(frappe.db.exists("Serial No", serial_no_id))
+
+ serial_no_id = "TEST-SNID-VAL-00001"
+ serial_nos = [{"batch_no": serial_no_id, "qty": 1}]
+
+ # Shouldn't throw duplicate entry error
+ make_serial_nos(item_code, serial_nos)
+ self.assertTrue(frappe.db.exists("Serial No", serial_no_id))
+
def get_batch_from_bundle(bundle):
from erpnext.stock.serial_batch_bundle import get_batch_nos