Merge pull request #38261 from s-aga-r/FIX-STOCK-RESERVATION-DIALOG-BOX
fix(ux): Sales Order Stock Reservation Dialog
diff --git a/README.md b/README.md
index 44bd729..710187a 100644
--- a/README.md
+++ b/README.md
@@ -73,8 +73,6 @@
1. [Issue Guidelines](https://github.com/frappe/erpnext/wiki/Issue-Guidelines)
1. [Report Security Vulnerabilities](https://erpnext.com/security)
1. [Pull Request Requirements](https://github.com/frappe/erpnext/wiki/Contribution-Guidelines)
-1. [Translations](https://translate.erpnext.com)
-
## License
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index 061bab3..fd052d0 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -66,7 +66,12 @@
"show_balance_in_coa",
"banking_tab",
"enable_party_matching",
- "enable_fuzzy_matching"
+ "enable_fuzzy_matching",
+ "reports_tab",
+ "remarks_section",
+ "general_ledger_remarks_length",
+ "column_break_lvjk",
+ "receivable_payable_remarks_length"
],
"fields": [
{
@@ -422,6 +427,34 @@
"fieldname": "round_row_wise_tax",
"fieldtype": "Check",
"label": "Round Tax Amount Row-wise"
+ },
+ {
+ "fieldname": "reports_tab",
+ "fieldtype": "Tab Break",
+ "label": "Reports"
+ },
+ {
+ "default": "0",
+ "description": "Truncates 'Remarks' column to set character length",
+ "fieldname": "general_ledger_remarks_length",
+ "fieldtype": "Int",
+ "label": "General Ledger"
+ },
+ {
+ "default": "0",
+ "description": "Truncates 'Remarks' column to set character length",
+ "fieldname": "receivable_payable_remarks_length",
+ "fieldtype": "Int",
+ "label": "Accounts Receivable/Payable"
+ },
+ {
+ "fieldname": "column_break_lvjk",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "remarks_section",
+ "fieldtype": "Section Break",
+ "label": "Remarks Column Length"
}
],
"icon": "icon-cog",
@@ -429,7 +462,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2023-08-28 00:12:02.740633",
+ "modified": "2023-11-20 09:37:47.650347",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index 22b6880..9684a0d 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -51,7 +51,7 @@
}, __('Make'));
}
- erpnext.accounts.unreconcile_payments.add_unreconcile_btn(frm);
+ erpnext.accounts.unreconcile_payment.add_unreconcile_btn(frm);
},
before_save: function(frm) {
if ((frm.doc.docstatus == 0) && (!frm.doc.is_system_generated)) {
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json
index 2eb54a5..906760e 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.json
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json
@@ -548,8 +548,16 @@
"icon": "fa fa-file-text",
"idx": 176,
"is_submittable": 1,
- "links": [],
- "modified": "2023-08-10 14:32:22.366895",
+ "links": [
+ {
+ "is_child_table": 1,
+ "link_doctype": "Bank Transaction Payments",
+ "link_fieldname": "payment_entry",
+ "parent_doctype": "Bank Transaction",
+ "table_fieldname": "payment_entries"
+ }
+ ],
+ "modified": "2023-11-23 12:11:04.128015",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 85ef6f7..1cff4c7 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -868,7 +868,7 @@
party_account_currency = d.account_currency
elif frappe.get_cached_value("Account", d.account, "account_type") in ["Bank", "Cash"]:
- bank_amount += d.debit_in_account_currency or d.credit_in_account_currency
+ bank_amount += flt(d.debit_in_account_currency) or flt(d.credit_in_account_currency)
bank_account_currency = d.account_currency
if party_type and pay_to_recd_from:
diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
index 3ba8cea..3132fe9 100644
--- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
+++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
@@ -203,7 +203,8 @@
"fieldtype": "Select",
"label": "Reference Type",
"no_copy": 1,
- "options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees\nFull and Final Statement\nPayment Entry"
+ "options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees\nFull and Final Statement\nPayment Entry",
+ "search_index": 1
},
{
"fieldname": "reference_name",
@@ -211,7 +212,8 @@
"in_list_view": 1,
"label": "Reference Name",
"no_copy": 1,
- "options": "reference_type"
+ "options": "reference_type",
+ "search_index": 1
},
{
"depends_on": "eval:doc.reference_type&&!in_list(doc.reference_type, ['Expense Claim', 'Asset', 'Employee Loan', 'Employee Advance'])",
@@ -278,13 +280,14 @@
"fieldtype": "Data",
"hidden": 1,
"label": "Reference Detail No",
- "no_copy": 1
+ "no_copy": 1,
+ "search_index": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2023-06-16 14:11:13.507807",
+ "modified": "2023-11-23 11:44:25.841187",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Account",
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index b3ae627..2611240 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -9,7 +9,7 @@
frappe.ui.form.on('Payment Entry', {
onload: function(frm) {
- frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger','Repost Accounting Ledger', 'Unreconcile Payments', 'Unreconcile Payment Entries'];
+ frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger','Repost Accounting Ledger', 'Unreconcile Payment', 'Unreconcile Payment Entries'];
if(frm.doc.__islocal) {
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
@@ -160,7 +160,7 @@
}, __('Actions'));
}
- erpnext.accounts.unreconcile_payments.add_unreconcile_btn(frm);
+ erpnext.accounts.unreconcile_payment.add_unreconcile_btn(frm);
},
validate_company: (frm) => {
@@ -853,6 +853,7 @@
var allocated_positive_outstanding = paid_amount + allocated_negative_outstanding;
} else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) {
+ total_negative_outstanding = flt(total_negative_outstanding, precision("outstanding_amount"))
if(paid_amount > total_negative_outstanding) {
if(total_negative_outstanding == 0) {
frappe.msgprint(
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index 4d50a35..aa18156 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -750,8 +750,16 @@
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
- "links": [],
- "modified": "2023-11-08 21:51:03.482709",
+ "links": [
+ {
+ "is_child_table": 1,
+ "link_doctype": "Bank Transaction Payments",
+ "link_fieldname": "payment_entry",
+ "parent_doctype": "Bank Transaction",
+ "table_fieldname": "payment_entries"
+ }
+ ],
+ "modified": "2023-11-23 12:07:20.887885",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index ef304bc..0344e3d 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -148,7 +148,7 @@
"Repost Payment Ledger Items",
"Repost Accounting Ledger",
"Repost Accounting Ledger Items",
- "Unreconcile Payments",
+ "Unreconcile Payment",
"Unreconcile Payment Entries",
)
super(PaymentEntry, self).on_cancel()
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
index b88791d..ccb9e64 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
@@ -212,9 +212,10 @@
],
"hide_toolbar": 1,
"icon": "icon-resize-horizontal",
+ "is_virtual": 1,
"issingle": 1,
"links": [],
- "modified": "2023-08-15 05:35:50.109290",
+ "modified": "2023-11-17 17:33:55.701726",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation",
@@ -239,6 +240,5 @@
],
"sort_field": "modified",
"sort_order": "DESC",
- "states": [],
- "track_changes": 1
+ "states": []
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index 43167be..6673e8d 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -29,6 +29,58 @@
self.accounting_dimension_filter_conditions = []
self.ple_posting_date_filter = []
+ def load_from_db(self):
+ # 'modified' attribute is required for `run_doc_method` to work properly.
+ doc_dict = frappe._dict(
+ {
+ "modified": None,
+ "company": None,
+ "party": None,
+ "party_type": None,
+ "receivable_payable_account": None,
+ "default_advance_account": None,
+ "from_invoice_date": None,
+ "to_invoice_date": None,
+ "invoice_limit": 50,
+ "from_payment_date": None,
+ "to_payment_date": None,
+ "payment_limit": 50,
+ "minimum_invoice_amount": None,
+ "minimum_payment_amount": None,
+ "maximum_invoice_amount": None,
+ "maximum_payment_amount": None,
+ "bank_cash_account": None,
+ "cost_center": None,
+ "payment_name": None,
+ "invoice_name": None,
+ }
+ )
+ super(Document, self).__init__(doc_dict)
+
+ def save(self):
+ return
+
+ @staticmethod
+ def get_list(args):
+ pass
+
+ @staticmethod
+ def get_count(args):
+ pass
+
+ @staticmethod
+ def get_stats(args):
+ pass
+
+ def db_insert(self, *args, **kwargs):
+ pass
+
+ def db_update(self, *args, **kwargs):
+ pass
+
+ def delete(self):
+ pass
+
@frappe.whitelist()
def get_unreconciled_entries(self):
self.get_nonreconciled_payment_entries()
diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
index 71bc498..d7a73f0 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
@@ -1137,6 +1137,40 @@
self.assertEqual(pay.unallocated_amount, 1000)
self.assertEqual(pay.difference_amount, 0)
+ def test_rounding_of_unallocated_amount(self):
+ self.supplier = "_Test Supplier USD"
+ pi = self.create_purchase_invoice(qty=1, rate=10, do_not_submit=True)
+ pi.supplier = self.supplier
+ pi.currency = "USD"
+ pi.conversion_rate = 80
+ pi.credit_to = self.creditors_usd
+ pi.save().submit()
+
+ pe = get_payment_entry(pi.doctype, pi.name)
+ pe.target_exchange_rate = 78.726500000
+ pe.received_amount = 26.75
+ pe.paid_amount = 2105.93
+ pe.references = []
+ pe.save().submit()
+
+ # unallocated_amount will have some rounding loss - 26.749950
+ self.assertNotEqual(pe.unallocated_amount, 26.75)
+
+ pr = frappe.get_doc("Payment Reconciliation")
+ pr.company = self.company
+ pr.party_type = "Supplier"
+ pr.party = self.supplier
+ pr.receivable_payable_account = self.creditors_usd
+ pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
+ pr.get_unreconciled_entries()
+
+ invoices = [invoice.as_dict() for invoice in pr.invoices]
+ payments = [payment.as_dict() for payment in pr.payments]
+ pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+
+ # Should not raise frappe.exceptions.ValidationError: Payment Entry has been modified after you pulled it. Please pull it again.
+ pr.reconcile()
+
def make_customer(customer_name, currency=None):
if not frappe.db.exists("Customer", customer_name):
diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json
index 5b8556e..491c678 100644
--- a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json
+++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json
@@ -159,9 +159,10 @@
"label": "Difference Posting Date"
}
],
+ "is_virtual": 1,
"istable": 1,
"links": [],
- "modified": "2023-10-23 10:44:56.066303",
+ "modified": "2023-11-17 17:33:38.612615",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation Allocation",
diff --git a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json
index c4dbd7e..7c9d49e 100644
--- a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json
+++ b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json
@@ -71,9 +71,10 @@
"label": "Exchange Rate"
}
],
+ "is_virtual": 1,
"istable": 1,
"links": [],
- "modified": "2022-11-08 18:18:02.502149",
+ "modified": "2023-11-17 17:33:45.455166",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation Invoice",
diff --git a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json
index 17f3900..d199236 100644
--- a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json
+++ b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json
@@ -107,9 +107,10 @@
"options": "Cost Center"
}
],
+ "is_virtual": 1,
"istable": 1,
"links": [],
- "modified": "2023-09-03 07:43:29.965353",
+ "modified": "2023-11-17 17:33:34.818530",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation Payment",
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
index f604707..955b66a 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
@@ -18,6 +18,7 @@
"is_pos",
"is_return",
"update_billed_amount_in_sales_order",
+ "update_billed_amount_in_delivery_note",
"column_break1",
"company",
"posting_date",
@@ -1550,12 +1551,19 @@
"fieldtype": "Currency",
"label": "Amount Eligible for Commission",
"read_only": 1
+ },
+ {
+ "default": "1",
+ "depends_on": "eval: doc.is_return && doc.return_against",
+ "fieldname": "update_billed_amount_in_delivery_note",
+ "fieldtype": "Check",
+ "label": "Update Billed Amount in Delivery Note"
}
],
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
- "modified": "2023-06-03 16:23:41.083409",
+ "modified": "2023-11-20 12:27:12.848149",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index e36e97b..9091a77 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -556,7 +556,7 @@
return bin_qty - pos_sales_qty, is_stock_item
else:
is_stock_item = True
- if frappe.db.exists("Product Bundle", item_code):
+ if frappe.db.exists("Product Bundle", {"name": item_code, "disabled": 0}):
return get_bundle_availability(item_code, warehouse), is_stock_item
else:
is_stock_item = False
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 2eaa337..4b0df12 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -180,7 +180,7 @@
}
this.frm.set_df_property("tax_withholding_category", "hidden", doc.apply_tds ? 0 : 1);
- erpnext.accounts.unreconcile_payments.add_unreconcile_btn(me.frm);
+ erpnext.accounts.unreconcile_payment.add_unreconcile_btn(me.frm);
}
unblock_invoice() {
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index e1f0f19..c6ae937 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -13,6 +13,7 @@
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,
+ validate_docs_for_voucher_types,
)
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
check_if_return_invoice_linked_with_payment_entry,
@@ -491,6 +492,7 @@
def validate_for_repost(self):
self.validate_write_off_account()
self.validate_expense_account()
+ validate_docs_for_voucher_types(["Purchase Invoice"])
validate_docs_for_deferred_accounting([], [self.name])
def on_submit(self):
@@ -525,7 +527,11 @@
if self.update_stock == 1:
self.repost_future_sle_and_gle()
- self.update_project()
+ if (
+ frappe.db.get_single_value("Buying Settings", "project_update_frequency") == "Each Transaction"
+ ):
+ self.update_project()
+
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
self.update_advance_tax_references()
@@ -1260,7 +1266,10 @@
if self.update_stock == 1:
self.repost_future_sle_and_gle()
- self.update_project()
+ if (
+ frappe.db.get_single_value("Buying Settings", "project_update_frequency") == "Each Transaction"
+ ):
+ self.update_project()
self.db_set("status", "Cancelled")
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
@@ -1279,13 +1288,21 @@
self.update_advance_tax_references(cancel=1)
def update_project(self):
- project_list = []
+ projects = frappe._dict()
for d in self.items:
- if d.project and d.project not in project_list:
- project = frappe.get_doc("Project", d.project)
- project.update_purchase_costing()
- project.db_update()
- project_list.append(d.project)
+ if d.project:
+ if self.docstatus == 1:
+ projects[d.project] = projects.get(d.project, 0) + d.base_net_amount
+ elif self.docstatus == 2:
+ projects[d.project] = projects.get(d.project, 0) - d.base_net_amount
+
+ pj = frappe.qb.DocType("Project")
+ for proj, value in projects.items():
+ res = (
+ frappe.qb.from_(pj).select(pj.total_purchase_cost).where(pj.name == proj).for_update().run()
+ )
+ current_purchase_cost = res and res[0][0] or 0
+ frappe.db.set_value("Project", proj, "total_purchase_cost", current_purchase_cost + value)
def validate_supplier_invoice(self):
if self.bill_date:
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 bcedb7c..71796c9 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -498,6 +498,7 @@
"fieldtype": "Column Break"
},
{
+ "allow_on_submit": 1,
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
@@ -505,6 +506,7 @@
"print_hide": 1
},
{
+ "allow_on_submit": 1,
"default": ":Company",
"depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "cost_center",
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 69cfe9f..1d72a46 100644
--- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
+++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
@@ -10,12 +10,7 @@
class RepostAccountingLedger(Document):
def __init__(self, *args, **kwargs):
super(RepostAccountingLedger, self).__init__(*args, **kwargs)
- self._allowed_types = [
- x.document_type
- for x in frappe.db.get_all(
- "Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"]
- )
- ]
+ self._allowed_types = get_allowed_types_from_settings()
def validate(self):
self.validate_vouchers()
@@ -56,15 +51,7 @@
def validate_vouchers(self):
if self.vouchers:
- # Validate voucher types
- voucher_types = set([x.voucher_type for x in self.vouchers])
- if disallowed_types := voucher_types.difference(self._allowed_types):
- frappe.throw(
- _("{0} types are not allowed. Only {1} are.").format(
- frappe.bold(comma_and(list(disallowed_types))),
- frappe.bold(comma_and(list(self._allowed_types))),
- )
- )
+ validate_docs_for_voucher_types([x.voucher_type for x in self.vouchers])
def get_existing_ledger_entries(self):
vouchers = [x.voucher_no for x in self.vouchers]
@@ -168,6 +155,15 @@
frappe.db.commit()
+def get_allowed_types_from_settings():
+ return [
+ x.document_type
+ for x in frappe.db.get_all(
+ "Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"]
+ )
+ ]
+
+
def validate_docs_for_deferred_accounting(sales_docs, purchase_docs):
docs_with_deferred_revenue = frappe.db.get_all(
"Sales Invoice Item",
@@ -191,6 +187,25 @@
)
+def validate_docs_for_voucher_types(doc_voucher_types):
+ allowed_types = get_allowed_types_from_settings()
+ # Validate voucher types
+ voucher_types = set(doc_voucher_types)
+ if disallowed_types := voucher_types.difference(allowed_types):
+ message = "are" if len(disallowed_types) > 1 else "is"
+ frappe.throw(
+ _("{0} {1} not allowed to be reposted. Modify {2} to enable reposting.").format(
+ frappe.bold(comma_and(list(disallowed_types))),
+ message,
+ frappe.bold(
+ frappe.utils.get_link_to_form(
+ "Repost Accounting Ledger Settings", "Repost Accounting Ledger Settings"
+ )
+ ),
+ )
+ )
+
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_repost_allowed_types(doctype, txt, searchfield, start, page_len, filters):
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index b1a7b10..6763e44 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -37,7 +37,7 @@
super.onload();
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log',
- 'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payments", "Unreconcile Payment Entries"];
+ 'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"];
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
// show debit_to in print format
@@ -184,7 +184,7 @@
}
}
- erpnext.accounts.unreconcile_payments.add_unreconcile_btn(me.frm);
+ erpnext.accounts.unreconcile_payment.add_unreconcile_btn(me.frm);
}
make_maintenance_schedule() {
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index cd725b9..f209487 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -1615,7 +1615,8 @@
"hide_seconds": 1,
"label": "Inter Company Invoice Reference",
"options": "Purchase Invoice",
- "read_only": 1
+ "read_only": 1,
+ "search_index": 1
},
{
"fieldname": "customer_group",
@@ -2156,7 +2157,7 @@
"label": "Use Company default Cost Center for Round off"
},
{
- "default": "0",
+ "default": "1",
"depends_on": "eval: doc.is_return",
"fieldname": "update_billed_amount_in_delivery_note",
"fieldtype": "Check",
@@ -2173,7 +2174,7 @@
"link_fieldname": "consolidated_invoice"
}
],
- "modified": "2023-11-03 14:39:38.012346",
+ "modified": "2023-11-23 16:56:29.679499",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index fa95ccd..85cb367 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -17,6 +17,7 @@
)
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
validate_docs_for_deferred_accounting,
+ validate_docs_for_voucher_types,
)
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
get_party_tax_withholding_details,
@@ -172,6 +173,7 @@
self.validate_write_off_account()
self.validate_account_for_change_amount()
self.validate_income_account()
+ validate_docs_for_voucher_types(["Sales Invoice"])
validate_docs_for_deferred_accounting([self.name], [])
def validate_fixed_asset(self):
@@ -395,7 +397,7 @@
"Repost Payment Ledger Items",
"Repost Accounting Ledger",
"Repost Accounting Ledger Items",
- "Unreconcile Payments",
+ "Unreconcile Payment",
"Unreconcile Payment Entries",
"Payment Ledger Entry",
"Serial and Batch Bundle",
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 5a41336..017bfa9 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -789,6 +789,28 @@
w = self.make()
self.assertEqual(w.outstanding_amount, w.base_rounded_total)
+ def test_rounded_total_with_cash_discount(self):
+ si = frappe.copy_doc(test_records[2])
+
+ item = copy.deepcopy(si.get("items")[0])
+ item.update(
+ {
+ "qty": 1,
+ "rate": 14960.66,
+ }
+ )
+
+ si.set("items", [item])
+ si.set("taxes", [])
+ si.apply_discount_on = "Grand Total"
+ si.is_cash_or_non_trade_discount = 1
+ si.discount_amount = 1
+ si.insert()
+
+ self.assertEqual(si.grand_total, 14959.66)
+ self.assertEqual(si.rounded_total, 14960)
+ self.assertEqual(si.rounding_adjustment, 0.34)
+
def test_payment(self):
w = self.make()
diff --git a/erpnext/accounts/doctype/unreconcile_payments/__init__.py b/erpnext/accounts/doctype/unreconcile_payment/__init__.py
similarity index 100%
rename from erpnext/accounts/doctype/unreconcile_payments/__init__.py
rename to erpnext/accounts/doctype/unreconcile_payment/__init__.py
diff --git a/erpnext/accounts/doctype/unreconcile_payments/test_unreconcile_payments.py b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py
similarity index 96%
rename from erpnext/accounts/doctype/unreconcile_payments/test_unreconcile_payments.py
rename to erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py
index 78e04bf..f404d99 100644
--- a/erpnext/accounts/doctype/unreconcile_payments/test_unreconcile_payments.py
+++ b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py
@@ -10,7 +10,7 @@
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
-class TestUnreconcilePayments(AccountsTestMixin, FrappeTestCase):
+class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase):
def setUp(self):
self.create_company()
self.create_customer()
@@ -73,7 +73,7 @@
unreconcile = frappe.get_doc(
{
- "doctype": "Unreconcile Payments",
+ "doctype": "Unreconcile Payment",
"company": self.company,
"voucher_type": pe.doctype,
"voucher_no": pe.name,
@@ -138,7 +138,7 @@
unreconcile = frappe.get_doc(
{
- "doctype": "Unreconcile Payments",
+ "doctype": "Unreconcile Payment",
"company": self.company,
"voucher_type": pe2.doctype,
"voucher_no": pe2.name,
@@ -196,7 +196,7 @@
unreconcile = frappe.get_doc(
{
- "doctype": "Unreconcile Payments",
+ "doctype": "Unreconcile Payment",
"company": self.company,
"voucher_type": pe.doctype,
"voucher_no": pe.name,
@@ -281,7 +281,7 @@
unreconcile = frappe.get_doc(
{
- "doctype": "Unreconcile Payments",
+ "doctype": "Unreconcile Payment",
"company": self.company,
"voucher_type": pe2.doctype,
"voucher_no": pe2.name,
diff --git a/erpnext/accounts/doctype/unreconcile_payments/unreconcile_payments.js b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.js
similarity index 93%
rename from erpnext/accounts/doctype/unreconcile_payments/unreconcile_payments.js
rename to erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.js
index c522567..70cefb1 100644
--- a/erpnext/accounts/doctype/unreconcile_payments/unreconcile_payments.js
+++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.js
@@ -1,7 +1,7 @@
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-frappe.ui.form.on("Unreconcile Payments", {
+frappe.ui.form.on("Unreconcile Payment", {
refresh(frm) {
frm.set_query("voucher_type", function() {
return {
diff --git a/erpnext/accounts/doctype/unreconcile_payments/unreconcile_payments.json b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json
similarity index 95%
rename from erpnext/accounts/doctype/unreconcile_payments/unreconcile_payments.json
rename to erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json
index f29e61b..f906dc6 100644
--- a/erpnext/accounts/doctype/unreconcile_payments/unreconcile_payments.json
+++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json
@@ -21,7 +21,7 @@
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
- "options": "Unreconcile Payments",
+ "options": "Unreconcile Payment",
"print_hide": 1,
"read_only": 1
},
@@ -61,7 +61,7 @@
"modified": "2023-08-28 17:42:50.261377",
"modified_by": "Administrator",
"module": "Accounts",
- "name": "Unreconcile Payments",
+ "name": "Unreconcile Payment",
"naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
@@ -90,4 +90,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/doctype/unreconcile_payments/unreconcile_payments.py b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py
similarity index 97%
rename from erpnext/accounts/doctype/unreconcile_payments/unreconcile_payments.py
rename to erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py
index 4f9fb50..77906a7 100644
--- a/erpnext/accounts/doctype/unreconcile_payments/unreconcile_payments.py
+++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py
@@ -15,7 +15,7 @@
)
-class UnreconcilePayments(Document):
+class UnreconcilePayment(Document):
def validate(self):
self.supported_types = ["Payment Entry", "Journal Entry"]
if not self.voucher_type in self.supported_types:
@@ -142,7 +142,7 @@
selections = frappe.json.loads(selections)
# assuming each row is a unique voucher
for row in selections:
- unrecon = frappe.new_doc("Unreconcile Payments")
+ unrecon = frappe.new_doc("Unreconcile Payment")
unrecon.company = row.get("company")
unrecon.voucher_type = row.get("voucher_type")
unrecon.voucher_no = row.get("voucher_no")
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 371474e..5c18e50 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -236,7 +236,9 @@
if shipping_address:
party_details.update(
shipping_address=shipping_address,
- shipping_address_display=render_address(shipping_address),
+ shipping_address_display=render_address(
+ shipping_address, check_permissions=not ignore_permissions
+ ),
**get_fetch_values(doctype, "shipping_address", shipping_address)
)
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 706d743..0e62ad6 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -7,7 +7,7 @@
import frappe
from frappe import _, qb, scrub
from frappe.query_builder import Criterion
-from frappe.query_builder.functions import Date, Sum
+from frappe.query_builder.functions import Date, Substring, Sum
from frappe.utils import cint, cstr, flt, getdate, nowdate
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
@@ -764,7 +764,12 @@
)
if self.filters.get("show_remarks"):
- query = query.select(ple.remarks)
+ if remarks_length := frappe.db.get_single_value(
+ "Accounts Settings", "receivable_payable_remarks_length"
+ ):
+ query = query.select(Substring(ple.remarks, 1, remarks_length).as_("remarks"))
+ else:
+ query = query.select(ple.remarks)
if self.filters.get("group_by_party"):
query = query.orderby(self.ple.party, self.ple.posting_date)
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index 94cd293..fa557a1 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -164,7 +164,12 @@
credit_in_account_currency """
if filters.get("show_remarks"):
- select_fields += """,remarks"""
+ if remarks_length := frappe.db.get_single_value(
+ "Accounts Settings", "general_ledger_remarks_length"
+ ):
+ select_fields += f",substr(remarks, 1, {remarks_length}) as 'remarks'"
+ else:
+ select_fields += """,remarks"""
order_by_statement = "order by posting_date, account, creation"
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 7d91309..9d32a03 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -183,6 +183,7 @@
cost_center=None,
ignore_account_permission=False,
account_type=None,
+ start_date=None,
):
if not account and frappe.form_dict.get("account"):
account = frappe.form_dict.get("account")
@@ -196,6 +197,8 @@
cost_center = frappe.form_dict.get("cost_center")
cond = ["is_cancelled=0"]
+ if start_date:
+ cond.append("posting_date >= %s" % frappe.db.escape(cstr(start_date)))
if date:
cond.append("posting_date <= %s" % frappe.db.escape(cstr(date)))
else:
@@ -1833,6 +1836,28 @@
Table("outstanding").amount_in_account_currency >= self.max_outstanding
)
+ if self.limit and self.get_invoices:
+ outstanding_vouchers = (
+ qb.from_(ple)
+ .select(
+ ple.against_voucher_no.as_("voucher_no"),
+ Sum(ple.amount_in_account_currency).as_("amount_in_account_currency"),
+ )
+ .where(ple.delinked == 0)
+ .where(Criterion.all(filter_on_against_voucher_no))
+ .where(Criterion.all(self.common_filter))
+ .groupby(ple.against_voucher_type, ple.against_voucher_no, ple.party_type, ple.party)
+ .orderby(ple.posting_date, ple.voucher_no)
+ .having(qb.Field("amount_in_account_currency") > 0)
+ .limit(self.limit)
+ .run()
+ )
+ if outstanding_vouchers:
+ filter_on_voucher_no.append(ple.voucher_no.isin([x[0] for x in outstanding_vouchers]))
+ filter_on_against_voucher_no.append(
+ ple.against_voucher_no.isin([x[0] for x in outstanding_vouchers])
+ )
+
# build query for voucher amount
query_voucher_amount = (
qb.from_(ple)
diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json
index d6b9c46..540a4f5 100644
--- a/erpnext/assets/doctype/asset/asset.json
+++ b/erpnext/assets/doctype/asset/asset.json
@@ -481,6 +481,7 @@
"read_only": 1
},
{
+ "default": "1",
"fieldname": "asset_quantity",
"fieldtype": "Int",
"label": "Asset Quantity",
@@ -571,7 +572,7 @@
"link_fieldname": "target_asset"
}
],
- "modified": "2023-11-15 17:40:17.315203",
+ "modified": "2023-11-20 20:57:37.010467",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 3c570d1..7b7953b 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -46,12 +46,28 @@
self.validate_item()
self.validate_cost_center()
self.set_missing_values()
- self.validate_finance_books()
- if not self.split_from:
- self.prepare_depreciation_data()
- update_draft_asset_depr_schedules(self)
self.validate_gross_and_purchase_amount()
self.validate_expected_value_after_useful_life()
+ self.validate_finance_books()
+
+ if not self.split_from:
+ self.prepare_depreciation_data()
+
+ if self.calculate_depreciation:
+ update_draft_asset_depr_schedules(self)
+
+ if frappe.db.exists("Asset", self.name):
+ asset_depr_schedules_names = make_draft_asset_depr_schedules_if_not_present(self)
+
+ if asset_depr_schedules_names:
+ asset_depr_schedules_links = get_comma_separated_links(
+ asset_depr_schedules_names, "Asset Depreciation Schedule"
+ )
+ frappe.msgprint(
+ _(
+ "Asset Depreciation Schedules created:<br>{0}<br><br>Please check, edit if needed, and submit the Asset."
+ ).format(asset_depr_schedules_links)
+ )
self.status = self.get_status()
@@ -61,17 +77,7 @@
if not self.booked_fixed_asset and self.validate_make_gl_entry():
self.make_gl_entries()
if self.calculate_depreciation and not self.split_from:
- asset_depr_schedules_names = make_draft_asset_depr_schedules_if_not_present(self)
convert_draft_asset_depr_schedules_into_active(self)
- if asset_depr_schedules_names:
- asset_depr_schedules_links = get_comma_separated_links(
- asset_depr_schedules_names, "Asset Depreciation Schedule"
- )
- frappe.msgprint(
- _(
- "Asset Depreciation Schedules created:<br>{0}<br><br>Please check, edit if needed, and submit the Asset."
- ).format(asset_depr_schedules_links)
- )
self.set_status()
add_asset_activity(self.name, _("Asset submitted"))
@@ -823,6 +829,7 @@
"expected_value_after_useful_life": flt(gross_purchase_amount)
* flt(d.salvage_value_percentage / 100),
"depreciation_start_date": d.depreciation_start_date or nowdate(),
+ "rate_of_depreciation": d.rate_of_depreciation,
}
)
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index 84a428c..66930c0 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -509,6 +509,9 @@
def depreciate_asset(asset_doc, date, notes):
+ if not asset_doc.calculate_depreciation:
+ return
+
asset_doc.flags.ignore_validate_update_after_submit = True
make_new_active_asset_depr_schedules_and_cancel_current_ones(
@@ -521,6 +524,9 @@
def reset_depreciation_schedule(asset_doc, date, notes):
+ if not asset_doc.calculate_depreciation:
+ return
+
asset_doc.flags.ignore_validate_update_after_submit = True
make_new_active_asset_depr_schedules_and_cancel_current_ones(
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json
index 0599992..0af93bf 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.json
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.json
@@ -17,6 +17,7 @@
"po_required",
"pr_required",
"blanket_order_allowance",
+ "project_update_frequency",
"column_break_12",
"maintain_same_rate",
"set_landed_cost_based_on_purchase_invoice_rate",
@@ -172,6 +173,14 @@
"fieldname": "blanket_order_allowance",
"fieldtype": "Float",
"label": "Blanket Order Allowance (%)"
+ },
+ {
+ "default": "Each Transaction",
+ "description": "How often should Project be updated of Total Purchase Cost ?",
+ "fieldname": "project_update_frequency",
+ "fieldtype": "Select",
+ "label": "Update frequency of Project",
+ "options": "Each Transaction\nManual"
}
],
"icon": "fa fa-cog",
@@ -179,7 +188,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2023-10-25 14:03:32.520418",
+ "modified": "2023-11-24 10:55:51.287327",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",
diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py
index 31bf439..b052f56 100644
--- a/erpnext/buying/doctype/supplier/supplier.py
+++ b/erpnext/buying/doctype/supplier/supplier.py
@@ -165,16 +165,17 @@
@frappe.validate_and_sanitize_search_inputs
def get_supplier_primary_contact(doctype, txt, searchfield, start, page_len, filters):
supplier = filters.get("supplier")
- return frappe.db.sql(
- """
- SELECT
- `tabContact`.name from `tabContact`,
- `tabDynamic Link`
- WHERE
- `tabContact`.name = `tabDynamic Link`.parent
- and `tabDynamic Link`.link_name = %(supplier)s
- and `tabDynamic Link`.link_doctype = 'Supplier'
- and `tabContact`.name like %(txt)s
- """,
- {"supplier": supplier, "txt": "%%%s%%" % txt},
- )
+ contact = frappe.qb.DocType("Contact")
+ dynamic_link = frappe.qb.DocType("Dynamic Link")
+
+ return (
+ frappe.qb.from_(contact)
+ .join(dynamic_link)
+ .on(contact.name == dynamic_link.parent)
+ .select(contact.name, contact.email_id)
+ .where(
+ (dynamic_link.link_name == supplier)
+ & (dynamic_link.link_doctype == "Supplier")
+ & (contact.name.like("%{0}%".format(txt)))
+ )
+ ).run(as_dict=False)
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
index ad1aa2b..1891261 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
@@ -20,6 +20,10 @@
"valid_till",
"quotation_number",
"amended_from",
+ "accounting_dimensions_section",
+ "cost_center",
+ "dimension_col_break",
+ "project",
"currency_and_price_list",
"currency",
"conversion_rate",
@@ -896,6 +900,27 @@
"fieldtype": "Small Text",
"label": "Billing Address Details",
"read_only": 1
+ },
+ {
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "label": "Cost Center",
+ "options": "Cost Center"
+ },
+ {
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "label": "Project",
+ "options": "Project"
+ },
+ {
+ "fieldname": "dimension_col_break",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "accounting_dimensions_section",
+ "fieldtype": "Section Break",
+ "label": "Accounting Dimensions"
}
],
"icon": "fa fa-shopping-cart",
@@ -903,7 +928,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2023-11-03 13:21:40.172508",
+ "modified": "2023-11-17 12:34:30.083077",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation",
diff --git a/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json b/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json
index 4bbcacf..a6229b5 100644
--- a/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json
+++ b/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json
@@ -68,6 +68,8 @@
"column_break_15",
"manufacturer_part_no",
"ad_sec_break",
+ "cost_center",
+ "dimension_col_break",
"project",
"section_break_44",
"page_break"
@@ -133,7 +135,6 @@
"fieldtype": "Column Break"
},
{
- "fetch_from": "item_code.image",
"fieldname": "image",
"fieldtype": "Attach",
"hidden": 1,
@@ -554,13 +555,23 @@
"fieldname": "expected_delivery_date",
"fieldtype": "Date",
"label": "Expected Delivery Date"
+ },
+ {
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "label": "Cost Center",
+ "options": "Cost Center"
+ },
+ {
+ "fieldname": "dimension_col_break",
+ "fieldtype": "Column Break"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2023-11-14 18:35:03.435817",
+ "modified": "2023-11-17 12:25:26.235367",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation Item",
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 38c1e82..d12d50d 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -239,7 +239,7 @@
references_map.setdefault(x.parent, []).append(x.name)
for doc, rows in references_map.items():
- unreconcile_doc = frappe.get_doc("Unreconcile Payments", doc)
+ unreconcile_doc = frappe.get_doc("Unreconcile Payment", doc)
for row in rows:
unreconcile_doc.remove(unreconcile_doc.get("allocations", {"name": row})[0])
@@ -248,9 +248,9 @@
unreconcile_doc.save(ignore_permissions=True)
# delete docs upon parent doc deletion
- unreconcile_docs = frappe.db.get_all("Unreconcile Payments", filters={"voucher_no": self.name})
+ unreconcile_docs = frappe.db.get_all("Unreconcile Payment", filters={"voucher_no": self.name})
for x in unreconcile_docs:
- _doc = frappe.get_doc("Unreconcile Payments", x.name)
+ _doc = frappe.get_doc("Unreconcile Payment", x.name)
if _doc.docstatus == 1:
_doc.cancel()
_doc.delete()
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index a470b47..68ad97d 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -758,7 +758,7 @@
"calculate_depreciation": 0,
"purchase_receipt_amount": purchase_amount,
"gross_purchase_amount": purchase_amount,
- "asset_quantity": row.qty if is_grouped_asset else 0,
+ "asset_quantity": row.qty if is_grouped_asset else 1,
"purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None,
"purchase_invoice": self.name if self.doctype == "Purchase Invoice" else None,
}
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 165e17b..e91212b 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -356,6 +356,7 @@
if doc.doctype == "Sales Invoice" or doc.doctype == "POS Invoice":
doc.consolidated_invoice = ""
doc.set("payments", [])
+ doc.update_billed_amount_in_delivery_note = True
for data in source.payments:
paid_amount = 0.00
base_paid_amount = 0.00
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index d34fbeb..5575a24 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -350,11 +350,12 @@
return il
def has_product_bundle(self, item_code):
- return frappe.db.sql(
- """select name from `tabProduct Bundle`
- where new_item_code=%s and docstatus != 2""",
- item_code,
- )
+ product_bundle = frappe.qb.DocType("Product Bundle")
+ return (
+ frappe.qb.from_(product_bundle)
+ .select(product_bundle.name)
+ .where((product_bundle.new_item_code == item_code) & (product_bundle.disabled == 0))
+ ).run()
def get_already_delivered_qty(self, current_docname, so, so_detail):
delivered_via_dn = frappe.db.sql(
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 96284d6..f9f68a1 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -54,6 +54,7 @@
if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"):
self.doc.grand_total -= self.doc.discount_amount
self.doc.base_grand_total -= self.doc.base_discount_amount
+ self.doc.rounding_adjustment = self.doc.base_rounding_adjustment = 0.0
self.set_rounded_total()
self.calculate_shipping_charges()
diff --git a/erpnext/crm/doctype/competitor/competitor.json b/erpnext/crm/doctype/competitor/competitor.json
index 280441f..fd6da23 100644
--- a/erpnext/crm/doctype/competitor/competitor.json
+++ b/erpnext/crm/doctype/competitor/competitor.json
@@ -29,8 +29,16 @@
}
],
"index_web_pages_for_search": 1,
- "links": [],
- "modified": "2021-10-21 12:43:59.106807",
+ "links": [
+ {
+ "is_child_table": 1,
+ "link_doctype": "Competitor Detail",
+ "link_fieldname": "competitor",
+ "parent_doctype": "Quotation",
+ "table_fieldname": "competitors"
+ }
+ ],
+ "modified": "2023-11-23 19:33:54.284279",
"modified_by": "Administrator",
"module": "CRM",
"name": "Competitor",
@@ -64,5 +72,6 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 5483a10..c6ab6f1 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -421,7 +421,7 @@
"hourly_long": [
"erpnext.accounts.doctype.process_subscription.process_subscription.create_subscription_process",
"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries",
- "erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction",
+ "erpnext.utilities.bulk_transaction.retry",
],
"daily": [
"erpnext.support.doctype.issue.issue.auto_close_tickets",
@@ -539,6 +539,8 @@
"Subcontracting Receipt",
"Subcontracting Receipt Item",
"Account Closing Balance",
+ "Supplier Quotation",
+ "Supplier Quotation Item",
]
get_matching_queries = (
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 0aeadce..a73502d 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -259,6 +259,7 @@
erpnext.patches.v15_0.saudi_depreciation_warning
erpnext.patches.v15_0.delete_saudi_doctypes
erpnext.patches.v14_0.show_loan_management_deprecation_warning
+erpnext.patches.v14_0.clear_reconciliation_values_from_singles
execute:frappe.rename_doc("Report", "TDS Payable Monthly", "Tax Withholding Details", force=True)
[post_model_sync]
@@ -344,11 +345,12 @@
erpnext.patches.v15_0.update_sre_from_voucher_details
erpnext.patches.v14_0.rename_over_order_allowance_field
erpnext.patches.v14_0.migrate_delivery_stop_lock_field
-execute:frappe.db.set_single_value("Payment Reconciliation", "invoice_limit", 50)
-execute:frappe.db.set_single_value("Payment Reconciliation", "payment_limit", 50)
erpnext.patches.v14_0.add_default_for_repost_settings
erpnext.patches.v15_0.rename_daily_depreciation_to_depreciation_amount_based_on_num_days_in_month
erpnext.patches.v15_0.rename_depreciation_amount_based_on_num_days_in_month_to_daily_prorata_based
erpnext.patches.v15_0.set_reserved_stock_in_bin
+erpnext.patches.v14_0.create_accounting_dimensions_in_supplier_quotation
+erpnext.patches.v14_0.update_zero_asset_quantity_field
+execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency", "Each Transaction")
# below migration patch should always run last
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
diff --git a/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py b/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py
index e53bdf8..08ddbbf 100644
--- a/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py
+++ b/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py
@@ -21,6 +21,9 @@
params = set({x.casefold(): x for x in params}.values())
for parameter in params:
+ if frappe.db.exists("Quality Inspection Parameter", parameter):
+ continue
+
frappe.get_doc(
{"doctype": "Quality Inspection Parameter", "parameter": parameter, "description": parameter}
).insert(ignore_permissions=True)
diff --git a/erpnext/patches/v14_0/clear_reconciliation_values_from_singles.py b/erpnext/patches/v14_0/clear_reconciliation_values_from_singles.py
new file mode 100644
index 0000000..c1f5b60
--- /dev/null
+++ b/erpnext/patches/v14_0/clear_reconciliation_values_from_singles.py
@@ -0,0 +1,17 @@
+from frappe import qb
+
+
+def execute():
+ """
+ Clear `tabSingles` and Payment Reconciliation tables of values
+ """
+ singles = qb.DocType("Singles")
+ qb.from_(singles).delete().where(singles.doctype == "Payment Reconciliation").run()
+ doctypes = [
+ "Payment Reconciliation Invoice",
+ "Payment Reconciliation Payment",
+ "Payment Reconciliation Allocation",
+ ]
+ for x in doctypes:
+ dt = qb.DocType(x)
+ qb.from_(dt).delete().run()
diff --git a/erpnext/patches/v14_0/create_accounting_dimensions_in_supplier_quotation.py b/erpnext/patches/v14_0/create_accounting_dimensions_in_supplier_quotation.py
new file mode 100644
index 0000000..6966db1
--- /dev/null
+++ b/erpnext/patches/v14_0/create_accounting_dimensions_in_supplier_quotation.py
@@ -0,0 +1,8 @@
+from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
+ create_accounting_dimensions_for_doctype,
+)
+
+
+def execute():
+ create_accounting_dimensions_for_doctype(doctype="Supplier Quotation")
+ create_accounting_dimensions_for_doctype(doctype="Supplier Quotation Item")
diff --git a/erpnext/patches/v14_0/update_zero_asset_quantity_field.py b/erpnext/patches/v14_0/update_zero_asset_quantity_field.py
new file mode 100644
index 0000000..0480f9b
--- /dev/null
+++ b/erpnext/patches/v14_0/update_zero_asset_quantity_field.py
@@ -0,0 +1,6 @@
+import frappe
+
+
+def execute():
+ asset = frappe.qb.DocType("Asset")
+ frappe.qb.update(asset).set(asset.asset_quantity, 1).where(asset.asset_quantity == 0).run()
diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js
index f366f77..2dac399 100644
--- a/erpnext/projects/doctype/project/project.js
+++ b/erpnext/projects/doctype/project/project.js
@@ -68,6 +68,10 @@
frm.events.create_duplicate(frm);
}, __("Actions"));
+ frm.add_custom_button(__('Update Total Purchase Cost'), () => {
+ frm.events.update_total_purchase_cost(frm);
+ }, __("Actions"));
+
frm.trigger("set_project_status_button");
@@ -92,6 +96,22 @@
},
+ update_total_purchase_cost: function(frm) {
+ frappe.call({
+ method: "erpnext.projects.doctype.project.project.recalculate_project_total_purchase_cost",
+ args: {project: frm.doc.name},
+ freeze: true,
+ freeze_message: __('Recalculating Purchase Cost against this Project...'),
+ callback: function(r) {
+ if (r && !r.exc) {
+ frappe.msgprint(__('Total Purchase Cost has been updated'));
+ frm.refresh();
+ }
+ }
+
+ });
+ },
+
set_project_status_button: function(frm) {
frm.add_custom_button(__('Set Project Status'), () => {
let d = new frappe.ui.Dialog({
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index e9aed1a..4f2e395 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -4,11 +4,11 @@
import frappe
from email_reply_parser import EmailReplyParser
-from frappe import _
+from frappe import _, qb
from frappe.desk.reportview import get_match_cond
from frappe.model.document import Document
from frappe.query_builder import Interval
-from frappe.query_builder.functions import Count, CurDate, Date, UnixTimestamp
+from frappe.query_builder.functions import Count, CurDate, Date, Sum, UnixTimestamp
from frappe.utils import add_days, flt, get_datetime, get_time, get_url, nowtime, today
from frappe.utils.user import is_website_user
@@ -249,12 +249,7 @@
self.per_gross_margin = (self.gross_margin / flt(self.total_billed_amount)) * 100
def update_purchase_costing(self):
- total_purchase_cost = frappe.db.sql(
- """select sum(base_net_amount)
- from `tabPurchase Invoice Item` where project = %s and docstatus=1""",
- self.name,
- )
-
+ total_purchase_cost = calculate_total_purchase_cost(self.name)
self.total_purchase_cost = total_purchase_cost and total_purchase_cost[0][0] or 0
def update_sales_amount(self):
@@ -695,3 +690,29 @@
def get_users_email(doc):
return [d.email for d in doc.users if frappe.db.get_value("User", d.user, "enabled")]
+
+
+def calculate_total_purchase_cost(project: str | None = None):
+ if project:
+ pitem = qb.DocType("Purchase Invoice Item")
+ frappe.qb.DocType("Purchase Invoice Item")
+ total_purchase_cost = (
+ qb.from_(pitem)
+ .select(Sum(pitem.base_net_amount))
+ .where((pitem.project == project) & (pitem.docstatus == 1))
+ .run(as_list=True)
+ )
+ return total_purchase_cost
+ return None
+
+
+@frappe.whitelist()
+def recalculate_project_total_purchase_cost(project: str | None = None):
+ if project:
+ total_purchase_cost = calculate_total_purchase_cost(project)
+ frappe.db.set_value(
+ "Project",
+ project,
+ "total_purchase_cost",
+ (total_purchase_cost and total_purchase_cost[0][0] or 0),
+ )
diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json
index 25a5455..4d2d225 100644
--- a/erpnext/projects/doctype/task/task.json
+++ b/erpnext/projects/doctype/task/task.json
@@ -57,6 +57,7 @@
],
"fields": [
{
+ "allow_in_quick_entry": 1,
"fieldname": "subject",
"fieldtype": "Data",
"in_global_search": 1,
@@ -66,6 +67,7 @@
"search_index": 1
},
{
+ "allow_in_quick_entry": 1,
"bold": 1,
"fieldname": "project",
"fieldtype": "Link",
@@ -396,7 +398,7 @@
"is_tree": 1,
"links": [],
"max_attachments": 5,
- "modified": "2023-09-28 13:52:05.861175",
+ "modified": "2023-11-20 11:42:41.884069",
"modified_by": "Administrator",
"module": "Projects",
"name": "Task",
@@ -416,6 +418,7 @@
"write": 1
}
],
+ "quick_entry": 1,
"search_fields": "subject",
"show_name_in_global_search": 1,
"show_preview_popup": 1,
diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js
index d1d07a7..eb7a97e 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.js
+++ b/erpnext/projects/doctype/timesheet/timesheet.js
@@ -111,6 +111,7 @@
frm.trigger('setup_filters');
frm.trigger('set_dynamic_field_label');
+ frm.trigger('set_route_options_for_new_task');
},
customer: function(frm) {
@@ -172,6 +173,14 @@
frm.refresh_fields();
},
+ set_route_options_for_new_task: (frm) => {
+ let task_field = frm.get_docfield('time_logs', 'task');
+
+ if (task_field) {
+ task_field.get_route_options_for_new_doc = (row) => ({'project': row.doc.project});
+ }
+ },
+
make_invoice: function(frm) {
let fields = [{
"fieldtype": "Link",
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index 11156f4..b9d801c 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -71,6 +71,12 @@
if args.is_billable:
if flt(args.billing_hours) == 0.0:
args.billing_hours = args.hours
+ elif flt(args.billing_hours) > flt(args.hours):
+ frappe.msgprint(
+ _("Warning - Row {0}: Billing Hours are more than Actual Hours").format(args.idx),
+ indicator="orange",
+ alert=True,
+ )
else:
args.billing_hours = 0
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 6b613ce..d24c4e6 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -43,6 +43,9 @@
if (this.frm.doc.apply_discount_on == "Grand Total" && this.frm.doc.is_cash_or_non_trade_discount) {
this.frm.doc.grand_total -= this.frm.doc.discount_amount;
this.frm.doc.base_grand_total -= this.frm.doc.base_discount_amount;
+ this.frm.doc.rounding_adjustment = 0;
+ this.frm.doc.base_rounding_adjustment = 0;
+ this.set_rounded_total();
}
await this.calculate_shipping_charges();
diff --git a/erpnext/public/js/utils/unreconcile.js b/erpnext/public/js/utils/unreconcile.js
index fa00ed2..79490a1 100644
--- a/erpnext/public/js/utils/unreconcile.js
+++ b/erpnext/public/js/utils/unreconcile.js
@@ -1,6 +1,6 @@
frappe.provide('erpnext.accounts');
-erpnext.accounts.unreconcile_payments = {
+erpnext.accounts.unreconcile_payment = {
add_unreconcile_btn(frm) {
if (frm.doc.docstatus == 1) {
if(((frm.doc.doctype == "Journal Entry") && (frm.doc.voucher_type != "Journal Entry"))
@@ -10,7 +10,7 @@
}
frappe.call({
- "method": "erpnext.accounts.doctype.unreconcile_payments.unreconcile_payments.doc_has_references",
+ "method": "erpnext.accounts.doctype.unreconcile_payment.unreconcile_payment.doc_has_references",
"args": {
"doctype": frm.doc.doctype,
"docname": frm.doc.name
@@ -18,7 +18,7 @@
callback: function(r) {
if (r.message) {
frm.add_custom_button(__("UnReconcile"), function() {
- erpnext.accounts.unreconcile_payments.build_unreconcile_dialog(frm);
+ erpnext.accounts.unreconcile_payment.build_unreconcile_dialog(frm);
}, __('Actions'));
}
}
@@ -74,7 +74,7 @@
// get linked payments
frappe.call({
- "method": "erpnext.accounts.doctype.unreconcile_payments.unreconcile_payments.get_linked_payments_for_doc",
+ "method": "erpnext.accounts.doctype.unreconcile_payment.unreconcile_payment.get_linked_payments_for_doc",
"args": {
"company": frm.doc.company,
"doctype": frm.doc.doctype,
@@ -96,8 +96,8 @@
let selected_allocations = values.allocations.filter(x=>x.__checked);
if (selected_allocations.length > 0) {
- let selection_map = erpnext.accounts.unreconcile_payments.build_selection_map(frm, selected_allocations);
- erpnext.accounts.unreconcile_payments.create_unreconcile_docs(selection_map);
+ let selection_map = erpnext.accounts.unreconcile_payment.build_selection_map(frm, selected_allocations);
+ erpnext.accounts.unreconcile_payment.create_unreconcile_docs(selection_map);
d.hide();
} else {
@@ -115,7 +115,7 @@
create_unreconcile_docs(selection_map) {
frappe.call({
- "method": "erpnext.accounts.doctype.unreconcile_payments.unreconcile_payments.create_unreconcile_doc_for_selection",
+ "method": "erpnext.accounts.doctype.unreconcile_payment.unreconcile_payment.create_unreconcile_doc_for_selection",
"args": {
"selections": selection_map
},
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 459fc9f..88ed1c6 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -629,8 +629,12 @@
"is_primary_contact": is_primary_contact,
"links": [{"link_doctype": args.get("doctype"), "link_name": args.get("name")}],
}
- if args.customer_type == "Individual":
- first, middle, last = parse_full_name(args.get("customer_name"))
+
+ party_type = args.customer_type if args.doctype == "Customer" else args.supplier_type
+ party_name_key = "customer_name" if args.doctype == "Customer" else "supplier_name"
+
+ if party_type == "Individual":
+ first, middle, last = parse_full_name(args.get(party_name_key))
values.update(
{
"first_name": first,
@@ -641,9 +645,10 @@
else:
values.update(
{
- "company_name": args.get("customer_name"),
+ "company_name": args.get(party_name_key),
}
)
+
contact = frappe.get_doc(values)
if args.get("email_id"):
@@ -672,10 +677,12 @@
title=_("Missing Values Required"),
)
+ party_name_key = "customer_name" if args.doctype == "Customer" else "supplier_name"
+
address = frappe.get_doc(
{
"doctype": "Address",
- "address_title": args.get("customer_name"),
+ "address_title": args.get(party_name_key),
"address_line1": args.get("address_line1"),
"address_line2": args.get("address_line2"),
"city": args.get("city"),
diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.json b/erpnext/selling/doctype/product_bundle/product_bundle.json
index 56155fb..c4f21b6 100644
--- a/erpnext/selling/doctype/product_bundle/product_bundle.json
+++ b/erpnext/selling/doctype/product_bundle/product_bundle.json
@@ -1,315 +1,119 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 1,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2013-06-20 11:53:21",
- "custom": 0,
- "description": "Aggregate group of **Items** into another **Item**. This is useful if you are bundling a certain **Items** into a package and you maintain stock of the packed **Items** and not the aggregate **Item**. \n\nThe package **Item** will have \"Is Stock Item\" as \"No\" and \"Is Sales Item\" as \"Yes\".\n\nFor Example: If you are selling Laptops and Backpacks separately and have a special price if the customer buys both, then the Laptop + Backpack will be a new Product Bundle Item.\n\nNote: BOM = Bill of Materials",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 0,
+ "actions": [],
+ "allow_import": 1,
+ "creation": "2013-06-20 11:53:21",
+ "description": "Aggregate group of **Items** into another **Item**. This is useful if you are bundling a certain **Items** into a package and you maintain stock of the packed **Items** and not the aggregate **Item**. \n\nThe package **Item** will have \"Is Stock Item\" as \"No\" and \"Is Sales Item\" as \"Yes\".\n\nFor Example: If you are selling Laptops and Backpacks separately and have a special price if the customer buys both, then the Laptop + Backpack will be a new Product Bundle Item.\n\nNote: BOM = Bill of Materials",
+ "doctype": "DocType",
+ "engine": "InnoDB",
+ "field_order": [
+ "basic_section",
+ "new_item_code",
+ "description",
+ "column_break_eonk",
+ "disabled",
+ "item_section",
+ "items",
+ "section_break_4",
+ "about"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "basic_section",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "basic_section",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fieldname": "new_item_code",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Parent Item",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "new_item_code",
- "oldfieldtype": "Data",
- "options": "Item",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "new_item_code",
+ "fieldtype": "Link",
+ "in_global_search": 1,
+ "in_list_view": 1,
+ "label": "Parent Item",
+ "no_copy": 1,
+ "oldfieldname": "new_item_code",
+ "oldfieldtype": "Data",
+ "options": "Item",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "description",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Description",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "description",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Description"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "List items that form the package.",
- "fieldname": "item_section",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Items",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "description": "List items that form the package.",
+ "fieldname": "item_section",
+ "fieldtype": "Section Break",
+ "label": "Items"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "items",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Items",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "sales_bom_items",
- "oldfieldtype": "Table",
- "options": "Product Bundle Item",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "items",
+ "fieldtype": "Table",
+ "label": "Items",
+ "oldfieldname": "sales_bom_items",
+ "oldfieldtype": "Table",
+ "options": "Product Bundle Item",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_4",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "section_break_4",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "about",
- "fieldtype": "HTML",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "",
- "length": 0,
- "no_copy": 0,
- "options": "<h3>About Product Bundle</h3>\n\n<p>Aggregate group of <b>Items</b> into another <b>Item</b>. This is useful if you are bundling a certain <b>Items</b> into a package and you maintain stock of the packed <b>Items</b> and not the aggregate <b>Item</b>.</p>\n<p>The package <b>Item</b> will have <code>Is Stock Item</code> as <b>No</b> and <code>Is Sales Item</code> as <b>Yes</b>.</p>\n<h4>Example:</h4>\n<p>If you are selling Laptops and Backpacks separately and have a special price if the customer buys both, then the Laptop + Backpack will be a new Product Bundle Item.</p>",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "fieldname": "about",
+ "fieldtype": "HTML",
+ "options": "<h3>About Product Bundle</h3>\n\n<p>Aggregate group of <b>Items</b> into another <b>Item</b>. This is useful if you are bundling a certain <b>Items</b> into a package and you maintain stock of the packed <b>Items</b> and not the aggregate <b>Item</b>.</p>\n<p>The package <b>Item</b> will have <code>Is Stock Item</code> as <b>No</b> and <code>Is Sales Item</code> as <b>Yes</b>.</p>\n<h4>Example:</h4>\n<p>If you are selling Laptops and Backpacks separately and have a special price if the customer buys both, then the Laptop + Backpack will be a new Product Bundle Item.</p>"
+ },
+ {
+ "default": "0",
+ "fieldname": "disabled",
+ "fieldtype": "Check",
+ "label": "Disabled"
+ },
+ {
+ "fieldname": "column_break_eonk",
+ "fieldtype": "Column Break"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-sitemap",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2020-09-18 17:26:09.703215",
- "modified_by": "Administrator",
- "module": "Selling",
- "name": "Product Bundle",
- "owner": "Administrator",
+ ],
+ "icon": "fa fa-sitemap",
+ "idx": 1,
+ "links": [],
+ "modified": "2023-11-22 15:20:46.805114",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "Product Bundle",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Stock Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Stock Manager",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Stock User",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
- },
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Stock User"
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Sales User",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_order": "ASC",
- "track_changes": 0,
- "track_seen": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "ASC",
+ "states": []
}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.py b/erpnext/selling/doctype/product_bundle/product_bundle.py
index ac83c0f..2fd9cc1 100644
--- a/erpnext/selling/doctype/product_bundle/product_bundle.py
+++ b/erpnext/selling/doctype/product_bundle/product_bundle.py
@@ -59,10 +59,12 @@
"""Validates, main Item is not a stock item"""
if frappe.db.get_value("Item", self.new_item_code, "is_stock_item"):
frappe.throw(_("Parent Item {0} must not be a Stock Item").format(self.new_item_code))
+ if frappe.db.get_value("Item", self.new_item_code, "is_fixed_asset"):
+ frappe.throw(_("Parent Item {0} must not be a Fixed Asset").format(self.new_item_code))
def validate_child_items(self):
for item in self.items:
- if frappe.db.exists("Product Bundle", item.item_code):
+ if frappe.db.exists("Product Bundle", {"name": item.item_code, "disabled": 0}):
frappe.throw(
_(
"Row #{0}: Child Item should not be a Product Bundle. Please remove Item {1} and Save"
@@ -73,12 +75,17 @@
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_new_item_code(doctype, txt, searchfield, start, page_len, filters):
- from erpnext.controllers.queries import get_match_cond
-
- return frappe.db.sql(
- """select name, item_name, description from tabItem
- where is_stock_item=0 and name not in (select name from `tabProduct Bundle`)
- and %s like %s %s limit %s offset %s"""
- % (searchfield, "%s", get_match_cond(doctype), "%s", "%s"),
- ("%%%s%%" % txt, page_len, start),
- )
+ product_bundles = frappe.db.get_list("Product Bundle", {"disabled": 0}, pluck="name")
+ item = frappe.qb.DocType("Item")
+ return (
+ frappe.qb.from_(item)
+ .select("*")
+ .where(
+ (item.is_stock_item == 0)
+ & (item.is_fixed_asset == 0)
+ & (item.name.notin(product_bundles))
+ & (item[searchfield].like(f"%{txt}%"))
+ )
+ .limit(page_len)
+ .offset(start)
+ ).run()
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index a97198a..a23599b 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -688,7 +688,9 @@
"Sales Order Item": {
"doctype": "Material Request Item",
"field_map": {"name": "sales_order_item", "parent": "sales_order"},
- "condition": lambda item: not frappe.db.exists("Product Bundle", item.item_code)
+ "condition": lambda item: not frappe.db.exists(
+ "Product Bundle", {"name": item.item_code, "disabled": 0}
+ )
and get_remaining_qty(item) > 0,
"postprocess": update_item,
},
@@ -1309,7 +1311,7 @@
def is_product_bundle(item_code):
- return frappe.db.exists("Product Bundle", item_code)
+ return frappe.db.exists("Product Bundle", {"name": item_code, "disabled": 0})
@frappe.whitelist()
@@ -1521,7 +1523,7 @@
product_bundle_parents = [
pb.new_item_code
for pb in frappe.get_all(
- "Product Bundle", {"new_item_code": ["in", item_codes]}, ["new_item_code"]
+ "Product Bundle", {"new_item_code": ["in", item_codes], "disabled": 0}, ["new_item_code"]
)
]
diff --git a/erpnext/accounts/doctype/unreconcile_payments/__init__.py b/erpnext/selling/report/lost_quotations/__init__.py
similarity index 100%
copy from erpnext/accounts/doctype/unreconcile_payments/__init__.py
copy to erpnext/selling/report/lost_quotations/__init__.py
diff --git a/erpnext/selling/report/lost_quotations/lost_quotations.js b/erpnext/selling/report/lost_quotations/lost_quotations.js
new file mode 100644
index 0000000..78e76cb
--- /dev/null
+++ b/erpnext/selling/report/lost_quotations/lost_quotations.js
@@ -0,0 +1,40 @@
+// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.query_reports["Lost Quotations"] = {
+ filters: [
+ {
+ fieldname: "company",
+ label: __("Company"),
+ fieldtype: "Link",
+ options: "Company",
+ default: frappe.defaults.get_user_default("Company"),
+ },
+ {
+ label: "Timespan",
+ fieldtype: "Select",
+ fieldname: "timespan",
+ options: [
+ "Last Week",
+ "Last Month",
+ "Last Quarter",
+ "Last 6 months",
+ "Last Year",
+ "This Week",
+ "This Month",
+ "This Quarter",
+ "This Year",
+ ],
+ default: "This Year",
+ reqd: 1,
+ },
+ {
+ fieldname: "group_by",
+ label: __("Group By"),
+ fieldtype: "Select",
+ options: ["Lost Reason", "Competitor"],
+ default: "Lost Reason",
+ reqd: 1,
+ },
+ ],
+};
diff --git a/erpnext/selling/report/lost_quotations/lost_quotations.json b/erpnext/selling/report/lost_quotations/lost_quotations.json
new file mode 100644
index 0000000..8915bab
--- /dev/null
+++ b/erpnext/selling/report/lost_quotations/lost_quotations.json
@@ -0,0 +1,30 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2023-11-23 18:00:19.141922",
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": null,
+ "letterhead": null,
+ "modified": "2023-11-23 19:27:28.854108",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "Lost Quotations",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Quotation",
+ "report_name": "Lost Quotations",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Sales User"
+ },
+ {
+ "role": "Sales Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/selling/report/lost_quotations/lost_quotations.py b/erpnext/selling/report/lost_quotations/lost_quotations.py
new file mode 100644
index 0000000..7c0bfbd
--- /dev/null
+++ b/erpnext/selling/report/lost_quotations/lost_quotations.py
@@ -0,0 +1,98 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from typing import Literal
+
+import frappe
+from frappe import _
+from frappe.model.docstatus import DocStatus
+from frappe.query_builder.functions import Coalesce, Count, Round, Sum
+from frappe.utils.data import get_timespan_date_range
+
+
+def execute(filters=None):
+ columns = get_columns(filters.get("group_by"))
+ from_date, to_date = get_timespan_date_range(filters.get("timespan").lower())
+ data = get_data(filters.get("company"), from_date, to_date, filters.get("group_by"))
+ return columns, data
+
+
+def get_columns(group_by: Literal["Lost Reason", "Competitor"]):
+ return [
+ {
+ "fieldname": "lost_reason" if group_by == "Lost Reason" else "competitor",
+ "label": _("Lost Reason") if group_by == "Lost Reason" else _("Competitor"),
+ "fieldtype": "Link",
+ "options": "Quotation Lost Reason" if group_by == "Lost Reason" else "Competitor",
+ "width": 200,
+ },
+ {
+ "filedname": "lost_quotations",
+ "label": _("Lost Quotations"),
+ "fieldtype": "Int",
+ "width": 150,
+ },
+ {
+ "filedname": "lost_quotations_pct",
+ "label": _("Lost Quotations %"),
+ "fieldtype": "Percent",
+ "width": 200,
+ },
+ {
+ "fieldname": "lost_value",
+ "label": _("Lost Value"),
+ "fieldtype": "Currency",
+ "width": 150,
+ },
+ {
+ "filedname": "lost_value_pct",
+ "label": _("Lost Value %"),
+ "fieldtype": "Percent",
+ "width": 200,
+ },
+ ]
+
+
+def get_data(
+ company: str, from_date: str, to_date: str, group_by: Literal["Lost Reason", "Competitor"]
+):
+ """Return quotation value grouped by lost reason or competitor"""
+ if group_by == "Lost Reason":
+ fieldname = "lost_reason"
+ dimension = frappe.qb.DocType("Quotation Lost Reason Detail")
+ elif group_by == "Competitor":
+ fieldname = "competitor"
+ dimension = frappe.qb.DocType("Competitor Detail")
+ else:
+ frappe.throw(_("Invalid Group By"))
+
+ q = frappe.qb.DocType("Quotation")
+
+ lost_quotation_condition = (
+ (q.status == "Lost")
+ & (q.docstatus == DocStatus.submitted())
+ & (q.transaction_date >= from_date)
+ & (q.transaction_date <= to_date)
+ & (q.company == company)
+ )
+
+ from_lost_quotations = frappe.qb.from_(q).where(lost_quotation_condition)
+ total_quotations = from_lost_quotations.select(Count(q.name))
+ total_value = from_lost_quotations.select(Sum(q.base_net_total))
+
+ query = (
+ frappe.qb.from_(q)
+ .select(
+ Coalesce(dimension[fieldname], _("Not Specified")),
+ Count(q.name).distinct(),
+ Round((Count(q.name).distinct() / total_quotations * 100), 2),
+ Sum(q.base_net_total),
+ Round((Sum(q.base_net_total) / total_value * 100), 2),
+ )
+ .left_join(dimension)
+ .on(dimension.parent == q.name)
+ .where(lost_quotation_condition)
+ .groupby(dimension[fieldname])
+ )
+
+ return query.run()
diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py
index 4fc20e6..6ed44ff 100644
--- a/erpnext/setup/doctype/email_digest/email_digest.py
+++ b/erpnext/setup/doctype/email_digest/email_digest.py
@@ -382,9 +382,10 @@
"""Get income to date"""
balance = 0.0
count = 0
+ fy_start_date = get_fiscal_year(self.future_to_date)[1]
for account in self.get_root_type_accounts(root_type):
- balance += get_balance_on(account, date=self.future_to_date)
+ balance += get_balance_on(account, date=self.future_to_date, start_date=fy_start_date)
count += get_count_on(account, fieldname, date=self.future_to_date)
if fieldname == "income":
diff --git a/erpnext/setup/doctype/quotation_lost_reason/quotation_lost_reason.json b/erpnext/setup/doctype/quotation_lost_reason/quotation_lost_reason.json
index 5d778ee..0eae08e 100644
--- a/erpnext/setup/doctype/quotation_lost_reason/quotation_lost_reason.json
+++ b/erpnext/setup/doctype/quotation_lost_reason/quotation_lost_reason.json
@@ -1,83 +1,58 @@
{
- "allow_copy": 0,
- "allow_import": 1,
- "allow_rename": 0,
- "autoname": "field:order_lost_reason",
- "beta": 0,
- "creation": "2013-01-10 16:34:24",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 0,
+ "actions": [],
+ "allow_import": 1,
+ "autoname": "field:order_lost_reason",
+ "creation": "2013-01-10 16:34:24",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "engine": "InnoDB",
+ "field_order": [
+ "order_lost_reason"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "order_lost_reason",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Quotation Lost Reason",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "order_lost_reason",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "fieldname": "order_lost_reason",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Quotation Lost Reason",
+ "oldfieldname": "order_lost_reason",
+ "oldfieldtype": "Data",
+ "reqd": 1,
+ "unique": 1
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-flag",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2016-07-25 05:24:25.533953",
- "modified_by": "Administrator",
- "module": "Setup",
- "name": "Quotation Lost Reason",
- "owner": "Administrator",
+ ],
+ "icon": "fa fa-flag",
+ "idx": 1,
+ "links": [
+ {
+ "is_child_table": 1,
+ "link_doctype": "Quotation Lost Reason Detail",
+ "link_fieldname": "lost_reason",
+ "parent_doctype": "Quotation",
+ "table_fieldname": "lost_reasons"
+ }
+ ],
+ "modified": "2023-11-23 19:31:02.743353",
+ "modified_by": "Administrator",
+ "module": "Setup",
+ "name": "Quotation Lost Reason",
+ "naming_rule": "By fieldname",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales Master Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Sales Master Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "track_seen": 0
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index 66dd33a..f240136 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -615,7 +615,7 @@
items_list = [item.item_code for item in self.items]
return frappe.db.get_all(
"Product Bundle",
- filters={"new_item_code": ["in", items_list]},
+ filters={"new_item_code": ["in", items_list], "disabled": 0},
pluck="name",
)
@@ -938,7 +938,7 @@
},
"postprocess": update_item,
"condition": lambda item: (
- not frappe.db.exists("Product Bundle", {"new_item_code": item.item_code})
+ not frappe.db.exists("Product Bundle", {"new_item_code": item.item_code, "disabled": 0})
and flt(item.packed_qty) < flt(item.qty)
),
},
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index c13d3eb..13f3be8 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -504,7 +504,7 @@
"fieldtype": "Table",
"hidden": 1,
"label": "Variant Attributes",
- "mandatory_depends_on": "has_variants",
+ "mandatory_depends_on": "eval:(doc.has_variants || doc.variant_of) && doc.variant_based_on==='Item Attribute'",
"options": "Item Variant Attribute"
},
{
@@ -888,7 +888,7 @@
"index_web_pages_for_search": 1,
"links": [],
"make_attachments_public": 1,
- "modified": "2023-09-11 13:46:32.688051",
+ "modified": "2023-09-18 15:41:32.688051",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index d8935fe..cb34497 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -512,8 +512,12 @@
def validate_duplicate_product_bundles_before_merge(self, old_name, new_name):
"Block merge if both old and new items have product bundles."
- old_bundle = frappe.get_value("Product Bundle", filters={"new_item_code": old_name})
- new_bundle = frappe.get_value("Product Bundle", filters={"new_item_code": new_name})
+ old_bundle = frappe.get_value(
+ "Product Bundle", filters={"new_item_code": old_name, "disabled": 0}
+ )
+ new_bundle = frappe.get_value(
+ "Product Bundle", filters={"new_item_code": new_name, "disabled": 0}
+ )
if old_bundle and new_bundle:
bundle_link = get_link_to_form("Product Bundle", old_bundle)
diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py
index a9e9ad1..35701c9 100644
--- a/erpnext/stock/doctype/packed_item/packed_item.py
+++ b/erpnext/stock/doctype/packed_item/packed_item.py
@@ -55,7 +55,7 @@
def is_product_bundle(item_code: str) -> bool:
- return bool(frappe.db.exists("Product Bundle", {"new_item_code": item_code}))
+ return bool(frappe.db.exists("Product Bundle", {"new_item_code": item_code, "disabled": 0}))
def get_indexed_packed_items_table(doc):
@@ -111,7 +111,7 @@
product_bundle_item.uom,
product_bundle_item.description,
)
- .where(product_bundle.new_item_code == item_code)
+ .where((product_bundle.new_item_code == item_code) & (product_bundle.disabled == 0))
.orderby(product_bundle_item.idx)
)
return query.run(as_dict=True)
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 3350a68..e7f6204 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -368,7 +368,9 @@
frappe.throw("Row #{0}: Item Code is Mandatory".format(item.idx))
if not cint(
frappe.get_cached_value("Item", item.item_code, "is_stock_item")
- ) and not frappe.db.exists("Product Bundle", {"new_item_code": item.item_code}):
+ ) and not frappe.db.exists(
+ "Product Bundle", {"new_item_code": item.item_code, "disabled": 0}
+ ):
continue
item_code = item.item_code
reference = item.sales_order_item or item.material_request_item
@@ -507,7 +509,9 @@
# bundle_item_code: Dict[component, qty]
product_bundle_qty_map = {}
for bundle_item_code in bundles:
- bundle = frappe.get_last_doc("Product Bundle", {"new_item_code": bundle_item_code})
+ bundle = frappe.get_last_doc(
+ "Product Bundle", {"new_item_code": bundle_item_code, "disabled": 0}
+ )
product_bundle_qty_map[bundle_item_code] = {item.item_code: item.qty for item in bundle.items}
return product_bundle_qty_map
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 9bc7e58..a7aa7e2 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -731,12 +731,18 @@
def update_assets(self, item, valuation_rate):
assets = frappe.db.get_all(
- "Asset", filters={"purchase_receipt": self.name, "item_code": item.item_code}
+ "Asset",
+ filters={"purchase_receipt": self.name, "item_code": item.item_code},
+ fields=["name", "asset_quantity"],
)
for asset in assets:
- frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(valuation_rate))
- frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(valuation_rate))
+ frappe.db.set_value(
+ "Asset", asset.name, "gross_purchase_amount", flt(valuation_rate) * asset.asset_quantity
+ )
+ frappe.db.set_value(
+ "Asset", asset.name, "purchase_receipt_amount", flt(valuation_rate) * asset.asset_quantity
+ )
def update_status(self, status):
self.set_status(update=True, status=status)
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 0a4cae7..abdbeb4 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
@@ -246,7 +246,7 @@
valuation_field = "rate"
child_table = "Subcontracting Receipt Supplied Item"
else:
- valuation_field = "rm_supp_cost"
+ valuation_field = "rate"
child_table = "Subcontracting Receipt Item"
precision = frappe.get_precision(child_table, valuation_field) or 2
@@ -406,7 +406,7 @@
if abs(abs(flt(self.total_qty, precision)) - abs(flt(row.get(qty_field), precision))) > 0.01:
self.throw_error_message(
- f"Total quantity {abs(self.total_qty)} in the Serial and Batch Bundle {bold(self.name)} does not match with the quantity {abs(row.get(qty_field))} for the Item {bold(self.item_code)} in the {self.voucher_type} # {self.voucher_no}"
+ f"Total quantity {abs(flt(self.total_qty))} in the Serial and Batch Bundle {bold(self.name)} does not match with the quantity {abs(flt(row.get(qty_field)))} for the Item {bold(self.item_code)} in the {self.voucher_type} # {self.voucher_no}"
)
def set_is_outward(self):
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index c766cab..d1a9cf2 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -149,7 +149,7 @@
def set_valuation_rate(out, args):
- if frappe.db.exists("Product Bundle", args.item_code, cache=True):
+ if frappe.db.exists("Product Bundle", {"name": args.item_code, "disabled": 0}, cache=True):
valuation_rate = 0.0
bundled_items = frappe.get_doc("Product Bundle", args.item_code)
diff --git a/erpnext/stock/report/item_prices/item_prices.py b/erpnext/stock/report/item_prices/item_prices.py
index ab47b4a..a53a9f2 100644
--- a/erpnext/stock/report/item_prices/item_prices.py
+++ b/erpnext/stock/report/item_prices/item_prices.py
@@ -202,7 +202,7 @@
bin_data = (
frappe.qb.from_(bin)
.select(
- bin.item_code, Sum(bin.actual_qty * bin.valuation_rate) / Sum(bin.actual_qty).as_("val_rate")
+ bin.item_code, (Sum(bin.actual_qty * bin.valuation_rate) / Sum(bin.actual_qty)).as_("val_rate")
)
.where(bin.actual_qty > 0)
.groupby(bin.item_code)
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json
index 8be1c1b..383a83b 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json
@@ -11,6 +11,7 @@
"naming_series",
"supplier",
"supplier_name",
+ "supplier_delivery_note",
"column_break1",
"company",
"posting_date",
@@ -634,12 +635,17 @@
"fieldtype": "Button",
"label": "Get Scrap Items",
"options": "get_scrap_items"
+ },
+ {
+ "fieldname": "supplier_delivery_note",
+ "fieldtype": "Data",
+ "label": "Supplier Delivery Note"
}
],
"in_create": 1,
"is_submittable": 1,
"links": [],
- "modified": "2023-08-26 10:52:04.050829",
+ "modified": "2023-11-16 13:04:00.710534",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Receipt",
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
index 6191a8c..f0e4e00 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
@@ -667,6 +667,104 @@
"Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 0
)
+ def test_subcontracting_receipt_valuation_for_fg_with_auto_created_serial_batch_bundle(self):
+ set_backflush_based_on("BOM")
+
+ fg_item = make_item(
+ properties={
+ "is_stock_item": 1,
+ "is_sub_contracted_item": 1,
+ "has_batch_no": 1,
+ "create_new_batch": 1,
+ "batch_number_series": "BSSNGS-.####",
+ }
+ ).name
+
+ rm_item1 = make_item(
+ properties={
+ "is_stock_item": 1,
+ "has_batch_no": 1,
+ "create_new_batch": 1,
+ "batch_number_series": "BNGS-.####",
+ }
+ ).name
+
+ rm_item2 = make_item(
+ properties={
+ "is_stock_item": 1,
+ "has_batch_no": 1,
+ "has_serial_no": 1,
+ "create_new_batch": 1,
+ "batch_number_series": "BNGS-.####",
+ "serial_no_series": "BNSS-.####",
+ }
+ ).name
+
+ rm_item3 = make_item(
+ properties={
+ "is_stock_item": 1,
+ "has_serial_no": 1,
+ "serial_no_series": "BSSSS-.####",
+ }
+ ).name
+
+ bom = make_bom(item=fg_item, raw_materials=[rm_item1, rm_item2, rm_item3])
+
+ rm_batch_no = None
+ for row in bom.items:
+ make_stock_entry(
+ item_code=row.item_code,
+ qty=1,
+ target="_Test Warehouse 1 - _TC",
+ rate=300,
+ )
+
+ service_items = [
+ {
+ "warehouse": "_Test Warehouse - _TC",
+ "item_code": "Subcontracted Service Item 1",
+ "qty": 1,
+ "rate": 100,
+ "fg_item": fg_item,
+ "fg_item_qty": 1,
+ },
+ ]
+ sco = get_subcontracting_order(service_items=service_items)
+
+ frappe.db.set_single_value(
+ "Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 1
+ )
+ scr = make_subcontracting_receipt(sco.name)
+ scr.save()
+ scr.submit()
+ scr.reload()
+
+ for row in scr.supplied_items:
+ self.assertEqual(row.rate, 300.00)
+ self.assertTrue(row.serial_and_batch_bundle)
+ auto_created_serial_batch = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_no": scr.name, "voucher_detail_no": row.name},
+ "auto_created_serial_and_batch_bundle",
+ )
+
+ self.assertTrue(auto_created_serial_batch)
+
+ self.assertEqual(scr.items[0].rm_cost_per_qty, 900)
+ self.assertEqual(scr.items[0].service_cost_per_qty, 100)
+ self.assertEqual(scr.items[0].rate, 1000)
+ valuation_rate = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_no": scr.name, "voucher_detail_no": scr.items[0].name},
+ "valuation_rate",
+ )
+
+ self.assertEqual(flt(valuation_rate), flt(1000))
+
+ frappe.db.set_single_value(
+ "Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 0
+ )
+
def test_subcontracting_receipt_raw_material_rate(self):
# Step - 1: Set Backflush Based On as "BOM"
set_backflush_based_on("BOM")
diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py
index 57c2f9d..df21b61 100644
--- a/erpnext/utilities/bulk_transaction.py
+++ b/erpnext/utilities/bulk_transaction.py
@@ -30,7 +30,10 @@
@frappe.whitelist()
-def retry(date: str | None):
+def retry(date: str | None = None):
+ if not date:
+ date = today()
+
if date:
failed_docs = frappe.db.get_all(
"Bulk Transaction Log Detail",
diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py
index 7eba35d..b083614 100644
--- a/erpnext/utilities/transaction_base.py
+++ b/erpnext/utilities/transaction_base.py
@@ -98,6 +98,7 @@
"Selling Settings", "None", ["maintain_same_rate_action", "role_to_override_stop_action"]
)
+ stop_actions = []
for ref_dt, ref_dn_field, ref_link_field in ref_details:
reference_names = [d.get(ref_link_field) for d in self.get("items") if d.get(ref_link_field)]
reference_details = self.get_reference_details(reference_names, ref_dt + " Item")
@@ -108,7 +109,7 @@
if abs(flt(d.rate - ref_rate, d.precision("rate"))) >= 0.01:
if action == "Stop":
if role_allowed_to_override not in frappe.get_roles():
- frappe.throw(
+ stop_actions.append(
_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4})").format(
d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate
)
@@ -121,6 +122,8 @@
title=_("Warning"),
indicator="orange",
)
+ if stop_actions:
+ frappe.throw(stop_actions, as_list=True)
def get_reference_details(self, reference_names, reference_doctype):
return frappe._dict(