From 77e92b38ebf5efc57710fde8eaa3187962c3ca2e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Patrick=20Ei=C3=9Fler?=
<77415730+PatrickDEissler@users.noreply.github.com>
Date: Fri, 17 Jan 2025 12:17:32 +0100
Subject: [PATCH 01/16] fix(Project): re-phrase welcome email (#45175)
(cherry picked from commit 8d661428651fa1ce47f5f3b217cfadad47e060a6)
# Conflicts:
# erpnext/projects/doctype/project/project.py
---
erpnext/projects/doctype/project/project.py | 24 +++++++++++----------
1 file changed, 13 insertions(+), 11 deletions(-)
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index 29a288ef671..c88dc3780d0 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -7,8 +7,15 @@ from email_reply_parser import EmailReplyParser
from frappe import _, qb
from frappe.desk.reportview import get_match_cond
from frappe.model.document import Document
+<<<<<<< HEAD
from frappe.query_builder.functions import Sum
from frappe.utils import add_days, flt, get_datetime, get_time, get_url, nowtime, today
+=======
+from frappe.query_builder import Interval
+from frappe.query_builder.functions import Count, CurDate, Date, Sum, UnixTimestamp
+from frappe.utils import add_days, flt, get_datetime, get_link_to_form, get_time, get_url, nowtime, today
+from frappe.utils.user import is_website_user
+>>>>>>> 8d66142865 (fix(Project): re-phrase welcome email (#45175))
from erpnext import get_default_company
from erpnext.controllers.queries import get_filters_cond
@@ -275,24 +282,19 @@ class Project(Document):
frappe.db.set_value("Project", new_name, "copied_from", new_name)
def send_welcome_email(self):
- url = get_url(f"/project/?name={self.name}")
- messages = (
- _("You have been invited to collaborate on the project: {0}").format(self.name),
- url,
- _("Join"),
- )
+ label = f"{self.project_name} ({self.name})"
+ url = get_link_to_form(self.doctype, self.name, label)
- content = """
-
{0}.
- {2}
- """
+ content = "{}
".format(
+ _("You have been invited to collaborate on the project {0}.").format(url)
+ )
for user in self.users:
if user.welcome_email_sent == 0:
frappe.sendmail(
user.user,
subject=_("Project Collaboration Invitation"),
- content=content.format(*messages),
+ content=content,
)
user.welcome_email_sent = 1
From 667e659e3f660bb0a94ea198c9e61671e58bce08 Mon Sep 17 00:00:00 2001
From: Lakshit Jain <108322669+ljain112@users.noreply.github.com>
Date: Fri, 17 Jan 2025 16:50:15 +0530
Subject: [PATCH 02/16] fix: round off tax withholding amount (#45271)
(cherry picked from commit ada272a29bf5b0c1ab2115a3d72b3d8a80bec11a)
---
.../tax_withholding_category/tax_withholding_category.py | 3 +++
erpnext/controllers/taxes_and_totals.py | 5 +++++
2 files changed, 8 insertions(+)
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
index dc8a34647e2..2115d44322d 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -124,6 +124,9 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
cost_center = get_cost_center(inv)
tax_row.update({"cost_center": cost_center})
+ if cint(tax_details.round_off_tax_amount):
+ inv.round_off_applicable_accounts_for_tax_withholding = tax_details.account_head
+
if inv.doctype == "Purchase Invoice":
return tax_row, tax_deducted_on_advances, voucher_wise_amount
else:
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 5b6a7b1506b..f98fdac3a6a 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -27,6 +27,11 @@ class calculate_taxes_and_totals:
self.doc = doc
frappe.flags.round_off_applicable_accounts = []
+ if doc.get("round_off_applicable_accounts_for_tax_withholding"):
+ frappe.flags.round_off_applicable_accounts.append(
+ doc.round_off_applicable_accounts_for_tax_withholding
+ )
+
self._items = self.filter_rows() if self.doc.doctype == "Quotation" else self.doc.get("items")
get_round_off_applicable_accounts(self.doc.company, frappe.flags.round_off_applicable_accounts)
From 524a8d77f75dade436043fbf344631262ca25eb4 Mon Sep 17 00:00:00 2001
From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com>
Date: Fri, 17 Jan 2025 17:02:34 +0530
Subject: [PATCH 03/16] fix: pos search by term items price (backport #45006)
(#45102)
* fix: load price list rate for pos search term
(cherry picked from commit 4b6cae156e2ece905158eaa649a9812d92bc87dd)
# Conflicts:
# erpnext/selling/page/point_of_sale/point_of_sale.py
* fix: load search term price with customer default price list
(cherry picked from commit 2beb485d7753e8b0cf399cf9025ea349a5857a3e)
* chore: resolve conflict
---------
Co-authored-by: diptanilsaha
Co-authored-by: ruthra kumar
---
.../selling/page/point_of_sale/point_of_sale.py | 15 ++++++++++-----
.../page/point_of_sale/pos_item_selector.js | 9 ++++++---
2 files changed, 16 insertions(+), 8 deletions(-)
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py
index bc94d3b706e..7db9faacca2 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -51,13 +51,18 @@ def search_by_term(search_term, warehouse, price_list):
item_stock_qty = item_stock_qty // item.get("conversion_factor", 1)
item.update({"actual_qty": item_stock_qty})
+ price_filters = {
+ "price_list": price_list,
+ "item_code": item_code,
+ }
+
+ if batch_no:
+ price_filters["batch_no"] = batch_no
+
price = frappe.get_list(
doctype="Item Price",
- filters={
- "price_list": price_list,
- "item_code": item_code,
- },
- fields=["uom", "currency", "price_list_rate"],
+ filters=price_filters,
+ fields=["uom", "currency", "price_list_rate", "batch_no"],
)
def __sort(p):
diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js
index dba36bd2429..7021777d158 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_selector.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js
@@ -325,13 +325,16 @@ erpnext.PointOfSale.ItemSelector = class {
}
filter_items({ search_term = "" } = {}) {
+ const selling_price_list = this.events.get_frm().doc.selling_price_list;
+
if (search_term) {
search_term = search_term.toLowerCase();
// memoize
this.search_index = this.search_index || {};
- if (this.search_index[search_term]) {
- const items = this.search_index[search_term];
+ this.search_index[selling_price_list] = this.search_index[selling_price_list] || {};
+ if (this.search_index[selling_price_list][search_term]) {
+ const items = this.search_index[selling_price_list][search_term];
this.items = items;
this.render_item_list(items);
this.auto_add_item && this.items.length == 1 && this.add_filtered_item_to_cart();
@@ -343,7 +346,7 @@ erpnext.PointOfSale.ItemSelector = class {
// eslint-disable-next-line no-unused-vars
const { items, serial_no, batch_no, barcode } = message;
if (search_term && !barcode) {
- this.search_index[search_term] = items;
+ this.search_index[selling_price_list][search_term] = items;
}
this.items = items;
this.render_item_list(items);
From 1ccf30d97b2c8b92346bb3d73d4adbad6ab4b621 Mon Sep 17 00:00:00 2001
From: barredterra <14891507+barredterra@users.noreply.github.com>
Date: Fri, 17 Jan 2025 14:38:03 +0100
Subject: [PATCH 04/16] chore: resolve confilcts
---
erpnext/projects/doctype/project/project.py | 9 +--------
1 file changed, 1 insertion(+), 8 deletions(-)
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index c88dc3780d0..6ea98c1baf5 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -7,15 +7,8 @@ from email_reply_parser import EmailReplyParser
from frappe import _, qb
from frappe.desk.reportview import get_match_cond
from frappe.model.document import Document
-<<<<<<< HEAD
from frappe.query_builder.functions import Sum
-from frappe.utils import add_days, flt, get_datetime, get_time, get_url, nowtime, today
-=======
-from frappe.query_builder import Interval
-from frappe.query_builder.functions import Count, CurDate, Date, Sum, UnixTimestamp
-from frappe.utils import add_days, flt, get_datetime, get_link_to_form, get_time, get_url, nowtime, today
-from frappe.utils.user import is_website_user
->>>>>>> 8d66142865 (fix(Project): re-phrase welcome email (#45175))
+from frappe.utils import add_days, flt, get_datetime, get_link_to_form, get_time, nowtime, today
from erpnext import get_default_company
from erpnext.controllers.queries import get_filters_cond
From 20bb15167dde06950da04679cf38ac8b58b4ed1f Mon Sep 17 00:00:00 2001
From: barredterra <14891507+barredterra@users.noreply.github.com>
Date: Fri, 17 Jan 2025 14:34:44 +0100
Subject: [PATCH 05/16] revert: avoid change to translatable string
---
erpnext/projects/doctype/project/project.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index 6ea98c1baf5..13fad07cb4a 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -279,7 +279,7 @@ class Project(Document):
url = get_link_to_form(self.doctype, self.name, label)
content = "{}
".format(
- _("You have been invited to collaborate on the project {0}.").format(url)
+ _("You have been invited to collaborate on the project: {0}").format(url)
)
for user in self.users:
From fe5de302565fa00f75838af535e08d2245e1b371 Mon Sep 17 00:00:00 2001
From: Rohit Waghchaure
Date: Wed, 18 Dec 2024 20:53:56 +0530
Subject: [PATCH 06/16] fix: do not reset picked items
(cherry picked from commit 34a80bfcd30b4f03be5886a286b2c8056a9931e1)
# Conflicts:
# erpnext/stock/doctype/pick_list/test_pick_list.py
---
erpnext/stock/doctype/pick_list/pick_list.py | 39 ++-
.../stock/doctype/pick_list/test_pick_list.py | 262 ++++++++++++++++++
2 files changed, 291 insertions(+), 10 deletions(-)
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 2176d75ee97..a2a07e6d11e 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -26,9 +26,8 @@ from erpnext.stock.get_item_details import get_conversion_factor
class PickList(Document):
def validate(self):
self.validate_for_qty()
- if self.pick_manually and self.get("locations"):
- self.validate_stock_qty()
- self.check_serial_no_status()
+ self.validate_stock_qty()
+ self.check_serial_no_status()
def before_save(self):
self.update_status()
@@ -42,14 +41,24 @@ class PickList(Document):
from erpnext.stock.doctype.batch.batch import get_batch_qty
for row in self.get("locations"):
- if row.batch_no and not row.qty:
+ if not row.picked_qty:
+ continue
+
+ if row.batch_no and row.picked_qty:
batch_qty = get_batch_qty(row.batch_no, row.warehouse, row.item_code)
- if row.qty > batch_qty:
+ if row.picked_qty > batch_qty:
frappe.throw(
_(
- "At Row #{0}: The picked quantity {1} for the item {2} is greater than available stock {3} for the batch {4} in the warehouse {5}."
- ).format(row.idx, row.item_code, batch_qty, row.batch_no, bold(row.warehouse)),
+ "At Row #{0}: The picked quantity {1} for the item {2} is greater than available stock {3} for the batch {4} in the warehouse {5}. Please restock the item."
+ ).format(
+ row.idx,
+ row.picked_qty,
+ row.item_code,
+ batch_qty,
+ row.batch_no,
+ bold(row.warehouse),
+ ),
title=_("Insufficient Stock"),
)
@@ -61,11 +70,11 @@ class PickList(Document):
"actual_qty",
)
- if row.qty > flt(bin_qty):
+ if row.picked_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}."
- ).format(row.idx, row.qty, bold(row.item_code), bin_qty, bold(row.warehouse)),
+ ).format(row.idx, row.picked_qty, bold(row.item_code), bin_qty, bold(row.warehouse)),
title=_("Insufficient Stock"),
)
@@ -253,7 +262,14 @@ class PickList(Document):
locations_replica = self.get("locations")
# reset
- self.delete_key("locations")
+ reset_rows = []
+ for row in self.get("locations"):
+ if not row.picked_qty:
+ reset_rows.append(row)
+
+ for row in reset_rows:
+ self.remove(row)
+
updated_locations = frappe._dict()
for item_doc in items:
item_code = item_doc.item_code
@@ -323,6 +339,9 @@ class PickList(Document):
# aggregate qty for same item
item_map = OrderedDict()
for item in locations:
+ if item.picked_qty:
+ continue
+
if not item.item_code:
frappe.throw(f"Row #{item.idx}: Item Code is Mandatory")
if not cint(
diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py
index 162fcadde7a..2fe80a3245c 100644
--- a/erpnext/stock/doctype/pick_list/test_pick_list.py
+++ b/erpnext/stock/doctype/pick_list/test_pick_list.py
@@ -725,8 +725,14 @@ class TestPickList(FrappeTestCase):
for item in items
]
+<<<<<<< HEAD
def get_picked_items_details(pick_list_doc):
items_data = {}
+=======
+ so = make_sales_order(item_code=item, qty=4, rate=100)
+ pl = create_pick_list(so.name)
+ self.assertFalse(pl.locations)
+>>>>>>> 34a80bfcd3 (fix: do not reset picked items)
for location in pick_list_doc.locations:
key = (location.warehouse, location.batch_no) if location.batch_no else location.warehouse
@@ -741,6 +747,7 @@ class TestPickList(FrappeTestCase):
return items_data
+<<<<<<< HEAD
# Step - 1: Setup - Create Items and Stock Entries
items_properties = [
{
@@ -748,9 +755,40 @@ class TestPickList(FrappeTestCase):
},
{
"valuation_rate": 200,
+=======
+ so = make_sales_order(item_code=item, qty=5, rate=100)
+
+ pl = create_pick_list(so.name)
+ pl.locations[0].qty = 5
+ pl.save()
+ pl.submit()
+ self.assertTrue(pl.locations[0].serial_no)
+ self.assertEqual(pl.locations[0].qty, 5.0)
+ self.assertTrue(hasattr(pl, "locations"))
+
+ so = make_sales_order(item_code=item, qty=5, rate=100)
+
+ pl = create_pick_list(so.name)
+ pl.save()
+ self.assertTrue(pl.locations[0].serial_no)
+ self.assertEqual(pl.locations[0].qty, 5.0)
+ self.assertTrue(hasattr(pl, "locations"))
+
+ so = make_sales_order(item_code=item, qty=4, rate=100)
+ pl = create_pick_list(so.name)
+ self.assertFalse(pl.locations)
+
+ def test_pick_list_validation_for_batch_no(self):
+ warehouse = "_Test Warehouse - _TC"
+ item = make_item(
+ "Test Batch Pick List Item",
+ properties={
+ "is_stock_item": 1,
+>>>>>>> 34a80bfcd3 (fix: do not reset picked items)
"has_batch_no": 1,
"create_new_batch": 1,
},
+<<<<<<< HEAD
{
"valuation_rate": 300,
"has_serial_no": 1,
@@ -758,6 +796,40 @@ class TestPickList(FrappeTestCase):
},
{
"valuation_rate": 400,
+=======
+ ).name
+
+ make_stock_entry(item=item, to_warehouse=warehouse, qty=10)
+
+ so = make_sales_order(item_code=item, qty=5, rate=100)
+
+ pl = create_pick_list(so.name)
+ pl.locations[0].qty = 5
+ pl.save()
+ pl.submit()
+ self.assertTrue(pl.locations[0].batch_no)
+ self.assertEqual(pl.locations[0].qty, 5.0)
+ self.assertTrue(hasattr(pl, "locations"))
+
+ so = make_sales_order(item_code=item, qty=5, rate=100)
+
+ pl = create_pick_list(so.name)
+ pl.save()
+ self.assertTrue(pl.locations[0].batch_no)
+ self.assertEqual(pl.locations[0].qty, 5.0)
+ self.assertTrue(hasattr(pl, "locations"))
+
+ so = make_sales_order(item_code=item, qty=4, rate=100)
+ pl = create_pick_list(so.name)
+ self.assertFalse(pl.locations)
+
+ def test_pick_list_validation_for_batch_no_and_serial_item(self):
+ warehouse = "_Test Warehouse - _TC"
+ item = make_item(
+ "Test Serialized Batch Pick List Item",
+ properties={
+ "is_stock_item": 1,
+>>>>>>> 34a80bfcd3 (fix: do not reset picked items)
"has_batch_no": 1,
"create_new_batch": 1,
"has_serial_no": 1,
@@ -768,8 +840,63 @@ class TestPickList(FrappeTestCase):
items = create_items(items_properties)
create_stock_entries(items)
+<<<<<<< HEAD
# Step - 2: Create Sales Order [1]
so1 = make_sales_order(item_list=get_item_list(items, qty=6))
+=======
+ so = make_sales_order(item_code=item, qty=5, rate=100)
+
+ pl = create_pick_list(so.name)
+ pl.locations[0].qty = 5
+ pl.save()
+ pl.submit()
+ self.assertTrue(pl.locations[0].batch_no)
+ self.assertTrue(pl.locations[0].serial_no)
+ self.assertEqual(pl.locations[0].qty, 5.0)
+ self.assertTrue(hasattr(pl, "locations"))
+
+ so = make_sales_order(item_code=item, qty=5, rate=100)
+
+ pl = create_pick_list(so.name)
+ pl.save()
+ self.assertTrue(pl.locations[0].batch_no)
+ self.assertTrue(pl.locations[0].serial_no)
+ self.assertEqual(pl.locations[0].qty, 5.0)
+ self.assertTrue(hasattr(pl, "locations"))
+
+ so = make_sales_order(item_code=item, qty=4, rate=100)
+ pl = create_pick_list(so.name)
+ self.assertFalse(pl.locations)
+
+ def test_pick_list_validation_for_multiple_batches_and_sales_order(self):
+ warehouse = "_Test Warehouse - _TC"
+ item = make_item(
+ "Test Batch Pick List Item For Multiple Batches",
+ properties={
+ "is_stock_item": 1,
+ "has_batch_no": 1,
+ "batch_number_series": "SN-BT-BATCH-SPLIMBATCH-.####",
+ "create_new_batch": 1,
+ },
+ ).name
+
+ make_stock_entry(item=item, to_warehouse=warehouse, qty=5)
+ make_stock_entry(item=item, to_warehouse=warehouse, qty=5)
+
+ so = make_sales_order(item_code=item, qty=6, rate=100)
+
+ pl1 = create_pick_list(so.name)
+ pl1.save()
+ self.assertEqual(pl1.locations[0].qty, 5.0)
+ self.assertEqual(pl1.locations[1].qty, 1.0)
+
+ so = make_sales_order(item_code=item, qty=4, rate=100)
+
+ pl = create_pick_list(so.name)
+ pl.save()
+ self.assertEqual(pl.locations[0].qty, 4.0)
+ self.assertTrue(hasattr(pl, "locations"))
+>>>>>>> 34a80bfcd3 (fix: do not reset picked items)
# Step - 3: Create and Submit Pick List [1] for Sales Order [1]
pl1 = create_pick_list(so1.name)
@@ -842,5 +969,140 @@ class TestPickList(FrappeTestCase):
for row in pl.locations:
row.qty = row.qty + 10
+ row.picked_qty = row.qty
self.assertRaises(frappe.ValidationError, pl.save)
+<<<<<<< HEAD
+=======
+
+ def test_over_allowance_picking(self):
+ warehouse = "_Test Warehouse - _TC"
+ item = make_item(
+ "Test Over Allowance Picking Item",
+ properties={
+ "is_stock_item": 1,
+ },
+ ).name
+
+ make_stock_entry(item=item, to_warehouse=warehouse, qty=100)
+
+ so = make_sales_order(item_code=item, qty=10, rate=100)
+
+ pl_doc = create_pick_list(so.name)
+ pl_doc.save()
+ self.assertEqual(pl_doc.locations[0].qty, 10)
+
+ pl_doc.locations[0].qty = 15
+ pl_doc.locations[0].stock_qty = 15
+ pl_doc.save()
+
+ self.assertEqual(pl_doc.locations[0].qty, 15)
+ self.assertRaises(frappe.ValidationError, pl_doc.submit)
+
+ frappe.db.set_single_value("Stock Settings", "over_picking_allowance", 50)
+
+ pl_doc.reload()
+ pl_doc.submit()
+
+ frappe.db.set_single_value("Stock Settings", "over_picking_allowance", 0)
+
+ def test_ignore_pricing_rule_in_pick_list(self):
+ frappe.flags.print_stmt = False
+ warehouse = "_Test Warehouse - _TC"
+ item = make_item(
+ properties={
+ "is_stock_item": 1,
+ "has_batch_no": 1,
+ "batch_number_series": "IPR-PICKLT-.######",
+ "create_new_batch": 1,
+ }
+ ).name
+
+ make_stock_entry(
+ item=item,
+ to_warehouse=warehouse,
+ qty=2,
+ basic_rate=100,
+ )
+
+ pricing_rule = frappe.get_doc(
+ {
+ "doctype": "Pricing Rule",
+ "title": "Same Free Item",
+ "price_or_product_discount": "Product",
+ "selling": 1,
+ "apply_on": "Item Code",
+ "items": [
+ {
+ "item_code": item,
+ }
+ ],
+ "same_item": 1,
+ "is_recursive": 1,
+ "recurse_for": 2,
+ "free_qty": 1,
+ "company": "_Test Company",
+ "customer": "_Test Customer",
+ }
+ )
+
+ pricing_rule.save()
+ frappe.flags.print_stmt = True
+
+ so = make_sales_order(item_code=item, qty=2, rate=100, do_not_save=True)
+ so.set_warehouse = warehouse
+ so.submit()
+
+ self.assertEqual(len(so.items), 2)
+ self.assertTrue(so.items[1].is_free_item)
+
+ pl = create_pick_list(so.name)
+ pl.ignore_pricing_rule = 1
+ pl.save()
+ pl.submit()
+
+ self.assertEqual(len(pl.locations), 1)
+
+ delivery_note = create_delivery_note(pl.name)
+
+ self.assertEqual(len(delivery_note.items), 1)
+
+ def test_pick_list_not_reset_batch(self):
+ warehouse = "_Test Warehouse - _TC"
+ item = make_item(
+ "Test Do Not Reset Picked Item",
+ properties={
+ "is_stock_item": 1,
+ "has_batch_no": 1,
+ "create_new_batch": 1,
+ "batch_number_series": "BTH-PICKLT-.######",
+ },
+ ).name
+
+ se = make_stock_entry(item=item, to_warehouse=warehouse, qty=10)
+ batch1 = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
+ se = make_stock_entry(item=item, to_warehouse=warehouse, qty=10)
+ batch2 = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
+
+ so = make_sales_order(item_code=item, qty=10, rate=100)
+
+ pl = create_pick_list(so.name)
+ pl.save()
+
+ for loc in pl.locations:
+ self.assertEqual(loc.batch_no, batch1)
+ loc.batch_no = batch2
+ loc.picked_qty = 0.0
+
+ pl.save()
+
+ for loc in pl.locations:
+ self.assertEqual(loc.batch_no, batch1)
+ loc.batch_no = batch2
+ loc.picked_qty = 10.0
+
+ pl.save()
+
+ for loc in pl.locations:
+ self.assertEqual(loc.batch_no, batch2)
+>>>>>>> 34a80bfcd3 (fix: do not reset picked items)
From 0b8cf3a369f8e98b04d14edf5345ac1d843b3365 Mon Sep 17 00:00:00 2001
From: rohitwaghchaure
Date: Sun, 19 Jan 2025 13:52:49 +0530
Subject: [PATCH 07/16] chore: fix conflicts
---
.../stock/doctype/pick_list/test_pick_list.py | 222 ------------------
1 file changed, 222 deletions(-)
diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py
index 2fe80a3245c..259290e0654 100644
--- a/erpnext/stock/doctype/pick_list/test_pick_list.py
+++ b/erpnext/stock/doctype/pick_list/test_pick_list.py
@@ -725,14 +725,8 @@ class TestPickList(FrappeTestCase):
for item in items
]
-<<<<<<< HEAD
def get_picked_items_details(pick_list_doc):
items_data = {}
-=======
- so = make_sales_order(item_code=item, qty=4, rate=100)
- pl = create_pick_list(so.name)
- self.assertFalse(pl.locations)
->>>>>>> 34a80bfcd3 (fix: do not reset picked items)
for location in pick_list_doc.locations:
key = (location.warehouse, location.batch_no) if location.batch_no else location.warehouse
@@ -747,7 +741,6 @@ class TestPickList(FrappeTestCase):
return items_data
-<<<<<<< HEAD
# Step - 1: Setup - Create Items and Stock Entries
items_properties = [
{
@@ -755,40 +748,9 @@ class TestPickList(FrappeTestCase):
},
{
"valuation_rate": 200,
-=======
- so = make_sales_order(item_code=item, qty=5, rate=100)
-
- pl = create_pick_list(so.name)
- pl.locations[0].qty = 5
- pl.save()
- pl.submit()
- self.assertTrue(pl.locations[0].serial_no)
- self.assertEqual(pl.locations[0].qty, 5.0)
- self.assertTrue(hasattr(pl, "locations"))
-
- so = make_sales_order(item_code=item, qty=5, rate=100)
-
- pl = create_pick_list(so.name)
- pl.save()
- self.assertTrue(pl.locations[0].serial_no)
- self.assertEqual(pl.locations[0].qty, 5.0)
- self.assertTrue(hasattr(pl, "locations"))
-
- so = make_sales_order(item_code=item, qty=4, rate=100)
- pl = create_pick_list(so.name)
- self.assertFalse(pl.locations)
-
- def test_pick_list_validation_for_batch_no(self):
- warehouse = "_Test Warehouse - _TC"
- item = make_item(
- "Test Batch Pick List Item",
- properties={
- "is_stock_item": 1,
->>>>>>> 34a80bfcd3 (fix: do not reset picked items)
"has_batch_no": 1,
"create_new_batch": 1,
},
-<<<<<<< HEAD
{
"valuation_rate": 300,
"has_serial_no": 1,
@@ -796,40 +758,6 @@ class TestPickList(FrappeTestCase):
},
{
"valuation_rate": 400,
-=======
- ).name
-
- make_stock_entry(item=item, to_warehouse=warehouse, qty=10)
-
- so = make_sales_order(item_code=item, qty=5, rate=100)
-
- pl = create_pick_list(so.name)
- pl.locations[0].qty = 5
- pl.save()
- pl.submit()
- self.assertTrue(pl.locations[0].batch_no)
- self.assertEqual(pl.locations[0].qty, 5.0)
- self.assertTrue(hasattr(pl, "locations"))
-
- so = make_sales_order(item_code=item, qty=5, rate=100)
-
- pl = create_pick_list(so.name)
- pl.save()
- self.assertTrue(pl.locations[0].batch_no)
- self.assertEqual(pl.locations[0].qty, 5.0)
- self.assertTrue(hasattr(pl, "locations"))
-
- so = make_sales_order(item_code=item, qty=4, rate=100)
- pl = create_pick_list(so.name)
- self.assertFalse(pl.locations)
-
- def test_pick_list_validation_for_batch_no_and_serial_item(self):
- warehouse = "_Test Warehouse - _TC"
- item = make_item(
- "Test Serialized Batch Pick List Item",
- properties={
- "is_stock_item": 1,
->>>>>>> 34a80bfcd3 (fix: do not reset picked items)
"has_batch_no": 1,
"create_new_batch": 1,
"has_serial_no": 1,
@@ -840,63 +768,8 @@ class TestPickList(FrappeTestCase):
items = create_items(items_properties)
create_stock_entries(items)
-<<<<<<< HEAD
# Step - 2: Create Sales Order [1]
so1 = make_sales_order(item_list=get_item_list(items, qty=6))
-=======
- so = make_sales_order(item_code=item, qty=5, rate=100)
-
- pl = create_pick_list(so.name)
- pl.locations[0].qty = 5
- pl.save()
- pl.submit()
- self.assertTrue(pl.locations[0].batch_no)
- self.assertTrue(pl.locations[0].serial_no)
- self.assertEqual(pl.locations[0].qty, 5.0)
- self.assertTrue(hasattr(pl, "locations"))
-
- so = make_sales_order(item_code=item, qty=5, rate=100)
-
- pl = create_pick_list(so.name)
- pl.save()
- self.assertTrue(pl.locations[0].batch_no)
- self.assertTrue(pl.locations[0].serial_no)
- self.assertEqual(pl.locations[0].qty, 5.0)
- self.assertTrue(hasattr(pl, "locations"))
-
- so = make_sales_order(item_code=item, qty=4, rate=100)
- pl = create_pick_list(so.name)
- self.assertFalse(pl.locations)
-
- def test_pick_list_validation_for_multiple_batches_and_sales_order(self):
- warehouse = "_Test Warehouse - _TC"
- item = make_item(
- "Test Batch Pick List Item For Multiple Batches",
- properties={
- "is_stock_item": 1,
- "has_batch_no": 1,
- "batch_number_series": "SN-BT-BATCH-SPLIMBATCH-.####",
- "create_new_batch": 1,
- },
- ).name
-
- make_stock_entry(item=item, to_warehouse=warehouse, qty=5)
- make_stock_entry(item=item, to_warehouse=warehouse, qty=5)
-
- so = make_sales_order(item_code=item, qty=6, rate=100)
-
- pl1 = create_pick_list(so.name)
- pl1.save()
- self.assertEqual(pl1.locations[0].qty, 5.0)
- self.assertEqual(pl1.locations[1].qty, 1.0)
-
- so = make_sales_order(item_code=item, qty=4, rate=100)
-
- pl = create_pick_list(so.name)
- pl.save()
- self.assertEqual(pl.locations[0].qty, 4.0)
- self.assertTrue(hasattr(pl, "locations"))
->>>>>>> 34a80bfcd3 (fix: do not reset picked items)
# Step - 3: Create and Submit Pick List [1] for Sales Order [1]
pl1 = create_pick_list(so1.name)
@@ -972,100 +845,6 @@ class TestPickList(FrappeTestCase):
row.picked_qty = row.qty
self.assertRaises(frappe.ValidationError, pl.save)
-<<<<<<< HEAD
-=======
-
- def test_over_allowance_picking(self):
- warehouse = "_Test Warehouse - _TC"
- item = make_item(
- "Test Over Allowance Picking Item",
- properties={
- "is_stock_item": 1,
- },
- ).name
-
- make_stock_entry(item=item, to_warehouse=warehouse, qty=100)
-
- so = make_sales_order(item_code=item, qty=10, rate=100)
-
- pl_doc = create_pick_list(so.name)
- pl_doc.save()
- self.assertEqual(pl_doc.locations[0].qty, 10)
-
- pl_doc.locations[0].qty = 15
- pl_doc.locations[0].stock_qty = 15
- pl_doc.save()
-
- self.assertEqual(pl_doc.locations[0].qty, 15)
- self.assertRaises(frappe.ValidationError, pl_doc.submit)
-
- frappe.db.set_single_value("Stock Settings", "over_picking_allowance", 50)
-
- pl_doc.reload()
- pl_doc.submit()
-
- frappe.db.set_single_value("Stock Settings", "over_picking_allowance", 0)
-
- def test_ignore_pricing_rule_in_pick_list(self):
- frappe.flags.print_stmt = False
- warehouse = "_Test Warehouse - _TC"
- item = make_item(
- properties={
- "is_stock_item": 1,
- "has_batch_no": 1,
- "batch_number_series": "IPR-PICKLT-.######",
- "create_new_batch": 1,
- }
- ).name
-
- make_stock_entry(
- item=item,
- to_warehouse=warehouse,
- qty=2,
- basic_rate=100,
- )
-
- pricing_rule = frappe.get_doc(
- {
- "doctype": "Pricing Rule",
- "title": "Same Free Item",
- "price_or_product_discount": "Product",
- "selling": 1,
- "apply_on": "Item Code",
- "items": [
- {
- "item_code": item,
- }
- ],
- "same_item": 1,
- "is_recursive": 1,
- "recurse_for": 2,
- "free_qty": 1,
- "company": "_Test Company",
- "customer": "_Test Customer",
- }
- )
-
- pricing_rule.save()
- frappe.flags.print_stmt = True
-
- so = make_sales_order(item_code=item, qty=2, rate=100, do_not_save=True)
- so.set_warehouse = warehouse
- so.submit()
-
- self.assertEqual(len(so.items), 2)
- self.assertTrue(so.items[1].is_free_item)
-
- pl = create_pick_list(so.name)
- pl.ignore_pricing_rule = 1
- pl.save()
- pl.submit()
-
- self.assertEqual(len(pl.locations), 1)
-
- delivery_note = create_delivery_note(pl.name)
-
- self.assertEqual(len(delivery_note.items), 1)
def test_pick_list_not_reset_batch(self):
warehouse = "_Test Warehouse - _TC"
@@ -1105,4 +884,3 @@ class TestPickList(FrappeTestCase):
for loc in pl.locations:
self.assertEqual(loc.batch_no, batch2)
->>>>>>> 34a80bfcd3 (fix: do not reset picked items)
From 7046a019216a0b721ee771c34a7b75ed15276fa3 Mon Sep 17 00:00:00 2001
From: rohitwaghchaure
Date: Sun, 19 Jan 2025 14:22:52 +0530
Subject: [PATCH 08/16] chore: fix test case
---
erpnext/stock/doctype/pick_list/test_pick_list.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py
index 259290e0654..f679d28c2b2 100644
--- a/erpnext/stock/doctype/pick_list/test_pick_list.py
+++ b/erpnext/stock/doctype/pick_list/test_pick_list.py
@@ -859,9 +859,11 @@ class TestPickList(FrappeTestCase):
).name
se = make_stock_entry(item=item, to_warehouse=warehouse, qty=10)
- batch1 = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
+ se.reload()
+ batch1 = se.items[0].batch_no
se = make_stock_entry(item=item, to_warehouse=warehouse, qty=10)
- batch2 = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
+ se.reload()
+ batch2 = se.items[0].batch_no
so = make_sales_order(item_code=item, qty=10, rate=100)
From f9420db3ca607e76dac4221c0ddf8b2b3e916269 Mon Sep 17 00:00:00 2001
From: Sudharsanan11
Date: Wed, 8 Jan 2025 13:51:33 +0530
Subject: [PATCH 09/16] fix: validate linked sales person
(cherry picked from commit e614f0779553005d4b7947b8a40aaffad0159816)
---
.../doctype/sales_person/sales_person.py | 23 +++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/erpnext/setup/doctype/sales_person/sales_person.py b/erpnext/setup/doctype/sales_person/sales_person.py
index 91a7008fabd..8af5e4aeb09 100644
--- a/erpnext/setup/doctype/sales_person/sales_person.py
+++ b/erpnext/setup/doctype/sales_person/sales_person.py
@@ -5,6 +5,7 @@
import frappe
from frappe import _
from frappe.utils import flt
+from frappe.utils.data import get_url_to_list
from frappe.utils.nestedset import NestedSet, get_root_of
from erpnext import get_default_currency
@@ -14,6 +15,9 @@ class SalesPerson(NestedSet):
nsm_parent_field = "parent_sales_person"
def validate(self):
+ if not self.enabled:
+ self.validate_sales_person()
+
if not self.parent_sales_person:
self.parent_sales_person = get_root_of("Sales Person")
@@ -55,6 +59,25 @@ class SalesPerson(NestedSet):
super().on_update()
self.validate_one_root()
+ def validate_sales_person(self):
+ sales_team = frappe.qb.DocType("Sales Team")
+
+ query = (
+ frappe.qb.from_(sales_team)
+ .select(sales_team.sales_person)
+ .where((sales_team.sales_person == self.name) & (sales_team.parenttype == "Customer"))
+ .groupby(sales_team.sales_person)
+ ).run(as_dict=True)
+
+ if query:
+ frappe.throw(
+ _("The Sales Person is linked with {0}").format(
+ frappe.bold(
+ f"""{"Customers"}"""
+ )
+ )
+ )
+
def get_email_id(self):
if self.employee:
user = frappe.db.get_value("Employee", self.employee, "user_id")
From 17535095e23f0b4859c6dd813be4e317b4683093 Mon Sep 17 00:00:00 2001
From: rs-rethik
Date: Fri, 20 Dec 2024 12:07:20 +0530
Subject: [PATCH 10/16] feat: add difference_posting_date field
(cherry picked from commit 225e56cbcae5c30172b52aa2e6cbc04a5ec01d43)
# Conflicts:
# erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json
# erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.py
# erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json
# erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.py
---
.../purchase_invoice_advance.json | 18 ++++++++++---
.../purchase_invoice_advance.py | 25 +++++++++++++++++++
.../sales_invoice_advance.json | 18 ++++++++++---
.../sales_invoice_advance.py | 25 +++++++++++++++++++
4 files changed, 80 insertions(+), 6 deletions(-)
diff --git a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json
index 9fcbf5c6339..2f6d30c0922 100644
--- a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json
+++ b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json
@@ -14,7 +14,8 @@
"advance_amount",
"allocated_amount",
"exchange_gain_loss",
- "ref_exchange_rate"
+ "ref_exchange_rate",
+ "difference_posting_date"
],
"fields": [
{
@@ -30,7 +31,7 @@
"width": "180px"
},
{
- "columns": 3,
+ "columns": 2,
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
@@ -40,7 +41,7 @@
"read_only": 1
},
{
- "columns": 3,
+ "columns": 2,
"fieldname": "remarks",
"fieldtype": "Text",
"in_list_view": 1,
@@ -111,13 +112,24 @@
"label": "Reference Exchange Rate",
"non_negative": 1,
"read_only": 1
+ },
+ {
+ "columns": 2,
+ "fieldname": "difference_posting_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Difference Posting Date"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
+<<<<<<< HEAD
"modified": "2021-09-26 15:47:28.167371",
+=======
+ "modified": "2024-12-20 12:04:46.729972",
+>>>>>>> 225e56cbca (feat: add difference_posting_date field)
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Advance",
diff --git a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.py b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.py
index 44e1f6d5685..6f8971c12f4 100644
--- a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.py
+++ b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.py
@@ -6,4 +6,29 @@ from frappe.model.document import Document
class PurchaseInvoiceAdvance(Document):
+<<<<<<< HEAD
+=======
+ # 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
+
+ advance_amount: DF.Currency
+ allocated_amount: DF.Currency
+ difference_posting_date: DF.Date | None
+ exchange_gain_loss: DF.Currency
+ parent: DF.Data
+ parentfield: DF.Data
+ parenttype: DF.Data
+ ref_exchange_rate: DF.Float
+ reference_name: DF.DynamicLink | None
+ reference_row: DF.Data | None
+ reference_type: DF.Link | None
+ remarks: DF.Text | None
+ # end: auto-generated types
+
+>>>>>>> 225e56cbca (feat: add difference_posting_date field)
pass
diff --git a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json
index f92b57a45e1..19336f84342 100644
--- a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json
+++ b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json
@@ -14,7 +14,8 @@
"advance_amount",
"allocated_amount",
"exchange_gain_loss",
- "ref_exchange_rate"
+ "ref_exchange_rate",
+ "difference_posting_date"
],
"fields": [
{
@@ -30,7 +31,7 @@
"width": "250px"
},
{
- "columns": 3,
+ "columns": 2,
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
@@ -41,7 +42,7 @@
"read_only": 1
},
{
- "columns": 3,
+ "columns": 2,
"fieldname": "remarks",
"fieldtype": "Text",
"in_list_view": 1,
@@ -112,13 +113,24 @@
"label": "Reference Exchange Rate",
"non_negative": 1,
"read_only": 1
+ },
+ {
+ "columns": 2,
+ "fieldname": "difference_posting_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Difference Posting Date"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
+<<<<<<< HEAD
"modified": "2021-09-26 15:47:46.911595",
+=======
+ "modified": "2024-12-20 11:58:28.962370",
+>>>>>>> 225e56cbca (feat: add difference_posting_date field)
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Advance",
diff --git a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.py b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.py
index 6d4bd4633c3..edfa42e5eb7 100644
--- a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.py
+++ b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.py
@@ -6,4 +6,29 @@ from frappe.model.document import Document
class SalesInvoiceAdvance(Document):
+<<<<<<< HEAD
+=======
+ # 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
+
+ advance_amount: DF.Currency
+ allocated_amount: DF.Currency
+ difference_posting_date: DF.Date | None
+ exchange_gain_loss: DF.Currency
+ parent: DF.Data
+ parentfield: DF.Data
+ parenttype: DF.Data
+ ref_exchange_rate: DF.Float
+ reference_name: DF.DynamicLink | None
+ reference_row: DF.Data | None
+ reference_type: DF.Link | None
+ remarks: DF.Text | None
+ # end: auto-generated types
+
+>>>>>>> 225e56cbca (feat: add difference_posting_date field)
pass
From 0fdd6817a6e1c966244bfcac0cc348421ca2a156 Mon Sep 17 00:00:00 2001
From: rs-rethik
Date: Fri, 20 Dec 2024 12:10:08 +0530
Subject: [PATCH 11/16] feat: use difference_posting_date for journal entry
posting_date
(cherry picked from commit ff1d040a6e1aa80c3b93835395db31a222dd5568)
---
erpnext/controllers/accounts_controller.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index fa26fea3bf0..f2dab6beffc 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1082,6 +1082,7 @@ class AccountsController(TransactionBase):
"advance_amount": flt(d.amount),
"allocated_amount": allocated_amount,
"ref_exchange_rate": flt(d.exchange_rate), # exchange_rate of advance entry
+ "difference_posting_date": self.posting_date,
}
self.append("advances", advance_row)
@@ -1332,7 +1333,6 @@ class AccountsController(TransactionBase):
gain_loss_account = frappe.get_cached_value(
"Company", self.company, "exchange_gain_loss_account"
)
-
je = create_gain_loss_journal(
self.company,
args.get("difference_posting_date") if args else self.posting_date,
@@ -1445,6 +1445,7 @@ class AccountsController(TransactionBase):
"Company", self.company, "exchange_gain_loss_account"
),
"exchange_gain_loss": flt(d.get("exchange_gain_loss")),
+ "difference_posting_date": d.get("difference_posting_date"),
}
)
lst.append(args)
From 52309fe0b639b2beeb40a7b5a37dbfeefa2a3af9 Mon Sep 17 00:00:00 2001
From: rs-rethik
Date: Fri, 20 Dec 2024 12:13:35 +0530
Subject: [PATCH 12/16] test: add unit test to validate journal entry posting
date
(cherry picked from commit c14a2d73bf10fd910e319bc62c2d8e117cce73f1)
# Conflicts:
# erpnext/controllers/tests/test_accounts_controller.py
---
.../tests/test_accounts_controller.py | 216 ++++++++++++++++++
1 file changed, 216 insertions(+)
diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py
index 4ada8e60d9b..029a5cb9659 100644
--- a/erpnext/controllers/tests/test_accounts_controller.py
+++ b/erpnext/controllers/tests/test_accounts_controller.py
@@ -2,6 +2,8 @@
# For license information, please see license.txt
+from datetime import datetime
+
import frappe
from frappe import qb
from frappe.query_builder.functions import Sum
@@ -1737,3 +1739,217 @@ class TestAccountsController(FrappeTestCase):
# Exchange Gain/Loss Journal should've been cancelled
exc_je_for_je1 = self.get_journals_for(je1.doctype, je1.name)
self.assertEqual(exc_je_for_je1, [])
+<<<<<<< HEAD
+=======
+
+ def test_70_advance_payment_against_sales_invoice_in_foreign_currency(self):
+ """
+ Customer advance booked under Liability
+ """
+ self.setup_advance_accounts_in_party_master()
+
+ adv = self.create_payment_entry(amount=1, source_exc_rate=83)
+ adv.save() # explicit 'save' is needed to trigger set_liability_account()
+ self.assertEqual(adv.paid_from, self.advance_received_usd)
+ adv.submit()
+
+ si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1, do_not_submit=True)
+ si.debit_to = self.debtors_usd
+ si.save().submit()
+ self.assert_ledger_outstanding(si.doctype, si.name, 80.0, 1.0)
+
+ pr = self.create_payment_reconciliation()
+ pr.receivable_payable_account = self.debtors_usd
+ pr.default_advance_account = self.advance_received_usd
+ pr.get_unreconciled_entries()
+ self.assertEqual(pr.invoices[0].invoice_number, si.name)
+ self.assertEqual(pr.payments[0].reference_name, adv.name)
+
+ # Allocate and Reconcile
+ invoices = [x.as_dict() for x in pr.invoices]
+ payments = [x.as_dict() for x in pr.payments]
+ pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+ pr.reconcile()
+ self.assertEqual(len(pr.invoices), 0)
+ self.assertEqual(len(pr.payments), 0)
+ self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0)
+
+ # Exc Gain/Loss journal should've been creatad
+ exc_je_for_si = self.get_journals_for(si.doctype, si.name)
+ exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
+ self.assertEqual(len(exc_je_for_si), 1)
+ self.assertEqual(len(exc_je_for_adv), 1)
+ self.assertEqual(exc_je_for_si, exc_je_for_adv)
+
+ adv.reload()
+ adv.cancel()
+ si.reload()
+ self.assert_ledger_outstanding(si.doctype, si.name, 80.0, 1.0)
+ # Exc Gain/Loss journal should've been cancelled
+ exc_je_for_si = self.get_journals_for(si.doctype, si.name)
+ exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
+ self.assertEqual(len(exc_je_for_si), 0)
+ self.assertEqual(len(exc_je_for_adv), 0)
+
+ self.remove_advance_accounts_from_party_master()
+
+ def test_71_advance_payment_against_purchase_invoice_in_foreign_currency(self):
+ """
+ Supplier advance booked under Asset
+ """
+ self.setup_advance_accounts_in_party_master()
+
+ usd_amount = 1
+ inr_amount = 85
+ exc_rate = 85
+ adv = create_payment_entry(
+ company=self.company,
+ payment_type="Pay",
+ party_type="Supplier",
+ party=self.supplier,
+ paid_from=self.cash,
+ paid_to=self.advance_paid_usd,
+ paid_amount=inr_amount,
+ )
+ adv.source_exchange_rate = 1
+ adv.target_exchange_rate = exc_rate
+ adv.received_amount = usd_amount
+ adv.paid_amount = exc_rate * usd_amount
+ adv.posting_date = nowdate()
+ adv.save()
+ # Make sure that advance account is still set
+ self.assertEqual(adv.paid_to, self.advance_paid_usd)
+ adv.submit()
+
+ pi = self.create_purchase_invoice(qty=1, conversion_rate=83, rate=1)
+ self.assertEqual(pi.credit_to, self.creditors_usd)
+ self.assert_ledger_outstanding(pi.doctype, pi.name, 83.0, 1.0)
+
+ pr = self.create_payment_reconciliation()
+ pr.party_type = "Supplier"
+ pr.party = self.supplier
+ pr.receivable_payable_account = self.creditors_usd
+ pr.default_advance_account = self.advance_paid_usd
+ pr.get_unreconciled_entries()
+ self.assertEqual(pr.invoices[0].invoice_number, pi.name)
+ self.assertEqual(pr.payments[0].reference_name, adv.name)
+
+ # Allocate and Reconcile
+ invoices = [x.as_dict() for x in pr.invoices]
+ payments = [x.as_dict() for x in pr.payments]
+ pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+ pr.reconcile()
+ self.assertEqual(len(pr.invoices), 0)
+ self.assertEqual(len(pr.payments), 0)
+ self.assert_ledger_outstanding(pi.doctype, pi.name, 0.0, 0.0)
+
+ # Exc Gain/Loss journal should've been creatad
+ exc_je_for_pi = self.get_journals_for(pi.doctype, pi.name)
+ exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
+ self.assertEqual(len(exc_je_for_pi), 1)
+ self.assertEqual(len(exc_je_for_adv), 1)
+ self.assertEqual(exc_je_for_pi, exc_je_for_adv)
+
+ adv.reload()
+ adv.cancel()
+ pi.reload()
+ self.assert_ledger_outstanding(pi.doctype, pi.name, 83.0, 1.0)
+ # Exc Gain/Loss journal should've been cancelled
+ exc_je_for_pi = self.get_journals_for(pi.doctype, pi.name)
+ exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
+ self.assertEqual(len(exc_je_for_pi), 0)
+ self.assertEqual(len(exc_je_for_adv), 0)
+
+ self.remove_advance_accounts_from_party_master()
+
+ def test_difference_posting_date_in_pi_and_si(self):
+ self.setup_advance_accounts_in_party_master()
+
+ # create payment entry for customer
+ adv = self.create_payment_entry(amount=1, source_exc_rate=83)
+ adv.save()
+ self.assertEqual(adv.paid_from, self.advance_received_usd)
+ adv.submit()
+
+ # create sales invoice with advance received
+ si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1, do_not_submit=True)
+ si.debit_to = self.debtors_usd
+ si.append(
+ "advances",
+ {
+ "reference_type": "Payment Entry",
+ "reference_name": "ACC-PAY-2024-00001",
+ "remarks": "Amount INR 1 received from _Test MC Customer USD\nTransaction reference no Test001 dated 2024-12-19",
+ "advance_amount": 1.0,
+ "allocated_amount": 1.0,
+ "exchange_gain_loss": 3.0,
+ "ref_exchange_rate": 83.0,
+ "difference_posting_date": add_days(nowdate(), -2),
+ },
+ )
+ si.save().submit()
+
+ # exc Gain/Loss journal should've been creatad
+ exc_je_for_si = self.get_journals_for(si.doctype, si.name)
+ exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
+ self.assertEqual(len(exc_je_for_si), 1)
+ self.assertEqual(len(exc_je_for_adv), 1)
+ self.assertEqual(exc_je_for_si, exc_je_for_adv)
+
+ # check jv created with difference_posting_date in sales invoice
+ jv = frappe.get_doc("Journal Entry", exc_je_for_si[0].parent)
+ sales_invoice = frappe.get_doc("Sales Invoice", si.name)
+ self.assertEqual(sales_invoice.advances[0].difference_posting_date, jv.posting_date)
+
+ # create payment entry for supplier
+ usd_amount = 1
+ inr_amount = 85
+ exc_rate = 85
+ adv = create_payment_entry(
+ company=self.company,
+ payment_type="Pay",
+ party_type="Supplier",
+ party=self.supplier,
+ paid_from=self.cash,
+ paid_to=self.advance_paid_usd,
+ paid_amount=inr_amount,
+ )
+ adv.source_exchange_rate = 1
+ adv.target_exchange_rate = exc_rate
+ adv.received_amount = usd_amount
+ adv.paid_amount = exc_rate * usd_amount
+ adv.posting_date = nowdate()
+ adv.save()
+ self.assertEqual(adv.paid_to, self.advance_paid_usd)
+ adv.submit()
+
+ # create purchase invoice with advance paid
+ pi = self.create_purchase_invoice(qty=1, conversion_rate=80, rate=1, do_not_submit=True)
+ pi.append(
+ "advances",
+ {
+ "reference_type": "Payment Entry",
+ "reference_name": "ACC-PAY-2024-00002",
+ "remarks": "Amount INR 1 paid to _Test MC Supplier USD\nTransaction reference no Test001 dated 2024-12-20",
+ "advance_amount": 1.0,
+ "allocated_amount": 1.0,
+ "exchange_gain_loss": 5.0,
+ "ref_exchange_rate": 85.0,
+ "difference_posting_date": add_days(nowdate(), -2),
+ },
+ )
+ pi.save().submit()
+ self.assertEqual(pi.credit_to, self.creditors_usd)
+
+ # exc Gain/Loss journal should've been creatad
+ exc_je_for_pi = self.get_journals_for(pi.doctype, pi.name)
+ exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
+ self.assertEqual(len(exc_je_for_pi), 1)
+ self.assertEqual(len(exc_je_for_adv), 1)
+ self.assertEqual(exc_je_for_pi, exc_je_for_adv)
+
+ # check jv created with difference_posting_date in purchase invoice
+ journal_voucher = frappe.get_doc("Journal Entry", exc_je_for_pi[0].parent)
+ purchase_invoice = frappe.get_doc("Purchase Invoice", pi.name)
+ self.assertEqual(purchase_invoice.advances[0].difference_posting_date, journal_voucher.posting_date)
+>>>>>>> c14a2d73bf (test: add unit test to validate journal entry posting date)
From 33a1da8194ca6d471373c81bcec9510bd4a5780b Mon Sep 17 00:00:00 2001
From: rs-rethik
Date: Fri, 20 Dec 2024 12:42:47 +0530
Subject: [PATCH 13/16] refactor: convert sql query to query builder
(cherry picked from commit 2d58e845e633943bae2d481f66d200b16ab91bdf)
---
erpnext/controllers/accounts_controller.py | 34 +++++++++++-----------
1 file changed, 17 insertions(+), 17 deletions(-)
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index f2dab6beffc..e3d5ebf7fa1 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -346,13 +346,14 @@ class AccountsController(TransactionBase):
== 1
)
).run()
- frappe.db.sql(
- "delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name)
- )
- frappe.db.sql(
- "delete from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s",
- (self.doctype, self.name),
- )
+ gle = frappe.qb.DocType("GL Entry")
+ frappe.qb.from_(gle).delete().where(
+ (gle.voucher_type == self.doctype) & (gle.voucher_no == self.name)
+ ).run()
+ sle = frappe.qb.DocType("Stock Ledger Entry")
+ frappe.qb.from_(gle).delete().where(
+ (sle.voucher_type == self.doctype) & (sle.voucher_no == self.name)
+ ).run()
def validate_return_against_account(self):
if self.doctype in ["Sales Invoice", "Purchase Invoice"] and self.is_return and self.return_against:
@@ -1027,11 +1028,12 @@ class AccountsController(TransactionBase):
def clear_unallocated_advances(self, childtype, parentfield):
self.set(parentfield, self.get(parentfield, {"allocated_amount": ["not in", [0, None, ""]]}))
- frappe.db.sql(
- """delete from `tab{}` where parentfield={} and parent = {}
- and allocated_amount = 0""".format(childtype, "%s", "%s"),
- (parentfield, self.name),
- )
+ doctype = frappe.qb.DocType(childtype)
+ frappe.qb.from_(doctype).delete().where(
+ (doctype.parentfield == parentfield)
+ & (doctype.parent == self.name)
+ & (doctype.allocated_amount == 0)
+ ).run()
@frappe.whitelist()
def apply_shipping_rule(self):
@@ -1972,11 +1974,9 @@ class AccountsController(TransactionBase):
for adv in self.advances:
consider_for_total_advance = True
if adv.reference_name == linked_doc_name:
- frappe.db.sql(
- f"""delete from `tab{self.doctype} Advance`
- where name = %s""",
- adv.name,
- )
+ doctype = frappe.qb.DocType(self.doctype + " Advance")
+ frappe.qb.from_(doctype).delete().where(doctype.name == adv.name).run()
+
consider_for_total_advance = False
if consider_for_total_advance:
From 49e3865265477a718564c6c92ff6be5c8b472fed Mon Sep 17 00:00:00 2001
From: rs-rethik
Date: Fri, 20 Dec 2024 14:00:54 +0530
Subject: [PATCH 14/16] fix: update query
(cherry picked from commit 854e37c05c454ad2d93ec0f4a70600f2ca7d5eec)
---
erpnext/controllers/accounts_controller.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index e3d5ebf7fa1..ecbf1177ee4 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -351,7 +351,7 @@ class AccountsController(TransactionBase):
(gle.voucher_type == self.doctype) & (gle.voucher_no == self.name)
).run()
sle = frappe.qb.DocType("Stock Ledger Entry")
- frappe.qb.from_(gle).delete().where(
+ frappe.qb.from_(sle).delete().where(
(sle.voucher_type == self.doctype) & (sle.voucher_no == self.name)
).run()
From 8764a321c73c124019dbfde72aa43efc32424fe6 Mon Sep 17 00:00:00 2001
From: ruthra kumar
Date: Tue, 21 Jan 2025 12:57:10 +0530
Subject: [PATCH 15/16] chore: resolve conflicts
---
.../purchase_invoice_advance.json | 4 -
.../purchase_invoice_advance.py | 25 --
.../sales_invoice_advance.json | 4 -
.../sales_invoice_advance.py | 25 --
.../tests/test_accounts_controller.py | 214 ------------------
5 files changed, 272 deletions(-)
diff --git a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json
index 2f6d30c0922..ebd309bfb67 100644
--- a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json
+++ b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json
@@ -125,11 +125,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
-<<<<<<< HEAD
- "modified": "2021-09-26 15:47:28.167371",
-=======
"modified": "2024-12-20 12:04:46.729972",
->>>>>>> 225e56cbca (feat: add difference_posting_date field)
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Advance",
diff --git a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.py b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.py
index 6f8971c12f4..44e1f6d5685 100644
--- a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.py
+++ b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.py
@@ -6,29 +6,4 @@ from frappe.model.document import Document
class PurchaseInvoiceAdvance(Document):
-<<<<<<< HEAD
-=======
- # 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
-
- advance_amount: DF.Currency
- allocated_amount: DF.Currency
- difference_posting_date: DF.Date | None
- exchange_gain_loss: DF.Currency
- parent: DF.Data
- parentfield: DF.Data
- parenttype: DF.Data
- ref_exchange_rate: DF.Float
- reference_name: DF.DynamicLink | None
- reference_row: DF.Data | None
- reference_type: DF.Link | None
- remarks: DF.Text | None
- # end: auto-generated types
-
->>>>>>> 225e56cbca (feat: add difference_posting_date field)
pass
diff --git a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json
index 19336f84342..d4e3b9d896c 100644
--- a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json
+++ b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json
@@ -126,11 +126,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
-<<<<<<< HEAD
- "modified": "2021-09-26 15:47:46.911595",
-=======
"modified": "2024-12-20 11:58:28.962370",
->>>>>>> 225e56cbca (feat: add difference_posting_date field)
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Advance",
diff --git a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.py b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.py
index edfa42e5eb7..6d4bd4633c3 100644
--- a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.py
+++ b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.py
@@ -6,29 +6,4 @@ from frappe.model.document import Document
class SalesInvoiceAdvance(Document):
-<<<<<<< HEAD
-=======
- # 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
-
- advance_amount: DF.Currency
- allocated_amount: DF.Currency
- difference_posting_date: DF.Date | None
- exchange_gain_loss: DF.Currency
- parent: DF.Data
- parentfield: DF.Data
- parenttype: DF.Data
- ref_exchange_rate: DF.Float
- reference_name: DF.DynamicLink | None
- reference_row: DF.Data | None
- reference_type: DF.Link | None
- remarks: DF.Text | None
- # end: auto-generated types
-
->>>>>>> 225e56cbca (feat: add difference_posting_date field)
pass
diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py
index 029a5cb9659..a184009b1e1 100644
--- a/erpnext/controllers/tests/test_accounts_controller.py
+++ b/erpnext/controllers/tests/test_accounts_controller.py
@@ -1739,217 +1739,3 @@ class TestAccountsController(FrappeTestCase):
# Exchange Gain/Loss Journal should've been cancelled
exc_je_for_je1 = self.get_journals_for(je1.doctype, je1.name)
self.assertEqual(exc_je_for_je1, [])
-<<<<<<< HEAD
-=======
-
- def test_70_advance_payment_against_sales_invoice_in_foreign_currency(self):
- """
- Customer advance booked under Liability
- """
- self.setup_advance_accounts_in_party_master()
-
- adv = self.create_payment_entry(amount=1, source_exc_rate=83)
- adv.save() # explicit 'save' is needed to trigger set_liability_account()
- self.assertEqual(adv.paid_from, self.advance_received_usd)
- adv.submit()
-
- si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1, do_not_submit=True)
- si.debit_to = self.debtors_usd
- si.save().submit()
- self.assert_ledger_outstanding(si.doctype, si.name, 80.0, 1.0)
-
- pr = self.create_payment_reconciliation()
- pr.receivable_payable_account = self.debtors_usd
- pr.default_advance_account = self.advance_received_usd
- pr.get_unreconciled_entries()
- self.assertEqual(pr.invoices[0].invoice_number, si.name)
- self.assertEqual(pr.payments[0].reference_name, adv.name)
-
- # Allocate and Reconcile
- invoices = [x.as_dict() for x in pr.invoices]
- payments = [x.as_dict() for x in pr.payments]
- pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
- pr.reconcile()
- self.assertEqual(len(pr.invoices), 0)
- self.assertEqual(len(pr.payments), 0)
- self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0)
-
- # Exc Gain/Loss journal should've been creatad
- exc_je_for_si = self.get_journals_for(si.doctype, si.name)
- exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
- self.assertEqual(len(exc_je_for_si), 1)
- self.assertEqual(len(exc_je_for_adv), 1)
- self.assertEqual(exc_je_for_si, exc_je_for_adv)
-
- adv.reload()
- adv.cancel()
- si.reload()
- self.assert_ledger_outstanding(si.doctype, si.name, 80.0, 1.0)
- # Exc Gain/Loss journal should've been cancelled
- exc_je_for_si = self.get_journals_for(si.doctype, si.name)
- exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
- self.assertEqual(len(exc_je_for_si), 0)
- self.assertEqual(len(exc_je_for_adv), 0)
-
- self.remove_advance_accounts_from_party_master()
-
- def test_71_advance_payment_against_purchase_invoice_in_foreign_currency(self):
- """
- Supplier advance booked under Asset
- """
- self.setup_advance_accounts_in_party_master()
-
- usd_amount = 1
- inr_amount = 85
- exc_rate = 85
- adv = create_payment_entry(
- company=self.company,
- payment_type="Pay",
- party_type="Supplier",
- party=self.supplier,
- paid_from=self.cash,
- paid_to=self.advance_paid_usd,
- paid_amount=inr_amount,
- )
- adv.source_exchange_rate = 1
- adv.target_exchange_rate = exc_rate
- adv.received_amount = usd_amount
- adv.paid_amount = exc_rate * usd_amount
- adv.posting_date = nowdate()
- adv.save()
- # Make sure that advance account is still set
- self.assertEqual(adv.paid_to, self.advance_paid_usd)
- adv.submit()
-
- pi = self.create_purchase_invoice(qty=1, conversion_rate=83, rate=1)
- self.assertEqual(pi.credit_to, self.creditors_usd)
- self.assert_ledger_outstanding(pi.doctype, pi.name, 83.0, 1.0)
-
- pr = self.create_payment_reconciliation()
- pr.party_type = "Supplier"
- pr.party = self.supplier
- pr.receivable_payable_account = self.creditors_usd
- pr.default_advance_account = self.advance_paid_usd
- pr.get_unreconciled_entries()
- self.assertEqual(pr.invoices[0].invoice_number, pi.name)
- self.assertEqual(pr.payments[0].reference_name, adv.name)
-
- # Allocate and Reconcile
- invoices = [x.as_dict() for x in pr.invoices]
- payments = [x.as_dict() for x in pr.payments]
- pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
- pr.reconcile()
- self.assertEqual(len(pr.invoices), 0)
- self.assertEqual(len(pr.payments), 0)
- self.assert_ledger_outstanding(pi.doctype, pi.name, 0.0, 0.0)
-
- # Exc Gain/Loss journal should've been creatad
- exc_je_for_pi = self.get_journals_for(pi.doctype, pi.name)
- exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
- self.assertEqual(len(exc_je_for_pi), 1)
- self.assertEqual(len(exc_je_for_adv), 1)
- self.assertEqual(exc_je_for_pi, exc_je_for_adv)
-
- adv.reload()
- adv.cancel()
- pi.reload()
- self.assert_ledger_outstanding(pi.doctype, pi.name, 83.0, 1.0)
- # Exc Gain/Loss journal should've been cancelled
- exc_je_for_pi = self.get_journals_for(pi.doctype, pi.name)
- exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
- self.assertEqual(len(exc_je_for_pi), 0)
- self.assertEqual(len(exc_je_for_adv), 0)
-
- self.remove_advance_accounts_from_party_master()
-
- def test_difference_posting_date_in_pi_and_si(self):
- self.setup_advance_accounts_in_party_master()
-
- # create payment entry for customer
- adv = self.create_payment_entry(amount=1, source_exc_rate=83)
- adv.save()
- self.assertEqual(adv.paid_from, self.advance_received_usd)
- adv.submit()
-
- # create sales invoice with advance received
- si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1, do_not_submit=True)
- si.debit_to = self.debtors_usd
- si.append(
- "advances",
- {
- "reference_type": "Payment Entry",
- "reference_name": "ACC-PAY-2024-00001",
- "remarks": "Amount INR 1 received from _Test MC Customer USD\nTransaction reference no Test001 dated 2024-12-19",
- "advance_amount": 1.0,
- "allocated_amount": 1.0,
- "exchange_gain_loss": 3.0,
- "ref_exchange_rate": 83.0,
- "difference_posting_date": add_days(nowdate(), -2),
- },
- )
- si.save().submit()
-
- # exc Gain/Loss journal should've been creatad
- exc_je_for_si = self.get_journals_for(si.doctype, si.name)
- exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
- self.assertEqual(len(exc_je_for_si), 1)
- self.assertEqual(len(exc_je_for_adv), 1)
- self.assertEqual(exc_je_for_si, exc_je_for_adv)
-
- # check jv created with difference_posting_date in sales invoice
- jv = frappe.get_doc("Journal Entry", exc_je_for_si[0].parent)
- sales_invoice = frappe.get_doc("Sales Invoice", si.name)
- self.assertEqual(sales_invoice.advances[0].difference_posting_date, jv.posting_date)
-
- # create payment entry for supplier
- usd_amount = 1
- inr_amount = 85
- exc_rate = 85
- adv = create_payment_entry(
- company=self.company,
- payment_type="Pay",
- party_type="Supplier",
- party=self.supplier,
- paid_from=self.cash,
- paid_to=self.advance_paid_usd,
- paid_amount=inr_amount,
- )
- adv.source_exchange_rate = 1
- adv.target_exchange_rate = exc_rate
- adv.received_amount = usd_amount
- adv.paid_amount = exc_rate * usd_amount
- adv.posting_date = nowdate()
- adv.save()
- self.assertEqual(adv.paid_to, self.advance_paid_usd)
- adv.submit()
-
- # create purchase invoice with advance paid
- pi = self.create_purchase_invoice(qty=1, conversion_rate=80, rate=1, do_not_submit=True)
- pi.append(
- "advances",
- {
- "reference_type": "Payment Entry",
- "reference_name": "ACC-PAY-2024-00002",
- "remarks": "Amount INR 1 paid to _Test MC Supplier USD\nTransaction reference no Test001 dated 2024-12-20",
- "advance_amount": 1.0,
- "allocated_amount": 1.0,
- "exchange_gain_loss": 5.0,
- "ref_exchange_rate": 85.0,
- "difference_posting_date": add_days(nowdate(), -2),
- },
- )
- pi.save().submit()
- self.assertEqual(pi.credit_to, self.creditors_usd)
-
- # exc Gain/Loss journal should've been creatad
- exc_je_for_pi = self.get_journals_for(pi.doctype, pi.name)
- exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
- self.assertEqual(len(exc_je_for_pi), 1)
- self.assertEqual(len(exc_je_for_adv), 1)
- self.assertEqual(exc_je_for_pi, exc_je_for_adv)
-
- # check jv created with difference_posting_date in purchase invoice
- journal_voucher = frappe.get_doc("Journal Entry", exc_je_for_pi[0].parent)
- purchase_invoice = frappe.get_doc("Purchase Invoice", pi.name)
- self.assertEqual(purchase_invoice.advances[0].difference_posting_date, journal_voucher.posting_date)
->>>>>>> c14a2d73bf (test: add unit test to validate journal entry posting date)
From 431fa225e3bc13e99ea7cd06e05dd3fbd16a01bf Mon Sep 17 00:00:00 2001
From: venkat102
Date: Mon, 13 Jan 2025 16:51:25 +0530
Subject: [PATCH 16/16] fix: include pos invoice in modifing key for returned
item validation
(cherry picked from commit 2936139c797e1e9a36e6b743fe616718bfacdc9b)
---
erpnext/controllers/sales_and_purchase_return.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index f661cc33c63..b2a4a4e0f7e 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -105,7 +105,7 @@ def validate_returned_items(doc):
for d in doc.get("items"):
key = d.item_code
raise_exception = False
- if doc.doctype in ["Purchase Receipt", "Purchase Invoice", "Sales Invoice"]:
+ if doc.doctype in ["Purchase Receipt", "Purchase Invoice", "Sales Invoice", "POS Invoice"]:
field = frappe.scrub(doc.doctype) + "_item"
if d.get(field):
key = (d.item_code, d.get(field))