Merge pull request #37229 from FHenry/dev_remove_regional_france
refactor!: Remove Regionalisation of France as now there is an App ERPNext France to manage it
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json
index 5ffd718..66b5c4b 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.json
+++ b/erpnext/accounts/doctype/payment_request/payment_request.json
@@ -268,8 +268,7 @@
"fieldname": "email_to",
"fieldtype": "Data",
"in_global_search": 1,
- "label": "To",
- "options": "Email"
+ "label": "To"
},
{
"depends_on": "eval: doc.payment_channel != \"Phone\"",
@@ -340,8 +339,8 @@
},
{
"fieldname": "payment_url",
- "hidden": 1,
"fieldtype": "Data",
+ "hidden": 1,
"length": 500,
"options": "URL",
"read_only": 1
@@ -396,7 +395,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2023-09-16 14:15:02.510890",
+ "modified": "2023-09-27 09:51:42.277638",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Request",
diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json
index 54a76b3..624b5f8 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json
+++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json
@@ -8,6 +8,7 @@
"transaction_date",
"posting_date",
"fiscal_year",
+ "year_start_date",
"amended_from",
"company",
"column_break1",
@@ -100,16 +101,22 @@
"fieldtype": "Text",
"label": "Error Message",
"read_only": 1
+ },
+ {
+ "fieldname": "year_start_date",
+ "fieldtype": "Date",
+ "label": "Year Start Date"
}
],
"icon": "fa fa-file-text",
"idx": 1,
"is_submittable": 1,
"links": [],
- "modified": "2022-07-20 14:51:04.714154",
+ "modified": "2023-09-11 20:19:11.810533",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Period Closing Voucher",
+ "naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -144,5 +151,6 @@
"search_fields": "posting_date, fiscal_year",
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"title_field": "closing_account_head"
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
index d984d86..674db6c 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
@@ -95,15 +95,23 @@
self.check_if_previous_year_closed()
- pce = frappe.db.sql(
- """select name from `tabPeriod Closing Voucher`
- where posting_date > %s and fiscal_year = %s and docstatus = 1 and company = %s""",
- (self.posting_date, self.fiscal_year, self.company),
+ pcv = frappe.qb.DocType("Period Closing Voucher")
+ existing_entry = (
+ frappe.qb.from_(pcv)
+ .select(pcv.name)
+ .where(
+ (pcv.posting_date >= self.posting_date)
+ & (pcv.fiscal_year == self.fiscal_year)
+ & (pcv.docstatus == 1)
+ & (pcv.company == self.company)
+ )
+ .run()
)
- if pce and pce[0][0]:
+
+ if existing_entry and existing_entry[0][0]:
frappe.throw(
_("Another Period Closing Entry {0} has been made after {1}").format(
- pce[0][0], self.posting_date
+ existing_entry[0][0], self.posting_date
)
)
@@ -130,18 +138,27 @@
frappe.enqueue(
process_gl_entries,
gl_entries=gl_entries,
+ voucher_name=self.name,
+ timeout=3000,
+ )
+
+ frappe.enqueue(
+ process_closing_entries,
+ gl_entries=gl_entries,
closing_entries=closing_entries,
voucher_name=self.name,
company=self.company,
closing_date=self.posting_date,
- queue="long",
+ timeout=3000,
)
+
frappe.msgprint(
_("The GL Entries will be processed in the background, it can take a few minutes."),
alert=True,
)
else:
- process_gl_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date)
+ process_gl_entries(gl_entries, self.name)
+ process_closing_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date)
def get_grouped_gl_entries(self, get_opening_entries=False):
closing_entries = []
@@ -322,17 +339,12 @@
return query.run(as_dict=1)
-def process_gl_entries(gl_entries, closing_entries, voucher_name, company, closing_date):
- from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
- make_closing_entries,
- )
+def process_gl_entries(gl_entries, voucher_name):
from erpnext.accounts.general_ledger import make_gl_entries
try:
if gl_entries:
make_gl_entries(gl_entries, merge_entries=False)
-
- make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date)
frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Completed")
except Exception as e:
frappe.db.rollback()
@@ -340,6 +352,19 @@
frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Failed")
+def process_closing_entries(gl_entries, closing_entries, voucher_name, company, closing_date):
+ from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
+ make_closing_entries,
+ )
+
+ try:
+ if gl_entries + closing_entries:
+ make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date)
+ except Exception as e:
+ frappe.db.rollback()
+ frappe.log_error(e)
+
+
def make_reverse_gl_entries(voucher_type, voucher_no):
from erpnext.accounts.general_ledger import make_reverse_gl_entries
diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
index 5d08e8d..1bd565e 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
@@ -10,7 +10,7 @@
from erpnext.accounts.doctype.finance_book.test_finance_book import create_finance_book
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
-from erpnext.accounts.utils import get_fiscal_year, now
+from erpnext.accounts.utils import get_fiscal_year
class TestPeriodClosingVoucher(unittest.TestCase):
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
index 9a5ad35..52ae951 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
@@ -145,7 +145,8 @@
def get_ar_filters(doc, entry):
return {
"report_date": doc.posting_date if doc.posting_date else None,
- "customer": entry.customer,
+ "party_type": "Customer",
+ "party": [entry.customer],
"customer_name": entry.customer_name if entry.customer_name else None,
"payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None,
"sales_partner": doc.sales_partner if doc.sales_partner else None,
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index c8c9ad1..095617d 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -65,6 +65,25 @@
erpnext.accounts.ledger_preview.show_stock_ledger_preview(this.frm);
}
+ if (this.frm.doc.repost_required && this.frm.doc.docstatus===1) {
+ this.frm.set_intro(__("Accounting entries for this invoice need to be reposted. Please click on 'Repost' button to update."));
+ this.frm.add_custom_button(__('Repost Accounting Entries'),
+ () => {
+ this.frm.call({
+ doc: this.frm.doc,
+ method: 'repost_accounting_entries',
+ freeze: true,
+ freeze_message: __('Reposting...'),
+ callback: (r) => {
+ if (!r.exc) {
+ frappe.msgprint(__('Accounting Entries are reposted.'));
+ me.frm.refresh();
+ }
+ }
+ });
+ }).removeClass('btn-default').addClass('btn-warning');
+ }
+
if(!doc.is_return && doc.docstatus == 1 && doc.outstanding_amount != 0){
if(doc.on_hold) {
this.frm.add_custom_button(
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 0599e19..e489882 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -166,6 +166,7 @@
"against_expense_account",
"column_break_63",
"unrealized_profit_loss_account",
+ "repost_required",
"subscription_section",
"subscription",
"auto_repeat",
@@ -191,8 +192,7 @@
"inter_company_invoice_reference",
"is_old_subcontracting_flow",
"remarks",
- "connections_tab",
- "column_break_38"
+ "connections_tab"
],
"fields": [
{
@@ -990,6 +990,7 @@
"print_hide": 1
},
{
+ "allow_on_submit": 1,
"fieldname": "cash_bank_account",
"fieldtype": "Link",
"label": "Cash/Bank Account",
@@ -1053,6 +1054,7 @@
"fieldtype": "Column Break"
},
{
+ "allow_on_submit": 1,
"depends_on": "eval:flt(doc.write_off_amount)!=0",
"fieldname": "write_off_account",
"fieldtype": "Link",
@@ -1217,6 +1219,7 @@
"read_only": 1
},
{
+ "allow_on_submit": 1,
"default": "No",
"fieldname": "is_opening",
"fieldtype": "Select",
@@ -1349,6 +1352,7 @@
"options": "Project"
},
{
+ "allow_on_submit": 1,
"depends_on": "eval:doc.is_internal_supplier",
"description": "Unrealized Profit/Loss account for intra-company transfers",
"fieldname": "unrealized_profit_loss_account",
@@ -1381,6 +1385,7 @@
"depends_on": "eval:doc.is_subcontracted",
"fieldname": "supplier_warehouse",
"fieldtype": "Link",
+ "ignore_user_permissions": 1,
"label": "Supplier Warehouse",
"no_copy": 1,
"options": "Warehouse",
@@ -1505,10 +1510,6 @@
"fieldtype": "Column Break"
},
{
- "fieldname": "column_break_38",
- "fieldtype": "Column Break"
- },
- {
"fieldname": "column_break_50",
"fieldtype": "Column Break"
},
@@ -1578,13 +1579,22 @@
"fieldname": "use_company_roundoff_cost_center",
"fieldtype": "Check",
"label": "Use Company Default Round Off Cost Center"
+ },
+ {
+ "default": "0",
+ "fieldname": "repost_required",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "label": "Repost Required",
+ "options": "Account",
+ "read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
- "modified": "2023-07-25 17:22:59.145031",
+ "modified": "2023-10-01 21:01:47.282533",
"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 5597271..85ed126 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -11,6 +11,9 @@
import erpnext
from erpnext.accounts.deferred_revenue import validate_service_stop_date
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
+from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
+ validate_docs_for_deferred_accounting,
+)
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
check_if_return_invoice_linked_with_payment_entry,
get_total_in_party_account_currency,
@@ -484,6 +487,11 @@
_("Stock cannot be updated against Purchase Receipt {0}").format(item.purchase_receipt)
)
+ def validate_for_repost(self):
+ self.validate_write_off_account()
+ self.validate_expense_account()
+ validate_docs_for_deferred_accounting([], [self.name])
+
def on_submit(self):
super(PurchaseInvoice, self).on_submit()
@@ -522,6 +530,18 @@
self.process_common_party_accounting()
+ def on_update_after_submit(self):
+ if hasattr(self, "repost_required"):
+ fields_to_check = [
+ "cash_bank_account",
+ "write_off_account",
+ "unrealized_profit_loss_account",
+ ]
+ child_tables = {"items": ("expense_account",), "taxes": ("account_head",)}
+ self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
+ self.validate_for_repost()
+ self.db_set("repost_required", self.needs_repost)
+
def make_gl_entries(self, gl_entries=None, from_repost=False):
if not gl_entries:
gl_entries = self.get_gl_entries()
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index b4dd75a..0aaea06 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -1744,7 +1744,6 @@
pi = make_purchase_invoice(
company="_Test Company",
- customer="_Test Supplier",
do_not_save=True,
do_not_submit=True,
rate=1000,
@@ -1862,7 +1861,6 @@
pi = make_purchase_invoice(
company="_Test Company",
- customer="_Test Supplier",
do_not_save=True,
do_not_submit=True,
rate=1000,
@@ -1892,6 +1890,32 @@
clear_dimension_defaults("Branch")
disable_dimension()
+ def test_repost_accounting_entries(self):
+ pi = make_purchase_invoice(
+ rate=1000,
+ price_list_rate=1000,
+ qty=1,
+ )
+ expected_gle = [
+ ["_Test Account Cost for Goods Sold - _TC", 1000, 0.0, nowdate()],
+ ["Creditors - _TC", 0.0, 1000, nowdate()],
+ ]
+ check_gl_entries(self, pi.name, expected_gle, nowdate())
+
+ pi.items[0].expense_account = "Service - _TC"
+ pi.save()
+ pi.load_from_db()
+ self.assertTrue(pi.repost_required)
+ pi.repost_accounting_entries()
+
+ expected_gle = [
+ ["Creditors - _TC", 0.0, 1000, nowdate()],
+ ["Service - _TC", 1000, 0.0, nowdate()],
+ ]
+ check_gl_entries(self, pi.name, expected_gle, nowdate())
+ pi.load_from_db()
+ self.assertFalse(pi.repost_required)
+
def set_advance_flag(company, flag, default_account):
frappe.db.set_value(
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 81c7577..3690142 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -473,6 +473,7 @@
"label": "Accounting"
},
{
+ "allow_on_submit": 1,
"fieldname": "expense_account",
"fieldtype": "Link",
"label": "Expense Head",
diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json
index d86abad..347cae0 100644
--- a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json
+++ b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json
@@ -86,6 +86,7 @@
"fieldtype": "Column Break"
},
{
+ "allow_on_submit": 1,
"columns": 2,
"fieldname": "account_head",
"fieldtype": "Link",
@@ -97,6 +98,7 @@
"reqd": 1
},
{
+ "allow_on_submit": 1,
"default": ":Company",
"fieldname": "cost_center",
"fieldtype": "Link",
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json
index 8d56c9b..5b7cd2b 100644
--- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json
+++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json
@@ -55,7 +55,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2023-07-27 15:47:58.975034",
+ "modified": "2023-09-26 14:21:27.362567",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Repost Accounting Ledger",
@@ -77,5 +77,6 @@
],
"sort_field": "modified",
"sort_order": "DESC",
- "states": []
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
index 4cf2ed2..dbb0971 100644
--- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
+++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
@@ -21,29 +21,8 @@
def validate_for_deferred_accounting(self):
sales_docs = [x.voucher_no for x in self.vouchers if x.voucher_type == "Sales Invoice"]
- docs_with_deferred_revenue = frappe.db.get_all(
- "Sales Invoice Item",
- filters={"parent": ["in", sales_docs], "docstatus": 1, "enable_deferred_revenue": True},
- fields=["parent"],
- as_list=1,
- )
-
purchase_docs = [x.voucher_no for x in self.vouchers if x.voucher_type == "Purchase Invoice"]
- docs_with_deferred_expense = frappe.db.get_all(
- "Purchase Invoice Item",
- filters={"parent": ["in", purchase_docs], "docstatus": 1, "enable_deferred_expense": 1},
- fields=["parent"],
- as_list=1,
- )
-
- if docs_with_deferred_revenue or docs_with_deferred_expense:
- frappe.throw(
- _("Documents: {0} have deferred revenue/expense enabled for them. Cannot repost.").format(
- frappe.bold(
- comma_and([x[0] for x in docs_with_deferred_expense + docs_with_deferred_revenue])
- )
- )
- )
+ validate_docs_for_deferred_accounting(sales_docs, purchase_docs)
def validate_for_closed_fiscal_year(self):
if self.vouchers:
@@ -139,14 +118,17 @@
return rendered_page
def on_submit(self):
- job_name = "repost_accounting_ledger_" + self.name
- frappe.enqueue(
- method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost",
- account_repost_doc=self.name,
- is_async=True,
- job_name=job_name,
- )
- frappe.msgprint(_("Repost has started in the background"))
+ if len(self.vouchers) > 1:
+ job_name = "repost_accounting_ledger_" + self.name
+ frappe.enqueue(
+ method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost",
+ account_repost_doc=self.name,
+ is_async=True,
+ job_name=job_name,
+ )
+ frappe.msgprint(_("Repost has started in the background"))
+ else:
+ start_repost(self.name)
@frappe.whitelist()
@@ -181,3 +163,26 @@
doc.make_gl_entries()
frappe.db.commit()
+
+
+def validate_docs_for_deferred_accounting(sales_docs, purchase_docs):
+ docs_with_deferred_revenue = frappe.db.get_all(
+ "Sales Invoice Item",
+ filters={"parent": ["in", sales_docs], "docstatus": 1, "enable_deferred_revenue": True},
+ fields=["parent"],
+ as_list=1,
+ )
+
+ docs_with_deferred_expense = frappe.db.get_all(
+ "Purchase Invoice Item",
+ filters={"parent": ["in", purchase_docs], "docstatus": 1, "enable_deferred_expense": 1},
+ fields=["parent"],
+ as_list=1,
+ )
+
+ if docs_with_deferred_revenue or docs_with_deferred_expense:
+ frappe.throw(
+ _("Documents: {0} have deferred revenue/expense enabled for them. Cannot repost.").format(
+ frappe.bold(comma_and([x[0] for x in docs_with_deferred_expense + docs_with_deferred_revenue]))
+ )
+ )
diff --git a/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json b/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json
index 5175fd1..ed8d395 100644
--- a/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json
+++ b/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json
@@ -99,7 +99,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2022-11-08 07:38:40.079038",
+ "modified": "2023-09-26 14:21:35.719727",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Repost Payment Ledger",
@@ -155,5 +155,6 @@
],
"sort_field": "modified",
"sort_order": "DESC",
- "states": []
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 7bdb2b4..f380825 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -11,13 +11,13 @@
import erpnext
from erpnext.accounts.deferred_revenue import validate_service_stop_date
-from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
- get_accounting_dimensions,
-)
from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
get_loyalty_program_details_with_points,
validate_loyalty_points,
)
+from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
+ validate_docs_for_deferred_accounting,
+)
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
get_party_tax_withholding_details,
)
@@ -168,6 +168,12 @@
self.validate_account_for_change_amount()
self.validate_income_account()
+ def validate_for_repost(self):
+ self.validate_write_off_account()
+ self.validate_account_for_change_amount()
+ self.validate_income_account()
+ validate_docs_for_deferred_accounting([self.name], [])
+
def validate_fixed_asset(self):
for d in self.get("items"):
if d.is_fixed_asset and d.meta.get_field("asset") and d.asset:
@@ -517,90 +523,21 @@
def on_update_after_submit(self):
if hasattr(self, "repost_required"):
- needs_repost = 0
-
- # Check if any field affecting accounting entry is altered
- doc_before_update = self.get_doc_before_save()
- accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"]
-
- # Check if opening entry check updated
- if doc_before_update.get("is_opening") != self.is_opening:
- needs_repost = 1
-
- if not needs_repost:
- # Parent Level Accounts excluding party account
- for field in (
- "additional_discount_account",
- "cash_bank_account",
- "account_for_change_amount",
- "write_off_account",
- "loyalty_redemption_account",
- "unrealized_profit_loss_account",
- ):
- if doc_before_update.get(field) != self.get(field):
- needs_repost = 1
- break
-
- # Check for parent accounting dimensions
- for dimension in accounting_dimensions:
- if doc_before_update.get(dimension) != self.get(dimension):
- needs_repost = 1
- break
-
- # Check for child tables
- if self.check_if_child_table_updated(
- "items",
- doc_before_update,
- ("income_account", "expense_account", "discount_account"),
- accounting_dimensions,
- ):
- needs_repost = 1
-
- if self.check_if_child_table_updated(
- "taxes", doc_before_update, ("account_head",), accounting_dimensions
- ):
- needs_repost = 1
-
- self.validate_accounts()
-
- # validate if deferred revenue is enabled for any item
- # Don't allow to update the invoice if deferred revenue is enabled
- if needs_repost:
- for item in self.get("items"):
- if item.enable_deferred_revenue:
- frappe.throw(
- _(
- "Deferred Revenue is enabled for item {0}. You cannot update the invoice after submission."
- ).format(item.item_code)
- )
-
- self.db_set("repost_required", needs_repost)
-
- def check_if_child_table_updated(
- self, child_table, doc_before_update, fields_to_check, accounting_dimensions
- ):
- # Check if any field affecting accounting entry is altered
- for index, item in enumerate(self.get(child_table)):
- for field in fields_to_check:
- if doc_before_update.get(child_table)[index].get(field) != item.get(field):
- return True
-
- for dimension in accounting_dimensions:
- if doc_before_update.get(child_table)[index].get(dimension) != item.get(dimension):
- return True
-
- return False
-
- @frappe.whitelist()
- def repost_accounting_entries(self):
- if self.repost_required:
- self.docstatus = 2
- self.make_gl_entries_on_cancel()
- self.docstatus = 1
- self.make_gl_entries()
- self.db_set("repost_required", 0)
- else:
- frappe.throw(_("No updates pending for reposting"))
+ fields_to_check = [
+ "additional_discount_account",
+ "cash_bank_account",
+ "account_for_change_amount",
+ "write_off_account",
+ "loyalty_redemption_account",
+ "unrealized_profit_loss_account",
+ ]
+ child_tables = {
+ "items": ("income_account", "expense_account", "discount_account"),
+ "taxes": ("account_head",),
+ }
+ self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
+ self.validate_for_repost()
+ self.db_set("repost_required", self.needs_repost)
def set_paid_amount(self):
paid_amount = 0.0
diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js
index 27a8570..9c73cbb 100644
--- a/erpnext/accounts/report/accounts_payable/accounts_payable.js
+++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js
@@ -95,30 +95,27 @@
"options": "Payment Terms Template"
},
{
- "fieldname": "party_type",
+ "fieldname":"party_type",
"label": __("Party Type"),
- "fieldtype": "Link",
- "options": "Party Type",
- get_query: () => {
- return {
- filters: {
- 'account_type': 'Payable'
- }
- };
- },
- on_change: () => {
+ "fieldtype": "Autocomplete",
+ options: get_party_type_options(),
+ on_change: function() {
frappe.query_report.set_filter_value('party', "");
- let party_type = frappe.query_report.get_filter_value('party_type');
frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier");
-
}
-
},
{
"fieldname":"party",
"label": __("Party"),
- "fieldtype": "Dynamic Link",
- "options": "party_type",
+ "fieldtype": "MultiSelectList",
+ get_data: function(txt) {
+ if (!frappe.query_report.filters) return;
+
+ let party_type = frappe.query_report.get_filter_value('party_type');
+ if (!party_type) return;
+
+ return frappe.db.get_link_options(party_type, txt);
+ },
},
{
"fieldname": "supplier_group",
@@ -167,3 +164,15 @@
}
erpnext.utils.add_dimensions('Accounts Payable', 9);
+
+function get_party_type_options() {
+ let options = [];
+ frappe.db.get_list(
+ "Party Type", {filters:{"account_type": "Payable"}, fields:['name']}
+ ).then((res) => {
+ res.forEach((party_type) => {
+ options.push(party_type.name);
+ });
+ });
+ return options;
+}
\ No newline at end of file
diff --git a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py
index 3cf93cc..9f03d92 100644
--- a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py
+++ b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py
@@ -34,7 +34,7 @@
filters = {
"company": self.company,
"party_type": "Supplier",
- "party": self.supplier,
+ "party": [self.supplier],
"report_date": today(),
"range1": 30,
"range2": 60,
diff --git a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js
index ea20072..9e575e6 100644
--- a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js
+++ b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js
@@ -72,10 +72,27 @@
}
},
{
- "fieldname":"supplier",
- "label": __("Supplier"),
- "fieldtype": "Link",
- "options": "Supplier"
+ "fieldname":"party_type",
+ "label": __("Party Type"),
+ "fieldtype": "Autocomplete",
+ options: get_party_type_options(),
+ on_change: function() {
+ frappe.query_report.set_filter_value('party', "");
+ frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier");
+ }
+ },
+ {
+ "fieldname":"party",
+ "label": __("Party"),
+ "fieldtype": "MultiSelectList",
+ get_data: function(txt) {
+ if (!frappe.query_report.filters) return;
+
+ let party_type = frappe.query_report.get_filter_value('party_type');
+ if (!party_type) return;
+
+ return frappe.db.get_link_options(party_type, txt);
+ },
},
{
"fieldname":"payment_terms_template",
@@ -105,3 +122,15 @@
}
erpnext.utils.add_dimensions('Accounts Payable Summary', 9);
+
+function get_party_type_options() {
+ let options = [];
+ frappe.db.get_list(
+ "Party Type", {filters:{"account_type": "Payable"}, fields:['name']}
+ ).then((res) => {
+ res.forEach((party_type) => {
+ options.push(party_type.name);
+ });
+ });
+ return options;
+}
\ No newline at end of file
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
index bb00d61..1073be0 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
@@ -1,6 +1,8 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
+frappe.provide("erpnext.utils");
+
frappe.query_reports["Accounts Receivable"] = {
"filters": [
{
@@ -38,30 +40,27 @@
}
},
{
- "fieldname": "party_type",
+ "fieldname":"party_type",
"label": __("Party Type"),
- "fieldtype": "Link",
- "options": "Party Type",
- "Default": "Customer",
- get_query: () => {
- return {
- filters: {
- 'account_type': 'Receivable'
- }
- };
- },
- on_change: () => {
+ "fieldtype": "Autocomplete",
+ options: get_party_type_options(),
+ on_change: function() {
frappe.query_report.set_filter_value('party', "");
- let party_type = frappe.query_report.get_filter_value('party_type');
frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer");
-
}
},
{
"fieldname":"party",
"label": __("Party"),
- "fieldtype": "Dynamic Link",
- "options": "party_type",
+ "fieldtype": "MultiSelectList",
+ get_data: function(txt) {
+ if (!frappe.query_report.filters) return;
+
+ let party_type = frappe.query_report.get_filter_value('party_type');
+ if (!party_type) return;
+
+ return frappe.db.get_link_options(party_type, txt);
+ },
},
{
"fieldname": "party_account",
@@ -194,3 +193,16 @@
}
erpnext.utils.add_dimensions('Accounts Receivable', 9);
+
+
+function get_party_type_options() {
+ let options = [];
+ frappe.db.get_list(
+ "Party Type", {filters:{"account_type": "Receivable"}, fields:['name']}
+ ).then((res) => {
+ res.forEach((party_type) => {
+ options.push(party_type.name);
+ });
+ });
+ return options;
+}
\ No newline at end of file
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 7942402..e3b671f 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -801,7 +801,7 @@
self.qb_selection_filter.append(self.filters.party_type == self.ple.party_type)
if self.filters.get("party"):
- self.qb_selection_filter.append(self.filters.party == self.ple.party)
+ self.qb_selection_filter.append(self.ple.party.isin(self.filters.party))
if self.filters.party_account:
self.qb_selection_filter.append(self.ple.account == self.filters.party_account)
diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
index b98916e..4307689 100644
--- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
@@ -573,7 +573,7 @@
filters = {
"company": self.company,
"party_type": "Customer",
- "party": self.customer,
+ "party": [self.customer],
"report_date": today(),
"range1": 30,
"range2": 60,
@@ -605,3 +605,41 @@
for field in expected:
with self.subTest(field=field):
self.assertEqual(report_output.get(field), expected.get(field))
+
+ def test_multi_select_party_filter(self):
+ self.customer1 = self.customer
+ self.create_customer("_Test Customer 2")
+ self.customer2 = self.customer
+ self.create_customer("_Test Customer 3")
+ self.customer3 = self.customer
+
+ filters = {
+ "company": self.company,
+ "party_type": "Customer",
+ "party": [self.customer1, self.customer3],
+ "report_date": today(),
+ "range1": 30,
+ "range2": 60,
+ "range3": 90,
+ "range4": 120,
+ }
+
+ si1 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
+ si1.customer = self.customer1
+ si1.save().submit()
+
+ si2 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
+ si2.customer = self.customer2
+ si2.save().submit()
+
+ si3 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
+ si3.customer = self.customer3
+ si3.save().submit()
+
+ # check invoice grand total and invoiced column's value for 3 payment terms
+ report = execute(filters)
+
+ expected_output = {self.customer1, self.customer3}
+ self.assertEqual(len(report[1]), 2)
+ output_for = set([x.party for x in report[1]])
+ self.assertEqual(output_for, expected_output)
diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js
index 715cd64..5ad10c7 100644
--- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js
+++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js
@@ -72,10 +72,27 @@
}
},
{
- "fieldname":"customer",
- "label": __("Customer"),
- "fieldtype": "Link",
- "options": "Customer"
+ "fieldname":"party_type",
+ "label": __("Party Type"),
+ "fieldtype": "Autocomplete",
+ options: get_party_type_options(),
+ on_change: function() {
+ frappe.query_report.set_filter_value('party', "");
+ frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer");
+ }
+ },
+ {
+ "fieldname":"party",
+ "label": __("Party"),
+ "fieldtype": "MultiSelectList",
+ get_data: function(txt) {
+ if (!frappe.query_report.filters) return;
+
+ let party_type = frappe.query_report.get_filter_value('party_type');
+ if (!party_type) return;
+
+ return frappe.db.get_link_options(party_type, txt);
+ },
},
{
"fieldname":"customer_group",
@@ -133,3 +150,15 @@
}
erpnext.utils.add_dimensions('Accounts Receivable Summary', 9);
+
+function get_party_type_options() {
+ let options = [];
+ frappe.db.get_list(
+ "Party Type", {filters:{"account_type": "Receivable"}, fields:['name']}
+ ).then((res) => {
+ res.forEach((party_type) => {
+ options.push(party_type.name);
+ });
+ });
+ return options;
+}
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py
index 7e95cb2..9c2b8bc 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.py
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.py
@@ -177,7 +177,7 @@
"item_code": stock_item.item_code,
"qty": stock_item.consumed_quantity,
"basic_rate": stock_item.valuation_rate,
- "serial_no": stock_item.serial_and_batch_bundle,
+ "serial_and_batch_bundle": stock_item.serial_and_batch_bundle,
"cost_center": self.cost_center,
"project": self.project,
},
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index 5b5cc2b..f74df66 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -477,6 +477,7 @@
"depends_on": "eval:doc.is_subcontracted",
"fieldname": "supplier_warehouse",
"fieldtype": "Link",
+ "ignore_user_permissions": 1,
"label": "Supplier Warehouse",
"options": "Warehouse"
},
@@ -1274,7 +1275,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
- "modified": "2023-09-13 16:21:07.361700",
+ "modified": "2023-10-01 20:58:07.851037",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",
diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js
index 08dc44c..70d2782 100644
--- a/erpnext/buying/doctype/supplier/supplier.js
+++ b/erpnext/buying/doctype/supplier/supplier.js
@@ -88,7 +88,7 @@
}, __("View"));
frm.add_custom_button(__('Accounts Payable'), function () {
- frappe.set_route('query-report', 'Accounts Payable', { supplier: frm.doc.name });
+ frappe.set_route('query-report', 'Accounts Payable', { party_type: "Supplier", party: frm.doc.name });
}, __("View"));
frm.add_custom_button(__('Bank Account'), function () {
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index e635aa7..6812940 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -243,13 +243,38 @@
_doc.cancel()
_doc.delete()
- def on_trash(self):
- # delete references in 'Repost Payment Ledger'
- rpi = frappe.qb.DocType("Repost Payment Ledger Items")
- frappe.qb.from_(rpi).delete().where(
- (rpi.voucher_type == self.doctype) & (rpi.voucher_no == self.name)
- ).run()
+ def _remove_references_in_repost_doctypes(self):
+ repost_doctypes = ["Repost Payment Ledger Items", "Repost Accounting Ledger Items"]
+ for _doctype in repost_doctypes:
+ dt = frappe.qb.DocType(_doctype)
+ rows = (
+ frappe.qb.from_(dt)
+ .select(dt.name, dt.parent, dt.parenttype)
+ .where((dt.voucher_type == self.doctype) & (dt.voucher_no == self.name))
+ .run(as_dict=True)
+ )
+
+ if rows:
+ references_map = frappe._dict()
+ for x in rows:
+ references_map.setdefault((x.parenttype, x.parent), []).append(x.name)
+
+ for doc, rows in references_map.items():
+ repost_doc = frappe.get_doc(doc[0], doc[1])
+
+ for row in rows:
+ if _doctype == "Repost Payment Ledger Items":
+ repost_doc.remove(repost_doc.get("repost_vouchers", {"name": row})[0])
+ else:
+ repost_doc.remove(repost_doc.get("vouchers", {"name": row})[0])
+
+ repost_doc.flags.ignore_validate_update_after_submit = True
+ repost_doc.flags.ignore_links = True
+ repost_doc.save(ignore_permissions=True)
+
+ def on_trash(self):
+ self._remove_references_in_repost_doctypes()
self._remove_references_in_unreconcile()
# delete sl and gl entries on deletion of transaction
@@ -1466,7 +1491,7 @@
"account": self.additional_discount_account,
"against": supplier_or_customer,
dr_or_cr: self.base_discount_amount,
- "cost_center": self.cost_center,
+ "cost_center": self.cost_center or erpnext.get_default_cost_center(self.company),
},
item=self,
)
@@ -2186,6 +2211,45 @@
_("Select finance book for the item {0} at row {1}").format(item.item_code, item.idx)
)
+ def check_if_fields_updated(self, fields_to_check, child_tables):
+ # Check if any field affecting accounting entry is altered
+ doc_before_update = self.get_doc_before_save()
+ accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"]
+
+ # Check if opening entry check updated
+ needs_repost = doc_before_update.get("is_opening") != self.is_opening
+
+ if not needs_repost:
+ # Parent Level Accounts excluding party account
+ fields_to_check += accounting_dimensions
+ for field in fields_to_check:
+ if doc_before_update.get(field) != self.get(field):
+ needs_repost = 1
+ break
+
+ if not needs_repost:
+ # Check for child tables
+ for table in child_tables:
+ needs_repost = check_if_child_table_updated(
+ doc_before_update.get(table), self.get(table), child_tables[table]
+ )
+ if needs_repost:
+ break
+
+ return needs_repost
+
+ @frappe.whitelist()
+ def repost_accounting_entries(self):
+ if self.repost_required:
+ repost_ledger = frappe.new_doc("Repost Accounting Ledger")
+ repost_ledger.company = self.company
+ repost_ledger.append("vouchers", {"voucher_type": self.doctype, "voucher_no": self.name})
+ repost_ledger.insert()
+ repost_ledger.submit()
+ self.db_set("repost_required", 0)
+ else:
+ frappe.throw(_("No updates pending for reposting"))
+
@frappe.whitelist()
def get_tax_rate(account_head):
@@ -3191,6 +3255,23 @@
parent.create_stock_reservation_entries()
+def check_if_child_table_updated(
+ child_table_before_update, child_table_after_update, fields_to_check
+):
+ accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"]
+ # Check if any field affecting accounting entry is altered
+ for index, item in enumerate(child_table_after_update):
+ for field in fields_to_check:
+ if child_table_before_update[index].get(field) != item.get(field):
+ return True
+
+ for dimension in accounting_dimensions:
+ if child_table_before_update[index].get(dimension) != item.get(dimension):
+ return True
+
+ return False
+
+
@erpnext.allow_regional
def validate_regional(doc):
pass
diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py
index d4270a7..5fa66b1 100644
--- a/erpnext/controllers/subcontracting_controller.py
+++ b/erpnext/controllers/subcontracting_controller.py
@@ -804,7 +804,7 @@
{
"item_code": item.rm_item_code,
"warehouse": self.supplier_warehouse,
- "actual_qty": -1 * flt(item.consumed_qty),
+ "actual_qty": -1 * flt(item.consumed_qty, item.precision("consumed_qty")),
"dependant_sle_voucher_detail_no": item.reference_name,
},
)
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index e88b791..fabdafc 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -1509,6 +1509,10 @@
def get_materials_from_other_locations(item, warehouses, new_mr_items, company):
from erpnext.stock.doctype.pick_list.pick_list import get_available_item_locations
+ stock_uom, purchase_uom = frappe.db.get_value(
+ "Item", item.get("item_code"), ["stock_uom", "purchase_uom"]
+ )
+
locations = get_available_item_locations(
item.get("item_code"), warehouses, item.get("quantity"), company, ignore_validation=True
)
@@ -1519,6 +1523,10 @@
if required_qty <= 0:
return
+ conversion_factor = 1.0
+ if purchase_uom != stock_uom and purchase_uom == item["uom"]:
+ conversion_factor = get_uom_conversion_factor(item["item_code"], item["uom"])
+
new_dict = copy.deepcopy(item)
quantity = required_qty if d.get("qty") > required_qty else d.get("qty")
@@ -1531,25 +1539,14 @@
}
)
- required_qty -= quantity
+ required_qty -= quantity / conversion_factor
new_mr_items.append(new_dict)
# raise purchase request for remaining qty
- if required_qty:
- stock_uom, purchase_uom = frappe.db.get_value(
- "Item", item["item_code"], ["stock_uom", "purchase_uom"]
- )
- if purchase_uom != stock_uom and purchase_uom == item["uom"]:
- conversion_factor = get_uom_conversion_factor(item["item_code"], item["uom"])
- if not (conversion_factor or frappe.flags.show_qty_in_stock_uom):
- frappe.throw(
- _("UOM Conversion factor ({0} -> {1}) not found for item: {2}").format(
- purchase_uom, stock_uom, item["item_code"]
- )
- )
-
- required_qty = required_qty / conversion_factor
+ precision = frappe.get_precision("Material Request Plan Item", "quantity")
+ if flt(required_qty, precision) > 0:
+ required_qty = required_qty
if frappe.db.get_value("UOM", purchase_uom, "must_be_whole_number"):
required_qty = ceil(required_qty)
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index 5292571..5d54c41 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -1050,6 +1050,59 @@
self.assertEqual(after_qty, before_qty)
+ def test_resered_qty_for_production_plan_for_work_order(self):
+ from erpnext.stock.utils import get_or_make_bin
+
+ bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC")
+ before_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
+
+ pln = create_production_plan(item_code="Test Production Item 1")
+
+ bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC")
+ after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
+
+ self.assertEqual(after_qty - before_qty, 1)
+
+ pln.make_work_order()
+
+ work_orders = []
+ for row in frappe.get_all("Work Order", filters={"production_plan": pln.name}, fields=["name"]):
+ wo_doc = frappe.get_doc("Work Order", row.name)
+ wo_doc.source_warehouse = "_Test Warehouse - _TC"
+ wo_doc.wip_warehouse = "_Test Warehouse 1 - _TC"
+ wo_doc.fg_warehouse = "_Test Warehouse - _TC"
+ for d in wo_doc.required_items:
+ d.source_warehouse = "_Test Warehouse - _TC"
+ make_stock_entry(
+ item_code=d.item_code,
+ qty=d.required_qty,
+ rate=100,
+ target="_Test Warehouse - _TC",
+ )
+
+ wo_doc.submit()
+ work_orders.append(wo_doc)
+
+ bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC")
+ after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
+
+ self.assertEqual(after_qty, before_qty)
+
+ rm_work_order = None
+ for wo_doc in work_orders:
+ for d in wo_doc.required_items:
+ if d.item_code == "Raw Material Item 1":
+ rm_work_order = wo_doc
+ break
+
+ if rm_work_order:
+ s = frappe.get_doc(make_se_from_wo(rm_work_order.name, "Material Transfer for Manufacture", 1))
+ s.submit()
+ bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC")
+ after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
+
+ self.assertEqual(after_qty, before_qty)
+
def test_resered_qty_for_production_plan_for_material_requests_with_multi_UOM(self):
from erpnext.stock.utils import get_or_make_bin
@@ -1177,6 +1230,64 @@
if row.item_code == "SubAssembly2 For SUB Test":
self.assertEqual(row.quantity, 10)
+ def test_transfer_and_purchase_mrp_for_purchase_uom(self):
+ from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
+ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
+
+ bom_tree = {
+ "Test FG Item INK PEN": {
+ "Test RM Item INK": {},
+ }
+ }
+
+ parent_bom = create_nested_bom(bom_tree, prefix="")
+ if not frappe.db.exists("UOM Conversion Detail", {"parent": "Test RM Item INK", "uom": "Kg"}):
+ doc = frappe.get_doc("Item", "Test RM Item INK")
+ doc.purchase_uom = "Kg"
+ doc.append("uoms", {"uom": "Kg", "conversion_factor": 0.5})
+ doc.save()
+
+ wh1 = create_warehouse("PNE Warehouse", company="_Test Company")
+ wh2 = create_warehouse("MBE Warehouse", company="_Test Company")
+ mrp_warhouse = create_warehouse("MRPBE Warehouse", company="_Test Company")
+
+ make_stock_entry(
+ item_code="Test RM Item INK",
+ qty=2,
+ rate=100,
+ target=wh1,
+ )
+
+ make_stock_entry(
+ item_code="Test RM Item INK",
+ qty=2,
+ rate=100,
+ target=wh2,
+ )
+
+ plan = create_production_plan(
+ item_code=parent_bom.item,
+ planned_qty=10,
+ do_not_submit=1,
+ warehouse="_Test Warehouse - _TC",
+ )
+
+ plan.for_warehouse = mrp_warhouse
+
+ items = get_items_for_material_requests(
+ plan.as_dict(), warehouses=[{"warehouse": wh1}, {"warehouse": wh2}]
+ )
+
+ for row in items:
+ row = frappe._dict(row)
+ if row.material_request_type == "Material Transfer":
+ self.assertTrue(row.from_warehouse in [wh1, wh2])
+ self.assertEqual(row.quantity, 2)
+
+ if row.material_request_type == "Purchase":
+ self.assertTrue(row.warehouse == mrp_warhouse)
+ self.assertEqual(row.quantity, 12)
+
def create_production_plan(**args):
"""
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 5ad79f9..d8fc220 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -1519,16 +1519,17 @@
wo = frappe.qb.DocType("Work Order")
wo_item = frappe.qb.DocType("Work Order Item")
+ if check_production_plan:
+ qty_field = wo_item.required_qty
+ else:
+ qty_field = Case()
+ qty_field = qty_field.when(wo.skip_transfer == 0, wo_item.required_qty - wo_item.transferred_qty)
+ qty_field = qty_field.else_(wo_item.required_qty - wo_item.consumed_qty)
+
query = (
frappe.qb.from_(wo)
.from_(wo_item)
- .select(
- Sum(
- Case()
- .when(wo.skip_transfer == 0, wo_item.required_qty - wo_item.transferred_qty)
- .else_(wo_item.required_qty - wo_item.consumed_qty)
- )
- )
+ .select(Sum(qty_field))
.where(
(wo_item.item_code == item_code)
& (wo_item.parent == wo.name)
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index a66b549..e9aed1a 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -70,6 +70,7 @@
tmp_task_details.append(template_task_details)
task = self.create_task_from_template(template_task_details)
project_tasks.append(task)
+
self.dependency_mapping(tmp_task_details, project_tasks)
def create_task_from_template(self, task_details):
@@ -108,36 +109,28 @@
def dependency_mapping(self, template_tasks, project_tasks):
for project_task in project_tasks:
- if project_task.get("template_task"):
- template_task = frappe.get_doc("Task", project_task.template_task)
- else:
- template_task = list(filter(lambda x: x.subject == project_task.subject, template_tasks))[0]
- template_task = frappe.get_doc("Task", template_task.name)
+ template_task = frappe.get_doc("Task", project_task.template_task)
self.check_depends_on_value(template_task, project_task, project_tasks)
self.check_for_parent_tasks(template_task, project_task, project_tasks)
def check_depends_on_value(self, template_task, project_task, project_tasks):
if template_task.get("depends_on") and not project_task.get("depends_on"):
+ project_template_map = {pt.template_task: pt for pt in project_tasks}
+
for child_task in template_task.get("depends_on"):
- child_task_subject = frappe.db.get_value("Task", child_task.task, "subject")
- corresponding_project_task = list(
- filter(lambda x: x.subject == child_task_subject, project_tasks)
- )
- if len(corresponding_project_task):
+ if project_template_map and project_template_map.get(child_task.task):
project_task.reload() # reload, as it might have been updated in the previous iteration
- project_task.append("depends_on", {"task": corresponding_project_task[0].name})
+ project_task.append("depends_on", {"task": project_template_map.get(child_task.task).name})
project_task.save()
def check_for_parent_tasks(self, template_task, project_task, project_tasks):
if template_task.get("parent_task") and not project_task.get("parent_task"):
- parent_task_subject = frappe.db.get_value("Task", template_task.get("parent_task"), "subject")
- corresponding_project_task = list(
- filter(lambda x: x.subject == parent_task_subject, project_tasks)
- )
- if len(corresponding_project_task):
- project_task.parent_task = corresponding_project_task[0].name
- project_task.save()
+ for pt in project_tasks:
+ if pt.template_task == template_task.parent_task:
+ project_task.parent_task = pt.name
+ project_task.save()
+ break
def is_row_updated(self, row, existing_task_data, fields):
if self.get("__islocal") or not existing_task_data:
diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json
index 05a70c3..25a5455 100644
--- a/erpnext/projects/doctype/task/task.json
+++ b/erpnext/projects/doctype/task/task.json
@@ -60,7 +60,6 @@
"fieldname": "subject",
"fieldtype": "Data",
"in_global_search": 1,
- "in_list_view": 1,
"in_standard_filter": 1,
"label": "Subject",
"reqd": 1,
@@ -140,7 +139,6 @@
"fieldname": "parent_task",
"fieldtype": "Link",
"ignore_user_permissions": 1,
- "in_list_view": 1,
"label": "Parent Task",
"options": "Task",
"search_index": 1
@@ -398,7 +396,7 @@
"is_tree": 1,
"links": [],
"max_attachments": 5,
- "modified": "2023-09-06 13:52:05.861175",
+ "modified": "2023-09-28 13:52:05.861175",
"modified_by": "Administrator",
"module": "Projects",
"name": "Task",
diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js
index 959cf50..907a775 100644
--- a/erpnext/public/js/financial_statements.js
+++ b/erpnext/public/js/financial_statements.js
@@ -6,8 +6,10 @@
if (data && column.fieldname=="account") {
value = data.account_name || value;
- column.link_onclick =
- "erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")";
+ if (data.account) {
+ column.link_onclick =
+ "erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")";
+ }
column.is_tree = true;
}
diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js
index e274a52..42932ad 100644
--- a/erpnext/selling/doctype/customer/customer.js
+++ b/erpnext/selling/doctype/customer/customer.js
@@ -138,7 +138,7 @@
// custom buttons
frm.add_custom_button(__('Accounts Receivable'), function () {
- frappe.set_route('query-report', 'Accounts Receivable', {customer:frm.doc.name});
+ frappe.set_route('query-report', 'Accounts Receivable', { party_type: "Customer", party: frm.doc.name });
}, __('View'));
frm.add_custom_button(__('Accounting Ledger'), function () {
diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json
index f2aabc5..dde2f9b 100644
--- a/erpnext/selling/doctype/quotation_item/quotation_item.json
+++ b/erpnext/selling/doctype/quotation_item/quotation_item.json
@@ -235,6 +235,7 @@
},
{
"collapsible": 1,
+ "collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount",
"fieldname": "discount_and_margin",
"fieldtype": "Section Break",
"label": "Discount and Margin"
@@ -666,7 +667,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2023-02-06 11:00:07.042364",
+ "modified": "2023-09-26 13:42:11.410294",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation Item",
diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json
index 3532d6b..a746ebe 100644
--- a/erpnext/setup/setup_wizard/data/country_wise_tax.json
+++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json
@@ -157,9 +157,14 @@
},
"Botswana": {
- "Botswana Tax": {
+ "Botswana Tax 14%": {
"account_name": "VAT",
- "tax_rate": 12.00
+ "tax_rate": 14.00
+ },
+ "Botswana Tax 12%": {
+ "account_name": "VAT",
+ "tax_rate": 12.00,
+ "default": 1
}
},
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index 912b908..c8a9e3e 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -464,6 +464,7 @@
"depends_on": "eval:doc.is_subcontracted",
"fieldname": "supplier_warehouse",
"fieldtype": "Link",
+ "ignore_user_permissions": 1,
"label": "Supplier Warehouse",
"no_copy": 1,
"oldfieldname": "supplier_warehouse",
@@ -1241,7 +1242,7 @@
"idx": 261,
"is_submittable": 1,
"links": [],
- "modified": "2023-07-04 17:23:17.025390",
+ "modified": "2023-10-01 21:00:44.556816",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt",
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index db71fe2..d3807b0 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -1464,8 +1464,6 @@
if not company:
company = frappe.get_cached_value("Warehouse", warehouse, "company")
- last_valuation_rate = None
-
# Get moving average rate of a specific batch number
if warehouse and serial_and_batch_bundle:
batch_obj = BatchNoValuation(
@@ -1482,21 +1480,18 @@
return batch_obj.get_incoming_rate()
# Get valuation rate from last sle for the same item and warehouse
- if not last_valuation_rate or last_valuation_rate[0][0] is None:
- last_valuation_rate = frappe.db.sql(
- """select valuation_rate
- from `tabStock Ledger Entry` force index (item_warehouse)
- where
- item_code = %s
- AND warehouse = %s
- AND valuation_rate >= 0
- AND is_cancelled = 0
- AND NOT (voucher_no = %s AND voucher_type = %s)
- order by posting_date desc, posting_time desc, name desc limit 1""",
- (item_code, warehouse, voucher_no, voucher_type),
- )
-
- if last_valuation_rate:
+ if last_valuation_rate := frappe.db.sql(
+ """select valuation_rate
+ from `tabStock Ledger Entry` force index (item_warehouse)
+ where
+ item_code = %s
+ AND warehouse = %s
+ AND valuation_rate >= 0
+ AND is_cancelled = 0
+ AND NOT (voucher_no = %s AND voucher_type = %s)
+ order by posting_date desc, posting_time desc, name desc limit 1""",
+ (item_code, warehouse, voucher_no, voucher_type),
+ ):
return flt(last_valuation_rate[0][0])
# If negative stock allowed, and item delivered without any incoming entry,
diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv
index 3180631..4f4a61f 100644
--- a/erpnext/translations/fr.csv
+++ b/erpnext/translations/fr.csv
@@ -7097,8 +7097,8 @@
No of Months,Nombre de mois,
Customer Items,Articles du clients,
Inspection Criteria,Critères d'Inspection,
-Inspection Required before Purchase,Inspection Requise avant Achat,
-Inspection Required before Delivery,Inspection Requise avant Livraison,
+Inspection Required before Purchase,Inspection Requise à la réception,
+Inspection Required before Delivery,Inspection Requise à l'expedition,
Default BOM,Nomenclature par Défaut,
Supply Raw Materials for Purchase,Fournir les Matières Premières pour l'Achat,
If subcontracted to a vendor,Si sous-traité à un fournisseur,
@@ -8840,3 +8840,21 @@
Is Mandatory,Est obligatoire,
WhatsApp,WhatsApp,
Make a call,Passer un coup de téléphone,
+No of Employees,Nb de salarié(e)s
+No. of Employees,Nb de salarié(e)s
+Annual Revenue,CA annuel
+Qualified By,Qualifié par
+Qualified on,Qualifié le
+Open Tasks,Tâche à faire ouverte
+No open task,Pas de Tâche à faire ouverte
+Open Events,Evénements ouvert
+No open event,Pas Evénements ouvert
+New Task,Nv. Tâche à faire
+No Notes,Pas de note
+New Note,Nouvelle Note
+Prospect Owner,Resp. du Prospect
+Deal Owner,Resp. de l'opportunité
+Stage,Etape
+Probability,Probabilité
+Closing,Clôture
+Allow Sales,Autoriser à la vente