diff --git a/.github/workflows/initiate_release.yml b/.github/workflows/initiate_release.yml
index ef38974ae27..ee60bad1049 100644
--- a/.github/workflows/initiate_release.yml
+++ b/.github/workflows/initiate_release.yml
@@ -9,7 +9,7 @@ on:
workflow_dispatch:
jobs:
- release:
+ stable-release:
name: Release
runs-on: ubuntu-latest
strategy:
@@ -30,3 +30,23 @@ jobs:
head: version-${{ matrix.version }}-hotfix
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
+
+ beta-release:
+ name: Release
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+
+ steps:
+ - uses: octokit/request-action@v2.x
+ with:
+ route: POST /repos/{owner}/{repo}/pulls
+ owner: frappe
+ repo: erpnext
+ title: |-
+ "chore: release v15 beta"
+ body: "Automated beta release."
+ base: version-15-beta
+ head: develop
+ env:
+ GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js
index 35d606ba3ae..6667193a54c 100644
--- a/erpnext/accounts/doctype/bank/bank.js
+++ b/erpnext/accounts/doctype/bank/bank.js
@@ -8,9 +8,6 @@ frappe.ui.form.on('Bank', {
},
refresh: function(frm) {
add_fields_to_mapping_table(frm);
-
- frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Bank' };
-
frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal);
if (frm.doc.__islocal) {
diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
index 598db642f33..3b5698b118a 100644
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
+++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
@@ -93,6 +93,12 @@ class ExchangeRateRevaluation(Document):
return True
+ def fetch_and_calculate_accounts_data(self):
+ accounts = self.get_accounts_data()
+ if accounts:
+ for acc in accounts:
+ self.append("accounts", acc)
+
@frappe.whitelist()
def get_accounts_data(self):
self.validate_mandatory()
@@ -252,8 +258,8 @@ class ExchangeRateRevaluation(Document):
new_balance_in_base_currency = 0
new_balance_in_account_currency = 0
- current_exchange_rate = calculate_exchange_rate_using_last_gle(
- company, d.account, d.party_type, d.party
+ current_exchange_rate = (
+ calculate_exchange_rate_using_last_gle(company, d.account, d.party_type, d.party) or 0.0
)
gain_loss = new_balance_in_account_currency - (
diff --git a/erpnext/accounts/doctype/item_tax_template/item_tax_template.json b/erpnext/accounts/doctype/item_tax_template/item_tax_template.json
index b42d712d88a..87f0ad10483 100644
--- a/erpnext/accounts/doctype/item_tax_template/item_tax_template.json
+++ b/erpnext/accounts/doctype/item_tax_template/item_tax_template.json
@@ -35,6 +35,7 @@
{
"fieldname": "company",
"fieldtype": "Link",
+ "in_filter": 1,
"in_list_view": 1,
"label": "Company",
"options": "Company",
@@ -56,7 +57,7 @@
}
],
"links": [],
- "modified": "2022-01-18 21:11:23.105589",
+ "modified": "2023-07-09 18:11:23.105589",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Item Tax Template",
@@ -102,4 +103,4 @@
"states": [],
"title_field": "title",
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 65ed4669b1e..dcd7295bae3 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -237,10 +237,9 @@ class PaymentEntry(AccountsController):
_("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name)
)
# The reference has already been partly paid
- elif (
- latest.outstanding_amount < latest.invoice_amount
- and flt(d.outstanding_amount, d.precision("outstanding_amount")) != latest.outstanding_amount
- ):
+ elif latest.outstanding_amount < latest.invoice_amount and flt(
+ d.outstanding_amount, d.precision("outstanding_amount")
+ ) != flt(latest.outstanding_amount, d.precision("outstanding_amount")):
frappe.throw(
_(
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts."
@@ -1433,6 +1432,9 @@ def get_outstanding_reference_documents(args, validate=False):
if args.get("party_type") == "Member":
return
+ if not args.get("get_outstanding_invoices") and not args.get("get_orders_to_be_billed"):
+ args["get_outstanding_invoices"] = True
+
ple = qb.DocType("Payment Ledger Entry")
common_filter = []
accounting_dimensions_filter = []
@@ -1627,60 +1629,59 @@ def get_orders_to_be_billed(
cost_center=None,
filters=None,
):
+ voucher_type = None
if party_type == "Customer":
voucher_type = "Sales Order"
elif party_type == "Supplier":
voucher_type = "Purchase Order"
- elif party_type == "Employee":
- voucher_type = None
+
+ if not voucher_type:
+ return []
# Add cost center condition
- if voucher_type:
- doc = frappe.get_doc({"doctype": voucher_type})
- condition = ""
- if doc and hasattr(doc, "cost_center") and doc.cost_center:
- condition = " and cost_center='%s'" % cost_center
+ doc = frappe.get_doc({"doctype": voucher_type})
+ condition = ""
+ if doc and hasattr(doc, "cost_center") and doc.cost_center:
+ condition = " and cost_center='%s'" % cost_center
- orders = []
- if voucher_type:
- if party_account_currency == company_currency:
- grand_total_field = "base_grand_total"
- rounded_total_field = "base_rounded_total"
- else:
- grand_total_field = "grand_total"
- rounded_total_field = "rounded_total"
+ if party_account_currency == company_currency:
+ grand_total_field = "base_grand_total"
+ rounded_total_field = "base_rounded_total"
+ else:
+ grand_total_field = "grand_total"
+ rounded_total_field = "rounded_total"
- orders = frappe.db.sql(
- """
- select
- name as voucher_no,
- if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount,
- (if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) - advance_paid) as outstanding_amount,
- transaction_date as posting_date
- from
- `tab{voucher_type}`
- where
- {party_type} = %s
- and docstatus = 1
- and company = %s
- and ifnull(status, "") != "Closed"
- and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid
- and abs(100 - per_billed) > 0.01
- {condition}
- order by
- transaction_date, name
- """.format(
- **{
- "rounded_total_field": rounded_total_field,
- "grand_total_field": grand_total_field,
- "voucher_type": voucher_type,
- "party_type": scrub(party_type),
- "condition": condition,
- }
- ),
- (party, company),
- as_dict=True,
- )
+ orders = frappe.db.sql(
+ """
+ select
+ name as voucher_no,
+ if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount,
+ (if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) - advance_paid) as outstanding_amount,
+ transaction_date as posting_date
+ from
+ `tab{voucher_type}`
+ where
+ {party_type} = %s
+ and docstatus = 1
+ and company = %s
+ and ifnull(status, "") != "Closed"
+ and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid
+ and abs(100 - per_billed) > 0.01
+ {condition}
+ order by
+ transaction_date, name
+ """.format(
+ **{
+ "rounded_total_field": rounded_total_field,
+ "grand_total_field": grand_total_field,
+ "voucher_type": voucher_type,
+ "party_type": scrub(party_type),
+ "condition": condition,
+ }
+ ),
+ (party, company),
+ as_dict=True,
+ )
order_list = []
for d in orders:
@@ -1713,6 +1714,8 @@ def get_negative_outstanding_invoices(
cost_center=None,
condition=None,
):
+ if party_type not in ["Customer", "Supplier"]:
+ return []
voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice"
account = "debit_to" if voucher_type == "Sales Invoice" else "credit_to"
supplier_condition = ""
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index e247e802536..d8759e95b87 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -549,6 +549,7 @@
"depends_on": "update_stock",
"fieldname": "rejected_warehouse",
"fieldtype": "Link",
+ "ignore_user_permissions": 1,
"label": "Rejected Warehouse",
"no_copy": 1,
"options": "Warehouse",
@@ -1576,7 +1577,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
- "modified": "2023-06-03 16:21:54.637245",
+ "modified": "2023-07-04 17:22:59.145031",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index c5187a2f469..4afc4512ff7 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -423,6 +423,7 @@
{
"fieldname": "rejected_warehouse",
"fieldtype": "Link",
+ "ignore_user_permissions": 1,
"label": "Rejected Warehouse",
"options": "Warehouse"
},
@@ -904,7 +905,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2023-07-02 18:39:41.495723",
+ "modified": "2023-07-04 17:22:21.501152",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/doctype/shareholder/shareholder.js b/erpnext/accounts/doctype/shareholder/shareholder.js
index c6f101e7f31..544d417a0e5 100644
--- a/erpnext/accounts/doctype/shareholder/shareholder.js
+++ b/erpnext/accounts/doctype/shareholder/shareholder.js
@@ -3,8 +3,6 @@
frappe.ui.form.on('Shareholder', {
refresh: function(frm) {
- frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Shareholder' };
-
frm.toggle_display(['contact_html'], !frm.doc.__islocal);
if (frm.doc.__islocal) {
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 9000b0d32ed..8b44b22e3d7 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -850,7 +850,7 @@ def get_held_invoices(party_type, party):
if party_type == "Supplier":
held_invoices = frappe.db.sql(
- "select name from `tabPurchase Invoice` where release_date IS NOT NULL and release_date > CURDATE()",
+ "select name from `tabPurchase Invoice` where on_hold = 1 and release_date IS NOT NULL and release_date > CURDATE()",
as_dict=1,
)
held_invoices = set(d["name"] for d in held_invoices)
@@ -1408,6 +1408,50 @@ def check_and_delete_linked_reports(report):
frappe.delete_doc("Desktop Icon", icon)
+def create_err_and_its_journals(companies: list = None) -> None:
+ if companies:
+ for company in companies:
+ err = frappe.new_doc("Exchange Rate Revaluation")
+ err.company = company.name
+ err.posting_date = nowdate()
+ err.rounding_loss_allowance = 0.0
+
+ err.fetch_and_calculate_accounts_data()
+ if err.accounts:
+ err.save().submit()
+ response = err.make_jv_entries()
+
+ if company.submit_err_jv:
+ jv = response.get("revaluation_jv", None)
+ jv and frappe.get_doc("Journal Entry", jv).submit()
+ jv = response.get("zero_balance_jv", None)
+ jv and frappe.get_doc("Journal Entry", jv).submit()
+
+
+def auto_create_exchange_rate_revaluation_daily() -> None:
+ """
+ Executed by background job
+ """
+ companies = frappe.db.get_all(
+ "Company",
+ filters={"auto_exchange_rate_revaluation": 1, "auto_err_frequency": "Daily"},
+ fields=["name", "submit_err_jv"],
+ )
+ create_err_and_its_journals(companies)
+
+
+def auto_create_exchange_rate_revaluation_weekly() -> None:
+ """
+ Executed by background job
+ """
+ companies = frappe.db.get_all(
+ "Company",
+ filters={"auto_exchange_rate_revaluation": 1, "auto_err_frequency": "Weekly"},
+ fields=["name", "submit_err_jv"],
+ )
+ create_err_and_its_journals(companies)
+
+
def get_payment_ledger_entries(gl_entries, cancel=0):
ple_map = []
if gl_entries:
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index 259568a24b1..e1431eae176 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -40,6 +40,7 @@ def post_depreciation_entries(date=None):
date = today()
failed_asset_names = []
+ error_log_names = []
for asset_name in get_depreciable_assets(date):
asset_doc = frappe.get_doc("Asset", asset_name)
@@ -50,10 +51,12 @@ def post_depreciation_entries(date=None):
except Exception as e:
frappe.db.rollback()
failed_asset_names.append(asset_name)
+ error_log = frappe.log_error(e)
+ error_log_names.append(error_log.name)
if failed_asset_names:
set_depr_entry_posting_status_for_failed_assets(failed_asset_names)
- notify_depr_entry_posting_error(failed_asset_names)
+ notify_depr_entry_posting_error(failed_asset_names, error_log_names)
frappe.db.commit()
@@ -239,7 +242,7 @@ def set_depr_entry_posting_status_for_failed_assets(failed_asset_names):
frappe.db.set_value("Asset", asset_name, "depr_entry_posting_status", "Failed")
-def notify_depr_entry_posting_error(failed_asset_names):
+def notify_depr_entry_posting_error(failed_asset_names, error_log_names):
recipients = get_users_with_role("Accounts Manager")
if not recipients:
@@ -247,7 +250,8 @@ def notify_depr_entry_posting_error(failed_asset_names):
subject = _("Error while posting depreciation entries")
- asset_links = get_comma_separated_asset_links(failed_asset_names)
+ asset_links = get_comma_separated_links(failed_asset_names, "Asset")
+ error_log_links = get_comma_separated_links(error_log_names, "Error Log")
message = (
_("Hello,")
@@ -257,23 +261,26 @@ def notify_depr_entry_posting_error(failed_asset_names):
)
+ "."
+ "
"
- + _(
- "Please raise a support ticket and share this email, or forward this email to your development team so that they can find the issue in the developer console by manually creating the depreciation entry via the asset's depreciation schedule table."
+ + _("Here are the error logs for the aforementioned failed depreciation entries: {0}").format(
+ error_log_links
)
+ + "."
+ + "
"
+ + _("Please share this email with your support team so that they can find and fix the issue.")
)
frappe.sendmail(recipients=recipients, subject=subject, message=message)
-def get_comma_separated_asset_links(asset_names):
- asset_links = []
+def get_comma_separated_links(names, doctype):
+ links = []
- for asset_name in asset_names:
- asset_links.append(get_link_to_form("Asset", asset_name))
+ for name in names:
+ links.append(get_link_to_form(doctype, name))
- asset_links = ", ".join(asset_links)
+ links = ", ".join(links)
- return asset_links
+ return links
@frappe.whitelist()
diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js
index 5b95d0fde37..372ca56b86b 100644
--- a/erpnext/buying/doctype/supplier/supplier.js
+++ b/erpnext/buying/doctype/supplier/supplier.js
@@ -66,8 +66,6 @@ frappe.ui.form.on("Supplier", {
},
refresh: function (frm) {
- frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Supplier' }
-
if (frappe.defaults.get_default("supp_master_name") != "Naming Series") {
frm.toggle_display("naming_series", false);
} else {
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index fec494a84c7..7b7c53ecfe1 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -437,18 +437,23 @@ class BuyingController(SubcontractingController):
# validate rate with ref PR
def validate_rejected_warehouse(self):
- for d in self.get("items"):
- if flt(d.rejected_qty) and not d.rejected_warehouse:
+ for item in self.get("items"):
+ if flt(item.rejected_qty) and not item.rejected_warehouse:
if self.rejected_warehouse:
- d.rejected_warehouse = self.rejected_warehouse
+ item.rejected_warehouse = self.rejected_warehouse
- if not d.rejected_warehouse:
+ if not item.rejected_warehouse:
frappe.throw(
- _("Row #{0}: Rejected Warehouse is mandatory against rejected Item {1}").format(
- d.idx, d.item_code
+ _("Row #{0}: Rejected Warehouse is mandatory for the rejected Item {1}").format(
+ item.idx, item.item_code
)
)
+ if item.get("rejected_warehouse") and (item.get("rejected_warehouse") == item.get("warehouse")):
+ frappe.throw(
+ _("Row #{0}: Accepted Warehouse and Rejected Warehouse cannot be same").format(item.idx)
+ )
+
# validate accepted and rejected qty
def validate_accepted_rejected_qty(self):
for d in self.get("items"):
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 954668055e1..173e812dbd0 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -669,7 +669,11 @@ def get_filters(
if reference_voucher_detail_no:
filters["voucher_detail_no"] = reference_voucher_detail_no
- if item_row and item_row.get("warehouse"):
+ if (
+ voucher_type in ["Purchase Receipt", "Purchase Invoice"]
+ and item_row
+ and item_row.get("warehouse")
+ ):
filters["warehouse"] = item_row.get("warehouse")
return filters
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 5137e030582..caf4b6f18bc 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -201,6 +201,12 @@ class StockController(AccountsController):
warehouse_asset_account = warehouse_account[item_row.get("warehouse")]["account"]
expense_account = frappe.get_cached_value("Company", self.company, "default_expense_account")
+ if not expense_account:
+ frappe.throw(
+ _(
+ "Please set default cost of goods sold account in company {0} for booking rounding gain and loss during stock transfer"
+ ).format(frappe.bold(self.company))
+ )
gl_list.append(
self.get_gl_dict(
diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js
index b98a27ede8e..9ac54183a21 100644
--- a/erpnext/crm/doctype/lead/lead.js
+++ b/erpnext/crm/doctype/lead/lead.js
@@ -30,11 +30,6 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller
var me = this;
let doc = this.frm.doc;
erpnext.toggle_naming_series();
- frappe.dynamic_link = {
- doc: doc,
- fieldname: 'name',
- doctype: 'Lead'
- };
if (!this.frm.is_new() && doc.__onload && !doc.__onload.is_customer) {
this.frm.add_custom_button(__("Customer"), this.make_customer, __("Create"));
diff --git a/erpnext/crm/doctype/prospect/prospect.js b/erpnext/crm/doctype/prospect/prospect.js
index 495ed291ae9..c1a7ff576c1 100644
--- a/erpnext/crm/doctype/prospect/prospect.js
+++ b/erpnext/crm/doctype/prospect/prospect.js
@@ -3,8 +3,6 @@
frappe.ui.form.on('Prospect', {
refresh (frm) {
- frappe.dynamic_link = { doc: frm.doc, fieldname: "name", doctype: frm.doctype };
-
if (!frm.is_new() && frappe.boot.user.can_create.includes("Customer")) {
frm.add_custom_button(__("Customer"), function() {
frappe.model.open_mapped_doc({
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index a6d939e74f8..d02d318b2d8 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -415,6 +415,10 @@ scheduler_events = {
"erpnext.selling.doctype.quotation.quotation.set_expired_status",
"erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status",
"erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email",
+ "erpnext.accounts.utils.auto_create_exchange_rate_revaluation_daily",
+ ],
+ "weekly": [
+ "erpnext.accounts.utils.auto_create_exchange_rate_revaluation_weekly",
],
"daily_long": [
"erpnext.setup.doctype.email_digest.email_digest.send",
diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js
index 540e767d323..60f0941559a 100644
--- a/erpnext/selling/doctype/customer/customer.js
+++ b/erpnext/selling/doctype/customer/customer.js
@@ -131,8 +131,6 @@ frappe.ui.form.on("Customer", {
erpnext.toggle_naming_series();
}
- frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Customer'}
-
if(!frm.doc.__islocal) {
frappe.contacts.render_address_and_contact(frm);
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index 333538722ee..f4682c1b806 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -81,8 +81,6 @@ frappe.ui.form.on("Company", {
disbale_coa_fields(frm);
frappe.contacts.render_address_and_contact(frm);
- frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Company'}
-
if (frappe.perm.has_perm("Cost Center", 0, 'read')) {
frm.add_custom_button(__('Cost Centers'), function() {
frappe.set_route('Tree', 'Cost Center', {'company': frm.doc.name});
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index 6292ad7349d..ed2852e87a1 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -95,6 +95,10 @@
"depreciation_cost_center",
"capital_work_in_progress_account",
"asset_received_but_not_billed",
+ "exchange_rate_revaluation_settings_section",
+ "auto_exchange_rate_revaluation",
+ "auto_err_frequency",
+ "submit_err_jv",
"budget_detail",
"exception_budget_approver_role",
"registration_info",
@@ -731,6 +735,29 @@
"fieldname": "book_advance_payments_in_separate_party_account",
"fieldtype": "Check",
"label": "Book Advance Payments in Separate Party Account"
+ },
+ {
+ "fieldname": "exchange_rate_revaluation_settings_section",
+ "fieldtype": "Section Break",
+ "label": "Exchange Rate Revaluation Settings"
+ },
+ {
+ "default": "0",
+ "fieldname": "auto_exchange_rate_revaluation",
+ "fieldtype": "Check",
+ "label": "Auto Create Exchange Rate Revaluation"
+ },
+ {
+ "fieldname": "auto_err_frequency",
+ "fieldtype": "Select",
+ "label": "Frequency",
+ "options": "Daily\nWeekly"
+ },
+ {
+ "default": "0",
+ "fieldname": "submit_err_jv",
+ "fieldtype": "Check",
+ "label": "Submit ERR Journals?"
}
],
"icon": "fa fa-building",
@@ -738,7 +765,7 @@
"image_field": "company_logo",
"is_tree": 1,
"links": [],
- "modified": "2023-06-23 18:22:27.219706",
+ "modified": "2023-07-07 05:41:41.537256",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",
diff --git a/erpnext/setup/doctype/sales_partner/sales_partner.js b/erpnext/setup/doctype/sales_partner/sales_partner.js
index 5656d43e852..f9e37705604 100644
--- a/erpnext/setup/doctype/sales_partner/sales_partner.js
+++ b/erpnext/setup/doctype/sales_partner/sales_partner.js
@@ -3,8 +3,6 @@
frappe.ui.form.on('Sales Partner', {
refresh: function(frm) {
- frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Sales Partner'}
-
if(frm.doc.__islocal){
hide_field(['address_html', 'contact_html', 'address_contacts']);
frappe.contacts.clear_address_and_contact(frm);
diff --git a/erpnext/stock/doctype/batch/batch.js b/erpnext/stock/doctype/batch/batch.js
index fa8b2bee558..3b07e4e80c1 100644
--- a/erpnext/stock/doctype/batch/batch.js
+++ b/erpnext/stock/doctype/batch/batch.js
@@ -47,8 +47,6 @@ frappe.ui.form.on('Batch', {
return;
}
- debugger
-
const section = frm.dashboard.add_section('', __("Stock Levels"));
// sort by qty
diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py
index 0e4132db8ed..7fb672c0cb1 100644
--- a/erpnext/stock/doctype/batch/test_batch.py
+++ b/erpnext/stock/doctype/batch/test_batch.py
@@ -59,6 +59,73 @@ class TestBatch(FrappeTestCase):
return receipt
+ def test_batch_stock_levels(self, batch_qty=100):
+ """Test automated batch creation from Purchase Receipt"""
+ self.make_batch_item("ITEM-BATCH-1")
+
+ receipt = frappe.get_doc(
+ dict(
+ doctype="Purchase Receipt",
+ supplier="_Test Supplier",
+ company="_Test Company",
+ items=[dict(item_code="ITEM-BATCH-1", qty=10, rate=10, warehouse="Stores - _TC")],
+ )
+ ).insert()
+ receipt.submit()
+
+ receipt.load_from_db()
+ batch_no = get_batch_from_bundle(receipt.items[0].serial_and_batch_bundle)
+
+ bundle_id = (
+ SerialBatchCreation(
+ {
+ "item_code": "ITEM-BATCH-1",
+ "warehouse": "_Test Warehouse - _TC",
+ "actual_qty": 20,
+ "voucher_type": "Purchase Receipt",
+ "batches": frappe._dict({batch_no: 20}),
+ "type_of_transaction": "Inward",
+ "company": receipt.company,
+ }
+ )
+ .make_serial_and_batch_bundle()
+ .name
+ )
+
+ receipt2 = frappe.get_doc(
+ dict(
+ doctype="Purchase Receipt",
+ supplier="_Test Supplier",
+ company="_Test Company",
+ items=[
+ dict(
+ item_code="ITEM-BATCH-1",
+ qty=20,
+ rate=10,
+ warehouse="_Test Warehouse - _TC",
+ serial_and_batch_bundle=bundle_id,
+ )
+ ],
+ )
+ ).insert()
+ receipt2.submit()
+
+ receipt.load_from_db()
+ receipt2.load_from_db()
+
+ self.assertTrue(receipt.items[0].serial_and_batch_bundle)
+ self.assertTrue(receipt2.items[0].serial_and_batch_bundle)
+
+ batchwise_qty = frappe._dict({})
+ for receipt in [receipt, receipt2]:
+ batch_no = get_batch_from_bundle(receipt.items[0].serial_and_batch_bundle)
+ key = (batch_no, receipt.items[0].warehouse)
+ batchwise_qty[key] = receipt.items[0].qty
+
+ batches = get_batch_qty(batch_no)
+ for d in batches:
+ self.assertEqual(d.qty, batchwise_qty[(d.batch_no, d.warehouse)])
+
def test_stock_entry_incoming(self):
"""Test batch creation via Stock Entry (Work Order)"""
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 8baae8a19c6..0ef3027bce3 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -318,6 +318,37 @@ class TestDeliveryNote(FrappeTestCase):
self.assertEqual(dn.per_returned, 100)
self.assertEqual(dn.status, "Return Issued")
+ def test_delivery_note_return_valuation_on_different_warehuose(self):
+ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
+
+ company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
+ item_code = "Test Return Valuation For DN"
+ make_item("Test Return Valuation For DN", {"is_stock_item": 1})
+ return_warehouse = create_warehouse("Returned Test Warehouse", company=company)
+
+ make_stock_entry(item_code=item_code, target="Stores - TCP1", qty=5, basic_rate=150)
+
+ dn = create_delivery_note(
+ item_code=item_code,
+ qty=5,
+ rate=500,
+ warehouse="Stores - TCP1",
+ company=company,
+ expense_account="Cost of Goods Sold - TCP1",
+ cost_center="Main - TCP1",
+ )
+
+ dn.submit()
+ self.assertEqual(dn.items[0].incoming_rate, 150)
+
+ from erpnext.controllers.sales_and_purchase_return import make_return_doc
+
+ return_dn = make_return_doc(dn.doctype, dn.name)
+ return_dn.items[0].warehouse = return_warehouse
+ return_dn.save().submit()
+
+ self.assertEqual(return_dn.items[0].incoming_rate, 150)
+
def test_return_single_item_from_bundled_items(self):
company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
diff --git a/erpnext/stock/doctype/manufacturer/manufacturer.js b/erpnext/stock/doctype/manufacturer/manufacturer.js
index bb7e314e14e..5b4990f08be 100644
--- a/erpnext/stock/doctype/manufacturer/manufacturer.js
+++ b/erpnext/stock/doctype/manufacturer/manufacturer.js
@@ -3,7 +3,6 @@
frappe.ui.form.on('Manufacturer', {
refresh: function(frm) {
- frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Manufacturer' };
if (frm.doc.__islocal) {
hide_field(['address_html','contact_html']);
frappe.contacts.clear_address_and_contact(frm);
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index b41e971c8ac..912b9086dd6 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -438,6 +438,7 @@
{
"fieldname": "rejected_warehouse",
"fieldtype": "Link",
+ "ignore_user_permissions": 1,
"label": "Rejected Warehouse",
"no_copy": 1,
"oldfieldname": "rejected_warehouse",
@@ -1240,7 +1241,7 @@
"idx": 261,
"is_submittable": 1,
"links": [],
- "modified": "2023-06-03 16:23:20.781368",
+ "modified": "2023-07-04 17:23:17.025390",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt",
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 07d6e86795a..ced894634f6 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -350,6 +350,15 @@ class TestPurchaseReceipt(FrappeTestCase):
pr.cancel()
self.assertFalse(frappe.db.get_value("Serial No", pr_row_1_serial_no, "warehouse"))
+ def test_rejected_warehouse_filter(self):
+ pr = frappe.copy_doc(test_records[0])
+ pr.get("items")[0].item_code = "_Test Serialized Item With Series"
+ pr.get("items")[0].qty = 3
+ pr.get("items")[0].rejected_qty = 2
+ pr.get("items")[0].received_qty = 5
+ pr.get("items")[0].rejected_warehouse = pr.get("items")[0].warehouse
+ self.assertRaises(frappe.ValidationError, pr.save)
+
def test_rejected_serial_no(self):
pr = frappe.copy_doc(test_records[0])
pr.get("items")[0].item_code = "_Test Serialized Item With Series"
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index 3929616f7cd..bc5e8a0f37d 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -502,6 +502,7 @@
{
"fieldname": "rejected_warehouse",
"fieldtype": "Link",
+ "ignore_user_permissions": 1,
"label": "Rejected Warehouse",
"no_copy": 1,
"oldfieldname": "rejected_warehouse",
@@ -1058,7 +1059,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2023-07-02 18:40:48.152637",
+ "modified": "2023-07-04 17:22:02.830029",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
index 75b6ec7ef8f..0c6d33bae21 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
@@ -1272,24 +1272,29 @@ def get_reserved_batches_for_pos(kwargs):
if ids:
for d in get_serial_batch_ledgers(kwargs.item_code, docstatus=1, name=ids):
- if d.batch_no not in pos_batches:
- pos_batches[d.batch_no] = frappe._dict(
+ key = (d.batch_no, d.warehouse)
+ if key not in pos_batches:
+ pos_batches[key] = frappe._dict(
{
"qty": d.qty,
"warehouse": d.warehouse,
}
)
else:
- pos_batches[d.batch_no].qty += d.qty
+ pos_batches[key].qty += d.qty
for row in pos_invoices:
if not row.batch_no:
continue
- if row.batch_no in pos_batches:
- pos_batches[row.batch_no] -= row.qty * -1 if row.is_return else row.qty
+ if kwargs.get("batch_no") and row.batch_no != kwargs.get("batch_no"):
+ continue
+
+ key = (row.batch_no, row.warehouse)
+ if key in pos_batches:
+ pos_batches[key] -= row.qty * -1 if row.is_return else row.qty
else:
- pos_batches[row.batch_no] = frappe._dict(
+ pos_batches[key] = frappe._dict(
{
"qty": (row.qty * -1 if row.is_return else row.qty),
"warehouse": row.warehouse,
@@ -1309,6 +1314,7 @@ def get_auto_batch_nos(kwargs):
update_available_batches(available_batches, stock_ledgers_batches, pos_invoice_batches)
available_batches = list(filter(lambda x: x.qty > 0, available_batches))
+
if not qty:
return available_batches
@@ -1351,10 +1357,11 @@ def get_qty_based_available_batches(available_batches, qty):
def update_available_batches(available_batches, reserved_batches=None, pos_invoice_batches=None):
for batches in [reserved_batches, pos_invoice_batches]:
if batches:
- for batch_no, data in batches.items():
+ for key, data in batches.items():
+ batch_no, warehouse = key
batch_not_exists = True
for batch in available_batches:
- if batch.batch_no == batch_no and batch.warehouse == data.warehouse:
+ if batch.batch_no == batch_no and batch.warehouse == warehouse:
batch.qty += data.qty
batch_not_exists = False
@@ -1563,7 +1570,7 @@ def get_stock_ledgers_batches(kwargs):
.groupby(stock_ledger_entry.batch_no, stock_ledger_entry.warehouse)
)
- for field in ["warehouse", "item_code"]:
+ for field in ["warehouse", "item_code", "batch_no"]:
if not kwargs.get(field):
continue
@@ -1582,6 +1589,10 @@ def get_stock_ledgers_batches(kwargs):
data = query.run(as_dict=True)
batches = {}
for d in data:
- batches[d.batch_no] = d
+ key = (d.batch_no, d.warehouse)
+ if key not in batches:
+ batches[key] = d
+ else:
+ batches[key].qty += d.qty
return batches
diff --git a/erpnext/stock/doctype/warehouse/warehouse.js b/erpnext/stock/doctype/warehouse/warehouse.js
index 746a1cbaf17..3819c0b24a1 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.js
+++ b/erpnext/stock/doctype/warehouse/warehouse.js
@@ -83,12 +83,6 @@ frappe.ui.form.on("Warehouse", {
}
frm.toggle_enable(["is_group", "company"], false);
-
- frappe.dynamic_link = {
- doc: frm.doc,
- fieldname: "name",
- doctype: "Warehouse",
- };
},
});
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json
index 9dee3aae466..4b3cc8365c5 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json
@@ -251,6 +251,7 @@
"description": "Sets 'Rejected Warehouse' in each row of the Items table.",
"fieldname": "rejected_warehouse",
"fieldtype": "Link",
+ "ignore_user_permissions": 1,
"label": "Rejected Warehouse",
"no_copy": 1,
"options": "Warehouse",
@@ -630,7 +631,7 @@
"in_create": 1,
"is_submittable": 1,
"links": [],
- "modified": "2023-06-03 16:18:39.088518",
+ "modified": "2023-07-06 18:43:16.171842",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Receipt",
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
index 4af38e516f1..60746d95f39 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
@@ -192,13 +192,23 @@ class SubcontractingReceipt(SubcontractingController):
self.total = total_amount
def validate_rejected_warehouse(self):
- if not self.rejected_warehouse:
- for item in self.items:
- if item.rejected_qty:
+ for item in self.items:
+ if flt(item.rejected_qty) and not item.rejected_warehouse:
+ if self.rejected_warehouse:
+ item.rejected_warehouse = self.rejected_warehouse
+
+ if not item.rejected_warehouse:
frappe.throw(
- _("Rejected Warehouse is mandatory against rejected Item {0}").format(item.item_code)
+ _("Row #{0}: Rejected Warehouse is mandatory for the rejected Item {1}").format(
+ item.idx, item.item_code
+ )
)
+ if item.get("rejected_warehouse") and (item.get("rejected_warehouse") == item.get("warehouse")):
+ frappe.throw(
+ _("Row #{0}: Accepted Warehouse and Rejected Warehouse cannot be same").format(item.idx)
+ )
+
def validate_available_qty_for_consumption(self):
for item in self.get("supplied_items"):
precision = item.precision("consumed_qty")
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json
index d550b758390..d72878061c1 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json
@@ -254,6 +254,7 @@
"depends_on": "eval: !parent.is_return",
"fieldname": "rejected_warehouse",
"fieldtype": "Link",
+ "ignore_user_permissions": 1,
"label": "Rejected Warehouse",
"no_copy": 1,
"options": "Warehouse",
@@ -494,7 +495,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2023-03-12 14:00:41.418681",
+ "modified": "2023-07-06 18:43:45.599761",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Receipt Item",