Merge branch 'version-14-hotfix' into mergify/bp/version-14-hotfix/pr-33777

This commit is contained in:
Deepesh Garg
2023-02-11 11:20:53 +05:30
committed by GitHub
13 changed files with 467 additions and 94 deletions

View File

@@ -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

View File

@@ -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")

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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",
},
]
};

View File

@@ -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

View File

@@ -0,0 +1,315 @@
{
"charts": [],
"content": "[{\"id\":\"_38WStznya\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"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\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"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"
}

View File

@@ -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);
});

View File

@@ -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:

View File

@@ -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)

View File

@@ -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"]

View File

@@ -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"
)