diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index bfe73f02cdc..078e51c905f 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -550,7 +550,7 @@ def get_due_date_from_template(template_name, posting_date, bill_date):
elif term.due_date_based_on == "Day(s) after the end of the invoice month":
due_date = max(due_date, add_days(get_last_day(due_date), term.credit_days))
else:
- due_date = max(due_date, add_months(get_last_day(due_date), term.credit_months))
+ due_date = max(due_date, get_last_day(add_months(due_date, term.credit_months)))
return due_date
diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py
index 646dba51ce9..c673be89b3f 100644
--- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py
+++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py
@@ -15,17 +15,6 @@ class TestBulkTransactionLog(unittest.TestCase):
create_customer()
create_item()
- def test_for_single_record(self):
- so_name = create_so()
- transaction_processing([{"name": so_name}], "Sales Order", "Sales Invoice")
- data = frappe.db.get_list(
- "Sales Invoice",
- filters={"posting_date": date.today(), "customer": "Bulk Customer"},
- fields=["*"],
- )
- if not data:
- self.fail("No Sales Invoice Created !")
-
def test_entry_in_log(self):
so_name = create_so()
transaction_processing([{"name": so_name}], "Sales Order", "Sales Invoice")
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index f0360b27dc0..920486a78ef 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -10,6 +10,7 @@ from frappe.utils import add_days, flt, getdate, nowdate
from frappe.utils.data import today
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
+from erpnext.accounts.party import get_due_date_from_template
from erpnext.buying.doctype.purchase_order.purchase_order import make_inter_company_sales_order
from erpnext.buying.doctype.purchase_order.purchase_order import (
make_purchase_invoice as make_pi_from_po,
@@ -685,6 +686,12 @@ class TestPurchaseOrder(FrappeTestCase):
else:
raise Exception
+ def test_default_payment_terms(self):
+ due_date = get_due_date_from_template(
+ "_Test Payment Term Template 1", "2023-02-03", None
+ ).strftime("%Y-%m-%d")
+ self.assertEqual(due_date, "2023-03-31")
+
def test_terms_are_not_copied_if_automatically_fetch_payment_terms_is_unchecked(self):
po = create_purchase_order(do_not_save=1)
po.payment_terms_template = "_Test Payment Term Template"
diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
index 47a66ad46f2..9b53421319d 100644
--- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
+++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
@@ -15,60 +15,4 @@ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
class TestProcurementTracker(FrappeTestCase):
- def test_result_for_procurement_tracker(self):
- filters = {"company": "_Test Procurement Company", "cost_center": "Main - _TPC"}
- expected_data = self.generate_expected_data()
- report = execute(filters)
-
- length = len(report[1])
- self.assertEqual(expected_data, report[1][length - 1])
-
- def generate_expected_data(self):
- if not frappe.db.exists("Company", "_Test Procurement Company"):
- frappe.get_doc(
- dict(
- doctype="Company",
- company_name="_Test Procurement Company",
- abbr="_TPC",
- default_currency="INR",
- country="Pakistan",
- )
- ).insert()
- warehouse = create_warehouse("_Test Procurement Warehouse", company="_Test Procurement Company")
- mr = make_material_request(
- company="_Test Procurement Company", warehouse=warehouse, cost_center="Main - _TPC"
- )
- po = make_purchase_order(mr.name)
- po.supplier = "_Test Supplier"
- po.get("items")[0].cost_center = "Main - _TPC"
- po.submit()
- pr = make_purchase_receipt(po.name)
- pr.get("items")[0].cost_center = "Main - _TPC"
- pr.submit()
- date_obj = datetime.date(datetime.now())
-
- po.load_from_db()
-
- expected_data = {
- "material_request_date": date_obj,
- "cost_center": "Main - _TPC",
- "project": None,
- "requesting_site": "_Test Procurement Warehouse - _TPC",
- "requestor": "Administrator",
- "material_request_no": mr.name,
- "item_code": "_Test Item",
- "quantity": 10.0,
- "unit_of_measurement": "_Test UOM",
- "status": "To Bill",
- "purchase_order_date": date_obj,
- "purchase_order": po.name,
- "supplier": "_Test Supplier",
- "estimated_cost": 0.0,
- "actual_cost": 0.0,
- "purchase_order_amt": po.net_total,
- "purchase_order_amt_in_company_currency": po.base_net_total,
- "expected_delivery_date": date_obj,
- "actual_delivery_date": date_obj,
- }
-
- return expected_data
+ pass
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 445620a1246..cf267716d58 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -716,6 +716,8 @@ class BuyingController(SubcontractingController):
asset.purchase_date = self.posting_date
asset.supplier = self.supplier
elif self.docstatus == 2:
+ if asset.docstatus == 2:
+ continue
if asset.docstatus == 0:
asset.set(field, None)
asset.supplier = None
diff --git a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.js b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.js
index a227b6d7973..458c79a1ea8 100644
--- a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.js
+++ b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.js
@@ -11,6 +11,40 @@ frappe.query_reports["Loan Interest Report"] = {
"options": "Company",
"default": frappe.defaults.get_user_default("Company"),
"reqd": 1
- }
+ },
+ {
+ "fieldname":"applicant_type",
+ "label": __("Applicant Type"),
+ "fieldtype": "Select",
+ "options": ["Customer", "Employee"],
+ "reqd": 1,
+ "default": "Customer",
+ on_change: function() {
+ frappe.query_report.set_filter_value('applicant', "");
+ }
+ },
+ {
+ "fieldname": "applicant",
+ "label": __("Applicant"),
+ "fieldtype": "Dynamic Link",
+ "get_options": function() {
+ var applicant_type = frappe.query_report.get_filter_value('applicant_type');
+ var applicant = frappe.query_report.get_filter_value('applicant');
+ if(applicant && !applicant_type) {
+ frappe.throw(__("Please select Applicant Type first"));
+ }
+ return applicant_type;
+ }
+ },
+ {
+ "fieldname":"from_date",
+ "label": __("From Date"),
+ "fieldtype": "Date",
+ },
+ {
+ "fieldname":"to_date",
+ "label": __("From Date"),
+ "fieldtype": "Date",
+ },
]
};
diff --git a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py
index 9186ce61743..58a7880a459 100644
--- a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py
+++ b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py
@@ -13,12 +13,12 @@ from erpnext.loan_management.report.applicant_wise_loan_security_exposure.applic
def execute(filters=None):
- columns = get_columns(filters)
+ columns = get_columns()
data = get_active_loan_details(filters)
return columns, data
-def get_columns(filters):
+def get_columns():
columns = [
{"label": _("Loan"), "fieldname": "loan", "fieldtype": "Link", "options": "Loan", "width": 160},
{"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": 160},
@@ -70,6 +70,13 @@ def get_columns(filters):
"options": "currency",
"width": 120,
},
+ {
+ "label": _("Accrued Principal"),
+ "fieldname": "accrued_principal",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 120,
+ },
{
"label": _("Total Repayment"),
"fieldname": "total_repayment",
@@ -137,11 +144,16 @@ def get_columns(filters):
def get_active_loan_details(filters):
-
- filter_obj = {"status": ("!=", "Closed")}
+ filter_obj = {
+ "status": ("!=", "Closed"),
+ "docstatus": 1,
+ }
if filters.get("company"):
filter_obj.update({"company": filters.get("company")})
+ if filters.get("applicant"):
+ filter_obj.update({"applicant": filters.get("applicant")})
+
loan_details = frappe.get_all(
"Loan",
fields=[
@@ -167,8 +179,8 @@ def get_active_loan_details(filters):
sanctioned_amount_map = get_sanctioned_amount_map()
penal_interest_rate_map = get_penal_interest_rate_map()
- payments = get_payments(loan_list)
- accrual_map = get_interest_accruals(loan_list)
+ payments = get_payments(loan_list, filters)
+ accrual_map = get_interest_accruals(loan_list, filters)
currency = erpnext.get_company_currency(filters.get("company"))
for loan in loan_details:
@@ -183,6 +195,7 @@ def get_active_loan_details(filters):
- flt(loan.written_off_amount),
"total_repayment": flt(payments.get(loan.loan)),
"accrued_interest": flt(accrual_map.get(loan.loan, {}).get("accrued_interest")),
+ "accrued_principal": flt(accrual_map.get(loan.loan, {}).get("accrued_principal")),
"interest_outstanding": flt(accrual_map.get(loan.loan, {}).get("interest_outstanding")),
"penalty": flt(accrual_map.get(loan.loan, {}).get("penalty")),
"penalty_interest": penal_interest_rate_map.get(loan.loan_type),
@@ -212,20 +225,35 @@ def get_sanctioned_amount_map():
)
-def get_payments(loans):
+def get_payments(loans, filters):
+ query_filters = {"against_loan": ("in", loans)}
+
+ if filters.get("from_date"):
+ query_filters.update({"posting_date": (">=", filters.get("from_date"))})
+
+ if filters.get("to_date"):
+ query_filters.update({"posting_date": ("<=", filters.get("to_date"))})
+
return frappe._dict(
frappe.get_all(
"Loan Repayment",
fields=["against_loan", "sum(amount_paid)"],
- filters={"against_loan": ("in", loans)},
+ filters=query_filters,
group_by="against_loan",
as_list=1,
)
)
-def get_interest_accruals(loans):
+def get_interest_accruals(loans, filters):
accrual_map = {}
+ query_filters = {"loan": ("in", loans)}
+
+ if filters.get("from_date"):
+ query_filters.update({"posting_date": (">=", filters.get("from_date"))})
+
+ if filters.get("to_date"):
+ query_filters.update({"posting_date": ("<=", filters.get("to_date"))})
interest_accruals = frappe.get_all(
"Loan Interest Accrual",
@@ -236,8 +264,9 @@ def get_interest_accruals(loans):
"penalty_amount",
"paid_interest_amount",
"accrual_type",
+ "payable_principal_amount",
],
- filters={"loan": ("in", loans)},
+ filters=query_filters,
order_by="posting_date desc",
)
@@ -246,6 +275,7 @@ def get_interest_accruals(loans):
entry.loan,
{
"accrued_interest": 0.0,
+ "accrued_principal": 0.0,
"undue_interest": 0.0,
"interest_outstanding": 0.0,
"last_accrual_date": "",
@@ -270,6 +300,7 @@ def get_interest_accruals(loans):
accrual_map[entry.loan]["undue_interest"] += entry.interest_amount - entry.paid_interest_amount
accrual_map[entry.loan]["accrued_interest"] += entry.interest_amount
+ accrual_map[entry.loan]["accrued_principal"] += entry.payable_principal_amount
if last_accrual_date and getdate(entry.posting_date) == last_accrual_date:
accrual_map[entry.loan]["penalty"] = entry.penalty_amount
diff --git a/erpnext/loan_management/workspace/loans/loans.json b/erpnext/loan_management/workspace/loans/loans.json
new file mode 100644
index 00000000000..c65be4efae9
--- /dev/null
+++ b/erpnext/loan_management/workspace/loans/loans.json
@@ -0,0 +1,315 @@
+{
+ "charts": [],
+ "content": "[{\"id\":\"_38WStznya\",\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"id\":\"t7o_K__1jB\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Loan Application\",\"col\":3}},{\"id\":\"IRiNDC6w1p\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Loan\",\"col\":3}},{\"id\":\"xbbo0FYbq0\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"7ZL4Bro-Vi\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"yhyioTViZ3\",\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"id\":\"oYFn4b1kSw\",\"type\":\"card\",\"data\":{\"card_name\":\"Loan\",\"col\":4}},{\"id\":\"vZepJF5tl9\",\"type\":\"card\",\"data\":{\"card_name\":\"Loan Processes\",\"col\":4}},{\"id\":\"k-393Mjhqe\",\"type\":\"card\",\"data\":{\"card_name\":\"Disbursement and Repayment\",\"col\":4}},{\"id\":\"6crJ0DBiBJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Loan Security\",\"col\":4}},{\"id\":\"Um5YwxVLRJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]",
+ "creation": "2020-03-12 16:35:55.299820",
+ "docstatus": 0,
+ "doctype": "Workspace",
+ "for_user": "",
+ "hide_custom": 0,
+ "icon": "loan",
+ "idx": 0,
+ "is_hidden": 0,
+ "label": "Loans",
+ "links": [
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Loan",
+ "link_count": 0,
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Loan Type",
+ "link_count": 0,
+ "link_to": "Loan Type",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Loan Application",
+ "link_count": 0,
+ "link_to": "Loan Application",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Loan",
+ "link_count": 0,
+ "link_to": "Loan",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Loan Processes",
+ "link_count": 0,
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Process Loan Security Shortfall",
+ "link_count": 0,
+ "link_to": "Process Loan Security Shortfall",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Process Loan Interest Accrual",
+ "link_count": 0,
+ "link_to": "Process Loan Interest Accrual",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Disbursement and Repayment",
+ "link_count": 0,
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Loan Disbursement",
+ "link_count": 0,
+ "link_to": "Loan Disbursement",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Loan Repayment",
+ "link_count": 0,
+ "link_to": "Loan Repayment",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Loan Write Off",
+ "link_count": 0,
+ "link_to": "Loan Write Off",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Loan Interest Accrual",
+ "link_count": 0,
+ "link_to": "Loan Interest Accrual",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Loan Security",
+ "link_count": 0,
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Loan Security Type",
+ "link_count": 0,
+ "link_to": "Loan Security Type",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Loan Security Price",
+ "link_count": 0,
+ "link_to": "Loan Security Price",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Loan Security",
+ "link_count": 0,
+ "link_to": "Loan Security",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Loan Security Pledge",
+ "link_count": 0,
+ "link_to": "Loan Security Pledge",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Loan Security Unpledge",
+ "link_count": 0,
+ "link_to": "Loan Security Unpledge",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Loan Security Shortfall",
+ "link_count": 0,
+ "link_to": "Loan Security Shortfall",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Reports",
+ "link_count": 6,
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Loan Repayment and Closure",
+ "link_count": 0,
+ "link_to": "Loan Repayment and Closure",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Loan Security Status",
+ "link_count": 0,
+ "link_to": "Loan Security Status",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Loan Interest Report",
+ "link_count": 0,
+ "link_to": "Loan Interest Report",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Loan Security Exposure",
+ "link_count": 0,
+ "link_to": "Loan Security Exposure",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Applicant-Wise Loan Security Exposure",
+ "link_count": 0,
+ "link_to": "Applicant-Wise Loan Security Exposure",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Loan Security Status",
+ "link_count": 0,
+ "link_to": "Loan Security Status",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ }
+ ],
+ "modified": "2023-01-31 19:47:13.114415",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Loans",
+ "owner": "Administrator",
+ "parent_page": "",
+ "public": 1,
+ "quick_lists": [],
+ "restrict_to_domain": "",
+ "roles": [],
+ "sequence_id": 16.0,
+ "shortcuts": [
+ {
+ "color": "Green",
+ "format": "{} Open",
+ "label": "Loan Application",
+ "link_to": "Loan Application",
+ "stats_filter": "{ \"status\": \"Open\" }",
+ "type": "DocType"
+ },
+ {
+ "label": "Loan",
+ "link_to": "Loan",
+ "type": "DocType"
+ },
+ {
+ "doc_view": "",
+ "label": "Dashboard",
+ "link_to": "Loan Dashboard",
+ "type": "Dashboard"
+ }
+ ],
+ "title": "Loans"
+}
\ No newline at end of file
diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js
index 6e7622c067f..bef438f9fd7 100644
--- a/erpnext/stock/dashboard/item_dashboard.js
+++ b/erpnext/stock/dashboard/item_dashboard.js
@@ -42,7 +42,7 @@ erpnext.stock.ItemDashboard = class ItemDashboard {
let warehouse = unescape(element.attr('data-warehouse'));
let actual_qty = unescape(element.attr('data-actual_qty'));
let disable_quick_entry = Number(unescape(element.attr('data-disable_quick_entry')));
- let entry_type = action === "Move" ? "Material Transfer" : null;
+ let entry_type = action === "Move" ? "Material Transfer" : "Material Receipt";
if (disable_quick_entry) {
open_stock_entry(item, warehouse, entry_type);
@@ -63,11 +63,19 @@ erpnext.stock.ItemDashboard = class ItemDashboard {
function open_stock_entry(item, warehouse, entry_type) {
frappe.model.with_doctype('Stock Entry', function () {
var doc = frappe.model.get_new_doc('Stock Entry');
- if (entry_type) doc.stock_entry_type = entry_type;
+ if (entry_type) {
+ doc.stock_entry_type = entry_type;
+ }
var row = frappe.model.add_child(doc, 'items');
row.item_code = item;
- row.s_warehouse = warehouse;
+
+ if (entry_type === "Material Transfer") {
+ row.s_warehouse = warehouse;
+ }
+ else {
+ row.t_warehouse = warehouse;
+ }
frappe.set_route('Form', doc.doctype, doc.name);
});
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 9a247317045..e263a278bef 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -2497,7 +2497,7 @@ def get_uom_details(item_code, uom, qty):
if not conversion_factor:
frappe.msgprint(
- _("UOM coversion factor required for UOM: {0} in Item: {1}").format(uom, item_code)
+ _("UOM conversion factor required for UOM: {0} in Item: {1}").format(uom, item_code)
)
ret = {"uom": ""}
else:
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index 38bf0a5f9e5..cc06bd709ad 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -1662,6 +1662,48 @@ class TestStockEntry(FrappeTestCase):
self.assertRaises(BatchExpiredError, se.save)
+ def test_negative_stock_reco(self):
+ from erpnext.controllers.stock_controller import BatchExpiredError
+ from erpnext.stock.doctype.batch.test_batch import make_new_batch
+
+ frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 0)
+
+ item_code = "Test Negative Item - 001"
+ item_doc = create_item(item_code=item_code, is_stock_item=1, valuation_rate=10)
+
+ make_stock_entry(
+ item_code=item_code,
+ posting_date=add_days(today(), -3),
+ posting_time="00:00:00",
+ purpose="Material Receipt",
+ qty=10,
+ to_warehouse="_Test Warehouse - _TC",
+ do_not_save=True,
+ )
+
+ make_stock_entry(
+ item_code=item_code,
+ posting_date=today(),
+ posting_time="00:00:00",
+ purpose="Material Receipt",
+ qty=8,
+ from_warehouse="_Test Warehouse - _TC",
+ do_not_save=True,
+ )
+
+ sr_doc = create_stock_reconciliation(
+ purpose="Stock Reconciliation",
+ posting_date=add_days(today(), -3),
+ posting_time="00:00:00",
+ item_code=item_code,
+ warehouse="_Test Warehouse - _TC",
+ valuation_rate=10,
+ qty=7,
+ do_not_submit=True,
+ )
+
+ self.assertRaises(frappe.ValidationError, sr_doc.submit)
+
def make_serialized_item(**args):
args = frappe._dict(args)
diff --git a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py
index b5c6764224b..abbb33b2f16 100644
--- a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py
+++ b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py
@@ -62,7 +62,7 @@ def execute(filters=None):
continue
total_stock_value = sum(item_value[(item, item_group)])
- row = [item, item_group, total_stock_value]
+ row = [item, item_map[item]["item_name"], item_group, total_stock_value]
fifo_queue = item_ageing[item]["fifo_queue"]
average_age = 0.00
@@ -89,10 +89,11 @@ def get_columns(filters):
"""return columns"""
columns = [
- _("Item") + ":Link/Item:180",
- _("Item Group") + "::100",
+ _("Item") + ":Link/Item:150",
+ _("Item Name") + ":Link/Item:150",
+ _("Item Group") + "::120",
_("Value") + ":Currency:120",
- _("Age") + ":Float:80",
+ _("Age") + ":Float:120",
]
return columns
@@ -123,7 +124,7 @@ def get_warehouse_list(filters):
def add_warehouse_column(columns, warehouse_list):
if len(warehouse_list) > 1:
- columns += [_("Total Qty") + ":Int:90"]
+ columns += [_("Total Qty") + ":Int:120"]
for wh in warehouse_list:
- columns += [_(wh.name) + ":Int:120"]
+ columns += [_(wh.name) + ":Int:100"]
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index d8b12ed5b92..08fc6fbd42f 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -1050,7 +1050,7 @@ class update_entries_after(object):
frappe.db.set_value("Bin", bin_name, updated_values, update_modified=True)
-def get_previous_sle_of_current_voucher(args, exclude_current_voucher=False):
+def get_previous_sle_of_current_voucher(args, operator="<", exclude_current_voucher=False):
"""get stock ledger entries filtered by specific posting datetime conditions"""
args["time_format"] = "%H:%i:%s"
@@ -1076,13 +1076,13 @@ def get_previous_sle_of_current_voucher(args, exclude_current_voucher=False):
posting_date < %(posting_date)s or
(
posting_date = %(posting_date)s and
- time_format(posting_time, %(time_format)s) < time_format(%(posting_time)s, %(time_format)s)
+ time_format(posting_time, %(time_format)s) {operator} time_format(%(posting_time)s, %(time_format)s)
)
)
order by timestamp(posting_date, posting_time) desc, creation desc
limit 1
for update""".format(
- voucher_condition=voucher_condition
+ operator=operator, voucher_condition=voucher_condition
),
args,
as_dict=1,
@@ -1375,7 +1375,7 @@ def get_stock_reco_qty_shift(args):
stock_reco_qty_shift = flt(args.actual_qty)
else:
# reco is being submitted
- last_balance = get_previous_sle_of_current_voucher(args, exclude_current_voucher=True).get(
+ last_balance = get_previous_sle_of_current_voucher(args, "<=", exclude_current_voucher=True).get(
"qty_after_transaction"
)