diff --git a/CODEOWNERS b/CODEOWNERS
index 7f8c4d1ac87..713941e589c 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -4,22 +4,22 @@
# the repo. Unless a later match takes precedence,
erpnext/accounts/ @deepeshgarg007 @ruthra-kumar
-erpnext/assets/ @anandbaburajan @deepeshgarg007
+erpnext/assets/ @khushi8112 @deepeshgarg007
erpnext/loan_management/ @deepeshgarg007
erpnext/regional @deepeshgarg007 @ruthra-kumar
erpnext/selling @deepeshgarg007 @ruthra-kumar
erpnext/support/ @deepeshgarg007
pos*
-erpnext/buying/ @rohitwaghchaure @s-aga-r
-erpnext/maintenance/ @rohitwaghchaure @s-aga-r
-erpnext/manufacturing/ @rohitwaghchaure @s-aga-r
-erpnext/quality_management/ @rohitwaghchaure @s-aga-r
-erpnext/stock/ @rohitwaghchaure @s-aga-r
-erpnext/subcontracting @rohitwaghchaure @s-aga-r
+erpnext/buying/ @rohitwaghchaure
+erpnext/maintenance/ @rohitwaghchaure
+erpnext/manufacturing/ @rohitwaghchaure
+erpnext/quality_management/ @rohitwaghchaure
+erpnext/stock/ @rohitwaghchaure
+erpnext/subcontracting @rohitwaghchaure
erpnext/controllers/ @deepeshgarg007 @rohitwaghchaure
erpnext/patches/ @deepeshgarg007
.github/ @deepeshgarg007
-pyproject.toml @ankush
+pyproject.toml @akhilnarang
diff --git a/erpnext/accounts/report/sales_register/test_sales_register.py b/erpnext/accounts/report/sales_register/test_sales_register.py
new file mode 100644
index 00000000000..95aa5add24c
--- /dev/null
+++ b/erpnext/accounts/report/sales_register/test_sales_register.py
@@ -0,0 +1,179 @@
+import frappe
+from frappe.tests.utils import FrappeTestCase
+from frappe.utils import getdate, today
+
+from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
+from erpnext.accounts.report.sales_register.sales_register import execute
+from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
+
+
+class TestItemWiseSalesRegister(AccountsTestMixin, FrappeTestCase):
+ def setUp(self):
+ self.create_company()
+ self.create_customer()
+ self.create_item()
+ self.create_child_cost_center()
+
+ def tearDown(self):
+ frappe.db.rollback()
+
+ def create_child_cost_center(self):
+ cc_name = "South Wing"
+ if frappe.db.exists("Cost Center", cc_name):
+ cc = frappe.get_doc("Cost Center", cc_name)
+ else:
+ parent = frappe.db.get_value("Cost Center", self.cost_center, "parent_cost_center")
+ cc = frappe.get_doc(
+ {
+ "doctype": "Cost Center",
+ "company": self.company,
+ "is_group": False,
+ "parent_cost_center": parent,
+ "cost_center_name": cc_name,
+ }
+ )
+ cc = cc.save()
+ self.south_cc = cc.name
+
+ def create_sales_invoice(self, rate=100, do_not_submit=False):
+ si = create_sales_invoice(
+ item=self.item,
+ company=self.company,
+ customer=self.customer,
+ debit_to=self.debit_to,
+ posting_date=today(),
+ parent_cost_center=self.cost_center,
+ cost_center=self.cost_center,
+ rate=rate,
+ price_list_rate=rate,
+ do_not_save=1,
+ )
+ si = si.save()
+ if not do_not_submit:
+ si = si.submit()
+ return si
+
+ def test_basic_report_output(self):
+ si = self.create_sales_invoice(rate=98)
+
+ filters = frappe._dict({"from_date": today(), "to_date": today(), "company": self.company})
+ report = execute(filters)
+
+ res = [x for x in report[1] if x.get("voucher_no") == si.name]
+
+ expected_result = {
+ "voucher_type": si.doctype,
+ "voucher_no": si.name,
+ "posting_date": getdate(),
+ "customer": self.customer,
+ "receivable_account": self.debit_to,
+ "net_total": 98.0,
+ "grand_total": 98.0,
+ "debit": 98.0,
+ }
+
+ report_output = {k: v for k, v in res[0].items() if k in expected_result}
+ self.assertDictEqual(report_output, expected_result)
+
+ def test_journal_with_cost_center_filter(self):
+ je1 = frappe.get_doc(
+ {
+ "doctype": "Journal Entry",
+ "voucher_type": "Journal Entry",
+ "company": self.company,
+ "posting_date": getdate(),
+ "accounts": [
+ {
+ "account": self.debit_to,
+ "party_type": "Customer",
+ "party": self.customer,
+ "credit_in_account_currency": 77,
+ "credit": 77,
+ "is_advance": "Yes",
+ "cost_center": self.cost_center,
+ },
+ {
+ "account": self.cash,
+ "debit_in_account_currency": 77,
+ "debit": 77,
+ },
+ ],
+ }
+ )
+ je1.submit()
+
+ je2 = frappe.get_doc(
+ {
+ "doctype": "Journal Entry",
+ "voucher_type": "Journal Entry",
+ "company": self.company,
+ "posting_date": getdate(),
+ "accounts": [
+ {
+ "account": self.debit_to,
+ "party_type": "Customer",
+ "party": self.customer,
+ "credit_in_account_currency": 98,
+ "credit": 98,
+ "is_advance": "Yes",
+ "cost_center": self.south_cc,
+ },
+ {
+ "account": self.cash,
+ "debit_in_account_currency": 98,
+ "debit": 98,
+ },
+ ],
+ }
+ )
+ je2.submit()
+
+ filters = frappe._dict(
+ {
+ "from_date": today(),
+ "to_date": today(),
+ "company": self.company,
+ "include_payments": True,
+ "customer": self.customer,
+ "cost_center": self.cost_center,
+ }
+ )
+ report_output = execute(filters)[1]
+ filtered_output = [x for x in report_output if x.get("voucher_no") == je1.name]
+ self.assertEqual(len(filtered_output), 1)
+ expected_result = {
+ "voucher_type": je1.doctype,
+ "voucher_no": je1.name,
+ "posting_date": je1.posting_date,
+ "customer": self.customer,
+ "receivable_account": self.debit_to,
+ "net_total": 77.0,
+ "credit": 77.0,
+ }
+ result_fields = {k: v for k, v in filtered_output[0].items() if k in expected_result}
+ self.assertDictEqual(result_fields, expected_result)
+
+ filters = frappe._dict(
+ {
+ "from_date": today(),
+ "to_date": today(),
+ "company": self.company,
+ "include_payments": True,
+ "customer": self.customer,
+ "cost_center": self.south_cc,
+ }
+ )
+ report_output = execute(filters)[1]
+ filtered_output = [x for x in report_output if x.get("voucher_no") == je2.name]
+ self.assertEqual(len(filtered_output), 1)
+ expected_result = {
+ "voucher_type": je2.doctype,
+ "voucher_no": je2.name,
+ "posting_date": je2.posting_date,
+ "customer": self.customer,
+ "receivable_account": self.debit_to,
+ "net_total": 98.0,
+ "credit": 98.0,
+ }
+ result_output = {k: v for k, v in filtered_output[0].items() if k in expected_result}
+ self.assertDictEqual(result_output, expected_result)
diff --git a/erpnext/accounts/report/utils.py b/erpnext/accounts/report/utils.py
index 8ecabaf73ec..5d606f648fa 100644
--- a/erpnext/accounts/report/utils.py
+++ b/erpnext/accounts/report/utils.py
@@ -255,7 +255,9 @@ def get_journal_entries(filters, args):
)
.orderby(je.posting_date, je.name, order=Order.desc)
)
- query = apply_common_conditions(filters, query, doctype="Journal Entry", payments=True)
+ query = apply_common_conditions(
+ filters, query, doctype="Journal Entry", child_doctype="Journal Entry Account", payments=True
+ )
journal_entries = query.run(as_dict=True)
return journal_entries
@@ -306,7 +308,9 @@ def apply_common_conditions(filters, query, doctype, child_doctype=None, payment
query = query.where(parent_doc.posting_date <= filters.to_date)
if payments:
- if filters.get("cost_center"):
+ if doctype == "Journal Entry" and filters.get("cost_center"):
+ query = query.where(child_doc.cost_center == filters.cost_center)
+ elif filters.get("cost_center"):
query = query.where(parent_doc.cost_center == filters.cost_center)
else:
if filters.get("cost_center"):
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 705e8fd6dd8..a828c117edb 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -456,9 +456,11 @@ class PurchaseOrder(BuyingController):
if not self.is_against_so():
return
for item in removed_items:
- prev_ordered_qty = frappe.get_cached_value(
- "Sales Order Item", item.get("sales_order_item"), "ordered_qty"
+ prev_ordered_qty = (
+ frappe.get_cached_value("Sales Order Item", item.get("sales_order_item"), "ordered_qty")
+ or 0.0
)
+
frappe.db.set_value(
"Sales Order Item", item.get("sales_order_item"), "ordered_qty", prev_ordered_qty - item.qty
)
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index dd3fab05ce6..e04618c857f 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -91,7 +91,8 @@ status_map = {
],
"Purchase Receipt": [
["Draft", None],
- ["To Bill", "eval:self.per_billed < 100 and self.docstatus == 1"],
+ ["To Bill", "eval:self.per_billed == 0 and self.docstatus == 1"],
+ ["Partly Billed", "eval:self.per_billed > 0 and self.per_billed < 100 and self.docstatus == 1"],
["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
["Cancelled", "eval:self.docstatus==2"],
diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py
index b051494a651..481c64a39c0 100755
--- a/erpnext/projects/doctype/task/task.py
+++ b/erpnext/projects/doctype/task/task.py
@@ -118,14 +118,14 @@ class Task(NestedSet):
def validate_parent_template_task(self):
if self.parent_task:
if not frappe.db.get_value("Task", self.parent_task, "is_template"):
- parent_task_format = f"""{self.parent_task}"""
+ parent_task_format = f"""{self.parent_task}"""
frappe.throw(_("Parent Task {0} is not a Template Task").format(parent_task_format))
def validate_depends_on_tasks(self):
if self.depends_on:
for task in self.depends_on:
if not frappe.db.get_value("Task", task.task, "is_template"):
- dependent_task_format = f"""{task.task}"""
+ dependent_task_format = f"""{task.task}"""
frappe.throw(_("Dependent Task {0} is not a Template Task").format(dependent_task_format))
def validate_completed_on(self):
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 79e14b9eb5b..c3ec62c34da 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -822,9 +822,14 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
if (frappe.meta.get_docfield(this.frm.doctype, "shipping_address") &&
['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'].includes(this.frm.doctype)) {
- erpnext.utils.get_shipping_address(this.frm, function() {
- set_party_account(set_pricing);
- });
+ let is_drop_ship = me.frm.doc.items.some(item => item.delivered_by_supplier);
+
+ if (!is_drop_ship) {
+ console.log('get_shipping_address');
+ erpnext.utils.get_shipping_address(this.frm, function() {
+ set_party_account(set_pricing);
+ });
+ }
} else {
set_party_account(set_pricing);
@@ -2213,7 +2218,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
payment_terms_template() {
var me = this;
const doc = this.frm.doc;
- if(doc.payment_terms_template && doc.doctype !== 'Delivery Note' && doc.is_return == 0) {
+ if(doc.payment_terms_template && doc.doctype !== 'Delivery Note' && !doc.is_return) {
var posting_date = doc.posting_date || doc.transaction_date;
frappe.call({
method: "erpnext.controllers.accounts_controller.get_payment_terms",
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 293066cde38..34f839b563d 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -1035,6 +1035,8 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t
"discount_percentage",
"discount_amount",
"pricing_rules",
+ "margin_type",
+ "margin_rate_or_amount",
],
"postprocess": update_item,
"condition": lambda doc: doc.ordered_qty < doc.stock_qty
@@ -1088,9 +1090,17 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None):
target.payment_schedule = []
if is_drop_ship_order(target):
- target.customer = source.customer
- target.customer_name = source.customer_name
- target.shipping_address = source.shipping_address_name
+ if source.shipping_address_name:
+ target.shipping_address = source.shipping_address_name
+ target.shipping_address_display = source.shipping_address
+ else:
+ target.shipping_address = source.customer_address
+ target.shipping_address_display = source.address_display
+
+ target.customer_contact_person = source.contact_person
+ target.customer_contact_display = source.contact_display
+ target.customer_contact_mobile = source.contact_mobile
+ target.customer_contact_email = source.contact_email
else:
target.customer = target.customer_name = target.shipping_address = None
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index aa25d625ffe..948437a8287 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -61,7 +61,7 @@ class PickList(Document):
"actual_qty",
)
- if row.qty > bin_qty:
+ if row.qty > flt(bin_qty):
frappe.throw(
_(
"At Row #{0}: The picked quantity {1} for the item {2} is greater than available stock {3} in the warehouse {4}."
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index 28c54dab9b2..fabeb74462f 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -885,7 +885,7 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "\nDraft\nTo Bill\nCompleted\nReturn Issued\nCancelled\nClosed",
+ "options": "\nDraft\nPartly Billed\nTo Bill\nCompleted\nReturn Issued\nCancelled\nClosed",
"print_hide": 1,
"print_width": "150px",
"read_only": 1,
@@ -1242,7 +1242,7 @@
"idx": 261,
"is_submittable": 1,
"links": [],
- "modified": "2024-03-20 16:05:31.713453",
+ "modified": "2024-11-13 16:55:14.129055",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt",
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 8ed59f452e2..faf305cfe9c 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -883,6 +883,8 @@ def get_billed_amount_against_po(po_items):
def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate=False):
# Update Billing % based on pending accepted qty
+ buying_settings = frappe.get_single("Buying Settings")
+
total_amount, total_billed_amount = 0, 0
item_wise_returned_qty = get_item_wise_returned_qty(pr_doc)
@@ -890,10 +892,15 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
returned_qty = flt(item_wise_returned_qty.get(item.name))
returned_amount = flt(returned_qty) * flt(item.rate)
pending_amount = flt(item.amount) - returned_amount
- total_billable_amount = pending_amount if item.billed_amt <= pending_amount else item.billed_amt
+ if buying_settings.bill_for_rejected_quantity_in_purchase_invoice:
+ pending_amount = flt(item.amount)
+
+ total_billable_amount = abs(flt(item.amount))
+ if pending_amount > 0:
+ total_billable_amount = pending_amount if item.billed_amt <= pending_amount else item.billed_amt
total_amount += total_billable_amount
- total_billed_amount += flt(item.billed_amt)
+ total_billed_amount += abs(flt(item.billed_amt))
if pr_doc.get("is_return") and not total_amount and total_billed_amount:
total_amount = total_billed_amount
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js
index ddc9bb026fb..fc4aabdaa18 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js
@@ -17,8 +17,10 @@ frappe.listview_settings["Purchase Receipt"] = {
return [__("Closed"), "green", "status,=,Closed"];
} else if (flt(doc.per_returned, 2) === 100) {
return [__("Return Issued"), "grey", "per_returned,=,100"];
- } else if (flt(doc.grand_total) !== 0 && flt(doc.per_billed, 2) < 100) {
+ } else if (flt(doc.grand_total) !== 0 && flt(doc.per_billed, 2) == 0) {
return [__("To Bill"), "orange", "per_billed,<,100"];
+ } else if (flt(doc.per_billed, 2) > 0 && flt(doc.per_billed, 2) < 100) {
+ return [__("Partly Billed"), "yellow", "per_billed,<,100"];
} else if (flt(doc.grand_total) === 0 || flt(doc.per_billed, 2) === 100) {
return [__("Completed"), "green", "per_billed,=,100"];
}
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 1c4feedbcfb..adb6e690596 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -595,7 +595,7 @@ class TestPurchaseReceipt(FrappeTestCase):
pr2.load_from_db()
self.assertEqual(pr2.get("items")[0].billed_amt, 2000)
self.assertEqual(pr2.per_billed, 80)
- self.assertEqual(pr2.status, "To Bill")
+ self.assertEqual(pr2.status, "Partly Billed")
pr2.cancel()
pi2.reload()
@@ -1006,7 +1006,7 @@ class TestPurchaseReceipt(FrappeTestCase):
pi.load_from_db()
pr.load_from_db()
- self.assertEqual(pr.status, "To Bill")
+ self.assertEqual(pr.status, "Partly Billed")
self.assertAlmostEqual(pr.per_billed, 50.0, places=2)
def test_purchase_receipt_with_exchange_rate_difference(self):
@@ -2683,6 +2683,54 @@ class TestPurchaseReceipt(FrappeTestCase):
self.assertEqual(pr.items[0].conversion_factor, 1.0)
+ def test_purchase_return_partial_debit_note(self):
+ pr = make_purchase_receipt(
+ company="_Test Company with perpetual inventory",
+ warehouse="Stores - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
+ )
+
+ return_pr = make_purchase_receipt(
+ company="_Test Company with perpetual inventory",
+ warehouse="Stores - TCP1",
+ supplier_warehouse="Work In Progress - TCP1",
+ is_return=1,
+ return_against=pr.name,
+ qty=-2,
+ do_not_submit=1,
+ )
+ return_pr.items[0].purchase_receipt_item = pr.items[0].name
+ return_pr.submit()
+
+ # because new_doc isn't considering is_return portion of status_updater
+ returned = frappe.get_doc("Purchase Receipt", return_pr.name)
+ returned.update_prevdoc_status()
+ pr.load_from_db()
+
+ # Check if Original PR updated
+ self.assertEqual(pr.items[0].returned_qty, 2)
+ self.assertEqual(pr.per_returned, 40)
+
+ # Create first partial debit_note
+ pi_1 = make_purchase_invoice(return_pr.name)
+ pi_1.items[0].qty = -1
+ pi_1.submit()
+
+ # Check if the first partial debit billing percentage got updated
+ return_pr.reload()
+ self.assertEqual(return_pr.per_billed, 50)
+ self.assertEqual(return_pr.status, "Partly Billed")
+
+ # Create second partial debit_note to complete the debit note
+ pi_2 = make_purchase_invoice(return_pr.name)
+ pi_2.items[0].qty = -1
+ pi_2.submit()
+
+ # Check if the second partial debit note billing percentage got updated
+ return_pr.reload()
+ self.assertEqual(return_pr.per_billed, 100)
+ self.assertEqual(return_pr.status, "Completed")
+
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier