Merge pull request #33380 from ruthra-kumar/err_for_invoices_should_reflect_in_ar_ap_report
fix: ERR journals should reported in AR/AP
diff --git a/.github/helper/install.sh b/.github/helper/install.sh
index 2bb950f..2b3d8cb 100644
--- a/.github/helper/install.sh
+++ b/.github/helper/install.sh
@@ -24,15 +24,14 @@
if [ "$DB" == "mariadb" ];then
- mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL character_set_server = 'utf8mb4'"
- mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
+ mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'"
+ mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
- mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
- mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE DATABASE test_frappe"
- mysql --host 127.0.0.1 --port 3306 -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
+ mysql --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
+ mysql --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE DATABASE test_frappe"
+ mysql --host 127.0.0.1 --port 3306 -u root -proot -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
- mysql --host 127.0.0.1 --port 3306 -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'"
- mysql --host 127.0.0.1 --port 3306 -u root -e "FLUSH PRIVILEGES"
+ mysql --host 127.0.0.1 --port 3306 -u root -proot -e "FLUSH PRIVILEGES"
fi
if [ "$DB" == "postgres" ];then
diff --git a/.github/helper/site_config_mariadb.json b/.github/helper/site_config_mariadb.json
index 948ad08..8c86f73 100644
--- a/.github/helper/site_config_mariadb.json
+++ b/.github/helper/site_config_mariadb.json
@@ -9,8 +9,8 @@
"mail_password": "test",
"admin_password": "admin",
"root_login": "root",
- "root_password": "travis",
+ "root_password": "root",
"host_name": "http://test_site:8000",
- "install_apps": ["erpnext"],
+ "install_apps": ["payments", "erpnext"],
"throttle_user_limit": 100
}
diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml
index 9e06254..d5f0052 100644
--- a/.github/workflows/patch.yml
+++ b/.github/workflows/patch.yml
@@ -25,7 +25,7 @@
mysql:
image: mariadb:10.3
env:
- MYSQL_ALLOW_EMPTY_PASSWORD: YES
+ MARIADB_ROOT_PASSWORD: 'root'
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
diff --git a/.github/workflows/server-tests-mariadb.yml b/.github/workflows/server-tests-mariadb.yml
index b40faa7..bbb8a7e 100644
--- a/.github/workflows/server-tests-mariadb.yml
+++ b/.github/workflows/server-tests-mariadb.yml
@@ -45,9 +45,9 @@
services:
mysql:
- image: mariadb:10.3
+ image: mariadb:10.6
env:
- MYSQL_ALLOW_EMPTY_PASSWORD: YES
+ MARIADB_ROOT_PASSWORD: 'root'
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
index 325a356..220b747 100644
--- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
+++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
@@ -485,6 +485,10 @@
"default_payable_account": frappe.db.get_value(
"Account", {"company": company.name, "account_type": "Payable", "is_group": 0}
),
+ "default_provisional_account": frappe.db.get_value(
+ "Account",
+ {"company": company.name, "account_type": "Service Received But Not Billed", "is_group": 0},
+ ),
}
)
diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py
index 4592421..3207e41 100644
--- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py
+++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py
@@ -9,10 +9,6 @@
from frappe.utils import add_days, add_years, cstr, getdate
-class FiscalYearIncorrectDate(frappe.ValidationError):
- pass
-
-
class FiscalYear(Document):
@frappe.whitelist()
def set_as_default(self):
@@ -53,23 +49,18 @@
)
def validate_dates(self):
+ self.validate_from_to_dates("year_start_date", "year_end_date")
if self.is_short_year:
# Fiscal Year can be shorter than one year, in some jurisdictions
# under certain circumstances. For example, in the USA and Germany.
return
- if getdate(self.year_start_date) > getdate(self.year_end_date):
- frappe.throw(
- _("Fiscal Year Start Date should be one year earlier than Fiscal Year End Date"),
- FiscalYearIncorrectDate,
- )
-
date = getdate(self.year_start_date) + relativedelta(years=1) - relativedelta(days=1)
if getdate(self.year_end_date) != date:
frappe.throw(
_("Fiscal Year End Date should be one year after Fiscal Year Start Date"),
- FiscalYearIncorrectDate,
+ frappe.exceptions.InvalidDates,
)
def on_update(self):
@@ -169,5 +160,6 @@
def get_from_and_to_date(fiscal_year):
- fields = ["year_start_date as from_date", "year_end_date as to_date"]
- return frappe.get_cached_value("Fiscal Year", fiscal_year, fields, as_dict=1)
+ fields = ["year_start_date", "year_end_date"]
+ cached_results = frappe.get_cached_value("Fiscal Year", fiscal_year, fields, as_dict=1)
+ return dict(from_date=cached_results.year_start_date, to_date=cached_results.year_end_date)
diff --git a/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py b/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py
index 6e946f7..181406b 100644
--- a/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py
+++ b/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py
@@ -7,8 +7,6 @@
import frappe
from frappe.utils import now_datetime
-from erpnext.accounts.doctype.fiscal_year.fiscal_year import FiscalYearIncorrectDate
-
test_ignore = ["Company"]
@@ -26,7 +24,7 @@
}
)
- self.assertRaises(FiscalYearIncorrectDate, fy.insert)
+ self.assertRaises(frappe.exceptions.InvalidDates, fy.insert)
def test_record_generator():
@@ -35,8 +33,8 @@
"doctype": "Fiscal Year",
"year": "_Test Short Fiscal Year 2011",
"is_short_year": 1,
- "year_end_date": "2011-04-01",
- "year_start_date": "2011-12-31",
+ "year_start_date": "2011-04-01",
+ "year_end_date": "2011-12-31",
}
]
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 1714fff..b63d57c 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -34,9 +34,6 @@
def __init__(self, *args, **kwargs):
super(JournalEntry, self).__init__(*args, **kwargs)
- def get_feed(self):
- return self.voucher_type
-
def validate(self):
if self.voucher_type == "Opening Entry":
self.is_opening = "Yes"
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index 3fc1adf..4a7a57b 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -305,6 +305,7 @@
"fieldname": "source_exchange_rate",
"fieldtype": "Float",
"label": "Exchange Rate",
+ "precision": "9",
"print_hide": 1,
"reqd": 1
},
@@ -334,6 +335,7 @@
"fieldname": "target_exchange_rate",
"fieldtype": "Float",
"label": "Exchange Rate",
+ "precision": "9",
"print_hide": 1,
"reqd": 1
},
@@ -731,7 +733,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2022-02-23 20:08:39.559814",
+ "modified": "2022-12-08 16:25:43.824051",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 9354e44..4be4764 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -684,35 +684,34 @@
)
def validate_payment_against_negative_invoice(self):
- if (self.payment_type == "Pay" and self.party_type == "Customer") or (
- self.payment_type == "Receive" and self.party_type == "Supplier"
+ if (self.payment_type != "Pay" or self.party_type != "Customer") and (
+ self.payment_type != "Receive" or self.party_type != "Supplier"
):
+ return
- total_negative_outstanding = sum(
- abs(flt(d.outstanding_amount)) for d in self.get("references") if flt(d.outstanding_amount) < 0
+ total_negative_outstanding = sum(
+ abs(flt(d.outstanding_amount)) for d in self.get("references") if flt(d.outstanding_amount) < 0
+ )
+
+ paid_amount = self.paid_amount if self.payment_type == "Receive" else self.received_amount
+ additional_charges = sum(flt(d.amount) for d in self.deductions)
+
+ if not total_negative_outstanding:
+ if self.party_type == "Customer":
+ msg = _("Cannot pay to Customer without any negative outstanding invoice")
+ else:
+ msg = _("Cannot receive from Supplier without any negative outstanding invoice")
+
+ frappe.throw(msg, InvalidPaymentEntry)
+
+ elif paid_amount - additional_charges > total_negative_outstanding:
+ frappe.throw(
+ _("Paid Amount cannot be greater than total negative outstanding amount {0}").format(
+ total_negative_outstanding
+ ),
+ InvalidPaymentEntry,
)
- paid_amount = self.paid_amount if self.payment_type == "Receive" else self.received_amount
- additional_charges = sum([flt(d.amount) for d in self.deductions])
-
- if not total_negative_outstanding:
- frappe.throw(
- _("Cannot {0} {1} {2} without any negative outstanding invoice").format(
- _(self.payment_type),
- (_("to") if self.party_type == "Customer" else _("from")),
- self.party_type,
- ),
- InvalidPaymentEntry,
- )
-
- elif paid_amount - additional_charges > total_negative_outstanding:
- frappe.throw(
- _("Paid Amount cannot be greater than total negative outstanding amount {0}").format(
- total_negative_outstanding
- ),
- InvalidPaymentEntry,
- )
-
def set_title(self):
if frappe.flags.in_import and self.title:
# do not set title dynamically if title exists during data import.
@@ -1188,6 +1187,7 @@
ple = qb.DocType("Payment Ledger Entry")
common_filter = []
+ accounting_dimensions_filter = []
posting_and_due_date = []
# confirm that Supplier is not blocked
@@ -1217,7 +1217,7 @@
# Add cost center condition
if args.get("cost_center"):
condition += " and cost_center='%s'" % args.get("cost_center")
- common_filter.append(ple.cost_center == args.get("cost_center"))
+ accounting_dimensions_filter.append(ple.cost_center == args.get("cost_center"))
date_fields_dict = {
"posting_date": ["from_posting_date", "to_posting_date"],
@@ -1243,6 +1243,7 @@
posting_date=posting_and_due_date,
min_outstanding=args.get("outstanding_amt_greater_than"),
max_outstanding=args.get("outstanding_amt_less_than"),
+ accounting_dimensions=accounting_dimensions_filter,
)
outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
index 8961167..3003c68 100644
--- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
+++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
@@ -25,7 +25,8 @@
"in_list_view": 1,
"label": "Type",
"options": "DocType",
- "reqd": 1
+ "reqd": 1,
+ "search_index": 1
},
{
"columns": 2,
@@ -35,7 +36,8 @@
"in_list_view": 1,
"label": "Name",
"options": "reference_doctype",
- "reqd": 1
+ "reqd": 1,
+ "search_index": 1
},
{
"fieldname": "due_date",
@@ -104,7 +106,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2021-09-26 17:06:55.597389",
+ "modified": "2022-12-12 12:31:44.919895",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Reference",
@@ -113,5 +115,6 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.js b/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.js
index 8f09bc3..aff067e 100644
--- a/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.js
+++ b/erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.js
@@ -3,6 +3,7 @@
frappe.ui.form.on('Payment Gateway Account', {
refresh(frm) {
+ erpnext.utils.check_payments_app();
if(!frm.doc.__islocal) {
frm.set_df_property('payment_gateway', 'read_only', 1);
}
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index 52efd33..ff212f2 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -23,6 +23,7 @@
def __init__(self, *args, **kwargs):
super(PaymentReconciliation, self).__init__(*args, **kwargs)
self.common_filter_conditions = []
+ self.accounting_dimension_filter_conditions = []
self.ple_posting_date_filter = []
@frappe.whitelist()
@@ -193,6 +194,7 @@
posting_date=self.ple_posting_date_filter,
min_outstanding=self.minimum_invoice_amount if self.minimum_invoice_amount else None,
max_outstanding=self.maximum_invoice_amount if self.maximum_invoice_amount else None,
+ accounting_dimensions=self.accounting_dimension_filter_conditions,
)
if self.invoice_limit:
@@ -381,7 +383,7 @@
self.common_filter_conditions.append(ple.company == self.company)
if self.get("cost_center") and (get_invoices or get_return_invoices):
- self.common_filter_conditions.append(ple.cost_center == self.cost_center)
+ self.accounting_dimension_filter_conditions.append(ple.cost_center == self.cost_center)
if get_invoices:
if self.from_invoice_date:
diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
index dae029b..6030134 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
@@ -8,6 +8,8 @@
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days, nowdate
+from erpnext import get_default_cost_center
+from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.party import get_party_account
@@ -20,6 +22,7 @@
self.create_item()
self.create_customer()
self.create_account()
+ self.create_cost_center()
self.clear_old_entries()
def tearDown(self):
@@ -216,6 +219,22 @@
)
return je
+ def create_cost_center(self):
+ # Setup cost center
+ cc_name = "Sub"
+
+ self.main_cc = frappe.get_doc("Cost Center", get_default_cost_center(self.company))
+
+ cc_exists = frappe.db.get_list("Cost Center", filters={"cost_center_name": cc_name})
+ if cc_exists:
+ self.sub_cc = frappe.get_doc("Cost Center", cc_exists[0].name)
+ else:
+ sub_cc = frappe.new_doc("Cost Center")
+ sub_cc.cost_center_name = "Sub"
+ sub_cc.parent_cost_center = self.main_cc.parent_cost_center
+ sub_cc.company = self.main_cc.company
+ self.sub_cc = sub_cc.save()
+
def test_filter_min_max(self):
# check filter condition minimum and maximum amount
self.create_sales_invoice(qty=1, rate=300)
@@ -578,3 +597,24 @@
self.assertEqual(len(pr.payments), 1)
self.assertEqual(pr.payments[0].amount, amount)
self.assertEqual(pr.payments[0].currency, "EUR")
+
+ def test_differing_cost_center_on_invoice_and_payment(self):
+ """
+ Cost Center filter should not affect outstanding amount calculation
+ """
+
+ si = self.create_sales_invoice(qty=1, rate=100, do_not_submit=True)
+ si.cost_center = self.main_cc.name
+ si.submit()
+ pr = get_payment_entry(si.doctype, si.name)
+ pr.cost_center = self.sub_cc.name
+ pr = pr.save().submit()
+
+ pr = self.create_payment_reconciliation()
+ pr.cost_center = self.main_cc.name
+
+ pr.get_unreconciled_entries()
+
+ # check PR tool output
+ self.assertEqual(len(pr.get("invoices")), 0)
+ self.assertEqual(len(pr.get("payments")), 0)
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 8665b70..d82083c 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -9,7 +9,6 @@
from frappe.model.document import Document
from frappe.utils import flt, get_url, nowdate
from frappe.utils.background_jobs import enqueue
-from payments.utils import get_payment_gateway_controller
from erpnext.accounts.doctype.payment_entry.payment_entry import (
get_company_defaults,
@@ -19,6 +18,14 @@
from erpnext.accounts.party import get_party_account, get_party_bank_account
from erpnext.accounts.utils import get_account_currency
from erpnext.erpnext_integrations.stripe_integration import create_stripe_subscription
+from erpnext.utilities import payment_app_import_guard
+
+
+def _get_payment_gateway_controller(*args, **kwargs):
+ with payment_app_import_guard():
+ from payments.utils import get_payment_gateway_controller
+
+ return get_payment_gateway_controller(*args, **kwargs)
class PaymentRequest(Document):
@@ -107,7 +114,7 @@
self.request_phone_payment()
def request_phone_payment(self):
- controller = get_payment_gateway_controller(self.payment_gateway)
+ controller = _get_payment_gateway_controller(self.payment_gateway)
request_amount = self.get_request_amount()
payment_record = dict(
@@ -156,7 +163,7 @@
def payment_gateway_validation(self):
try:
- controller = get_payment_gateway_controller(self.payment_gateway)
+ controller = _get_payment_gateway_controller(self.payment_gateway)
if hasattr(controller, "on_payment_request_submission"):
return controller.on_payment_request_submission(self)
else:
@@ -189,7 +196,7 @@
)
data.update({"company": frappe.defaults.get_defaults().company})
- controller = get_payment_gateway_controller(self.payment_gateway)
+ controller = _get_payment_gateway_controller(self.payment_gateway)
controller.validate_transaction_currency(self.currency)
if hasattr(controller, "validate_minimum_transaction_amount"):
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
index ed46d85..2943500 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
@@ -10,7 +10,7 @@
import frappe
from frappe import _, throw
from frappe.model.document import Document
-from frappe.utils import cint, flt, getdate
+from frappe.utils import cint, flt
apply_on_dict = {"Item Code": "items", "Item Group": "item_groups", "Brand": "brands"}
@@ -184,8 +184,7 @@
if self.is_cumulative and not (self.valid_from and self.valid_upto):
frappe.throw(_("Valid from and valid upto fields are mandatory for the cumulative"))
- if self.valid_from and self.valid_upto and getdate(self.valid_from) > getdate(self.valid_upto):
- frappe.throw(_("Valid from date must be less than valid upto date"))
+ self.validate_from_to_dates("valid_from", "valid_upto")
def validate_condition(self):
if (
@@ -256,7 +255,7 @@
for item in item_list:
args_copy = copy.deepcopy(args)
args_copy.update(item)
- data = get_pricing_rule_for_item(args_copy, item.get("price_list_rate"), doc=doc)
+ data = get_pricing_rule_for_item(args_copy, doc=doc)
out.append(data)
if (
@@ -293,7 +292,7 @@
pricing_rule.uom = row.uom
-def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False):
+def get_pricing_rule_for_item(args, doc=None, for_validate=False):
from erpnext.accounts.doctype.pricing_rule.utils import (
get_applied_pricing_rules,
get_pricing_rule_items,
diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
index d27f65e..5bb366a 100644
--- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
@@ -1123,7 +1123,7 @@
"apply_on": args.apply_on or "Item Code",
"applicable_for": args.applicable_for,
"selling": args.selling or 0,
- "currency": "USD",
+ "currency": "INR",
"apply_discount_on_rate": args.apply_discount_on_rate or 0,
"buying": args.buying or 0,
"min_qty": args.min_qty or 0.0,
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index bb54b23..3989f8a 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -250,6 +250,17 @@
and ifnull(`tabPricing Rule`.valid_upto, '2500-12-31')"""
values["transaction_date"] = args.get("transaction_date")
+ if args.get("doctype") in [
+ "Quotation",
+ "Sales Order",
+ "Delivery Note",
+ "Sales Invoice",
+ "POS Invoice",
+ ]:
+ conditions += """ and ifnull(`tabPricing Rule`.selling, 0) = 1"""
+ else:
+ conditions += """ and ifnull(`tabPricing Rule`.buying, 0) = 1"""
+
return conditions
@@ -669,7 +680,7 @@
item_details.free_item_data.append(free_item_data_args)
-def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False):
+def apply_pricing_rule_for_free_items(doc, pricing_rule_args):
if pricing_rule_args:
items = tuple((d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item)
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index a03157e..6281400 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -64,12 +64,13 @@
"tax_withholding_net_total",
"base_tax_withholding_net_total",
"taxes_section",
+ "tax_category",
"taxes_and_charges",
"column_break_58",
- "tax_category",
- "column_break_49",
"shipping_rule",
+ "column_break_49",
"incoterm",
+ "named_place",
"section_break_51",
"taxes",
"totals",
@@ -1541,13 +1542,19 @@
"fieldtype": "Link",
"label": "Incoterm",
"options": "Incoterm"
+ },
+ {
+ "depends_on": "incoterm",
+ "fieldname": "named_place",
+ "fieldtype": "Data",
+ "label": "Named Place"
}
],
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
- "modified": "2022-11-25 12:44:29.935567",
+ "modified": "2022-12-12 18:37:38.142688",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 0a92820..0e9f976 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -231,7 +231,9 @@
)
if (
- cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate")) and not self.is_return
+ cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate"))
+ and not self.is_return
+ and not self.is_internal_supplier
):
self.validate_rate_with_reference_doc(
[
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 18d2b5c..4729d9c 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -61,12 +61,13 @@
"total",
"net_total",
"taxes_section",
+ "tax_category",
"taxes_and_charges",
"column_break_38",
"shipping_rule",
- "incoterm",
"column_break_55",
- "tax_category",
+ "incoterm",
+ "named_place",
"section_break_40",
"taxes",
"section_break_43",
@@ -921,6 +922,7 @@
"fieldtype": "Table",
"hide_days": 1,
"hide_seconds": 1,
+ "label": "Sales Taxes and Charges",
"oldfieldname": "other_charges",
"oldfieldtype": "Table",
"options": "Sales Taxes and Charges"
@@ -2121,6 +2123,12 @@
"fieldtype": "Link",
"label": "Incoterm",
"options": "Incoterm"
+ },
+ {
+ "depends_on": "incoterm",
+ "fieldname": "named_place",
+ "fieldtype": "Data",
+ "label": "Named Place"
}
],
"icon": "fa fa-file-text",
@@ -2133,7 +2141,7 @@
"link_fieldname": "consolidated_invoice"
}
],
- "modified": "2022-11-17 17:17:10.883487",
+ "modified": "2022-12-12 18:34:33.409895",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py
index 9dab4e9..8708342 100644
--- a/erpnext/accounts/doctype/subscription/subscription.py
+++ b/erpnext/accounts/doctype/subscription/subscription.py
@@ -280,7 +280,8 @@
self.validate_plans_billing_cycle(self.get_billing_cycle_and_interval())
self.validate_end_date()
self.validate_to_follow_calendar_months()
- self.cost_center = erpnext.get_default_cost_center(self.get("company"))
+ if not self.cost_center:
+ self.cost_center = erpnext.get_default_cost_center(self.get("company"))
def validate_trial_period(self):
"""
diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.js b/erpnext/accounts/doctype/subscription_plan/subscription_plan.js
index 7d6f2ae..00727f1 100644
--- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.js
+++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.js
@@ -5,5 +5,9 @@
price_determination: function(frm) {
frm.toggle_reqd("cost", frm.doc.price_determination === 'Fixed rate');
frm.toggle_reqd("price_list", frm.doc.price_determination === 'Based on price list');
- }
+ },
+
+ subscription_plan: function (frm) {
+ erpnext.utils.check_payments_app();
+ },
});
diff --git a/erpnext/accounts/doctype/tax_rule/tax_rule.py b/erpnext/accounts/doctype/tax_rule/tax_rule.py
index 4d20129..87c5e6d 100644
--- a/erpnext/accounts/doctype/tax_rule/tax_rule.py
+++ b/erpnext/accounts/doctype/tax_rule/tax_rule.py
@@ -32,7 +32,7 @@
def validate(self):
self.validate_tax_template()
- self.validate_date()
+ self.validate_from_to_dates("from_date", "to_date")
self.validate_filters()
self.validate_use_for_shopping_cart()
@@ -51,10 +51,6 @@
if not (self.sales_tax_template or self.purchase_tax_template):
frappe.throw(_("Tax Template is mandatory."))
- def validate_date(self):
- if self.from_date and self.to_date and self.from_date > self.to_date:
- frappe.throw(_("From Date cannot be greater than To Date"))
-
def validate_filters(self):
filters = {
"tax_type": self.tax_type,
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
index 30ed91b..b834d14 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -121,12 +121,24 @@
else:
tax_row = get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted)
+ cost_center = get_cost_center(inv)
+ tax_row.update({"cost_center": cost_center})
+
if inv.doctype == "Purchase Invoice":
return tax_row, tax_deducted_on_advances, voucher_wise_amount
else:
return tax_row
+def get_cost_center(inv):
+ cost_center = frappe.get_cached_value("Company", inv.company, "cost_center")
+
+ if len(inv.get("taxes", [])) > 0:
+ cost_center = inv.get("taxes")[0].cost_center
+
+ return cost_center
+
+
def get_tax_withholding_details(tax_withholding_category, posting_date, company):
tax_withholding = frappe.get_doc("Tax Withholding Category", tax_withholding_category)
diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
index e93fb61..d269e1f 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
@@ -533,12 +533,13 @@
],
filters={"company": company, "root_type": root_type},
):
- if account.account_name not in added_accounts:
+ if account.account_number:
+ account_key = account.account_number + "-" + account.account_name
+ else:
+ account_key = account.account_name
+
+ if account_key not in added_accounts:
accounts.append(account)
- if account.account_number:
- account_key = account.account_number + "-" + account.account_name
- else:
- account_key = account.account_name
added_accounts.append(account_key)
return accounts
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index dacc809..99e86ae 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -503,7 +503,7 @@
invoice_portion = 100
elif row.invoice_portion:
invoice_portion = row.invoice_portion
- else:
+ elif row.payment_amount:
invoice_portion = row.payment_amount * 100 / row.base_net_amount
if i == 0:
diff --git a/erpnext/accounts/report/tax_detail/tax_detail.py b/erpnext/accounts/report/tax_detail/tax_detail.py
index ba8d307..ba733c2 100644
--- a/erpnext/accounts/report/tax_detail/tax_detail.py
+++ b/erpnext/accounts/report/tax_detail/tax_detail.py
@@ -234,8 +234,11 @@
if field in ["item_tax_rate", "base_net_amount"]:
return None
- if doctype == "GL Entry" and field in ["debit", "credit"]:
- column.update({"label": _("Amount"), "fieldname": "amount"})
+ if doctype == "GL Entry":
+ if field in ["debit", "credit"]:
+ column.update({"label": _("Amount"), "fieldname": "amount"})
+ elif field == "voucher_type":
+ column.update({"fieldtype": "Data", "options": ""})
if field == "taxes_and_charges":
column.update({"label": _("Taxes and Charges Template")})
diff --git a/erpnext/accounts/report/utils.py b/erpnext/accounts/report/utils.py
index eed5836..d3cd290 100644
--- a/erpnext/accounts/report/utils.py
+++ b/erpnext/accounts/report/utils.py
@@ -28,7 +28,7 @@
filters["presentation_currency"] if filters.get("presentation_currency") else company_currency
)
- report_date = filters.get("to_date")
+ report_date = filters.get("to_date") or filters.get("period_end_date")
if not report_date:
fiscal_year_to_date = get_from_and_to_date(filters.get("to_fiscal_year"))["to_date"]
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 41702d6..1e573b0 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -836,6 +836,7 @@
posting_date=None,
min_outstanding=None,
max_outstanding=None,
+ accounting_dimensions=None,
):
ple = qb.DocType("Payment Ledger Entry")
@@ -866,6 +867,7 @@
min_outstanding=min_outstanding,
max_outstanding=max_outstanding,
get_invoices=True,
+ accounting_dimensions=accounting_dimensions or [],
)
for d in invoice_list:
@@ -1615,6 +1617,7 @@
.where(ple.delinked == 0)
.where(Criterion.all(filter_on_voucher_no))
.where(Criterion.all(self.common_filter))
+ .where(Criterion.all(self.dimensions_filter))
.where(Criterion.all(self.voucher_posting_date))
.groupby(ple.voucher_type, ple.voucher_no, ple.party_type, ple.party)
)
@@ -1702,6 +1705,7 @@
max_outstanding=None,
get_payments=False,
get_invoices=False,
+ accounting_dimensions=None,
):
"""
Fetch voucher amount and outstanding amount from Payment Ledger using Database CTE
@@ -1717,6 +1721,7 @@
self.reset()
self.vouchers = vouchers
self.common_filter = common_filter or []
+ self.dimensions_filter = accounting_dimensions or []
self.voucher_posting_date = posting_date or []
self.min_outstanding = min_outstanding
self.max_outstanding = max_outstanding
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index 136a033..2bec273 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -12,6 +12,7 @@
get_first_day,
get_last_day,
getdate,
+ is_last_day_of_the_month,
nowdate,
)
@@ -264,7 +265,7 @@
asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
asset.precision("gross_purchase_amount"),
)
- this_month_depr_amount = 9000.0 if get_last_day(date) == date else 0
+ this_month_depr_amount = 9000.0 if is_last_day_of_the_month(date) else 0
self.assertEquals(accumulated_depr_amount, 18000.0 + this_month_depr_amount)
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index 9349626..ce7de87 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -62,12 +62,13 @@
"set_reserve_warehouse",
"supplied_items",
"taxes_section",
+ "tax_category",
"taxes_and_charges",
"column_break_53",
- "tax_category",
- "column_break_50",
"shipping_rule",
+ "column_break_50",
"incoterm",
+ "named_place",
"section_break_52",
"taxes",
"totals",
@@ -1256,13 +1257,19 @@
"fieldtype": "Link",
"label": "Incoterm",
"options": "Incoterm"
+ },
+ {
+ "depends_on": "incoterm",
+ "fieldname": "named_place",
+ "fieldtype": "Data",
+ "label": "Named Place"
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
- "modified": "2022-11-17 17:28:07.729943",
+ "modified": "2022-12-12 18:36:37.455134",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",
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 31a4837..a9f5afb 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
@@ -22,6 +22,13 @@
}
};
}
+
+ frm.set_query('warehouse', 'items', () => ({
+ filters: {
+ company: frm.doc.company,
+ is_group: 0
+ }
+ }));
},
onload: function(frm) {
@@ -50,44 +57,96 @@
});
}, __("Tools"));
- frm.add_custom_button(__('Download PDF'), () => {
- var suppliers = [];
- const fields = [{
- fieldtype: 'Link',
- label: __('Select a Supplier'),
- fieldname: 'supplier',
- options: 'Supplier',
- reqd: 1,
- get_query: () => {
- return {
- filters: [
- ["Supplier", "name", "in", frm.doc.suppliers.map((row) => {return row.supplier;})]
- ]
- }
- }
- }];
-
- frappe.prompt(fields, data => {
- var child = locals[cdt][cdn]
-
- var w = window.open(
- frappe.urllib.get_full_url("/api/method/erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_pdf?"
- +"doctype="+encodeURIComponent(frm.doc.doctype)
- +"&name="+encodeURIComponent(frm.doc.name)
- +"&supplier="+encodeURIComponent(data.supplier)
- +"&no_letterhead=0"));
- if(!w) {
- frappe.msgprint(__("Please enable pop-ups")); return;
- }
+ frm.add_custom_button(
+ __("Download PDF"),
+ () => {
+ frappe.prompt(
+ [
+ {
+ fieldtype: "Link",
+ label: "Select a Supplier",
+ fieldname: "supplier",
+ options: "Supplier",
+ reqd: 1,
+ default: frm.doc.suppliers?.length == 1 ? frm.doc.suppliers[0].supplier : "",
+ get_query: () => {
+ return {
+ filters: [
+ [
+ "Supplier",
+ "name",
+ "in",
+ frm.doc.suppliers.map((row) => {
+ return row.supplier;
+ }),
+ ],
+ ],
+ };
+ },
+ },
+ {
+ fieldtype: "Section Break",
+ label: "Print Settings",
+ fieldname: "print_settings",
+ collapsible: 1,
+ },
+ {
+ fieldtype: "Link",
+ label: "Print Format",
+ fieldname: "print_format",
+ options: "Print Format",
+ placeholder: "Standard",
+ get_query: () => {
+ return {
+ filters: {
+ doc_type: "Request for Quotation",
+ },
+ };
+ },
+ },
+ {
+ fieldtype: "Link",
+ label: "Language",
+ fieldname: "language",
+ options: "Language",
+ default: frappe.boot.lang,
+ },
+ {
+ fieldtype: "Link",
+ label: "Letter Head",
+ fieldname: "letter_head",
+ options: "Letter Head",
+ default: frm.doc.letter_head,
+ },
+ ],
+ (data) => {
+ var w = window.open(
+ 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 || "",
+ }).toString()
+ )
+ );
+ if (!w) {
+ frappe.msgprint(__("Please enable pop-ups"));
+ return;
+ }
+ },
+ "Download PDF for Supplier",
+ "Download"
+ );
},
- 'Download PDF for Supplier',
- 'Download');
- },
- __("Tools"));
+ __("Tools")
+ );
- frm.page.set_inner_btn_group_as_primary(__('Create'));
+ frm.page.set_inner_btn_group_as_primary(__("Create"));
}
-
},
make_supplier_quotation: function(frm) {
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 bdbc9ce..dbc3644 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -389,10 +389,17 @@
@frappe.whitelist()
-def get_pdf(doctype, name, supplier):
- doc = get_rfq_doc(doctype, name, supplier)
- if doc:
- download_pdf(doctype, name, doc=doc)
+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):
diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py
index bebff1c..120b2f8 100644
--- a/erpnext/buying/doctype/supplier/supplier.py
+++ b/erpnext/buying/doctype/supplier/supplier.py
@@ -20,9 +20,6 @@
class Supplier(TransactionBase):
- def get_feed(self):
- return self.supplier_name
-
def onload(self):
"""Load address and contacts in `__onload`"""
load_address_and_contact(self)
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
index 7776ab8..c5b369b 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
@@ -40,12 +40,13 @@
"total",
"net_total",
"taxes_section",
+ "tax_category",
"taxes_and_charges",
"column_break_34",
- "tax_category",
- "column_break_36",
"shipping_rule",
+ "column_break_36",
"incoterm",
+ "named_place",
"section_break_38",
"taxes",
"totals",
@@ -830,6 +831,12 @@
"fieldtype": "Link",
"label": "Incoterm",
"options": "Incoterm"
+ },
+ {
+ "depends_on": "incoterm",
+ "fieldname": "named_place",
+ "fieldtype": "Data",
+ "label": "Named Place"
}
],
"icon": "fa fa-shopping-cart",
@@ -837,7 +844,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2022-11-17 17:27:32.179686",
+ "modified": "2022-12-12 18:35:39.740974",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation",
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index b7a80c1..334a2d8 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -197,7 +197,7 @@
validate_einvoice_fields(self)
- if self.doctype != "Material Request":
+ if self.doctype != "Material Request" and not self.ignore_pricing_rule:
apply_pricing_rule_on_transaction(self)
def before_cancel(self):
@@ -2311,7 +2311,7 @@
elif term.due_date_based_on == "Day(s) after the end of the invoice month":
due_date = add_days(get_last_day(date), term.credit_days)
elif term.due_date_based_on == "Month(s) after the end of the invoice month":
- due_date = add_months(get_last_day(date), term.credit_months)
+ due_date = get_last_day(add_months(date, term.credit_months))
return due_date
@@ -2323,7 +2323,7 @@
elif term.discount_validity_based_on == "Day(s) after the end of the invoice month":
discount_validity = add_days(get_last_day(date), term.discount_validity)
elif term.discount_validity_based_on == "Month(s) after the end of the invoice month":
- discount_validity = add_months(get_last_day(date), term.discount_validity)
+ discount_validity = get_last_day(add_months(date, term.discount_validity))
return discount_validity
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 0514604..7989a40 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -25,10 +25,6 @@
def __setup__(self):
self.flags.ignore_permlevel_for_fields = ["buying_price_list", "price_list_currency"]
- def get_feed(self):
- if self.get("supplier_name"):
- return _("From {0} | {1} {2}").format(self.supplier_name, self.currency, self.grand_total)
-
def validate(self):
super(BuyingController, self).validate()
if getattr(self, "supplier", None) and not self.supplier_name:
@@ -322,17 +318,18 @@
)
if self.is_internal_transfer():
- if rate != d.rate:
- d.rate = rate
- frappe.msgprint(
- _(
- "Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
- ).format(d.idx),
- alert=1,
- )
- d.discount_percentage = 0.0
- d.discount_amount = 0.0
- d.margin_rate_or_amount = 0.0
+ if self.doctype == "Purchase Receipt" or self.get("update_stock"):
+ if rate != d.rate:
+ d.rate = rate
+ frappe.msgprint(
+ _(
+ "Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
+ ).format(d.idx),
+ alert=1,
+ )
+ d.discount_percentage = 0.0
+ d.discount_amount = 0.0
+ d.margin_rate_or_amount = 0.0
def validate_for_subcontracting(self):
if self.is_subcontracted and self.get("is_old_subcontracting_flow"):
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 965335b..8b073a4 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -19,9 +19,6 @@
def __setup__(self):
self.flags.ignore_permlevel_for_fields = ["selling_price_list", "price_list_currency"]
- def get_feed(self):
- return _("To {0} | {1} {2}").format(self.customer_name, self.currency, self.grand_total)
-
def onload(self):
super(SellingController, self).onload()
if self.doctype in ("Sales Order", "Delivery Note", "Sales Invoice"):
@@ -442,30 +439,31 @@
# For internal transfers use incoming rate as the valuation rate
if self.is_internal_transfer():
- if d.doctype == "Packed Item":
- incoming_rate = flt(
- flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
- d.precision("incoming_rate"),
- )
- if d.incoming_rate != incoming_rate:
- d.incoming_rate = incoming_rate
- else:
- rate = flt(
- flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
- d.precision("rate"),
- )
- if d.rate != rate:
- d.rate = rate
- frappe.msgprint(
- _(
- "Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
- ).format(d.idx),
- alert=1,
+ if self.doctype == "Delivery Note" or self.get("update_stock"):
+ if d.doctype == "Packed Item":
+ incoming_rate = flt(
+ flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
+ d.precision("incoming_rate"),
)
+ if d.incoming_rate != incoming_rate:
+ d.incoming_rate = incoming_rate
+ else:
+ rate = flt(
+ flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
+ d.precision("rate"),
+ )
+ if d.rate != rate:
+ d.rate = rate
+ frappe.msgprint(
+ _(
+ "Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
+ ).format(d.idx),
+ alert=1,
+ )
- d.discount_percentage = 0.0
- d.discount_amount = 0.0
- d.margin_rate_or_amount = 0.0
+ d.discount_percentage = 0.0
+ d.discount_amount = 0.0
+ d.margin_rate_or_amount = 0.0
elif self.get("return_against"):
# Get incoming rate of return entry from reference document
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index 6e7d2b3..d497297 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -347,16 +347,21 @@
)
def warn_about_bypassing_with_role(self, item, qty_or_amount, role):
- action = _("Over Receipt/Delivery") if qty_or_amount == "qty" else _("Overbilling")
+ if qty_or_amount == "qty":
+ msg = _("Over Receipt/Delivery of {0} {1} ignored for item {2} because you have {3} role.")
+ else:
+ msg = _("Overbilling of {0} {1} ignored for item {2} because you have {3} role.")
- msg = _("{} of {} {} ignored for item {} because you have {} role.").format(
- action,
- _(item["target_ref_field"].title()),
- frappe.bold(item["reduce_by"]),
- frappe.bold(item.get("item_code")),
- role,
+ frappe.msgprint(
+ msg.format(
+ _(item["target_ref_field"].title()),
+ frappe.bold(item["reduce_by"]),
+ frappe.bold(item.get("item_code")),
+ role,
+ ),
+ indicator="orange",
+ alert=True,
)
- frappe.msgprint(msg, indicator="orange", alert=True)
def update_qty(self, update_modified=True):
"""Updates qty or amount at row level
diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py
index 0d12499..b0ff5d4 100644
--- a/erpnext/crm/doctype/lead/lead.py
+++ b/erpnext/crm/doctype/lead/lead.py
@@ -14,9 +14,6 @@
class Lead(SellingController, CRMNote):
- def get_feed(self):
- return "{0}: {1}".format(_(self.status), self.lead_name)
-
def onload(self):
customer = frappe.db.get_value("Customer", {"lead_name": self.name})
self.get("__onload").is_customer = customer
@@ -453,6 +450,7 @@
"Lead",
or_filters={
"phone": ["like", "%{}".format(number)],
+ "whatsapp_no": ["like", "%{}".format(number)],
"mobile_no": ["like", "%{}".format(number)],
},
limit=1,
diff --git a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py
index d23a22a..dea3f2d 100644
--- a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py
+++ b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py
@@ -217,7 +217,7 @@
def check_for_assigned_to(self, period, value, count_or_amount, assigned_to, info):
if self.filters.get("assigned_to"):
- for data in json.loads(info.get("opportunity_owner")):
+ for data in json.loads(info.get("opportunity_owner") or "[]"):
if data == self.filters.get("assigned_to"):
self.set_formatted_data(period, data, count_or_amount, assigned_to)
else:
diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js
index 69b9cfa..c37fa2f 100644
--- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js
+++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js
@@ -48,5 +48,11 @@
frm.set_value('default_customer_group', '');
frm.set_value('quotation_series', '');
}
+ },
+
+ enable_checkout: function(frm) {
+ if (frm.doc.enable_checkout) {
+ erpnext.utils.check_payments_app();
+ }
}
});
diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.js b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.js
index b649d9d..2411297 100644
--- a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.js
+++ b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.js
@@ -2,4 +2,7 @@
// For license information, please see license.txt
frappe.ui.form.on('GoCardless Settings', {
+ refresh: function(frm) {
+ erpnext.utils.check_payments_app();
+ }
});
diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.json b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.json
index 9738106..cca3653 100644
--- a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.json
+++ b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.json
@@ -173,7 +173,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-02-12 14:18:47.209114",
+ "modified": "2022-02-12 14:18:47.209114",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "GoCardless Settings",
@@ -201,7 +201,6 @@
"write": 1
}
],
- "quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py
index f9a293f..4a29a6a 100644
--- a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py
+++ b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py
@@ -10,7 +10,8 @@
from frappe.integrations.utils import create_request_log
from frappe.model.document import Document
from frappe.utils import call_hook_method, cint, flt, get_url
-from payments.utils import create_payment_gateway
+
+from erpnext.utilities import payment_app_import_guard
class GoCardlessSettings(Document):
@@ -30,6 +31,9 @@
frappe.throw(e)
def on_update(self):
+ with payment_app_import_guard():
+ from payments.utils import create_payment_gateway
+
create_payment_gateway(
"GoCardless-" + self.gateway_name, settings="GoCardLess Settings", controller=self.gateway_name
)
diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.js b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.js
index 7c8ae5c..447d720 100644
--- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.js
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.js
@@ -7,6 +7,8 @@
},
refresh: function(frm) {
+ erpnext.utils.check_payments_app();
+
frappe.realtime.on("refresh_mpesa_dashboard", function(){
frm.reload_doc();
frm.events.setup_account_balance_html(frm);
diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py
index b534783..a298e11 100644
--- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py
@@ -9,13 +9,13 @@
from frappe.integrations.utils import create_request_log
from frappe.model.document import Document
from frappe.utils import call_hook_method, fmt_money, get_request_site_address
-from payments.utils import create_payment_gateway
from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_connector import MpesaConnector
from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_custom_fields import (
create_custom_pos_fields,
)
from erpnext.erpnext_integrations.utils import create_mode_of_payment
+from erpnext.utilities import payment_app_import_guard
class MpesaSettings(Document):
@@ -30,6 +30,9 @@
)
def on_update(self):
+ with payment_app_import_guard():
+ from payments.utils import create_payment_gateway
+
create_custom_pos_fields()
create_payment_gateway(
"Mpesa-" + self.payment_gateway_name,
diff --git a/erpnext/erpnext_integrations/stripe_integration.py b/erpnext/erpnext_integrations/stripe_integration.py
index 2d7e8a5..634e5c2 100644
--- a/erpnext/erpnext_integrations/stripe_integration.py
+++ b/erpnext/erpnext_integrations/stripe_integration.py
@@ -2,12 +2,16 @@
# For license information, please see license.txt
import frappe
-import stripe
from frappe import _
from frappe.integrations.utils import create_request_log
+from erpnext.utilities import payment_app_import_guard
+
def create_stripe_subscription(gateway_controller, data):
+ with payment_app_import_guard():
+ import stripe
+
stripe_settings = frappe.get_doc("Stripe Settings", gateway_controller)
stripe_settings.data = frappe._dict(data)
@@ -35,6 +39,9 @@
def create_subscription_on_stripe(stripe_settings):
+ with payment_app_import_guard():
+ import stripe
+
items = []
for payment_plan in stripe_settings.payment_plans:
plan = frappe.db.get_value("Subscription Plan", payment_plan.plan, "product_price_id")
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 92601b3..fd19d25 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -8,7 +8,6 @@
app_license = "GNU General Public License (v3)"
source_link = "https://github.com/frappe/erpnext"
app_logo_url = "/assets/erpnext/images/erpnext-logo.svg"
-required_apps = ["payments"]
develop_version = "14.x.x-develop"
diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
index 0d319bf..b900b21 100644
--- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
+++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
@@ -10,9 +10,6 @@
class MaintenanceVisit(TransactionBase):
- def get_feed(self):
- return _("To {0}").format(self.customer_name)
-
def validate_serial_no(self):
for d in self.get("purposes"):
if d.serial_no and not frappe.db.exists("Serial No", d.serial_no):
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index 694dc79..f568264 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -635,6 +635,10 @@
bom.submit()
bom_name = bom.name
+ ste1 = test_stock_entry.make_stock_entry(
+ item_code=rm1, target="_Test Warehouse - _TC", qty=32, basic_rate=5000.0
+ )
+
work_order = make_wo_order_test_record(
item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1
)
@@ -659,11 +663,29 @@
work_order.insert()
work_order.submit()
self.assertEqual(work_order.has_batch_no, 1)
- ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 30))
+ batches = frappe.get_all("Batch", filters={"reference_name": work_order.name})
+ self.assertEqual(len(batches), 3)
+ batches = [batch.name for batch in batches]
+
+ ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 10))
for row in ste1.get("items"):
if row.is_finished_item:
self.assertEqual(row.item_code, fg_item)
self.assertEqual(row.qty, 10)
+ self.assertTrue(row.batch_no in batches)
+ batches.remove(row.batch_no)
+
+ ste1.submit()
+
+ remaining_batches = []
+ ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 20))
+ for row in ste1.get("items"):
+ if row.is_finished_item:
+ self.assertEqual(row.item_code, fg_item)
+ self.assertEqual(row.qty, 10)
+ remaining_batches.append(row.batch_no)
+
+ self.assertEqual(sorted(remaining_batches), sorted(batches))
frappe.db.set_value("Manufacturing Settings", None, "make_serial_no_batch_from_work_order", 0)
@@ -1132,6 +1154,36 @@
except frappe.MandatoryError:
self.fail("Batch generation causing failing in Work Order")
+ @change_settings("Manufacturing Settings", {"make_serial_no_batch_from_work_order": 1})
+ def test_auto_serial_no_creation(self):
+ from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
+
+ fg_item = frappe.generate_hash(length=20)
+ child_item = frappe.generate_hash(length=20)
+
+ bom_tree = {fg_item: {child_item: {}}}
+
+ create_nested_bom(bom_tree, prefix="")
+
+ item = frappe.get_doc("Item", fg_item)
+ item.has_serial_no = 1
+ item.serial_no_series = f"{item.name}.#####"
+ item.save()
+
+ try:
+ wo_order = make_wo_order_test_record(item=fg_item, qty=2, skip_transfer=True)
+ serial_nos = wo_order.serial_no
+ stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
+ stock_entry.set_work_order_details()
+ stock_entry.set_serial_no_batch_for_finished_good()
+ for row in stock_entry.items:
+ if row.item_code == fg_item:
+ self.assertTrue(row.serial_no)
+ self.assertEqual(sorted(get_serial_nos(row.serial_no)), sorted(get_serial_nos(serial_nos)))
+
+ except frappe.MandatoryError:
+ self.fail("Batch generation causing failing in Work Order")
+
@change_settings(
"Manufacturing Settings",
{"backflush_raw_materials_based_on": "Material Transferred for Manufacture"},
diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
index 1e1b435..cdf1541 100644
--- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
+++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
@@ -4,7 +4,7 @@
import frappe
from frappe import _
-from frappe.query_builder.functions import Floor, Sum
+from frappe.query_builder.functions import Sum
from pypika.terms import ExistsCriterion
@@ -58,9 +58,9 @@
bom_item.description,
bom_item.stock_qty,
bom_item.stock_uom,
- bom_item.stock_qty * qty_to_produce / bom.quantity,
- Sum(bin.actual_qty).as_("actual_qty"),
- Sum(Floor(bin.actual_qty / (bom_item.stock_qty * qty_to_produce / bom.quantity))),
+ (bom_item.stock_qty / bom.quantity) * qty_to_produce,
+ Sum(bin.actual_qty),
+ Sum(bin.actual_qty) / (bom_item.stock_qty / bom.quantity),
)
.where((bom_item.parent == filters.get("bom")) & (bom_item.parenttype == "BOM"))
.groupby(bom_item.item_code)
diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
index 16c25ce..109d9ab 100644
--- a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
+++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
@@ -49,7 +49,7 @@
parent.bom_no,
parent.fg_warehouse.as_("warehouse"),
)
- .where(parent.status.notin(["Completed", "Stopped"]))
+ .where(parent.status.notin(["Completed", "Stopped", "Closed"]))
)
if order_by == "Planned Start Date":
@@ -79,10 +79,11 @@
query = query.where(child.parent.isin(self.filters.docnames))
if doctype == "Sales Order":
- query = query.select(
- child.delivery_date,
- parent.base_grand_total,
- ).where((child.stock_qty > child.produced_qty) & (parent.per_delivered < 100.0))
+ query = query.select(child.delivery_date, parent.base_grand_total,).where(
+ (child.stock_qty > child.produced_qty)
+ & (parent.per_delivered < 100.0)
+ & (parent.status.notin(["Completed", "Closed"]))
+ )
if order_by == "Delivery Date":
query = query.orderby(child.delivery_date, order=Order.asc)
@@ -91,7 +92,9 @@
elif doctype == "Material Request":
query = query.select(child.schedule_date,).where(
- (parent.per_ordered < 100) & (parent.material_request_type == "Manufacture")
+ (parent.per_ordered < 100)
+ & (parent.material_request_type == "Manufacture")
+ & (parent.status != "Stopped")
)
if order_by == "Required Date":
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 166faf9..0aad1d3 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -318,3 +318,4 @@
erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization
erpnext.patches.v14_0.update_partial_tds_fields
erpnext.patches.v14_0.create_incoterms_and_migrate_shipment
+erpnext.patches.v14_0.setup_clear_repost_logs
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/update_exchange_rate_settings.py b/erpnext/patches/v13_0/update_exchange_rate_settings.py
index 746195f..130a7bf 100644
--- a/erpnext/patches/v13_0/update_exchange_rate_settings.py
+++ b/erpnext/patches/v13_0/update_exchange_rate_settings.py
@@ -1,5 +1,8 @@
+import frappe
+
from erpnext.setup.install import setup_currency_exchange
def execute():
+ frappe.reload_doc("accounts", "doctype", "currency_exchange_settings")
setup_currency_exchange()
diff --git a/erpnext/patches/v14_0/setup_clear_repost_logs.py b/erpnext/patches/v14_0/setup_clear_repost_logs.py
new file mode 100644
index 0000000..be9ddca
--- /dev/null
+++ b/erpnext/patches/v14_0/setup_clear_repost_logs.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
+# License: MIT. See LICENSE
+
+from erpnext.setup.install import setup_log_settings
+
+
+def execute():
+ setup_log_settings()
diff --git a/erpnext/portal/utils.py b/erpnext/portal/utils.py
index 7be8c5d..c8b03e6 100644
--- a/erpnext/portal/utils.py
+++ b/erpnext/portal/utils.py
@@ -102,7 +102,7 @@
contact = frappe.new_doc("Contact")
contact.update({"first_name": fullname, "email_id": user})
contact.append("links", dict(link_doctype=doctype, link_name=party_name))
- contact.append("email_ids", dict(email_id=user))
+ contact.append("email_ids", dict(email_id=user, is_primary=True))
contact.flags.ignore_mandatory = True
contact.insert(ignore_permissions=True)
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index d80133c..4735f24 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -15,9 +15,6 @@
class Project(Document):
- def get_feed(self):
- return "{0}: {1}".format(_(self.status), frappe.safe_decode(self.project_name))
-
def onload(self):
self.set_onload(
"activity_summary",
@@ -42,6 +39,8 @@
self.send_welcome_email()
self.update_costing()
self.update_percent_complete()
+ self.validate_from_to_dates("expected_start_date", "expected_end_date")
+ self.validate_from_to_dates("actual_start_date", "actual_end_date")
def copy_from_template(self):
"""
diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py
index fa50785..2dde542 100755
--- a/erpnext/projects/doctype/task/task.py
+++ b/erpnext/projects/doctype/task/task.py
@@ -9,6 +9,7 @@
from frappe.desk.form.assign_to import clear, close_all_assignments
from frappe.model.mapper import get_mapped_doc
from frappe.utils import add_days, cstr, date_diff, flt, get_link_to_form, getdate, today
+from frappe.utils.data import format_date
from frappe.utils.nestedset import NestedSet
@@ -16,16 +17,9 @@
pass
-class EndDateCannotBeGreaterThanProjectEndDateError(frappe.ValidationError):
- pass
-
-
class Task(NestedSet):
nsm_parent_field = "parent_task"
- def get_feed(self):
- return "{0}: {1}".format(_(self.status), self.subject)
-
def get_customer_details(self):
cust = frappe.db.sql("select customer_name from `tabCustomer` where name=%s", self.customer)
if cust:
@@ -34,8 +28,6 @@
def validate(self):
self.validate_dates()
- self.validate_parent_expected_end_date()
- self.validate_parent_project_dates()
self.validate_progress()
self.validate_status()
self.update_depends_on()
@@ -43,51 +35,42 @@
self.validate_completed_on()
def validate_dates(self):
- if (
- self.exp_start_date
- and self.exp_end_date
- and getdate(self.exp_start_date) > getdate(self.exp_end_date)
- ):
- frappe.throw(
- _("{0} can not be greater than {1}").format(
- frappe.bold("Expected Start Date"), frappe.bold("Expected End Date")
- )
- )
-
- if (
- self.act_start_date
- and self.act_end_date
- and getdate(self.act_start_date) > getdate(self.act_end_date)
- ):
- frappe.throw(
- _("{0} can not be greater than {1}").format(
- frappe.bold("Actual Start Date"), frappe.bold("Actual End Date")
- )
- )
+ self.validate_from_to_dates("exp_start_date", "exp_end_date")
+ self.validate_from_to_dates("act_start_date", "act_end_date")
+ self.validate_parent_expected_end_date()
+ self.validate_parent_project_dates()
def validate_parent_expected_end_date(self):
- if self.parent_task:
- parent_exp_end_date = frappe.db.get_value("Task", self.parent_task, "exp_end_date")
- if parent_exp_end_date and getdate(self.get("exp_end_date")) > getdate(parent_exp_end_date):
- frappe.throw(
- _(
- "Expected End Date should be less than or equal to parent task's Expected End Date {0}."
- ).format(getdate(parent_exp_end_date))
- )
+ if not self.parent_task or not self.exp_end_date:
+ return
+
+ parent_exp_end_date = frappe.db.get_value("Task", self.parent_task, "exp_end_date")
+ if not parent_exp_end_date:
+ return
+
+ if getdate(self.exp_end_date) > getdate(parent_exp_end_date):
+ frappe.throw(
+ _(
+ "Expected End Date should be less than or equal to parent task's Expected End Date {0}."
+ ).format(format_date(parent_exp_end_date)),
+ frappe.exceptions.InvalidDates,
+ )
def validate_parent_project_dates(self):
if not self.project or frappe.flags.in_test:
return
- expected_end_date = frappe.db.get_value("Project", self.project, "expected_end_date")
-
- if expected_end_date:
- validate_project_dates(
- getdate(expected_end_date), self, "exp_start_date", "exp_end_date", "Expected"
- )
- validate_project_dates(
- getdate(expected_end_date), self, "act_start_date", "act_end_date", "Actual"
- )
+ if project_end_date := frappe.db.get_value("Project", self.project, "expected_end_date"):
+ project_end_date = getdate(project_end_date)
+ for fieldname in ("exp_start_date", "exp_end_date", "act_start_date", "act_end_date"):
+ task_date = self.get(fieldname)
+ if task_date and date_diff(project_end_date, getdate(task_date)) < 0:
+ frappe.throw(
+ _("Task's {0} cannot be after Project's Expected End Date.").format(
+ _(self.meta.get_label(fieldname))
+ ),
+ frappe.exceptions.InvalidDates,
+ )
def validate_status(self):
if self.is_template and self.status != "Template":
@@ -398,15 +381,3 @@
def on_doctype_update():
frappe.db.add_index("Task", ["lft", "rgt"])
-
-
-def validate_project_dates(project_end_date, task, task_start, task_end, actual_or_expected_date):
- if task.get(task_start) and date_diff(project_end_date, getdate(task.get(task_start))) < 0:
- frappe.throw(
- _("Task's {0} Start Date cannot be after Project's End Date.").format(actual_or_expected_date)
- )
-
- if task.get(task_end) and date_diff(project_end_date, getdate(task.get(task_end))) < 0:
- frappe.throw(
- _("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date)
- )
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 4c3e9dc..1f8a5e3 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -47,29 +47,36 @@
await this.calculate_shipping_charges();
- // Advance calculation applicable to Sales /Purchase Invoice
- if(in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype)
- && this.frm.doc.docstatus < 2 && !this.frm.doc.is_return) {
+ // Advance calculation applicable to Sales/Purchase Invoice
+ if (
+ in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype)
+ && this.frm.doc.docstatus < 2
+ && !this.frm.doc.is_return
+ ) {
this.calculate_total_advance(update_paid_amount);
}
- if (in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype) && this.frm.doc.is_pos &&
- this.frm.doc.is_return) {
- if (this.frm.doc.doctype == "Sales Invoice") {
- this.set_total_amount_to_default_mop();
- }
+ if (
+ in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype)
+ && this.frm.doc.is_pos
+ && this.frm.doc.is_return
+ ) {
+ this.set_total_amount_to_default_mop();
this.calculate_paid_amount();
}
// Sales person's commission
- if(in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"], this.frm.doc.doctype)) {
+ if (in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"], this.frm.doc.doctype)) {
this.calculate_commission();
this.calculate_contribution();
}
// Update paid amount on return/debit note creation
- if(this.frm.doc.doctype === "Purchase Invoice" && this.frm.doc.is_return
- && (this.frm.doc.grand_total > this.frm.doc.paid_amount)) {
+ if (
+ this.frm.doc.doctype === "Purchase Invoice"
+ && this.frm.doc.is_return
+ && (this.frm.doc.grand_total > this.frm.doc.paid_amount)
+ ) {
this.frm.doc.paid_amount = flt(this.frm.doc.grand_total, precision("grand_total"));
}
@@ -775,21 +782,30 @@
let grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total;
let base_grand_total = this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total;
- if(this.frm.doc.party_account_currency == this.frm.doc.currency) {
- var total_amount_to_pay = flt((grand_total - this.frm.doc.total_advance
- - this.frm.doc.write_off_amount), precision("grand_total"));
+ if (this.frm.doc.party_account_currency == this.frm.doc.currency) {
+ var total_amount_to_pay = flt(
+ grand_total - this.frm.doc.total_advance - this.frm.doc.write_off_amount,
+ precision("grand_total")
+ );
} else {
var total_amount_to_pay = flt(
- (flt(base_grand_total, precision("base_grand_total"))
- - this.frm.doc.total_advance - this.frm.doc.base_write_off_amount),
+ (
+ flt(
+ base_grand_total,
+ precision("base_grand_total")
+ )
+ - this.frm.doc.total_advance - this.frm.doc.base_write_off_amount
+ ),
precision("base_grand_total")
);
}
+
this.frm.doc.payments.find(pay => {
if (pay.default) {
pay.amount = total_amount_to_pay;
}
});
+
this.frm.refresh_fields();
}
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 46ac808..58d8de2 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -1130,10 +1130,13 @@
qty(doc, cdt, cdn) {
let item = frappe.get_doc(cdt, cdn);
- item.pricing_rules = ''
- this.conversion_factor(doc, cdt, cdn, true);
- this.calculate_stock_uom_rate(doc, cdt, cdn);
- this.apply_pricing_rule(item, true);
+ // item.pricing_rules = ''
+ frappe.run_serially([
+ () => this.remove_pricing_rule(item),
+ () => this.conversion_factor(doc, cdt, cdn, true),
+ () => this.calculate_stock_uom_rate(doc, cdt, cdn),
+ () => this.apply_pricing_rule(item, true)
+ ]);
}
calculate_stock_uom_rate(doc, cdt, cdn) {
@@ -1357,16 +1360,21 @@
var item_list = [];
$.each(this.frm.doc["items"] || [], function(i, d) {
- if (d.item_code && !d.is_free_item) {
- item_list.push({
- "doctype": d.doctype,
- "name": d.name,
- "item_code": d.item_code,
- "pricing_rules": d.pricing_rules,
- "parenttype": d.parenttype,
- "parent": d.parent,
- "price_list_rate": d.price_list_rate
- })
+ if (d.item_code) {
+ if (d.is_free_item) {
+ // Simply remove free items
+ me.frm.get_field("items").grid.grid_rows[i].remove();
+ } else {
+ item_list.push({
+ "doctype": d.doctype,
+ "name": d.name,
+ "item_code": d.item_code,
+ "pricing_rules": d.pricing_rules,
+ "parenttype": d.parenttype,
+ "parent": d.parent,
+ "price_list_rate": d.price_list_rate
+ })
+ }
}
});
return this.frm.call({
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index 6d64625..d37b7bb 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -333,8 +333,18 @@
}
frappe.ui.form.make_quick_entry(doctype, null, null, new_doc);
});
- }
+ },
+ // check if payments app is installed on site, if not warn user.
+ check_payments_app: () => {
+ if (frappe.boot.versions && !frappe.boot.versions.payments) {
+ const marketplace_link = '<a href="https://frappecloud.com/marketplace/apps/payments">Marketplace</a>'
+ const github_link = '<a href="https://github.com/frappe/payments/">GitHub</a>'
+ const msg = __("payments app is not installed. Please install it from {0} or {1}", [marketplace_link, github_link])
+ frappe.msgprint(msg);
+ }
+
+ },
});
erpnext.utils.select_alternate_items = function(opts) {
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index d0eb377..12ecb01 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -6,7 +6,7 @@
import frappe
import frappe.defaults
-from frappe import _, msgprint
+from frappe import _, msgprint, qb
from frappe.contacts.address_and_contact import (
delete_contact_and_address,
load_address_and_contact,
@@ -27,9 +27,6 @@
class Customer(TransactionBase):
- def get_feed(self):
- return self.customer_name
-
def onload(self):
"""Load address and contacts in `__onload`"""
load_address_and_contact(self)
@@ -732,12 +729,15 @@
@frappe.validate_and_sanitize_search_inputs
def get_customer_primary_contact(doctype, txt, searchfield, start, page_len, filters):
customer = filters.get("customer")
- return frappe.db.sql(
- """
- select `tabContact`.name from `tabContact`, `tabDynamic Link`
- where `tabContact`.name = `tabDynamic Link`.parent and `tabDynamic Link`.link_name = %(customer)s
- and `tabDynamic Link`.link_doctype = 'Customer'
- and `tabContact`.name like %(txt)s
- """,
- {"customer": customer, "txt": "%%%s%%" % txt},
+
+ con = qb.DocType("Contact")
+ dlink = qb.DocType("Dynamic Link")
+
+ return (
+ qb.from_(con)
+ .join(dlink)
+ .on(con.name == dlink.parent)
+ .select(con.name, con.full_name, con.email_id)
+ .where((dlink.link_name == customer) & (con.name.like(f"%{txt}%")))
+ .run()
)
diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json
index 08918f4..eb2c0a4 100644
--- a/erpnext/selling/doctype/quotation/quotation.json
+++ b/erpnext/selling/doctype/quotation/quotation.json
@@ -43,12 +43,13 @@
"total",
"net_total",
"taxes_section",
+ "tax_category",
"taxes_and_charges",
"column_break_36",
- "tax_category",
- "column_break_34",
"shipping_rule",
+ "column_break_34",
"incoterm",
+ "named_place",
"section_break_36",
"taxes",
"section_break_39",
@@ -1059,13 +1060,19 @@
"fieldtype": "Link",
"label": "Incoterm",
"options": "Incoterm"
+ },
+ {
+ "depends_on": "incoterm",
+ "fieldname": "named_place",
+ "fieldtype": "Data",
+ "label": "Named Place"
}
],
"icon": "fa fa-shopping-cart",
"idx": 82,
"is_submittable": 1,
"links": [],
- "modified": "2022-11-17 17:20:54.984348",
+ "modified": "2022-12-12 18:32:28.671332",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation",
diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py
index 6f0b381..b151dd5 100644
--- a/erpnext/selling/doctype/quotation/test_quotation.py
+++ b/erpnext/selling/doctype/quotation/test_quotation.py
@@ -30,6 +30,24 @@
self.assertTrue(sales_order.get("payment_schedule"))
+ def test_maintain_rate_in_sales_cycle_is_enforced(self):
+ from erpnext.selling.doctype.quotation.quotation import make_sales_order
+
+ maintain_rate = frappe.db.get_single_value("Selling Settings", "maintain_same_sales_rate")
+ frappe.db.set_single_value("Selling Settings", "maintain_same_sales_rate", 1)
+
+ quotation = frappe.copy_doc(test_records[0])
+ quotation.transaction_date = nowdate()
+ quotation.valid_till = add_months(quotation.transaction_date, 1)
+ quotation.insert()
+ quotation.submit()
+
+ sales_order = make_sales_order(quotation.name)
+ sales_order.items[0].rate = 1
+ self.assertRaises(frappe.ValidationError, sales_order.save)
+
+ frappe.db.set_single_value("Selling Settings", "maintain_same_sales_rate", maintain_rate)
+
def test_make_sales_order_with_different_currency(self):
from erpnext.selling.doctype.quotation.quotation import make_sales_order
diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json
index 9ec32cb..ccea840 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.json
+++ b/erpnext/selling/doctype/sales_order/sales_order.json
@@ -58,12 +58,13 @@
"total",
"net_total",
"taxes_section",
+ "tax_category",
"taxes_and_charges",
"column_break_38",
- "tax_category",
- "column_break_49",
"shipping_rule",
+ "column_break_49",
"incoterm",
+ "named_place",
"section_break_40",
"taxes",
"section_break_43",
@@ -1630,13 +1631,19 @@
"fieldtype": "Link",
"label": "Incoterm",
"options": "Incoterm"
+ },
+ {
+ "depends_on": "incoterm",
+ "fieldname": "named_place",
+ "fieldtype": "Data",
+ "label": "Named Place"
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
- "modified": "2022-11-17 17:22:00.413878",
+ "modified": "2022-12-12 18:34:00.681780",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 78e2370..0013c95 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -194,7 +194,7 @@
)
if cint(frappe.db.get_single_value("Selling Settings", "maintain_same_sales_rate")):
- self.validate_rate_with_reference_doc([["Quotation", "prev_docname", "quotation_item"]])
+ self.validate_rate_with_reference_doc([["Quotation", "prevdoc_docname", "quotation_item"]])
def update_enquiry_status(self, prevdoc, flag):
enq = frappe.db.sql(
diff --git a/erpnext/selling/doctype/sales_order/sales_order_dashboard.py b/erpnext/selling/doctype/sales_order/sales_order_dashboard.py
index ace2e29..5c4b578 100644
--- a/erpnext/selling/doctype/sales_order/sales_order_dashboard.py
+++ b/erpnext/selling/doctype/sales_order/sales_order_dashboard.py
@@ -12,7 +12,10 @@
"Auto Repeat": "reference_document",
"Maintenance Visit": "prevdoc_docname",
},
- "internal_links": {"Quotation": ["items", "prevdoc_docname"]},
+ "internal_links": {
+ "Quotation": ["items", "prevdoc_docname"],
+ "Material Request": ["items", "material_request"],
+ },
"transactions": [
{
"label": _("Fulfillment"),
diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js
index 91748bc..f3f931e 100644
--- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js
+++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js
@@ -45,6 +45,12 @@
}
},
{
+ "fieldname": "warehouse",
+ "label": __("Warehouse"),
+ "fieldtype": "Link",
+ "options": "Warehouse"
+ },
+ {
"fieldname": "status",
"label": __("Status"),
"fieldtype": "MultiSelectList",
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 720aa41..63d339a 100644
--- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
+++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
@@ -53,6 +53,9 @@
if filters.get("status"):
conditions += " and so.status in %(status)s"
+ if filters.get("warehouse"):
+ conditions += " and soi.warehouse = %(warehouse)s"
+
return conditions
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index d6f2378..07ee289 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -70,9 +70,6 @@
self.abbr = self.abbr.strip()
- # if self.get('__islocal') and len(self.abbr) > 5:
- # frappe.throw(_("Abbreviation cannot have more than 5 characters"))
-
if not self.abbr.strip():
frappe.throw(_("Abbreviation is mandatory"))
diff --git a/erpnext/setup/doctype/employee/employee.py b/erpnext/setup/doctype/employee/employee.py
index 13a6f20..facefa3 100755
--- a/erpnext/setup/doctype/employee/employee.py
+++ b/erpnext/setup/doctype/employee/employee.py
@@ -145,33 +145,10 @@
if self.date_of_birth and getdate(self.date_of_birth) > getdate(today()):
throw(_("Date of Birth cannot be greater than today."))
- if (
- self.date_of_birth
- and self.date_of_joining
- and getdate(self.date_of_birth) >= getdate(self.date_of_joining)
- ):
- throw(_("Date of Joining must be greater than Date of Birth"))
-
- elif (
- self.date_of_retirement
- and self.date_of_joining
- and (getdate(self.date_of_retirement) <= getdate(self.date_of_joining))
- ):
- throw(_("Date Of Retirement must be greater than Date of Joining"))
-
- elif (
- self.relieving_date
- and self.date_of_joining
- and (getdate(self.relieving_date) < getdate(self.date_of_joining))
- ):
- throw(_("Relieving Date must be greater than or equal to Date of Joining"))
-
- elif (
- self.contract_end_date
- and self.date_of_joining
- and (getdate(self.contract_end_date) <= getdate(self.date_of_joining))
- ):
- throw(_("Contract End Date must be greater than Date of Joining"))
+ self.validate_from_to_dates("date_of_birth", "date_of_joining")
+ self.validate_from_to_dates("date_of_joining", "date_of_retirement")
+ self.validate_from_to_dates("date_of_joining", "relieving_date")
+ self.validate_from_to_dates("date_of_joining", "contract_end_date")
def validate_email(self):
if self.company_email:
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index 411176b..95bbf84 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -152,7 +152,7 @@
if from_item and frappe.request.environ.get("HTTP_REFERER"):
# base page after 'Home' will vary on Item page
- last_page = frappe.request.environ["HTTP_REFERER"].split("/")[-1]
+ last_page = frappe.request.environ["HTTP_REFERER"].split("/")[-1].split("?")[0]
if last_page and last_page in ("shop-by-category", "all-products"):
base_nav_page_title = " ".join(last_page.split("-")).title()
base_nav_page = {"name": _(base_nav_page_title), "route": "/" + last_page}
diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
index c18a4b2..4256a7d 100644
--- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
+++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
@@ -204,7 +204,7 @@
@frappe.whitelist()
def get_doctypes_to_be_ignored():
- doctypes_to_be_ignored_list = [
+ doctypes_to_be_ignored = [
"Account",
"Cost Center",
"Warehouse",
@@ -223,4 +223,7 @@
"Customer",
"Supplier",
]
- return doctypes_to_be_ignored_list
+
+ doctypes_to_be_ignored.extend(frappe.get_hooks("company_data_to_be_ignored") or [])
+
+ return doctypes_to_be_ignored
diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py
index d3b47f9..1f7dddf 100644
--- a/erpnext/setup/install.py
+++ b/erpnext/setup/install.py
@@ -30,6 +30,7 @@
add_company_to_session_defaults()
add_standard_navbar_items()
add_app_name()
+ setup_log_settings()
frappe.db.commit()
@@ -197,3 +198,10 @@
def add_app_name():
frappe.db.set_value("System Settings", None, "app_name", "ERPNext")
+
+
+def setup_log_settings():
+ log_settings = frappe.get_single("Log Settings")
+ log_settings.append("logs_to_clear", {"ref_doctype": "Repost Item Valuation", "days": 60})
+
+ log_settings.save(ignore_permissions=True)
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json
index 80e4bcb..165a56b 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.json
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.json
@@ -57,12 +57,13 @@
"total",
"net_total",
"taxes_section",
+ "tax_category",
"taxes_and_charges",
"column_break_43",
- "tax_category",
- "column_break_39",
"shipping_rule",
+ "column_break_39",
"incoterm",
+ "named_place",
"section_break_41",
"taxes",
"section_break_44",
@@ -1388,13 +1389,19 @@
"fieldtype": "Link",
"label": "Incoterm",
"options": "Incoterm"
+ },
+ {
+ "depends_on": "incoterm",
+ "fieldname": "named_place",
+ "fieldtype": "Data",
+ "label": "Named Place"
}
],
"icon": "fa fa-truck",
"idx": 146,
"is_submittable": 1,
"links": [],
- "modified": "2022-11-17 17:22:42.860790",
+ "modified": "2022-12-12 18:38:53.067799",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index 04aee42..94f63a5 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -22,9 +22,6 @@
class MaterialRequest(BuyingController):
- def get_feed(self):
- return
-
def check_if_already_pulled(self):
pass
@@ -596,7 +593,9 @@
if source.material_request_type == "Customer Provided":
target.purpose = "Material Receipt"
- target.set_missing_values()
+ target.set_transfer_qty()
+ target.set_actual_qty()
+ target.calculate_rate_and_amount(raise_error_if_no_rate=False)
target.set_stock_entry_type()
target.set_job_card_data()
diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py
index 4d05d7a..d606751 100644
--- a/erpnext/stock/doctype/packed_item/packed_item.py
+++ b/erpnext/stock/doctype/packed_item/packed_item.py
@@ -48,7 +48,7 @@
update_packed_item_from_cancelled_doc(item_row, bundle_item, pi_row, doc)
if set_price_from_children: # create/update bundle item wise price dict
- update_product_bundle_rate(parent_items_price, pi_row)
+ update_product_bundle_rate(parent_items_price, pi_row, item_row)
if parent_items_price:
set_product_bundle_rate_amount(doc, parent_items_price) # set price in bundle item
@@ -247,7 +247,7 @@
return prev_doc_packed_items_map
-def update_product_bundle_rate(parent_items_price, pi_row):
+def update_product_bundle_rate(parent_items_price, pi_row, item_row):
"""
Update the price dict of Product Bundles based on the rates of the Items in the bundle.
@@ -259,7 +259,7 @@
if not rate:
parent_items_price[key] = 0.0
- parent_items_price[key] += flt(pi_row.rate)
+ parent_items_price[key] += flt((pi_row.rate * pi_row.qty) / item_row.stock_qty)
def set_product_bundle_rate_amount(doc, parent_items_price):
diff --git a/erpnext/stock/doctype/packed_item/test_packed_item.py b/erpnext/stock/doctype/packed_item/test_packed_item.py
index ad7fd9a..ad06732 100644
--- a/erpnext/stock/doctype/packed_item/test_packed_item.py
+++ b/erpnext/stock/doctype/packed_item/test_packed_item.py
@@ -126,8 +126,8 @@
so.packed_items[1].rate = 200
so.save()
- self.assertEqual(so.items[0].rate, 350)
- self.assertEqual(so.items[0].amount, 700)
+ self.assertEqual(so.items[0].rate, 700)
+ self.assertEqual(so.items[0].amount, 1400)
def test_newly_mapped_doc_packed_items(self):
"Test impact on packed items in newly mapped DN from SO."
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index ab91d7c..8f04358 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -58,12 +58,13 @@
"total",
"net_total",
"taxes_charges_section",
+ "tax_category",
"taxes_and_charges",
"shipping_col",
- "tax_category",
- "column_break_53",
"shipping_rule",
+ "column_break_53",
"incoterm",
+ "named_place",
"taxes_section",
"taxes",
"totals",
@@ -1225,13 +1226,19 @@
"fieldtype": "Link",
"label": "Incoterm",
"options": "Incoterm"
+ },
+ {
+ "depends_on": "incoterm",
+ "fieldname": "named_place",
+ "fieldtype": "Data",
+ "label": "Named Place"
}
],
"icon": "fa fa-truck",
"idx": 261,
"is_submittable": 1,
"links": [],
- "modified": "2022-11-17 17:29:30.067536",
+ "modified": "2022-12-12 18:40:32.447752",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt",
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 673fcb5..3739cb8 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -173,7 +173,9 @@
)
if (
- cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate")) and not self.is_return
+ cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate"))
+ and not self.is_return
+ and not self.is_internal_supplier
):
self.validate_rate_with_reference_doc(
[["Purchase Order", "purchase_order", "purchase_order_item"]]
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
index 8e914e6..bbed099 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
@@ -5,6 +5,8 @@
from frappe import _
from frappe.exceptions import QueryDeadlockError, QueryTimeoutError
from frappe.model.document import Document
+from frappe.query_builder import DocType, Interval
+from frappe.query_builder.functions import Now
from frappe.utils import cint, get_link_to_form, get_weekday, getdate, now, nowtime
from frappe.utils.user import get_users_with_role
from rq.timeouts import JobTimeoutException
@@ -21,6 +23,18 @@
class RepostItemValuation(Document):
+ @staticmethod
+ def clear_old_logs(days=None):
+ days = days or 90
+ table = DocType("Repost Item Valuation")
+ frappe.db.delete(
+ table,
+ filters=(
+ (table.modified < (Now() - Interval(days=days)))
+ & (table.status.isin(["Completed", "Skipped"]))
+ ),
+ )
+
def validate(self):
self.set_status(write=False)
self.reset_field_values()
diff --git a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
index f166775..96ac435 100644
--- a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
@@ -6,8 +6,7 @@
import frappe
from frappe.tests.utils import FrappeTestCase
-from frappe.utils import nowdate
-from frappe.utils.data import add_to_date, today
+from frappe.utils import add_days, add_to_date, now, nowdate, today
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.utils import repost_gle_for_stock_vouchers
@@ -86,6 +85,33 @@
msg=f"Exepcted false from : {case}",
)
+ def test_clear_old_logs(self):
+ # create 10 logs
+ for i in range(1, 20):
+ repost_doc = frappe.get_doc(
+ doctype="Repost Item Valuation",
+ item_code="_Test Item",
+ warehouse="_Test Warehouse - _TC",
+ based_on="Item and Warehouse",
+ posting_date=nowdate(),
+ status="Skipped",
+ posting_time="00:01:00",
+ ).insert(ignore_permissions=True)
+
+ repost_doc.load_from_db()
+ repost_doc.modified = add_days(now(), days=-i * 10)
+ repost_doc.db_update_all()
+
+ logs = frappe.get_all("Repost Item Valuation", filters={"status": "Skipped"})
+ self.assertTrue(len(logs) > 10)
+
+ from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import RepostItemValuation
+
+ RepostItemValuation.clear_old_logs(days=1)
+
+ logs = frappe.get_all("Repost Item Valuation", filters={"status": "Skipped"})
+ self.assertTrue(len(logs) == 0)
+
def test_create_item_wise_repost_item_valuation_entries(self):
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index b116735..d401f81 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -83,9 +83,6 @@
}
)
- def get_feed(self):
- return self.stock_entry_type
-
def onload(self):
for item in self.get("items"):
item.update(get_bin_details(item.item_code, item.s_warehouse))
@@ -659,6 +656,13 @@
if d.allow_zero_valuation_rate:
d.basic_rate = 0.0
+ frappe.msgprint(
+ _(
+ "Row {0}: Item rate has been updated to zero as Allow Zero Valuation Rate is checked for item {1}"
+ ).format(d.idx, d.item_code),
+ alert=1,
+ )
+
elif d.is_finished_item:
if self.purpose == "Manufacture":
d.basic_rate = self.get_basic_rate_for_manufactured_item(
@@ -1538,6 +1542,7 @@
"reference_name": self.pro_doc.name,
"reference_doctype": self.pro_doc.doctype,
"qty_to_produce": (">", 0),
+ "batch_qty": ("=", 0),
}
fields = ["qty_to_produce as qty", "produced_qty", "name"]
@@ -2231,16 +2236,16 @@
d.qty -= process_loss_dict[d.item_code][1]
def set_serial_no_batch_for_finished_good(self):
- args = {}
+ serial_nos = []
if self.pro_doc.serial_no:
- self.get_serial_nos_for_fg(args)
+ serial_nos = self.get_serial_nos_for_fg() or []
for row in self.items:
if row.is_finished_item and row.item_code == self.pro_doc.production_item:
- if args.get("serial_no"):
- row.serial_no = "\n".join(args["serial_no"][0 : cint(row.qty)])
+ if serial_nos:
+ row.serial_no = "\n".join(serial_nos[0 : cint(row.qty)])
- def get_serial_nos_for_fg(self, args):
+ def get_serial_nos_for_fg(self):
fields = [
"`tabStock Entry`.`name`",
"`tabStock Entry Detail`.`qty`",
@@ -2251,14 +2256,12 @@
filters = [
["Stock Entry", "work_order", "=", self.work_order],
["Stock Entry", "purpose", "=", "Manufacture"],
- ["Stock Entry", "docstatus", "=", 1],
+ ["Stock Entry", "docstatus", "<", 2],
["Stock Entry Detail", "item_code", "=", self.pro_doc.production_item],
]
stock_entries = frappe.get_all("Stock Entry", fields=fields, filters=filters)
-
- if self.pro_doc.serial_no:
- args["serial_no"] = self.get_available_serial_nos(stock_entries)
+ return self.get_available_serial_nos(stock_entries)
def get_available_serial_nos(self, stock_entries):
used_serial_nos = []
diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py
index 6e06d23..430a8d1 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.py
+++ b/erpnext/stock/doctype/warehouse/warehouse.py
@@ -2,12 +2,10 @@
# License: GNU General Public License v3. See license.txt
-from collections import defaultdict
-
import frappe
from frappe import _, throw
from frappe.contacts.address_and_contact import load_address_and_contact
-from frappe.utils import cint, flt
+from frappe.utils import cint
from frappe.utils.nestedset import NestedSet
from pypika.terms import ExistsCriterion
@@ -166,60 +164,7 @@
["company", "in", (company, None, "")],
]
- warehouses = frappe.get_list(doctype, fields=fields, filters=filters, order_by="name")
-
- company_currency = ""
- if company:
- company_currency = frappe.get_cached_value("Company", company, "default_currency")
-
- warehouse_wise_value = get_warehouse_wise_stock_value(company)
-
- # return warehouses
- for wh in warehouses:
- wh["balance"] = warehouse_wise_value.get(wh.value)
- if company_currency:
- wh["company_currency"] = company_currency
- return warehouses
-
-
-def get_warehouse_wise_stock_value(company):
- warehouses = frappe.get_all(
- "Warehouse", fields=["name", "parent_warehouse"], filters={"company": company}
- )
- parent_warehouse = {d.name: d.parent_warehouse for d in warehouses}
-
- filters = {"warehouse": ("in", [data.name for data in warehouses])}
- bin_data = frappe.get_all(
- "Bin",
- fields=["sum(stock_value) as stock_value", "warehouse"],
- filters=filters,
- group_by="warehouse",
- )
-
- warehouse_wise_stock_value = defaultdict(float)
- for row in bin_data:
- if not row.stock_value:
- continue
-
- warehouse_wise_stock_value[row.warehouse] = row.stock_value
- update_value_in_parent_warehouse(
- warehouse_wise_stock_value, parent_warehouse, row.warehouse, row.stock_value
- )
-
- return warehouse_wise_stock_value
-
-
-def update_value_in_parent_warehouse(
- warehouse_wise_stock_value, parent_warehouse_dict, warehouse, stock_value
-):
- parent_warehouse = parent_warehouse_dict.get(warehouse)
- if not parent_warehouse:
- return
-
- warehouse_wise_stock_value[parent_warehouse] += flt(stock_value)
- update_value_in_parent_warehouse(
- warehouse_wise_stock_value, parent_warehouse_dict, parent_warehouse, stock_value
- )
+ return frappe.get_list(doctype, fields=fields, filters=filters, order_by="name")
@frappe.whitelist()
diff --git a/erpnext/stock/doctype/warehouse/warehouse_tree.js b/erpnext/stock/doctype/warehouse/warehouse_tree.js
index e9e14c7..eb635e6 100644
--- a/erpnext/stock/doctype/warehouse/warehouse_tree.js
+++ b/erpnext/stock/doctype/warehouse/warehouse_tree.js
@@ -17,11 +17,4 @@
description: __("Child nodes can be only created under 'Group' type nodes")}
],
ignore_fields:["parent_warehouse"],
- onrender: function(node) {
- if (node.data && node.data.balance!==undefined) {
- $('<span class="balance-area pull-right">'
- + format_currency((node.data.balance), node.data.company_currency)
- + '</span>').insertBefore(node.$ul);
- }
- }
}
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 108611c..1741d65 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -113,7 +113,7 @@
if args.get(key) is None:
args[key] = value
- data = get_pricing_rule_for_item(args, out.price_list_rate, doc, for_validate=for_validate)
+ data = get_pricing_rule_for_item(args, doc=doc, for_validate=for_validate)
out.update(data)
@@ -828,9 +828,9 @@
):
if frappe.has_permission("Item Price", "write"):
price_list_rate = (
- (args.rate + args.discount_amount) / args.get("conversion_factor")
+ (flt(args.rate) + flt(args.discount_amount)) / args.get("conversion_factor")
if args.get("conversion_factor")
- else (args.rate + args.discount_amount)
+ else (flt(args.rate) + flt(args.discount_amount))
)
item_price = frappe.db.get_value(
@@ -1305,7 +1305,7 @@
item_doc = frappe.db.get_value("Item", args.item_code, ["name", "variant_of"], as_dict=1)
item_details = get_price_list_rate(args, item_doc)
- item_details.update(get_pricing_rule_for_item(args, item_details.price_list_rate))
+ item_details.update(get_pricing_rule_for_item(args))
return item_details
diff --git a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
index a6fc049..c4358b8 100644
--- a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
+++ b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
@@ -82,7 +82,7 @@
item.safety_stock,
item.lead_time_days,
)
- .where(item.is_stock_item == 1)
+ .where((item.is_stock_item == 1) & (item.disabled == 0))
)
if brand := filters.get("brand"):
diff --git a/erpnext/stock/report/warehouse_wise_stock_balance/__init__.py b/erpnext/stock/report/warehouse_wise_stock_balance/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/report/warehouse_wise_stock_balance/__init__.py
diff --git a/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.js b/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.js
new file mode 100644
index 0000000..58a043e
--- /dev/null
+++ b/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.js
@@ -0,0 +1,20 @@
+// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Warehouse Wise Stock Balance"] = {
+ "filters": [
+ {
+ "fieldname":"company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "options": "Company",
+ "reqd": 1,
+ "default": frappe.defaults.get_user_default("Company")
+ }
+ ],
+ "initial_depth": 3,
+ "tree": true,
+ "parent_field": "parent_warehouse",
+ "name_field": "warehouse"
+};
diff --git a/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.json b/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.json
new file mode 100644
index 0000000..4f7ec65
--- /dev/null
+++ b/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.json
@@ -0,0 +1,30 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2022-12-06 14:15:31.924345",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "json": "{}",
+ "modified": "2022-12-06 14:16:55.969214",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Warehouse Wise Stock Balance",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Stock Ledger Entry",
+ "report_name": "Warehouse Wise Stock Balance",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Stock User"
+ },
+ {
+ "role": "Accounts Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.py b/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.py
new file mode 100644
index 0000000..d364b57
--- /dev/null
+++ b/erpnext/stock/report/warehouse_wise_stock_balance/warehouse_wise_stock_balance.py
@@ -0,0 +1,103 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from typing import Any, Dict, List, Optional, TypedDict
+
+import frappe
+from frappe import _
+from frappe.query_builder.functions import Sum
+
+
+class StockBalanceFilter(TypedDict):
+ company: Optional[str]
+ warehouse: Optional[str]
+
+
+SLEntry = Dict[str, Any]
+
+
+def execute(filters=None):
+ columns, data = [], []
+ columns = get_columns()
+ data = get_data(filters)
+
+ return columns, data
+
+
+def get_warehouse_wise_balance(filters: StockBalanceFilter) -> List[SLEntry]:
+ sle = frappe.qb.DocType("Stock Ledger Entry")
+
+ query = (
+ frappe.qb.from_(sle)
+ .select(sle.warehouse, Sum(sle.stock_value_difference).as_("stock_balance"))
+ .where((sle.docstatus < 2) & (sle.is_cancelled == 0))
+ .groupby(sle.warehouse)
+ )
+
+ if filters.get("company"):
+ query = query.where(sle.company == filters.get("company"))
+
+ data = query.run(as_list=True)
+ return frappe._dict(data) if data else frappe._dict()
+
+
+def get_warehouses(report_filters: StockBalanceFilter):
+ return frappe.get_all(
+ "Warehouse",
+ fields=["name", "parent_warehouse", "is_group"],
+ filters={"company": report_filters.company},
+ order_by="lft",
+ )
+
+
+def get_data(filters: StockBalanceFilter):
+ warehouse_balance = get_warehouse_wise_balance(filters)
+ warehouses = get_warehouses(filters)
+
+ for warehouse in warehouses:
+ warehouse.stock_balance = warehouse_balance.get(warehouse.name, 0) or 0.0
+
+ update_indent(warehouses)
+ set_balance_in_parent(warehouses)
+
+ return warehouses
+
+
+def update_indent(warehouses):
+ for warehouse in warehouses:
+
+ def add_indent(warehouse, indent):
+ warehouse.indent = indent
+ for child in warehouses:
+ if child.parent_warehouse == warehouse.name:
+ add_indent(child, indent + 1)
+
+ if warehouse.is_group:
+ add_indent(warehouse, warehouse.indent or 0)
+
+
+def set_balance_in_parent(warehouses):
+ # sort warehouses by indent in descending order
+ warehouses = sorted(warehouses, key=lambda x: x.get("indent", 0), reverse=1)
+
+ for warehouse in warehouses:
+
+ def update_balance(warehouse, balance):
+ for parent in warehouses:
+ if warehouse.parent_warehouse == parent.name:
+ parent.stock_balance += balance
+
+ update_balance(warehouse, warehouse.stock_balance)
+
+
+def get_columns():
+ return [
+ {
+ "label": _("Warehouse"),
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "options": "Warehouse",
+ "width": 200,
+ },
+ {"label": _("Stock Balance"), "fieldname": "stock_balance", "fieldtype": "Float", "width": 150},
+ ]
diff --git a/erpnext/stock/workspace/stock/stock.json b/erpnext/stock/workspace/stock/stock.json
index ed33067..de5e6de 100644
--- a/erpnext/stock/workspace/stock/stock.json
+++ b/erpnext/stock/workspace/stock/stock.json
@@ -5,7 +5,7 @@
"label": "Warehouse wise Stock Value"
}
],
- "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Stock\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Warehouse wise Stock Value\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Quick Access</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Material Request\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Receipt\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Delivery Note\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Ledger\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Balance\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Masters & Reports</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Items and Pricing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Transactions\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Serial No and Batch\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]",
+ "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Stock\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Warehouse wise Stock Value\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Quick Access</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Material Request\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Receipt\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Delivery Note\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Ledger\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Balance\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Masters & Reports</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Items and Pricing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Transactions\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Serial No and Batch\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]",
"creation": "2020-03-02 15:43:10.096528",
"docstatus": 0,
"doctype": "Workspace",
@@ -210,80 +210,6 @@
{
"hidden": 0,
"is_query_report": 0,
- "label": "Stock Reports",
- "link_count": 0,
- "onboard": 0,
- "type": "Card Break"
- },
- {
- "dependencies": "Item",
- "hidden": 0,
- "is_query_report": 1,
- "label": "Stock Ledger",
- "link_count": 0,
- "link_to": "Stock Ledger",
- "link_type": "Report",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "Item",
- "hidden": 0,
- "is_query_report": 1,
- "label": "Stock Balance",
- "link_count": 0,
- "link_to": "Stock Balance",
- "link_type": "Report",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "Item",
- "hidden": 0,
- "is_query_report": 1,
- "label": "Stock Projected Qty",
- "link_count": 0,
- "link_to": "Stock Projected Qty",
- "link_type": "Report",
- "onboard": 1,
- "type": "Link"
- },
- {
- "dependencies": "Item",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Stock Summary",
- "link_count": 0,
- "link_to": "stock-balance",
- "link_type": "Page",
- "onboard": 0,
- "type": "Link"
- },
- {
- "dependencies": "Item",
- "hidden": 0,
- "is_query_report": 1,
- "label": "Stock Ageing",
- "link_count": 0,
- "link_to": "Stock Ageing",
- "link_type": "Report",
- "onboard": 0,
- "type": "Link"
- },
- {
- "dependencies": "Item",
- "hidden": 0,
- "is_query_report": 1,
- "label": "Item Price Stock",
- "link_count": 0,
- "link_to": "Item Price Stock",
- "link_type": "Report",
- "onboard": 0,
- "type": "Link"
- },
- {
- "hidden": 0,
- "is_query_report": 0,
"label": "Settings",
"link_count": 0,
"onboard": 0,
@@ -705,15 +631,100 @@
"link_type": "Report",
"onboard": 0,
"type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Stock Reports",
+ "link_count": 7,
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "Item",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Stock Ledger",
+ "link_count": 0,
+ "link_to": "Stock Ledger",
+ "link_type": "Report",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Item",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Stock Balance",
+ "link_count": 0,
+ "link_to": "Stock Balance",
+ "link_type": "Report",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Item",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Stock Projected Qty",
+ "link_count": 0,
+ "link_to": "Stock Projected Qty",
+ "link_type": "Report",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Item",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Stock Summary",
+ "link_count": 0,
+ "link_to": "stock-balance",
+ "link_type": "Page",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Item",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Stock Ageing",
+ "link_count": 0,
+ "link_to": "Stock Ageing",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Item",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Item Price Stock",
+ "link_count": 0,
+ "link_to": "Item Price Stock",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Warehouse Wise Stock Balance",
+ "link_count": 0,
+ "link_to": "Warehouse Wise Stock Balance",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
}
],
- "modified": "2022-01-13 17:47:38.339931",
+ "modified": "2022-12-06 17:03:56.397272",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock",
"owner": "Administrator",
"parent_page": "",
"public": 1,
+ "quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 24.0,
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index 7f3e0cf..f23419e 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -18,9 +18,6 @@
class Issue(Document):
- def get_feed(self):
- return "{0}: {1}".format(_(self.status), self.subject)
-
def validate(self):
if self.is_new() and self.via_customer_portal:
self.flags.create_communication = True
diff --git a/erpnext/support/doctype/warranty_claim/warranty_claim.py b/erpnext/support/doctype/warranty_claim/warranty_claim.py
index c86356f..ff63b77 100644
--- a/erpnext/support/doctype/warranty_claim/warranty_claim.py
+++ b/erpnext/support/doctype/warranty_claim/warranty_claim.py
@@ -10,9 +10,6 @@
class WarrantyClaim(TransactionBase):
- def get_feed(self):
- return _("{0}: From {1}").format(self.status, self.customer_name)
-
def validate(self):
if session["user"] != "Guest" and not self.customer:
frappe.throw(_("Customer is required"))
diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv
index 0fdd3c9..1014e27 100644
--- a/erpnext/translations/de.csv
+++ b/erpnext/translations/de.csv
@@ -1849,6 +1849,8 @@
Outstanding Cheques and Deposits to clear,Ausstehende Schecks und Anzahlungen zum verbuchen,
Outstanding for {0} cannot be less than zero ({1}),Ausstände für {0} können nicht kleiner als Null sein ({1}),
Outward taxable supplies(zero rated),Steuerpflichtige Lieferungen aus dem Ausland (null bewertet),
+Over Receipt/Delivery of {0} {1} ignored for item {2} because you have {3} role.,"Überhöhte Annahme bzw. Lieferung von Artikel {2} mit {0} {1} wurde ignoriert, weil Sie die Rolle {3} haben."
+Overbilling of {0} {1} ignored for item {2} because you have {3} role.,"Überhöhte Abrechnung von Artikel {2} mit {0} {1} wurde ignoriert, weil Sie die Rolle {3} haben."
Overdue,Überfällig,
Overlap in scoring between {0} and {1},Überlappung beim Scoring zwischen {0} und {1},
Overlapping conditions found between:,Überlagernde Bedingungen gefunden zwischen:,
diff --git a/erpnext/translations/zh.csv b/erpnext/translations/zh.csv
index 716f1f2..d1f1b07 100644
--- a/erpnext/translations/zh.csv
+++ b/erpnext/translations/zh.csv
@@ -453,11 +453,11 @@
Cancel the journal entry {0} first,首先取消日记条目{0},
Canceled,取消,
"Cannot Submit, Employees left to mark attendance",无法提交,不能为已离职员工登记考勤,
-Cannot be a fixed asset item as Stock Ledger is created.,不能成为股票分类账创建的固定资产项目。,
+Cannot be a fixed asset item as Stock Ledger is created.,不能成为库存分类账创建的固定资产项目。,
Cannot cancel because submitted Stock Entry {0} exists,不能取消,因为提交的仓储记录{0}已经存在,
Cannot cancel transaction for Completed Work Order.,无法取消已完成工单的交易。,
Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3},无法取消{0} {1},因为序列号{2}不属于仓库{3},
-Cannot change Attributes after stock transaction. Make a new Item and transfer stock to the new Item,股票交易后不能更改属性。创建一个新项目并将库存转移到新项目,
+Cannot change Attributes after stock transaction. Make a new Item and transfer stock to the new Item,库存交易后不能更改属性。创建一个新项目并将库存转移到新项目,
Cannot change Fiscal Year Start Date and Fiscal Year End Date once the Fiscal Year is saved.,财年保存后便不能更改财年开始日期和结束日期,
Cannot change Service Stop Date for item in row {0},无法更改行{0}中项目的服务停止日期,
Cannot change Variant properties after stock transaction. You will have to make a new Item to do this.,存货业务发生后不能更改变体物料的属性。需要新建新物料。,
@@ -2309,7 +2309,7 @@
Received,收到,
Received On,收到的,
Received Quantity,收到的数量,
-Received Stock Entries,收到的股票条目,
+Received Stock Entries,收到的库存条目,
Receiver List is empty. Please create Receiver List,接收人列表为空。请创建接收人列表,
Recipients,收件人,
Reconcile,核消(对帐),
@@ -2783,7 +2783,7 @@
State/UT Tax,州/ UT税,
Statement of Account,对账单,
Status must be one of {0},状态必须是{0}中的一个,
-Stock,股票,
+Stock,库存,
Stock Adjustment,库存调整,
Stock Analytics,库存分析,
Stock Assets,库存资产,
@@ -2963,7 +2963,7 @@
The holiday on {0} is not between From Date and To Date,在{0}这个节日之间没有从日期和结束日期,
The name of the institute for which you are setting up this system.,对于要为其建立这个系统的该机构的名称。,
The name of your company for which you are setting up this system.,贵公司的名称,
-The number of shares and the share numbers are inconsistent,股份数量和股票数量不一致,
+The number of shares and the share numbers are inconsistent,股份数量和库存数量不一致,
The payment gateway account in plan {0} is different from the payment gateway account in this payment request,计划{0}中的支付网关帐户与此付款请求中的支付网关帐户不同,
The selected BOMs are not for the same item,所选物料清单不能用于同一个物料,
The selected item cannot have Batch,所选物料不能有批次,
@@ -3514,7 +3514,7 @@
Ageing Range 4,老化范围4,
Allocated amount cannot be greater than unadjusted amount,分配的金额不能大于未调整的金额,
Allocated amount cannot be negative,分配数量不能为负数,
-"Difference Account must be a Asset/Liability type account, since this Stock Entry is an Opening Entry",差异账户必须是资产/负债类型账户,因为此股票分录是开仓分录,
+"Difference Account must be a Asset/Liability type account, since this Stock Entry is an Opening Entry",差异账户必须是资产/负债类型账户,因为此库存分录是开仓分录,
Error in some rows,某些行出错,
Import Successful,导入成功,
Please save first,请先保存,
@@ -3531,7 +3531,7 @@
Invalid GSTIN! The input you've entered doesn't match the GSTIN format for UIN Holders or Non-Resident OIDAR Service Providers,GSTIN无效!您输入的输入与UIN持有人或非居民OIDAR服务提供商的GSTIN格式不符,
Invoice Grand Total,发票总计,
Last carbon check date cannot be a future date,最后的碳检查日期不能是未来的日期,
-Make Stock Entry,进入股票,
+Make Stock Entry,进入库存,
Quality Feedback,质量反馈,
Quality Feedback Template,质量反馈模板,
Rules for applying different promotional schemes.,适用不同促销计划的规则。,
@@ -3626,7 +3626,7 @@
BOM Comparison Tool,BOM比较工具,
BOM recursion: {0} cannot be child of {1},BOM递归:{0}不能是{1}的子代,
BOM recursion: {0} cannot be parent or child of {1},BOM递归:{0}不能是{1}的父级或子级,
-Back to Home,回到家,
+Back to Home,回到主页,
Back to Messages,回到消息,
Bank Data mapper doesn't exist,银行数据映射器不存在,
Bank Details,银行明细,
@@ -3786,7 +3786,7 @@
Help Article,帮助文章,
"Helps you keep tracks of Contracts based on Supplier, Customer and Employee",帮助您根据供应商,客户和员工记录合同,
Helps you manage appointments with your leads,帮助您管理潜在客户的约会,
-Home,家,
+Home,主页,
IBAN is not valid,IBAN无效,
Import Data from CSV / Excel files.,从CSV / Excel文件导入数据。,
In Progress,进行中,
@@ -4064,8 +4064,8 @@
Status,状态,
Status must be Cancelled or Completed,状态必须已取消或已完成,
Stock Balance Report,库存余额报告,
-Stock Entry has been already created against this Pick List,已经根据此选择列表创建了股票输入,
-Stock Ledger ID,股票分类帐编号,
+Stock Entry has been already created against this Pick List,已经根据此选择列表创建了库存输入,
+Stock Ledger ID,库存分类帐编号,
Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.,库存值({0})和帐户余额({1})与帐户{2}及其链接的仓库不同步。,
Stores - {0},商店-{0},
Student with email {0} does not exist,电子邮件{0}的学生不存在,
@@ -6127,7 +6127,7 @@
Procedure Prescription,程序处方,
Service Unit,服务单位,
Consumables,耗材,
-Consume Stock,消费股票,
+Consume Stock,消费库存,
Invoice Consumables Separately,发票耗材分开,
Consumption Invoiced,消费发票,
Consumable Total Amount,耗材总量,
@@ -8285,8 +8285,8 @@
Warranty Period (Days),保修期限(天数),
Serial No Details,序列号信息,
MAT-STE-.YYYY.-,MAT-STE-.YYYY.-,
-Stock Entry Type,股票进入类型,
-Stock Entry (Outward GIT),股票进入(外向GIT),
+Stock Entry Type,库存进入类型,
+Stock Entry (Outward GIT),库存进入(外向GIT),
Material Consumption for Manufacture,生产所需的材料消耗,
Repack,包装,
Send to Subcontractor,发送给分包商,
@@ -8318,8 +8318,8 @@
BOM No. for a Finished Good Item,成品物料的物料清单编号,
Material Request used to make this Stock Entry,创建此手工库存移动的材料申请,
Subcontracted Item,外包物料,
-Against Stock Entry,反对股票进入,
-Stock Entry Child,股票入境儿童,
+Against Stock Entry,反对库存进入,
+Stock Entry Child,库存入境儿童,
PO Supplied Item,PO提供的物品,
Reference Purchase Receipt,参考购买收据,
Stock Ledger Entry,库存分类帐分录,
@@ -8571,7 +8571,7 @@
Serial No Status,序列号状态,
Serial No Warranty Expiry,序列号/保修到期,
Stock Ageing,库存账龄,
-Stock and Account Value Comparison,股票和账户价值比较,
+Stock and Account Value Comparison,库存和账户价值比较,
Stock Projected Qty,预期可用库存,
Student and Guardian Contact Details,学生和监护人联系方式,
Student Batch-Wise Attendance,学生按批考勤,
@@ -9655,7 +9655,7 @@
Notify by Email on Creation of Automatic Material Request,通过电子邮件通知创建自动物料请求,
Allow Material Transfer from Delivery Note to Sales Invoice,允许物料从交货单转移到销售发票,
Allow Material Transfer from Purchase Receipt to Purchase Invoice,允许从收货到采购发票的物料转移,
-Freeze Stocks Older Than (Days),冻结大于(天)的股票,
+Freeze Stocks Older Than (Days),冻结大于(天)的库存,
Role Allowed to Edit Frozen Stock,允许角色编辑冻结库存,
The unallocated amount of Payment Entry {0} is greater than the Bank Transaction's unallocated amount,付款条目{0}的未分配金额大于银行交易的未分配金额,
Payment Received,已收到付款,
@@ -9698,7 +9698,7 @@
Item {0} {1},项目{0} {1},
Last Stock Transaction for item {0} under warehouse {1} was on {2}.,仓库{1}下项目{0}的上次库存交易在{2}上。,
Stock Transactions for Item {0} under warehouse {1} cannot be posted before this time.,在此之前,不能过帐仓库{1}下物料{0}的库存交易。,
-Posting future stock transactions are not allowed due to Immutable Ledger,由于总帐不可变,不允许过帐未来的股票交易,
+Posting future stock transactions are not allowed due to Immutable Ledger,由于总帐不可变,不允许过帐未来的库存交易,
A BOM with name {0} already exists for item {1}.,项目{1}的名称为{0}的BOM已存在。,
{0}{1} Did you rename the item? Please contact Administrator / Tech support,{0} {1}您是否重命名了该项目?请联系管理员/技术支持,
At row #{0}: the sequence id {1} cannot be less than previous row sequence id {2},在第{0}行:序列ID {1}不能小于上一行的序列ID {2},
diff --git a/erpnext/utilities/__init__.py b/erpnext/utilities/__init__.py
index c2b4229..24bfdc6 100644
--- a/erpnext/utilities/__init__.py
+++ b/erpnext/utilities/__init__.py
@@ -1,6 +1,9 @@
## temp utility
+from contextlib import contextmanager
+
import frappe
+from frappe import _
from frappe.utils import cstr
from erpnext.utilities.activation import get_level
@@ -35,3 +38,16 @@
domain = frappe.get_cached_value("Company", cstr(company), "domain")
return {"company": company, "domain": domain, "activation": get_level()}
+
+
+@contextmanager
+def payment_app_import_guard():
+ marketplace_link = '<a href="https://frappecloud.com/marketplace/apps/payments">Marketplace</a>'
+ github_link = '<a href="https://github.com/frappe/payments/">GitHub</a>'
+ msg = _("payments app is not installed. Please install it from {} or {}").format(
+ marketplace_link, github_link
+ )
+ try:
+ yield
+ except ImportError:
+ frappe.throw(msg, title=_("Missing Payments App"))
diff --git a/erpnext/utilities/product.py b/erpnext/utilities/product.py
index 04ee0b3..afe9654 100644
--- a/erpnext/utilities/product.py
+++ b/erpnext/utilities/product.py
@@ -110,6 +110,7 @@
"conversion_rate": 1,
"for_shopping_cart": True,
"currency": frappe.db.get_value("Price List", price_list, "currency"),
+ "doctype": "Quotation",
}
)