Merge branch 'develop' into anand_codeowners
diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py
index 331adb4..b4df0a5 100644
--- a/erpnext/accounts/doctype/dunning/dunning.py
+++ b/erpnext/accounts/doctype/dunning/dunning.py
@@ -40,7 +40,7 @@
def on_cancel(self):
if self.dunning_amount:
- self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
+ self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
def make_gl_entries(self):
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index 154fdc0..675a328 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -234,7 +234,7 @@
def allocate_entries(self, args):
self.validate_entries()
- invoice_exchange_map = self.get_invoice_exchange_map(args.get("invoices"))
+ invoice_exchange_map = self.get_invoice_exchange_map(args.get("invoices"), args.get("payments"))
default_exchange_gain_loss_account = frappe.get_cached_value(
"Company", self.company, "exchange_gain_loss_account"
)
@@ -253,6 +253,9 @@
pay["amount"] = 0
inv["exchange_rate"] = invoice_exchange_map.get(inv.get("invoice_number"))
+ if pay.get("reference_type") in ["Sales Invoice", "Purchase Invoice"]:
+ pay["exchange_rate"] = invoice_exchange_map.get(pay.get("reference_name"))
+
res.difference_amount = self.get_difference_amount(pay, inv, res["allocated_amount"])
res.difference_account = default_exchange_gain_loss_account
res.exchange_rate = inv.get("exchange_rate")
@@ -407,13 +410,21 @@
if not self.get("payments"):
frappe.throw(_("No records found in the Payments table"))
- def get_invoice_exchange_map(self, invoices):
+ def get_invoice_exchange_map(self, invoices, payments):
sales_invoices = [
d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Sales Invoice"
]
+
+ sales_invoices.extend(
+ [d.get("reference_name") for d in payments if d.get("reference_type") == "Sales Invoice"]
+ )
purchase_invoices = [
d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Purchase Invoice"
]
+ purchase_invoices.extend(
+ [d.get("reference_name") for d in payments if d.get("reference_type") == "Purchase Invoice"]
+ )
+
invoice_exchange_map = frappe._dict()
if sales_invoices:
diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
index 00e3934..f9dda05 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
@@ -473,6 +473,11 @@
invoices = [x.as_dict() for x in pr.get("invoices")]
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+
+ # Cr Note and Invoice are of the same currency. There shouldn't any difference amount.
+ for row in pr.allocation:
+ self.assertEqual(flt(row.get("difference_amount")), 0.0)
+
pr.reconcile()
pr.get_unreconciled_entries()
@@ -506,6 +511,11 @@
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.allocation[0].allocated_amount = allocated_amount
+
+ # Cr Note and Invoice are of the same currency. There shouldn't any difference amount.
+ for row in pr.allocation:
+ self.assertEqual(flt(row.get("difference_amount")), 0.0)
+
pr.reconcile()
# assert outstanding
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index fc837c7..52eb29b 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -45,21 +45,20 @@
frappe.throw(_("To create a Payment Request reference document is required"))
def validate_payment_request_amount(self):
- existing_payment_request_amount = get_existing_payment_request_amount(
- self.reference_doctype, self.reference_name
+ existing_payment_request_amount = flt(
+ get_existing_payment_request_amount(self.reference_doctype, self.reference_name)
)
- if existing_payment_request_amount:
- ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
- if not hasattr(ref_doc, "order_type") or getattr(ref_doc, "order_type") != "Shopping Cart":
- ref_amount = get_amount(ref_doc, self.payment_account)
+ ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
+ if not hasattr(ref_doc, "order_type") or getattr(ref_doc, "order_type") != "Shopping Cart":
+ ref_amount = get_amount(ref_doc, self.payment_account)
- if existing_payment_request_amount + flt(self.grand_total) > ref_amount:
- frappe.throw(
- _("Total Payment Request amount cannot be greater than {0} amount").format(
- self.reference_doctype
- )
+ if existing_payment_request_amount + flt(self.grand_total) > ref_amount:
+ frappe.throw(
+ _("Total Payment Request amount cannot be greater than {0} amount").format(
+ self.reference_doctype
)
+ )
def validate_currency(self):
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index a03de9e..2608c03 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -1512,9 +1512,12 @@
ref_doc = frappe.get_doc(voucher_type, voucher_no)
# Didn't use db_set for optimisation purpose
- ref_doc.outstanding_amount = outstanding["outstanding_in_account_currency"]
+ ref_doc.outstanding_amount = outstanding["outstanding_in_account_currency"] or 0.0
frappe.db.set_value(
- voucher_type, voucher_no, "outstanding_amount", outstanding["outstanding_in_account_currency"]
+ voucher_type,
+ voucher_no,
+ "outstanding_amount",
+ outstanding["outstanding_in_account_currency"] or 0.0,
)
ref_doc.set_status(update=True)
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 54f0d94..4f7d9ad 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -712,6 +712,8 @@
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/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 9fcb769..fc6793a 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -252,6 +252,7 @@
child.parent = par.name and par.docstatus = 1
and par.is_return = 1 and par.return_against = %s
group by item_code
+ for update
""".format(
column, doc.doctype, doc.doctype
),
diff --git a/erpnext/crm/doctype/lead_source/lead_source.json b/erpnext/crm/doctype/lead_source/lead_source.json
index 723c6d9..c3cedcc 100644
--- a/erpnext/crm/doctype/lead_source/lead_source.json
+++ b/erpnext/crm/doctype/lead_source/lead_source.json
@@ -26,10 +26,11 @@
}
],
"links": [],
- "modified": "2021-02-08 12:51:48.971517",
+ "modified": "2023-02-10 00:51:44.973957",
"modified_by": "Administrator",
"module": "CRM",
"name": "Lead Source",
+ "naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
@@ -58,5 +59,7 @@
],
"quick_entry": 1,
"sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": [],
+ "translated_doctype": 1
}
\ No newline at end of file
diff --git a/erpnext/crm/doctype/sales_stage/sales_stage.json b/erpnext/crm/doctype/sales_stage/sales_stage.json
index 77aa559..caf8ff5 100644
--- a/erpnext/crm/doctype/sales_stage/sales_stage.json
+++ b/erpnext/crm/doctype/sales_stage/sales_stage.json
@@ -18,10 +18,11 @@
}
],
"links": [],
- "modified": "2020-05-20 12:22:01.866472",
+ "modified": "2023-02-10 01:40:23.713390",
"modified_by": "Administrator",
"module": "CRM",
"name": "Sales Stage",
+ "naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
@@ -40,5 +41,7 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
- "track_changes": 1
+ "states": [],
+ "track_changes": 1,
+ "translated_doctype": 1
}
\ No newline at end of file
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 a227b6d..458c79a 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 @@
"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 9186ce6..58a7880 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 @@
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},
@@ -71,6 +71,13 @@
"width": 120,
},
{
+ "label": _("Accrued Principal"),
+ "fieldname": "accrued_principal",
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 120,
+ },
+ {
"label": _("Total Repayment"),
"fieldname": "total_repayment",
"fieldtype": "Currency",
@@ -137,11 +144,16 @@
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 @@
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 @@
- 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_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 @@
"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 @@
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 @@
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 0000000..c65be4e
--- /dev/null
+++ b/erpnext/loan_management/workspace/loans/loans.json
@@ -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 & 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"
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index c2b331f..db699b9 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -289,7 +289,7 @@
{
"fieldname": "scrap_items",
"fieldtype": "Table",
- "label": "Items",
+ "label": "Scrap Items",
"options": "BOM Scrap Item"
},
{
@@ -605,7 +605,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
- "modified": "2023-01-10 07:47:08.652616",
+ "modified": "2023-02-13 17:31:37.504565",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM",
diff --git a/erpnext/patches/v11_0/update_sales_partner_type.py b/erpnext/patches/v11_0/update_sales_partner_type.py
index 2d37fd6..72fd424 100644
--- a/erpnext/patches/v11_0/update_sales_partner_type.py
+++ b/erpnext/patches/v11_0/update_sales_partner_type.py
@@ -1,16 +1,17 @@
import frappe
-from frappe import _
def execute():
- from erpnext.setup.setup_wizard.operations.install_fixtures import default_sales_partner_type
+ from erpnext.setup.setup_wizard.operations.install_fixtures import read_lines
frappe.reload_doc("selling", "doctype", "sales_partner_type")
frappe.local.lang = frappe.db.get_default("lang") or "en"
+ default_sales_partner_type = read_lines("sales_partner_type.txt")
+
for s in default_sales_partner_type:
- insert_sales_partner_type(_(s))
+ insert_sales_partner_type(s)
# get partner type in existing forms (customized)
# and create a document if not created
diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py
index e098c3e..828a55e 100644
--- a/erpnext/projects/doctype/timesheet/test_timesheet.py
+++ b/erpnext/projects/doctype/timesheet/test_timesheet.py
@@ -161,6 +161,37 @@
to_time = timesheet.time_logs[0].to_time
self.assertEqual(to_time, add_to_date(from_time, hours=2, as_datetime=True))
+ def test_per_billed_hours(self):
+ """If amounts are 0, per_billed should be calculated based on hours."""
+ ts = frappe.new_doc("Timesheet")
+ ts.total_billable_amount = 0
+ ts.total_billed_amount = 0
+ ts.total_billable_hours = 2
+
+ ts.total_billed_hours = 0.5
+ ts.calculate_percentage_billed()
+ self.assertEqual(ts.per_billed, 25)
+
+ ts.total_billed_hours = 2
+ ts.calculate_percentage_billed()
+ self.assertEqual(ts.per_billed, 100)
+
+ def test_per_billed_amount(self):
+ """If amounts are > 0, per_billed should be calculated based on amounts, regardless of hours."""
+ ts = frappe.new_doc("Timesheet")
+ ts.total_billable_hours = 2
+ ts.total_billed_hours = 1
+ ts.total_billable_amount = 200
+ ts.total_billed_amount = 50
+ ts.calculate_percentage_billed()
+ self.assertEqual(ts.per_billed, 25)
+
+ ts.total_billed_hours = 3
+ ts.total_billable_amount = 200
+ ts.total_billed_amount = 200
+ ts.calculate_percentage_billed()
+ self.assertEqual(ts.per_billed, 100)
+
def make_timesheet(
employee,
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index f3bd09a..d482a46 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -64,6 +64,8 @@
self.per_billed = 0
if self.total_billed_amount > 0 and self.total_billable_amount > 0:
self.per_billed = (self.total_billed_amount * 100) / self.total_billable_amount
+ elif self.total_billed_hours > 0 and self.total_billable_hours > 0:
+ self.per_billed = (self.total_billed_hours * 100) / self.total_billable_hours
def update_billing_hours(self, args):
if args.is_billable:
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 2ce0c7e..a87c3ec 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -126,7 +126,16 @@
frappe.model.round_floats_in(item);
item.net_rate = item.rate;
item.qty = item.qty === undefined ? (me.frm.doc.is_return ? -1 : 1) : item.qty;
- item.net_amount = item.amount = flt(item.rate * item.qty, precision("amount", item));
+
+ if (!(me.frm.doc.is_return || me.frm.doc.is_debit_note)) {
+ item.net_amount = item.amount = flt(item.rate * item.qty, precision("amount", item));
+ }
+ else {
+ let qty = item.qty || 1;
+ qty = me.frm.doc.is_return ? -1 * qty : qty;
+ item.net_amount = item.amount = flt(item.rate * qty, precision("amount", item));
+ }
+
item.item_tax_amount = 0.0;
item.total_weight = flt(item.weight_per_unit * item.stock_qty);
diff --git a/erpnext/selling/doctype/industry_type/industry_type.json b/erpnext/selling/doctype/industry_type/industry_type.json
index 6c49f0f..3c8ab8e 100644
--- a/erpnext/selling/doctype/industry_type/industry_type.json
+++ b/erpnext/selling/doctype/industry_type/industry_type.json
@@ -1,123 +1,68 @@
{
- "allow_copy": 0,
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "field:industry",
- "beta": 0,
- "creation": "2012-03-27 14:36:09",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 0,
+ "actions": [],
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "field:industry",
+ "creation": "2012-03-27 14:36:09",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "engine": "InnoDB",
+ "field_order": [
+ "industry"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "industry",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "label": "Industry",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "industry",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "fieldname": "industry",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Industry",
+ "oldfieldname": "industry",
+ "oldfieldtype": "Data",
+ "reqd": 1,
+ "unique": 1
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-flag",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2020-09-18 17:26:09.703215",
- "modified_by": "Administrator",
- "module": "Selling",
- "name": "Industry Type",
- "owner": "Administrator",
+ ],
+ "icon": "fa fa-flag",
+ "idx": 1,
+ "links": [],
+ "modified": "2023-02-10 03:14:40.735763",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "Industry Type",
+ "naming_rule": "By fieldname",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Sales Manager",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales User",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
- },
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Sales User"
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales Master Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Sales Master Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "track_seen": 0
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "translated_doctype": 1
}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js
index 6b42e4d..b348bd3 100644
--- a/erpnext/selling/doctype/quotation/quotation.js
+++ b/erpnext/selling/doctype/quotation/quotation.js
@@ -85,11 +85,15 @@
}
if (doc.docstatus == 1 && !["Lost", "Ordered"].includes(doc.status)) {
- this.frm.add_custom_button(
- __("Sales Order"),
- this.frm.cscript["Make Sales Order"],
- __("Create")
- );
+ if (frappe.boot.sysdefaults.allow_sales_order_creation_for_expired_quotation
+ || (!doc.valid_till)
+ || frappe.datetime.get_diff(doc.valid_till, frappe.datetime.get_today()) >= 0) {
+ this.frm.add_custom_button(
+ __("Sales Order"),
+ this.frm.cscript["Make Sales Order"],
+ __("Create")
+ );
+ }
if(doc.status!=="Ordered") {
this.frm.add_custom_button(__('Set as Lost'), () => {
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index 6836d56..063813b 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -195,6 +195,17 @@
@frappe.whitelist()
def make_sales_order(source_name: str, target_doc=None):
+ if not frappe.db.get_singles_value(
+ "Selling Settings", "allow_sales_order_creation_for_expired_quotation"
+ ):
+ quotation = frappe.db.get_value(
+ "Quotation", source_name, ["transaction_date", "valid_till"], as_dict=1
+ )
+ if quotation.valid_till and (
+ quotation.valid_till < quotation.transaction_date or quotation.valid_till < getdate(nowdate())
+ ):
+ frappe.throw(_("Validity period of this quotation has ended."))
+
return _make_sales_order(source_name, target_doc)
diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py
index 5aaba4f..cdf5f5d 100644
--- a/erpnext/selling/doctype/quotation/test_quotation.py
+++ b/erpnext/selling/doctype/quotation/test_quotation.py
@@ -144,11 +144,21 @@
def test_so_from_expired_quotation(self):
from erpnext.selling.doctype.quotation.quotation import make_sales_order
+ frappe.db.set_single_value(
+ "Selling Settings", "allow_sales_order_creation_for_expired_quotation", 0
+ )
+
quotation = frappe.copy_doc(test_records[0])
quotation.valid_till = add_days(nowdate(), -1)
quotation.insert()
quotation.submit()
+ self.assertRaises(frappe.ValidationError, make_sales_order, quotation.name)
+
+ frappe.db.set_single_value(
+ "Selling Settings", "allow_sales_order_creation_for_expired_quotation", 1
+ )
+
make_sales_order(quotation.name)
def test_shopping_cart_without_website_item(self):
diff --git a/erpnext/selling/doctype/sales_partner_type/sales_partner_type.json b/erpnext/selling/doctype/sales_partner_type/sales_partner_type.json
index e7dd0d8..a9b500a 100644
--- a/erpnext/selling/doctype/sales_partner_type/sales_partner_type.json
+++ b/erpnext/selling/doctype/sales_partner_type/sales_partner_type.json
@@ -1,94 +1,47 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "field:sales_partner_type",
- "beta": 0,
- "creation": "2018-06-11 13:15:57.404716",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "autoname": "field:sales_partner_type",
+ "creation": "2018-06-11 13:15:57.404716",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "sales_partner_type"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "sales_partner_type",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Sales Partner Type",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "sales_partner_type",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Sales Partner Type",
+ "reqd": 1,
+ "unique": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-06-11 13:45:13.554307",
- "modified_by": "Administrator",
- "module": "Selling",
- "name": "Sales Partner Type",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "links": [],
+ "modified": "2023-02-10 01:00:20.110800",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "Sales Partner Type",
+ "naming_rule": "By fieldname",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "translated_doctype": 1
}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json
index 2abb169..6ea66a0 100644
--- a/erpnext/selling/doctype/selling_settings/selling_settings.json
+++ b/erpnext/selling/doctype/selling_settings/selling_settings.json
@@ -27,6 +27,7 @@
"column_break_5",
"allow_multiple_items",
"allow_against_multiple_purchase_orders",
+ "allow_sales_order_creation_for_expired_quotation",
"hide_tax_id",
"enable_discount_accounting"
],
@@ -172,6 +173,12 @@
"fieldname": "enable_discount_accounting",
"fieldtype": "Check",
"label": "Enable Discount Accounting for Selling"
+ },
+ {
+ "default": "0",
+ "fieldname": "allow_sales_order_creation_for_expired_quotation",
+ "fieldtype": "Check",
+ "label": "Allow Sales Order Creation For Expired Quotation"
}
],
"icon": "fa fa-cog",
@@ -179,7 +186,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2022-05-31 19:39:48.398738",
+ "modified": "2023-02-04 12:37:53.380857",
"modified_by": "Administrator",
"module": "Selling",
"name": "Selling Settings",
diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js
index 0a356b9..89ce61ab 100644
--- a/erpnext/selling/page/point_of_sale/pos_payment.js
+++ b/erpnext/selling/page/point_of_sale/pos_payment.js
@@ -322,6 +322,11 @@
this.focus_on_default_mop();
}
+ after_render() {
+ const frm = this.events.get_frm();
+ frm.script_manager.trigger("after_payment_render", frm.doc.doctype, frm.doc.docname);
+ }
+
edit_cart() {
this.events.toggle_other_sections(false);
this.toggle_component(false);
@@ -332,6 +337,7 @@
this.toggle_component(true);
this.render_payment_section();
+ this.after_render();
}
toggle_remarks_control() {
diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js
index 991ac71..990d736 100644
--- a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js
+++ b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js
@@ -103,6 +103,11 @@
return options
}
},
+ {
+ "fieldname":"only_immediate_upcoming_term",
+ "label": __("Show only the Immediate Upcoming Term"),
+ "fieldtype": "Check",
+ },
]
return filters;
}
diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py
index 8bf5686..3682c5f 100644
--- a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py
+++ b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py
@@ -4,6 +4,7 @@
import frappe
from frappe import _, qb, query_builder
from frappe.query_builder import Criterion, functions
+from frappe.utils.dateutils import getdate
def get_columns():
@@ -208,6 +209,7 @@
)
.where(
(so.docstatus == 1)
+ & (so.status.isin(["To Deliver and Bill", "To Bill"]))
& (so.payment_terms_template != "NULL")
& (so.company == conditions.company)
& (so.transaction_date[conditions.start_date : conditions.end_date])
@@ -291,6 +293,18 @@
return sales_orders
+def filter_for_immediate_upcoming_term(filters, sales_orders):
+ if filters.only_immediate_upcoming_term and sales_orders:
+ immediate_term_found = set()
+ filtered_data = []
+ for order in sales_orders:
+ if order.name not in immediate_term_found and order.due_date > getdate():
+ filtered_data.append(order)
+ immediate_term_found.add(order.name)
+ return filtered_data
+ return sales_orders
+
+
def execute(filters=None):
columns = get_columns()
sales_orders, so_invoices = get_so_with_invoices(filters)
@@ -298,6 +312,8 @@
sales_orders = filter_on_calculated_status(filters, sales_orders)
+ sales_orders = filter_for_immediate_upcoming_term(filters, sales_orders)
+
prepare_chart(sales_orders)
data = sales_orders
diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
index 63d339a..2969123 100644
--- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
+++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
@@ -175,7 +175,9 @@
# update existing entry
so_row = sales_order_map[so_name]
so_row["required_date"] = max(getdate(so_row["delivery_date"]), getdate(row["delivery_date"]))
- so_row["delay"] = min(so_row["delay"], row["delay"])
+ so_row["delay"] = (
+ min(so_row["delay"], row["delay"]) if row["delay"] and so_row["delay"] else so_row["delay"]
+ )
# sum numeric columns
fields = [
diff --git a/erpnext/setup/doctype/designation/designation.json b/erpnext/setup/doctype/designation/designation.json
index 2cbbb04..a5b2ac9 100644
--- a/erpnext/setup/doctype/designation/designation.json
+++ b/erpnext/setup/doctype/designation/designation.json
@@ -31,7 +31,7 @@
"icon": "fa fa-bookmark",
"idx": 1,
"links": [],
- "modified": "2022-06-28 17:10:26.853753",
+ "modified": "2023-02-10 01:53:41.319386",
"modified_by": "Administrator",
"module": "Setup",
"name": "Designation",
@@ -58,5 +58,6 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "ASC",
- "states": []
+ "states": [],
+ "translated_doctype": 1
}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.js b/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.js
index 3680906..c3605bf 100644
--- a/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.js
+++ b/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.js
@@ -1,13 +1,6 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
-
-
-//--------- ONLOAD -------------
-cur_frm.cscript.onload = function(doc, cdt, cdn) {
-
-}
-
-cur_frm.cscript.refresh = function(doc, cdt, cdn) {
-
-}
+// frappe.ui.form.on("Terms and Conditions", {
+// refresh(frm) {}
+// });
diff --git a/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json b/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json
index f14b243..f884864 100644
--- a/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json
+++ b/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json
@@ -33,7 +33,6 @@
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
- "in_list_view": 1,
"label": "Disabled"
},
{
@@ -60,12 +59,14 @@
"default": "1",
"fieldname": "selling",
"fieldtype": "Check",
+ "in_list_view": 1,
"label": "Selling"
},
{
"default": "1",
"fieldname": "buying",
"fieldtype": "Check",
+ "in_list_view": 1,
"label": "Buying"
},
{
@@ -76,10 +77,11 @@
"icon": "icon-legal",
"idx": 1,
"links": [],
- "modified": "2022-06-16 15:07:38.094844",
+ "modified": "2023-02-01 14:33:39.246532",
"modified_by": "Administrator",
"module": "Setup",
"name": "Terms and Conditions",
+ "naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
@@ -133,5 +135,6 @@
"quick_entry": 1,
"show_name_in_global_search": 1,
"sort_field": "modified",
- "sort_order": "ASC"
+ "sort_order": "ASC",
+ "states": []
}
\ No newline at end of file
diff --git a/erpnext/setup/setup_wizard/data/designation.txt b/erpnext/setup/setup_wizard/data/designation.txt
new file mode 100644
index 0000000..4c6d7bd
--- /dev/null
+++ b/erpnext/setup/setup_wizard/data/designation.txt
@@ -0,0 +1,31 @@
+Accountant
+Administrative Assistant
+Administrative Officer
+Analyst
+Associate
+Business Analyst
+Business Development Manager
+Consultant
+Chief Executive Officer
+Chief Financial Officer
+Chief Operating Officer
+Chief Technology Officer
+Customer Service Representative
+Designer
+Engineer
+Executive Assistant
+Finance Manager
+HR Manager
+Head of Marketing and Sales
+Manager
+Managing Director
+Marketing Manager
+Marketing Specialist
+President
+Product Manager
+Project Manager
+Researcher
+Sales Representative
+Secretary
+Software Developer
+Vice President
diff --git a/erpnext/setup/setup_wizard/data/industry_type.py b/erpnext/setup/setup_wizard/data/industry_type.py
deleted file mode 100644
index 0bc3f32..0000000
--- a/erpnext/setup/setup_wizard/data/industry_type.py
+++ /dev/null
@@ -1,57 +0,0 @@
-from frappe import _
-
-
-def get_industry_types():
- return [
- _("Accounting"),
- _("Advertising"),
- _("Aerospace"),
- _("Agriculture"),
- _("Airline"),
- _("Apparel & Accessories"),
- _("Automotive"),
- _("Banking"),
- _("Biotechnology"),
- _("Broadcasting"),
- _("Brokerage"),
- _("Chemical"),
- _("Computer"),
- _("Consulting"),
- _("Consumer Products"),
- _("Cosmetics"),
- _("Defense"),
- _("Department Stores"),
- _("Education"),
- _("Electronics"),
- _("Energy"),
- _("Entertainment & Leisure"),
- _("Executive Search"),
- _("Financial Services"),
- _("Food, Beverage & Tobacco"),
- _("Grocery"),
- _("Health Care"),
- _("Internet Publishing"),
- _("Investment Banking"),
- _("Legal"),
- _("Manufacturing"),
- _("Motion Picture & Video"),
- _("Music"),
- _("Newspaper Publishers"),
- _("Online Auctions"),
- _("Pension Funds"),
- _("Pharmaceuticals"),
- _("Private Equity"),
- _("Publishing"),
- _("Real Estate"),
- _("Retail & Wholesale"),
- _("Securities & Commodity Exchanges"),
- _("Service"),
- _("Soap & Detergent"),
- _("Software"),
- _("Sports"),
- _("Technology"),
- _("Telecommunications"),
- _("Television"),
- _("Transportation"),
- _("Venture Capital"),
- ]
diff --git a/erpnext/setup/setup_wizard/data/industry_type.txt b/erpnext/setup/setup_wizard/data/industry_type.txt
new file mode 100644
index 0000000..eadc689
--- /dev/null
+++ b/erpnext/setup/setup_wizard/data/industry_type.txt
@@ -0,0 +1,51 @@
+Accounting
+Advertising
+Aerospace
+Agriculture
+Airline
+Apparel & Accessories
+Automotive
+Banking
+Biotechnology
+Broadcasting
+Brokerage
+Chemical
+Computer
+Consulting
+Consumer Products
+Cosmetics
+Defense
+Department Stores
+Education
+Electronics
+Energy
+Entertainment & Leisure
+Executive Search
+Financial Services
+Food, Beverage & Tobacco
+Grocery
+Health Care
+Internet Publishing
+Investment Banking
+Legal
+Manufacturing
+Motion Picture & Video
+Music
+Newspaper Publishers
+Online Auctions
+Pension Funds
+Pharmaceuticals
+Private Equity
+Publishing
+Real Estate
+Retail & Wholesale
+Securities & Commodity Exchanges
+Service
+Soap & Detergent
+Software
+Sports
+Technology
+Telecommunications
+Television
+Transportation
+Venture Capital
diff --git a/erpnext/setup/setup_wizard/data/lead_source.txt b/erpnext/setup/setup_wizard/data/lead_source.txt
new file mode 100644
index 0000000..00ca180
--- /dev/null
+++ b/erpnext/setup/setup_wizard/data/lead_source.txt
@@ -0,0 +1,10 @@
+Existing Customer
+Reference
+Advertisement
+Cold Calling
+Exhibition
+Supplier Reference
+Mass Mailing
+Customer's Vendor
+Campaign
+Walk In
diff --git a/erpnext/setup/setup_wizard/data/sales_partner_type.txt b/erpnext/setup/setup_wizard/data/sales_partner_type.txt
new file mode 100644
index 0000000..68e9b9a
--- /dev/null
+++ b/erpnext/setup/setup_wizard/data/sales_partner_type.txt
@@ -0,0 +1,7 @@
+Channel Partner
+Distributor
+Dealer
+Agent
+Retailer
+Implementation Partner
+Reseller
diff --git a/erpnext/setup/setup_wizard/data/sales_stage.txt b/erpnext/setup/setup_wizard/data/sales_stage.txt
new file mode 100644
index 0000000..2808ce7
--- /dev/null
+++ b/erpnext/setup/setup_wizard/data/sales_stage.txt
@@ -0,0 +1,8 @@
+Prospecting
+Qualification
+Needs Analysis
+Value Proposition
+Identifying Decision Makers
+Perception Analysis
+Proposal/Price Quote
+Negotiation/Review
diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py
index 4d9b871..6bc1771 100644
--- a/erpnext/setup/setup_wizard/operations/install_fixtures.py
+++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py
@@ -4,6 +4,7 @@
import json
import os
+from pathlib import Path
import frappe
from frappe import _
@@ -16,28 +17,10 @@
from erpnext.accounts.doctype.account.account import RootNotEditable
from erpnext.regional.address_template.setup import set_up_address_templates
-default_lead_sources = [
- "Existing Customer",
- "Reference",
- "Advertisement",
- "Cold Calling",
- "Exhibition",
- "Supplier Reference",
- "Mass Mailing",
- "Customer's Vendor",
- "Campaign",
- "Walk In",
-]
-default_sales_partner_type = [
- "Channel Partner",
- "Distributor",
- "Dealer",
- "Agent",
- "Retailer",
- "Implementation Partner",
- "Reseller",
-]
+def read_lines(filename: str) -> list[str]:
+ """Return a list of lines from a file in the data directory."""
+ return (Path(__file__).parent.parent / "data" / filename).read_text().splitlines()
def install(country=None):
@@ -85,7 +68,11 @@
# Stock Entry Type
{"doctype": "Stock Entry Type", "name": "Material Issue", "purpose": "Material Issue"},
{"doctype": "Stock Entry Type", "name": "Material Receipt", "purpose": "Material Receipt"},
- {"doctype": "Stock Entry Type", "name": "Material Transfer", "purpose": "Material Transfer"},
+ {
+ "doctype": "Stock Entry Type",
+ "name": "Material Transfer",
+ "purpose": "Material Transfer",
+ },
{"doctype": "Stock Entry Type", "name": "Manufacture", "purpose": "Manufacture"},
{"doctype": "Stock Entry Type", "name": "Repack", "purpose": "Repack"},
{
@@ -103,22 +90,6 @@
"name": "Material Consumption for Manufacture",
"purpose": "Material Consumption for Manufacture",
},
- # Designation
- {"doctype": "Designation", "designation_name": _("CEO")},
- {"doctype": "Designation", "designation_name": _("Manager")},
- {"doctype": "Designation", "designation_name": _("Analyst")},
- {"doctype": "Designation", "designation_name": _("Engineer")},
- {"doctype": "Designation", "designation_name": _("Accountant")},
- {"doctype": "Designation", "designation_name": _("Secretary")},
- {"doctype": "Designation", "designation_name": _("Associate")},
- {"doctype": "Designation", "designation_name": _("Administrative Officer")},
- {"doctype": "Designation", "designation_name": _("Business Development Manager")},
- {"doctype": "Designation", "designation_name": _("HR Manager")},
- {"doctype": "Designation", "designation_name": _("Project Manager")},
- {"doctype": "Designation", "designation_name": _("Head of Marketing and Sales")},
- {"doctype": "Designation", "designation_name": _("Software Developer")},
- {"doctype": "Designation", "designation_name": _("Designer")},
- {"doctype": "Designation", "designation_name": _("Researcher")},
# territory: with two default territories, one for home country and one named Rest of the World
{
"doctype": "Territory",
@@ -291,28 +262,18 @@
{"doctype": "Market Segment", "market_segment": _("Lower Income")},
{"doctype": "Market Segment", "market_segment": _("Middle Income")},
{"doctype": "Market Segment", "market_segment": _("Upper Income")},
- # Sales Stages
- {"doctype": "Sales Stage", "stage_name": _("Prospecting")},
- {"doctype": "Sales Stage", "stage_name": _("Qualification")},
- {"doctype": "Sales Stage", "stage_name": _("Needs Analysis")},
- {"doctype": "Sales Stage", "stage_name": _("Value Proposition")},
- {"doctype": "Sales Stage", "stage_name": _("Identifying Decision Makers")},
- {"doctype": "Sales Stage", "stage_name": _("Perception Analysis")},
- {"doctype": "Sales Stage", "stage_name": _("Proposal/Price Quote")},
- {"doctype": "Sales Stage", "stage_name": _("Negotiation/Review")},
# Warehouse Type
{"doctype": "Warehouse Type", "name": "Transit"},
]
- from erpnext.setup.setup_wizard.data.industry_type import get_industry_types
-
- records += [{"doctype": "Industry Type", "industry": d} for d in get_industry_types()]
- # records += [{"doctype":"Operation", "operation": d} for d in get_operations()]
- records += [{"doctype": "Lead Source", "source_name": _(d)} for d in default_lead_sources]
-
- records += [
- {"doctype": "Sales Partner Type", "sales_partner_type": _(d)} for d in default_sales_partner_type
- ]
+ for doctype, title_field, filename in (
+ ("Designation", "designation_name", "designation.txt"),
+ ("Sales Stage", "stage_name", "sales_stage.txt"),
+ ("Industry Type", "industry", "industry_type.txt"),
+ ("Lead Source", "source_name", "lead_source.txt"),
+ ("Sales Partner Type", "sales_partner_type", "sales_partner_type.txt"),
+ ):
+ records += [{"doctype": doctype, title_field: title} for title in read_lines(filename)]
base_path = frappe.get_app_path("erpnext", "stock", "doctype")
response = frappe.read_file(
@@ -335,16 +296,11 @@
make_default_records()
make_records(records)
set_up_address_templates(default_country=country)
- set_more_defaults()
- update_global_search_doctypes()
-
-
-def set_more_defaults():
- # Do more setup stuff that can be done here with no dependencies
update_selling_defaults()
update_buying_defaults()
add_uom_data()
update_item_variant_settings()
+ update_global_search_doctypes()
def update_selling_defaults():
@@ -381,7 +337,7 @@
)
for d in uoms:
if not frappe.db.exists("UOM", _(d.get("uom_name"))):
- uom_doc = frappe.get_doc(
+ frappe.get_doc(
{
"doctype": "UOM",
"uom_name": _(d.get("uom_name")),
@@ -402,9 +358,10 @@
frappe.get_doc({"doctype": "UOM Category", "category_name": _(d.get("category"))}).db_insert()
if not frappe.db.exists(
- "UOM Conversion Factor", {"from_uom": _(d.get("from_uom")), "to_uom": _(d.get("to_uom"))}
+ "UOM Conversion Factor",
+ {"from_uom": _(d.get("from_uom")), "to_uom": _(d.get("to_uom"))},
):
- uom_conversion = frappe.get_doc(
+ frappe.get_doc(
{
"doctype": "UOM Conversion Factor",
"category": _(d.get("category")),
@@ -412,7 +369,7 @@
"to_uom": _(d.get("to_uom")),
"value": d.get("value"),
}
- ).insert(ignore_permissions=True)
+ ).db_insert()
def add_market_segments():
@@ -468,7 +425,7 @@
make_records(records)
-def install_defaults(args=None):
+def install_defaults(args=None): # nosemgrep
records = [
# Price Lists
{
@@ -493,7 +450,7 @@
# enable default currency
frappe.db.set_value("Currency", args.get("currency"), "enabled", 1)
- frappe.db.set_value("Stock Settings", None, "email_footer_address", args.get("company_name"))
+ frappe.db.set_single_value("Stock Settings", "email_footer_address", args.get("company_name"))
set_global_defaults(args)
update_stock_settings()
@@ -540,7 +497,8 @@
company_name = args.get("company_name")
bank_account_group = frappe.db.get_value(
- "Account", {"account_type": "Bank", "is_group": 1, "root_type": "Asset", "company": company_name}
+ "Account",
+ {"account_type": "Bank", "is_group": 1, "root_type": "Asset", "company": company_name},
)
if bank_account_group:
bank_account = frappe.get_doc(
diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py
index 2f77dd6..49ba78c 100644
--- a/erpnext/setup/setup_wizard/operations/taxes_setup.py
+++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py
@@ -158,6 +158,7 @@
# Ingone validations to make doctypes faster
doc.flags.ignore_links = True
doc.flags.ignore_validate = True
+ doc.flags.ignore_mandatory = True
doc.insert(ignore_permissions=True)
return doc
diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py
index bb120ea..62936fc 100644
--- a/erpnext/startup/boot.py
+++ b/erpnext/startup/boot.py
@@ -25,6 +25,12 @@
frappe.db.get_single_value("CRM Settings", "default_valid_till")
)
+ bootinfo.sysdefaults.allow_sales_order_creation_for_expired_quotation = cint(
+ frappe.db.get_single_value(
+ "Selling Settings", "allow_sales_order_creation_for_expired_quotation"
+ )
+ )
+
# if no company, show a dialog box to create a new company
bootinfo.customer_count = frappe.db.sql("""SELECT count(*) FROM `tabCustomer`""")[0][0]
diff --git a/erpnext/stock/doctype/item_price/test_records.json b/erpnext/stock/doctype/item_price/test_records.json
index 0a3d7e8..afe5ad6 100644
--- a/erpnext/stock/doctype/item_price/test_records.json
+++ b/erpnext/stock/doctype/item_price/test_records.json
@@ -38,5 +38,19 @@
"price_list_rate": 1000,
"valid_from": "2017-04-10",
"valid_upto": "2017-04-17"
+ },
+ {
+ "doctype": "Item Price",
+ "item_code": "_Test Item",
+ "price_list": "_Test Buying Price List",
+ "price_list_rate": 100,
+ "supplier": "_Test Supplier"
+ },
+ {
+ "doctype": "Item Price",
+ "item_code": "_Test Item",
+ "price_list": "_Test Selling Price List",
+ "price_list_rate": 200,
+ "customer": "_Test Customer"
}
]
diff --git a/erpnext/stock/doctype/price_list/test_records.json b/erpnext/stock/doctype/price_list/test_records.json
index 7ca949c..e02a7ad 100644
--- a/erpnext/stock/doctype/price_list/test_records.json
+++ b/erpnext/stock/doctype/price_list/test_records.json
@@ -31,5 +31,21 @@
"enabled": 1,
"price_list_name": "_Test Price List Rest of the World",
"selling": 1
+ },
+ {
+ "buying": 0,
+ "currency": "USD",
+ "doctype": "Price List",
+ "enabled": 1,
+ "price_list_name": "_Test Selling Price List",
+ "selling": 1
+ },
+ {
+ "buying": 1,
+ "currency": "USD",
+ "doctype": "Price List",
+ "enabled": 1,
+ "price_list_name": "_Test Buying Price List",
+ "selling": 0
}
]
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 8c20ca0..7f69397 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -2494,7 +2494,7 @@
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/get_item_details.py b/erpnext/stock/get_item_details.py
index 5af1441..b53f429 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -88,8 +88,15 @@
update_party_blanket_order(args, out)
+ # Never try to find a customer price if customer is set in these Doctype
+ current_customer = args.customer
+ if args.get("doctype") in ["Purchase Order", "Purchase Receipt", "Purchase Invoice"]:
+ args.customer = None
+
out.update(get_price_list_rate(args, item))
+ args.customer = current_customer
+
if args.customer and cint(args.is_pos):
out.update(get_pos_profile_item_details(args.company, args, update_data=True))
diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py
index 14cedd2..439ed7a 100644
--- a/erpnext/stock/stock_balance.py
+++ b/erpnext/stock/stock_balance.py
@@ -121,7 +121,7 @@
and parenttype='Sales Order'
and item_code != parent_item
and exists (select * from `tabSales Order` so
- where name = dnpi_in.parent and docstatus = 1 and status != 'Closed')
+ where name = dnpi_in.parent and docstatus = 1 and status not in ('On Hold', 'Closed'))
) dnpi)
union
(select stock_qty as dnpi_qty, qty as so_item_qty,
@@ -131,7 +131,7 @@
and (so_item.delivered_by_supplier is null or so_item.delivered_by_supplier = 0)
and exists(select * from `tabSales Order` so
where so.name = so_item.parent and so.docstatus = 1
- and so.status != 'Closed'))
+ and so.status not in ('On Hold', 'Closed')))
) tab
where
so_item_qty >= so_item_delivered_qty
diff --git a/erpnext/stock/tests/test_get_item_details.py b/erpnext/stock/tests/test_get_item_details.py
new file mode 100644
index 0000000..b53e29e
--- /dev/null
+++ b/erpnext/stock/tests/test_get_item_details.py
@@ -0,0 +1,40 @@
+import json
+
+import frappe
+from frappe.test_runner import make_test_records
+from frappe.tests.utils import FrappeTestCase
+
+from erpnext.stock.get_item_details import get_item_details
+
+test_ignore = ["BOM"]
+test_dependencies = ["Customer", "Supplier", "Item", "Price List", "Item Price"]
+
+
+class TestGetItemDetail(FrappeTestCase):
+ def setUp(self):
+ make_test_records("Price List")
+ super().setUp()
+
+ def test_get_item_detail_purchase_order(self):
+
+ args = frappe._dict(
+ {
+ "item_code": "_Test Item",
+ "company": "_Test Company",
+ "customer": "_Test Customer",
+ "conversion_rate": 1.0,
+ "price_list_currency": "USD",
+ "plc_conversion_rate": 1.0,
+ "doctype": "Purchase Order",
+ "name": None,
+ "supplier": "_Test Supplier",
+ "transaction_date": None,
+ "conversion_rate": 1.0,
+ "price_list": "_Test Buying Price List",
+ "is_subcontracted": 0,
+ "ignore_pricing_rule": 1,
+ "qty": 1,
+ }
+ )
+ details = get_item_details(args)
+ self.assertEqual(details.get("price_list_rate"), 100)
diff --git a/erpnext/www/shop-by-category/index.py b/erpnext/www/shop-by-category/index.py
index 8a92418..219747c 100644
--- a/erpnext/www/shop-by-category/index.py
+++ b/erpnext/www/shop-by-category/index.py
@@ -51,21 +51,31 @@
return tab_values
-def get_category_records(categories):
+def get_category_records(categories: list):
categorical_data = {}
- for category in categories:
- if category == "item_group":
+
+ for c in categories:
+ if c == "item_group":
categorical_data["item_group"] = frappe.db.get_all(
"Item Group",
filters={"parent_item_group": "All Item Groups", "show_in_website": 1},
fields=["name", "parent_item_group", "is_group", "image", "route"],
)
- else:
- doctype = frappe.unscrub(category)
- fields = ["name"]
- if frappe.get_meta(doctype, cached=True).get_field("image"):
+
+ continue
+
+ doctype = frappe.unscrub(c)
+ fields = ["name"]
+
+ try:
+ meta = frappe.get_meta(doctype, cached=True)
+ if meta.get_field("image"):
fields += ["image"]
- categorical_data[category] = frappe.db.get_all(doctype, fields=fields)
+ data = frappe.db.get_all(doctype, fields=fields)
+ categorical_data[c] = data
+ except BaseException:
+ frappe.throw(_("DocType {} not found").format(doctype))
+ continue
return categorical_data