diff --git a/CODEOWNERS b/CODEOWNERS
index e406f8f56ee..b4503017f12 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -4,7 +4,7 @@
# the repo. Unless a later match takes precedence,
erpnext/accounts/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
-erpnext/assets/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
+erpnext/assets/ @anandbaburajan @deepeshgarg007
erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007
erpnext/regional @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/selling @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py
index 331adb4b8e3..b4df0a5270c 100644
--- a/erpnext/accounts/doctype/dunning/dunning.py
+++ b/erpnext/accounts/doctype/dunning/dunning.py
@@ -40,7 +40,7 @@ class Dunning(AccountsController):
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 154fdc039d4..675a3287fa4 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -234,7 +234,7 @@ class PaymentReconciliation(Document):
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 @@ class PaymentReconciliation(Document):
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 @@ class PaymentReconciliation(Document):
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 00e3934f10c..f9dda0593b0 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 @@ class TestPaymentReconciliation(FrappeTestCase):
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 @@ class TestPaymentReconciliation(FrappeTestCase):
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 fc837c75a30..52eb29b3bbd 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -45,21 +45,20 @@ class PaymentRequest(Document):
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/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py
index 43b95dca80e..58276970232 100644
--- a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py
+++ b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py
@@ -135,6 +135,34 @@ def get_assets(filters):
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and ads.asset = a.name and ads.docstatus=1 and ads.name = ds.parent and ifnull(ds.journal_entry, '') != ''
group by a.asset_category
union
+ SELECT a.asset_category,
+ ifnull(sum(case when gle.posting_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then
+ gle.debit
+ else
+ 0
+ end), 0) as accumulated_depreciation_as_on_from_date,
+ ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s
+ and a.disposal_date <= %(to_date)s and gle.posting_date <= a.disposal_date then
+ gle.debit
+ else
+ 0
+ end), 0) as depreciation_eliminated_during_the_period,
+ ifnull(sum(case when gle.posting_date >= %(from_date)s and gle.posting_date <= %(to_date)s
+ and (ifnull(a.disposal_date, 0) = 0 or gle.posting_date <= a.disposal_date) then
+ gle.debit
+ else
+ 0
+ end), 0) as depreciation_amount_during_the_period
+ from `tabGL Entry` gle
+ join `tabAsset` a on
+ gle.against_voucher = a.name
+ join `tabAsset Category Account` aca on
+ aca.parent = a.asset_category and aca.company_name = %(company)s
+ join `tabCompany` company on
+ company.name = %(company)s
+ where a.docstatus=1 and a.company=%(company)s and a.calculate_depreciation=0 and a.purchase_date <= %(to_date)s and gle.debit != 0 and gle.is_cancelled = 0 and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account)
+ group by a.asset_category
+ union
SELECT a.asset_category,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s) then
0
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index a03de9e1940..2608c03ffe5 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -1512,9 +1512,12 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa
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/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index 4ed99f7e496..a61e8de7481 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -209,62 +209,62 @@ frappe.ui.form.on('Asset', {
return
}
- var x_intervals = [frm.doc.purchase_date];
+ var x_intervals = [frappe.format(frm.doc.purchase_date, { fieldtype: 'Date' })];
var asset_values = [frm.doc.gross_purchase_amount];
- var last_depreciation_date = frm.doc.purchase_date;
- if(frm.doc.opening_accumulated_depreciation) {
- last_depreciation_date = frappe.datetime.add_months(frm.doc.next_depreciation_date,
- -1*frm.doc.frequency_of_depreciation);
-
- x_intervals.push(last_depreciation_date);
- asset_values.push(flt(frm.doc.gross_purchase_amount) -
- flt(frm.doc.opening_accumulated_depreciation));
- }
if(frm.doc.calculate_depreciation) {
- if (frm.doc.finance_books.length == 1) {
- let depr_schedule = (await frappe.call(
- "erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule.get_depr_schedule",
- {
- asset_name: frm.doc.name,
- status: frm.doc.docstatus ? "Active" : "Draft",
- finance_book: frm.doc.finance_books[0].finance_book || null
- }
- )).message;
-
- $.each(depr_schedule || [], function(i, v) {
- x_intervals.push(v.schedule_date);
- var asset_value = flt(frm.doc.gross_purchase_amount) - flt(v.accumulated_depreciation_amount);
- if(v.journal_entry) {
- last_depreciation_date = v.schedule_date;
- asset_values.push(asset_value);
- } else {
- if (in_list(["Scrapped", "Sold"], frm.doc.status)) {
- asset_values.push(null);
- } else {
- asset_values.push(asset_value)
- }
- }
- });
+ if(frm.doc.opening_accumulated_depreciation) {
+ var depreciation_date = frappe.datetime.add_months(
+ frm.doc.finance_books[0].depreciation_start_date,
+ -1 * frm.doc.finance_books[0].frequency_of_depreciation
+ );
+ x_intervals.push(frappe.format(depreciation_date, { fieldtype: 'Date' }));
+ asset_values.push(flt(frm.doc.gross_purchase_amount - frm.doc.opening_accumulated_depreciation, precision('gross_purchase_amount')));
}
+
+ let depr_schedule = (await frappe.call(
+ "erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule.get_depr_schedule",
+ {
+ asset_name: frm.doc.name,
+ status: frm.doc.docstatus ? "Active" : "Draft",
+ finance_book: frm.doc.finance_books[0].finance_book || null
+ }
+ )).message;
+
+ $.each(depr_schedule || [], function(i, v) {
+ x_intervals.push(frappe.format(v.schedule_date, { fieldtype: 'Date' }));
+ var asset_value = flt(frm.doc.gross_purchase_amount - v.accumulated_depreciation_amount, precision('gross_purchase_amount'));
+ if(v.journal_entry) {
+ asset_values.push(asset_value);
+ } else {
+ if (in_list(["Scrapped", "Sold"], frm.doc.status)) {
+ asset_values.push(null);
+ } else {
+ asset_values.push(asset_value)
+ }
+ }
+ });
} else {
+ if(frm.doc.opening_accumulated_depreciation) {
+ x_intervals.push(frappe.format(frm.doc.creation.split(" ")[0], { fieldtype: 'Date' }));
+ asset_values.push(flt(frm.doc.gross_purchase_amount - frm.doc.opening_accumulated_depreciation, precision('gross_purchase_amount')));
+ }
+
let depr_entries = (await frappe.call({
method: "get_manual_depreciation_entries",
doc: frm.doc,
})).message;
$.each(depr_entries || [], function(i, v) {
- x_intervals.push(v.posting_date);
- last_depreciation_date = v.posting_date;
+ x_intervals.push(frappe.format(v.posting_date, { fieldtype: 'Date' }));
let last_asset_value = asset_values[asset_values.length - 1]
asset_values.push(last_asset_value - v.value);
});
}
if(in_list(["Scrapped", "Sold"], frm.doc.status)) {
- x_intervals.push(frm.doc.disposal_date);
+ x_intervals.push(frappe.format(frm.doc.disposal_date, { fieldtype: 'Date' }));
asset_values.push(0);
- last_depreciation_date = frm.doc.disposal_date;
}
frm.dashboard.render_graph({
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 4f1cacaad50..e00f3a5834f 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -429,25 +429,16 @@ class Asset(AccountsController):
def get_value_after_depreciation(self, finance_book=None):
if not self.calculate_depreciation:
- return self.value_after_depreciation
+ return flt(self.value_after_depreciation, self.precision("gross_purchase_amount"))
if not finance_book:
- return self.get("finance_books")[0].value_after_depreciation
+ return flt(
+ self.get("finance_books")[0].value_after_depreciation, self.precision("gross_purchase_amount")
+ )
for row in self.get("finance_books"):
if finance_book == row.finance_book:
- return row.value_after_depreciation
-
- def _get_value_after_depreciation_for_making_schedule(self, fb_row):
- # value_after_depreciation - current Asset value
- if self.docstatus == 1 and fb_row.value_after_depreciation:
- value_after_depreciation = flt(fb_row.value_after_depreciation)
- else:
- value_after_depreciation = flt(self.gross_purchase_amount) - flt(
- self.opening_accumulated_depreciation
- )
-
- return value_after_depreciation
+ return flt(row.value_after_depreciation, self.precision("gross_purchase_amount"))
def get_default_finance_book_idx(self):
if not self.get("default_finance_book") and self.company:
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
index 7615fbc86f9..6f026625441 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
@@ -134,7 +134,7 @@ class AssetDepreciationSchedule(Document):
):
asset_doc.validate_asset_finance_books(row)
- value_after_depreciation = asset_doc._get_value_after_depreciation_for_making_schedule(row)
+ value_after_depreciation = _get_value_after_depreciation_for_making_schedule(asset_doc, row)
row.value_after_depreciation = value_after_depreciation
if update_asset_finance_book_row:
@@ -325,6 +325,17 @@ class AssetDepreciationSchedule(Document):
)
+def _get_value_after_depreciation_for_making_schedule(asset_doc, fb_row):
+ if asset_doc.docstatus == 1 and fb_row.value_after_depreciation:
+ value_after_depreciation = flt(fb_row.value_after_depreciation)
+ else:
+ value_after_depreciation = flt(asset_doc.gross_purchase_amount) - flt(
+ asset_doc.opening_accumulated_depreciation
+ )
+
+ return value_after_depreciation
+
+
def make_draft_asset_depr_schedules_if_not_present(asset_doc):
for row in asset_doc.get("finance_books"):
draft_asset_depr_schedule_name = get_asset_depr_schedule_name(
diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
index cead72eae5a..51a6a86e9f6 100644
--- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
+++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
@@ -5,7 +5,7 @@
import frappe
from frappe import _
from frappe.query_builder.functions import Sum
-from frappe.utils import cstr, formatdate, getdate
+from frappe.utils import cstr, flt, formatdate, getdate
from erpnext.accounts.report.financial_statements import (
get_fiscal_year_data,
@@ -102,13 +102,9 @@ def get_data(filters):
]
assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields)
- finance_book_filter = ("is", "not set")
- if filters.finance_book:
- finance_book_filter = ("=", filters.finance_book)
-
assets_linked_to_fb = frappe.db.get_all(
doctype="Asset Finance Book",
- filters={"finance_book": finance_book_filter},
+ filters={"finance_book": filters.finance_book or ("is", "not set")},
pluck="parent",
)
@@ -194,7 +190,7 @@ def get_depreciation_amount_of_asset(asset, depreciation_amount_map, filters):
else:
depr_amount = get_manual_depreciation_amount_of_asset(asset, filters)
- return depr_amount
+ return flt(depr_amount, 2)
def get_finance_book_value_map(filters):
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
index a9f5afb2e98..2f0b7862a82 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
@@ -124,12 +124,11 @@ frappe.ui.form.on("Request for Quotation",{
frappe.urllib.get_full_url(
"/api/method/erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_pdf?" +
new URLSearchParams({
- doctype: frm.doc.doctype,
name: frm.doc.name,
supplier: data.supplier,
print_format: data.print_format || "Standard",
language: data.language || frappe.boot.lang,
- letter_head: data.letter_head || frm.doc.letter_head || "",
+ letterhead: data.letter_head || frm.doc.letter_head || "",
}).toString()
)
);
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index 8e9ded98421..7927beb8233 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -3,6 +3,7 @@
import json
+from typing import Optional
import frappe
from frappe import _
@@ -388,24 +389,26 @@ def create_rfq_items(sq_doc, supplier, data):
@frappe.whitelist()
-def get_pdf(doctype, name, supplier, print_format=None, language=None, letter_head=None):
- # permissions get checked in `download_pdf`
- if doc := get_rfq_doc(doctype, name, supplier):
- download_pdf(
- doctype,
- name,
- print_format,
- doc=doc,
- language=language,
- letter_head=letter_head or None,
- )
-
-
-def get_rfq_doc(doctype, name, supplier):
+def get_pdf(
+ name: str,
+ supplier: str,
+ print_format: Optional[str] = None,
+ language: Optional[str] = None,
+ letterhead: Optional[str] = None,
+):
+ doc = frappe.get_doc("Request for Quotation", name)
if supplier:
- doc = frappe.get_doc(doctype, name)
doc.update_supplier_part_no(supplier)
- return doc
+
+ # permissions get checked in `download_pdf`
+ download_pdf(
+ doc.doctype,
+ doc.name,
+ print_format,
+ doc=doc,
+ language=language,
+ letterhead=letterhead or None,
+ )
@frappe.whitelist()
diff --git a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
index 064b806e953..d250e6f18a9 100644
--- a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
@@ -8,6 +8,7 @@ from frappe.utils import nowdate
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import (
create_supplier_quotation,
+ get_pdf,
make_supplier_quotation_from_rfq,
)
from erpnext.crm.doctype.opportunity.opportunity import make_request_for_quotation as make_rfq
@@ -124,6 +125,11 @@ class TestRequestforQuotation(FrappeTestCase):
rfq.status = "Draft"
rfq.submit()
+ def test_get_pdf(self):
+ rfq = make_request_for_quotation()
+ get_pdf(rfq.name, rfq.get("suppliers")[0].supplier)
+ self.assertEqual(frappe.local.response.type, "pdf")
+
def make_request_for_quotation(**args):
"""
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 9fcb769bc8c..fc6793a9bbc 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -252,6 +252,7 @@ def get_already_returned_items(doc):
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 723c6d993d7..c3cedcc7a63 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 77aa559b771..caf8ff5b36b 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 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/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index c2b331fcfd1..db699b94d8f 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 2d37fd69b19..72fd424b245 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 e098c3e3c45..828a55e7bc1 100644
--- a/erpnext/projects/doctype/timesheet/test_timesheet.py
+++ b/erpnext/projects/doctype/timesheet/test_timesheet.py
@@ -161,6 +161,37 @@ class TestTimesheet(unittest.TestCase):
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 f3bd09a67a4..d482a46053c 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -64,6 +64,8 @@ class Timesheet(Document):
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 029d6c0c417..623b338e181 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -129,7 +129,16 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
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 6c49f0f6dda..3c8ab8e47ae 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 a67f9b05cc2..f77dce8f1ab 100644
--- a/erpnext/selling/doctype/quotation/quotation.js
+++ b/erpnext/selling/doctype/quotation/quotation.js
@@ -85,11 +85,15 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext.
}
if (doc.docstatus == 1 && !["Lost", "Ordered"].includes(doc.status)) {
- this.frm.add_custom_button(
- __("Sales Order"),
- () => this.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.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 a4a5667f8e1..185f63c345e 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -249,6 +249,17 @@ def get_list_context(context=None):
@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 5aaba4fa435..cdf5f5d00c5 100644
--- a/erpnext/selling/doctype/quotation/test_quotation.py
+++ b/erpnext/selling/doctype/quotation/test_quotation.py
@@ -144,11 +144,21 @@ class TestQuotation(FrappeTestCase):
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 e7dd0d84a0a..a9b500a625f 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 2abb169b8a0..6ea66a02378 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 0a356b9a6fb..89ce61ab168 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 @@ erpnext.PointOfSale.Payment = class {
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 @@ erpnext.PointOfSale.Payment = class {
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 991ac719cdc..990d736baa4 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 @@ function get_filters() {
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 8bf56865a7d..3682c5fd62e 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 @@ def get_so_with_invoices(filters):
)
.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 @@ def filter_on_calculated_status(filters, sales_orders):
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 @@ def execute(filters=None):
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 63d339a839d..29691230f22 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 @@ def prepare_data(data, so_elapsed_time, filters):
# 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 2cbbb04ed91..a5b2ac9128a 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 3680906057f..c3605bf0e8b 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 f14b243512f..f884864acfa 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 00000000000..4c6d7bdea8a
--- /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 0bc3f32eb09..00000000000
--- 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 00000000000..eadc689e312
--- /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 00000000000..00ca1808bb5
--- /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 00000000000..68e9b9ac732
--- /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 00000000000..2808ce79855
--- /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 4d9b871e5e7..6bc17718ae0 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 frappe.utils import cstr, getdate
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 @@ def install(country=None):
# 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 @@ def install(country=None):
"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 @@ def install(country=None):
{"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 @@ def install(country=None):
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 @@ def add_uom_data():
)
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 @@ def add_uom_data():
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 @@ def add_uom_data():
"to_uom": _(d.get("to_uom")),
"value": d.get("value"),
}
- ).insert(ignore_permissions=True)
+ ).db_insert()
def add_market_segments():
@@ -468,7 +425,7 @@ def install_company(args):
make_records(records)
-def install_defaults(args=None):
+def install_defaults(args=None): # nosemgrep
records = [
# Price Lists
{
@@ -493,7 +450,7 @@ def install_defaults(args=None):
# 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 @@ def create_bank_account(args):
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 2f77dd6ae56..49ba78c63a4 100644
--- a/erpnext/setup/setup_wizard/operations/taxes_setup.py
+++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py
@@ -158,6 +158,7 @@ def make_taxes_and_charges_template(company_name, doctype, template):
# 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 bb120eaa6b3..62936fcfb89 100644
--- a/erpnext/startup/boot.py
+++ b/erpnext/startup/boot.py
@@ -25,6 +25,12 @@ def boot_session(bootinfo):
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 0a3d7e81985..afe5ad65b75 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/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 156e5917f23..c1f1b0d1352 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -110,8 +110,11 @@ frappe.ui.form.on('Material Request', {
if (frm.doc.material_request_type === "Material Transfer") {
add_create_pick_list_button();
- frm.add_custom_button(__("Transfer Material"),
+ frm.add_custom_button(__("Material Transfer"),
() => frm.events.make_stock_entry(frm), __('Create'));
+
+ frm.add_custom_button(__("Material Transfer (In Transit)"),
+ () => frm.events.make_in_transit_stock_entry(frm), __('Create'));
}
if (frm.doc.material_request_type === "Material Issue") {
@@ -333,6 +336,46 @@ frappe.ui.form.on('Material Request', {
});
},
+ make_in_transit_stock_entry(frm) {
+ frappe.prompt(
+ [
+ {
+ label: __('In Transit Warehouse'),
+ fieldname: 'in_transit_warehouse',
+ fieldtype: 'Link',
+ options: 'Warehouse',
+ reqd: 1,
+ get_query: () => {
+ return{
+ filters: {
+ 'company': frm.doc.company,
+ 'is_group': 0,
+ 'warehouse_type': 'Transit'
+ }
+ }
+ }
+ }
+ ],
+ (values) => {
+ frappe.call({
+ method: "erpnext.stock.doctype.material_request.material_request.make_in_transit_stock_entry",
+ args: {
+ source_name: frm.doc.name,
+ in_transit_warehouse: values.in_transit_warehouse
+ },
+ callback: function(r) {
+ if (r.message) {
+ let doc = frappe.model.sync(r.message);
+ frappe.set_route('Form', doc[0].doctype, doc[0].name);
+ }
+ }
+ })
+ },
+ __('In Transit Transfer'),
+ __("Create Stock Entry")
+ )
+ },
+
create_pick_list: (frm) => {
frappe.model.open_mapped_doc({
method: "erpnext.stock.doctype.material_request.material_request.create_pick_list",
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index 94f63a599b5..6426fe8015a 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -716,3 +716,14 @@ def create_pick_list(source_name, target_doc=None):
doc.set_item_locations()
return doc
+
+
+@frappe.whitelist()
+def make_in_transit_stock_entry(source_name, in_transit_warehouse):
+ ste_doc = make_stock_entry(source_name)
+ ste_doc.add_to_transit = 1
+
+ for row in ste_doc.items:
+ row.t_warehouse = in_transit_warehouse
+
+ return ste_doc
diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py
index f0a94997fe8..a707c74c7db 100644
--- a/erpnext/stock/doctype/material_request/test_material_request.py
+++ b/erpnext/stock/doctype/material_request/test_material_request.py
@@ -11,6 +11,7 @@ from frappe.utils import flt, today
from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.material_request.material_request import (
+ make_in_transit_stock_entry,
make_purchase_order,
make_stock_entry,
make_supplier_quotation,
@@ -56,6 +57,22 @@ class TestMaterialRequest(FrappeTestCase):
self.assertEqual(se.doctype, "Stock Entry")
self.assertEqual(len(se.get("items")), len(mr.get("items")))
+ def test_in_transit_make_stock_entry(self):
+ mr = frappe.copy_doc(test_records[0]).insert()
+
+ self.assertRaises(frappe.ValidationError, make_stock_entry, mr.name)
+
+ mr = frappe.get_doc("Material Request", mr.name)
+ mr.material_request_type = "Material Transfer"
+ mr.submit()
+
+ in_transit_warehouse = get_in_transit_warehouse(mr.company)
+ se = make_in_transit_stock_entry(mr.name, in_transit_warehouse)
+
+ self.assertEqual(se.doctype, "Stock Entry")
+ for row in se.get("items"):
+ self.assertEqual(row.t_warehouse, in_transit_warehouse)
+
def _insert_stock_entry(self, qty1, qty2, warehouse=None):
se = frappe.get_doc(
{
@@ -742,6 +759,36 @@ class TestMaterialRequest(FrappeTestCase):
self.assertEqual(existing_requested_qty, current_requested_qty)
+def get_in_transit_warehouse(company):
+ if not frappe.db.exists("Warehouse Type", "Transit"):
+ frappe.get_doc(
+ {
+ "doctype": "Warehouse Type",
+ "name": "Transit",
+ }
+ ).insert()
+
+ in_transit_warehouse = frappe.db.exists(
+ "Warehouse", {"warehouse_type": "Transit", "company": company}
+ )
+
+ if not in_transit_warehouse:
+ in_transit_warehouse = (
+ frappe.get_doc(
+ {
+ "doctype": "Warehouse",
+ "warehouse_name": "Transit",
+ "warehouse_type": "Transit",
+ "company": company,
+ }
+ )
+ .insert()
+ .name
+ )
+
+ return in_transit_warehouse
+
+
def make_material_request(**args):
args = frappe._dict(args)
mr = frappe.new_doc("Material Request")
diff --git a/erpnext/stock/doctype/price_list/test_records.json b/erpnext/stock/doctype/price_list/test_records.json
index 7ca949c4026..e02a7adbd8b 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 8c20ca0211e..7f69397fce9 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -2494,7 +2494,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/get_item_details.py b/erpnext/stock/get_item_details.py
index 5af144110f0..b53f429edf2 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -88,8 +88,15 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
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 14cedd2e8a9..439ed7a8e09 100644
--- a/erpnext/stock/stock_balance.py
+++ b/erpnext/stock/stock_balance.py
@@ -121,7 +121,7 @@ def get_reserved_qty(item_code, warehouse):
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 @@ def get_reserved_qty(item_code, warehouse):
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 00000000000..b53e29e9e8e
--- /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 8a92418d25e..219747c9f8a 100644
--- a/erpnext/www/shop-by-category/index.py
+++ b/erpnext/www/shop-by-category/index.py
@@ -51,21 +51,31 @@ def get_tabs(categories):
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