Merge pull request #39525 from rohitwaghchaure/fixed-auto-mr-email-with-user-permissions-issue
fix: email list for auto reorder material request
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json
index 2ec0b7f..56b22a6 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json
@@ -36,16 +36,16 @@
}
},
"Fixed Assets": {
- "Capital Equipments": {
+ "Capital Equipment": {
"account_type": "Fixed Asset"
},
- "Electronic Equipments": {
+ "Electronic Equipment": {
"account_type": "Fixed Asset"
},
- "Furnitures and Fixtures": {
+ "Furniture and Fixtures": {
"account_type": "Fixed Asset"
},
- "Office Equipments": {
+ "Office Equipment": {
"account_type": "Fixed Asset"
},
"Plants and Machineries": {
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py
index e30ad24..0699932 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py
@@ -23,13 +23,13 @@
_("Tax Assets"): {"is_group": 1},
},
_("Fixed Assets"): {
- _("Capital Equipments"): {"account_type": "Fixed Asset"},
- _("Electronic Equipments"): {"account_type": "Fixed Asset"},
- _("Furnitures and Fixtures"): {"account_type": "Fixed Asset"},
- _("Office Equipments"): {"account_type": "Fixed Asset"},
+ _("Capital Equipment"): {"account_type": "Fixed Asset"},
+ _("Electronic Equipment"): {"account_type": "Fixed Asset"},
+ _("Furniture and Fixtures"): {"account_type": "Fixed Asset"},
+ _("Office Equipment"): {"account_type": "Fixed Asset"},
_("Plants and Machineries"): {"account_type": "Fixed Asset"},
_("Buildings"): {"account_type": "Fixed Asset"},
- _("Softwares"): {"account_type": "Fixed Asset"},
+ _("Software"): {"account_type": "Fixed Asset"},
_("Accumulated Depreciation"): {"account_type": "Accumulated Depreciation"},
_("CWIP Account"): {
"account_type": "Capital Work in Progress",
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py b/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py
index 0e46f1e..ee4da73 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py
@@ -36,13 +36,13 @@
"account_number": "1100-1600",
},
_("Fixed Assets"): {
- _("Capital Equipments"): {"account_type": "Fixed Asset", "account_number": "1710"},
- _("Electronic Equipments"): {"account_type": "Fixed Asset", "account_number": "1720"},
- _("Furnitures and Fixtures"): {"account_type": "Fixed Asset", "account_number": "1730"},
- _("Office Equipments"): {"account_type": "Fixed Asset", "account_number": "1740"},
+ _("Capital Equipment"): {"account_type": "Fixed Asset", "account_number": "1710"},
+ _("Electronic Equipment"): {"account_type": "Fixed Asset", "account_number": "1720"},
+ _("Furniture and Fixtures"): {"account_type": "Fixed Asset", "account_number": "1730"},
+ _("Office Equipment"): {"account_type": "Fixed Asset", "account_number": "1740"},
_("Plants and Machineries"): {"account_type": "Fixed Asset", "account_number": "1750"},
_("Buildings"): {"account_type": "Fixed Asset", "account_number": "1760"},
- _("Softwares"): {"account_type": "Fixed Asset", "account_number": "1770"},
+ _("Software"): {"account_type": "Fixed Asset", "account_number": "1770"},
_("Accumulated Depreciation"): {
"account_type": "Accumulated Depreciation",
"account_number": "1780",
diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py
index 30eebef..eb3e00b 100644
--- a/erpnext/accounts/doctype/account/test_account.py
+++ b/erpnext/accounts/doctype/account/test_account.py
@@ -119,7 +119,7 @@
InvalidAccountMergeError,
merge_account,
"Capital Stock - _TC",
- "Softwares - _TC",
+ "Software - _TC",
)
# Raise error as currency doesn't match
diff --git a/erpnext/accounts/doctype/bank_account/bank_account.py b/erpnext/accounts/doctype/bank_account/bank_account.py
index 4b99b19..ace4bb1 100644
--- a/erpnext/accounts/doctype/bank_account/bank_account.py
+++ b/erpnext/accounts/doctype/bank_account/bank_account.py
@@ -55,7 +55,7 @@
def validate_company(self):
if self.is_company_account and not self.company:
- frappe.throw(_("Company is manadatory for company account"))
+ frappe.throw(_("Company is mandatory for company account"))
def validate_iban(self):
"""
diff --git a/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.py b/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.py
index 0af2caf..4326c40 100644
--- a/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.py
+++ b/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.py
@@ -48,11 +48,11 @@
def on_submit(self):
if not self.bank_guarantee_number:
- frappe.throw(_("Enter the Bank Guarantee Number before submittting."))
+ frappe.throw(_("Enter the Bank Guarantee Number before submitting."))
if not self.name_of_beneficiary:
- frappe.throw(_("Enter the name of the Beneficiary before submittting."))
+ frappe.throw(_("Enter the name of the Beneficiary before submitting."))
if not self.bank:
- frappe.throw(_("Enter the name of the bank or lending institution before submittting."))
+ frappe.throw(_("Enter the name of the bank or lending institution before submitting."))
@frappe.whitelist()
diff --git a/erpnext/accounts/doctype/coupon_code/coupon_code.json b/erpnext/accounts/doctype/coupon_code/coupon_code.json
index 7dc5e9d..c6b1477 100644
--- a/erpnext/accounts/doctype/coupon_code/coupon_code.json
+++ b/erpnext/accounts/doctype/coupon_code/coupon_code.json
@@ -80,7 +80,7 @@
{
"fieldname": "valid_upto",
"fieldtype": "Date",
- "label": "Valid Upto"
+ "label": "Valid Up To"
},
{
"depends_on": "eval: doc.coupon_type == \"Promotional\"",
@@ -115,7 +115,7 @@
"read_only": 1
}
],
- "modified": "2019-10-19 14:48:14.602481",
+ "modified": "2024-01-24 02:20:26.145996",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Coupon Code",
diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js
index db4f7c4..c80bf62 100644
--- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js
+++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js
@@ -154,7 +154,7 @@
}
});
},
- primary_action_label: __('Get Invocies')
+ primary_action_label: __('Get Invoices')
});
d.show();
},
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 40d552b..7579da8 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -186,9 +186,12 @@
def update_advance_paid(self):
advance_paid = frappe._dict()
+ advance_payment_doctypes = frappe.get_hooks(
+ "advance_payment_customer_doctypes"
+ ) + frappe.get_hooks("advance_payment_supplier_doctypes")
for d in self.get("accounts"):
if d.is_advance:
- if d.reference_type in frappe.get_hooks("advance_payment_doctypes"):
+ if d.reference_type in advance_payment_doctypes:
advance_paid.setdefault(d.reference_type, []).append(d.reference_name)
for voucher_type, order_list in advance_paid.items():
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
index f5f8f8a..acd9933 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
@@ -270,7 +270,7 @@
errors, "<a href='/app/List/Error Log' class='variant-click'>Error Log</a>"
),
indicator="red",
- title=_("Error Occured"),
+ title=_("Error Occurred"),
)
return names
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 2954d2f..62e2181 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -640,7 +640,7 @@
get_outstanding_invoices_or_orders: function(frm, get_outstanding_invoices, get_orders_to_be_billed) {
const today = frappe.datetime.get_today();
- const fields = [
+ let fields = [
{fieldtype:"Section Break", label: __("Posting Date")},
{fieldtype:"Date", label: __("From Date"),
fieldname:"from_posting_date", default:frappe.datetime.add_days(today, -30)},
@@ -655,18 +655,29 @@
fieldname:"outstanding_amt_greater_than", default: 0},
{fieldtype:"Column Break"},
{fieldtype:"Float", label: __("Less Than Amount"), fieldname:"outstanding_amt_less_than"},
- {fieldtype:"Section Break"},
- {fieldtype:"Link", label:__("Cost Center"), fieldname:"cost_center", options:"Cost Center",
- "get_query": function() {
- return {
- "filters": {"company": frm.doc.company}
- }
+ ];
+
+ if (frm.dimension_filters) {
+ let column_break_insertion_point = Math.ceil((frm.dimension_filters.length)/2);
+
+ fields.push({fieldtype:"Section Break"});
+ frm.dimension_filters.map((elem, idx)=>{
+ fields.push({
+ fieldtype: "Link",
+ label: elem.document_type == "Cost Center" ? "Cost Center" : elem.label,
+ options: elem.document_type,
+ fieldname: elem.fieldname || elem.document_type
+ });
+ if(idx+1 == column_break_insertion_point) {
+ fields.push({fieldtype:"Column Break"});
}
- },
- {fieldtype:"Column Break"},
+ });
+ }
+
+ fields = fields.concat([
{fieldtype:"Section Break"},
{fieldtype:"Check", label: __("Allocate Payment Amount"), fieldname:"allocate_payment_amount", default:1},
- ];
+ ]);
let btn_text = "";
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index dbebbb0..cfe0d9c 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -13,6 +13,7 @@
from pypika.functions import Coalesce, Sum
import erpnext
+from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
from erpnext.accounts.doctype.bank_account.bank_account import (
get_bank_account_details,
get_party_bank_account,
@@ -189,7 +190,7 @@
def set_liability_account(self):
# Auto setting liability account should only be done during 'draft' status
- if self.docstatus > 0:
+ if self.docstatus > 0 or self.payment_type == "Internal Transfer":
return
if not frappe.db.get_value(
@@ -925,7 +926,10 @@
def calculate_base_allocated_amount_for_reference(self, d) -> float:
base_allocated_amount = 0
- if d.reference_doctype in frappe.get_hooks("advance_payment_doctypes"):
+ advance_payment_doctypes = frappe.get_hooks(
+ "advance_payment_customer_doctypes"
+ ) + frappe.get_hooks("advance_payment_supplier_doctypes")
+ if d.reference_doctype in advance_payment_doctypes:
# When referencing Sales/Purchase Order, use the source/target exchange rate depending on payment type.
# This is so there are no Exchange Gain/Loss generated for such doctypes
@@ -1423,8 +1427,11 @@
def update_advance_paid(self):
if self.payment_type in ("Receive", "Pay") and self.party:
+ advance_payment_doctypes = frappe.get_hooks(
+ "advance_payment_customer_doctypes"
+ ) + frappe.get_hooks("advance_payment_supplier_doctypes")
for d in self.get("references"):
- if d.allocated_amount and d.reference_doctype in frappe.get_hooks("advance_payment_doctypes"):
+ if d.allocated_amount and d.reference_doctype in advance_payment_doctypes:
frappe.get_doc(
d.reference_doctype, d.reference_name, for_update=True
).set_total_advance_paid()
@@ -1671,6 +1678,13 @@
condition += " and cost_center='%s'" % args.get("cost_center")
accounting_dimensions_filter.append(ple.cost_center == args.get("cost_center"))
+ # dynamic dimension filters
+ active_dimensions = get_dimensions()[0]
+ for dim in active_dimensions:
+ if args.get(dim.fieldname):
+ condition += " and {0}='{1}'".format(dim.fieldname, args.get(dim.fieldname))
+ accounting_dimensions_filter.append(ple[dim.fieldname] == args.get(dim.fieldname))
+
date_fields_dict = {
"posting_date": ["from_posting_date", "to_posting_date"],
"due_date": ["from_due_date", "to_due_date"],
@@ -1904,6 +1918,12 @@
if doc and hasattr(doc, "cost_center") and doc.cost_center:
condition = " and cost_center='%s'" % cost_center
+ # dynamic dimension filters
+ active_dimensions = get_dimensions()[0]
+ for dim in active_dimensions:
+ if filters.get(dim.fieldname):
+ condition += " and {0}='{1}'".format(dim.fieldname, filters.get(dim.fieldname))
+
if party_account_currency == company_currency:
grand_total_field = "base_grand_total"
rounded_total_field = "base_rounded_total"
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
index fc90c3d..99593de 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
@@ -95,6 +95,8 @@
this.frm.change_custom_button_type(__('Allocate'), null, 'default');
}
+ this.frm.trigger("set_query_for_dimension_filters");
+
// check for any running reconciliation jobs
if (this.frm.doc.receivable_payable_account) {
this.frm.call({
@@ -125,6 +127,25 @@
}
}
+ set_query_for_dimension_filters() {
+ frappe.call({
+ method: "erpnext.accounts.doctype.payment_reconciliation.payment_reconciliation.get_queries_for_dimension_filters",
+ args: {
+ company: this.frm.doc.company,
+ },
+ callback: (r) => {
+ if (!r.exc && r.message) {
+ r.message.forEach(x => {
+ this.frm.set_query(x.fieldname, () => {
+ return {
+ 'filters': x.filters
+ };
+ });
+ });
+ }
+ }
+ });
+ }
company() {
this.frm.set_value('party', '');
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
index ccb9e64..666926f 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
@@ -25,7 +25,9 @@
"invoice_limit",
"payment_limit",
"bank_cash_account",
+ "accounting_dimensions_section",
"cost_center",
+ "dimension_col_break",
"sec_break1",
"invoice_name",
"invoices",
@@ -208,6 +210,18 @@
"fieldname": "payment_name",
"fieldtype": "Data",
"label": "Filter on Payment"
+ },
+ {
+ "collapsible": 1,
+ "collapsible_depends_on": "eval: doc.invoices.length == 0",
+ "depends_on": "eval:doc.receivable_payable_account",
+ "fieldname": "accounting_dimensions_section",
+ "fieldtype": "Section Break",
+ "label": "Accounting Dimensions Filter"
+ },
+ {
+ "fieldname": "dimension_col_break",
+ "fieldtype": "Column Break"
}
],
"hide_toolbar": 1,
@@ -215,7 +229,7 @@
"is_virtual": 1,
"issingle": 1,
"links": [],
- "modified": "2023-11-17 17:33:55.701726",
+ "modified": "2023-12-14 13:38:16.264013",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation",
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index ed0921b..b2716c9 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -10,6 +10,7 @@
from frappe.utils import flt, fmt_money, get_link_to_form, getdate, nowdate, today
import erpnext
+from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
from erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation import (
is_any_doc_running,
)
@@ -70,6 +71,7 @@
self.common_filter_conditions = []
self.accounting_dimension_filter_conditions = []
self.ple_posting_date_filter = []
+ self.dimensions = get_dimensions()[0]
def load_from_db(self):
# 'modified' attribute is required for `run_doc_method` to work properly.
@@ -172,6 +174,14 @@
if self.payment_name:
condition.update({"name": self.payment_name})
+ # pass dynamic dimension filter values to query builder
+ dimensions = {}
+ for x in self.dimensions:
+ dimension = x.fieldname
+ if self.get(dimension):
+ dimensions.update({dimension: self.get(dimension)})
+ condition.update({"accounting_dimensions": dimensions})
+
payment_entries = get_advance_payment_entries_for_regional(
self.party_type,
self.party,
@@ -185,66 +195,67 @@
return payment_entries
def get_jv_entries(self):
- condition = self.get_conditions()
+ je = qb.DocType("Journal Entry")
+ jea = qb.DocType("Journal Entry Account")
+ conditions = self.get_journal_filter_conditions()
+
+ # Dimension filters
+ for x in self.dimensions:
+ dimension = x.fieldname
+ if self.get(dimension):
+ conditions.append(jea[dimension] == self.get(dimension))
if self.payment_name:
- condition += f" and t1.name like '%%{self.payment_name}%%'"
+ conditions.append(je.name.like(f"%%{self.payment_name}%%"))
if self.get("cost_center"):
- condition += f" and t2.cost_center = '{self.cost_center}' "
+ conditions.append(jea.cost_center == self.cost_center)
dr_or_cr = (
"credit_in_account_currency"
if erpnext.get_party_account_type(self.party_type) == "Receivable"
else "debit_in_account_currency"
)
+ conditions.append(jea[dr_or_cr].gt(0))
- bank_account_condition = (
- "t2.against_account like %(bank_cash_account)s" if self.bank_cash_account else "1=1"
+ if self.bank_cash_account:
+ conditions.append(jea.against_account.like(f"%%{self.bank_cash_account}%%"))
+
+ journal_query = (
+ qb.from_(je)
+ .inner_join(jea)
+ .on(jea.parent == je.name)
+ .select(
+ ConstantColumn("Journal Entry").as_("reference_type"),
+ je.name.as_("reference_name"),
+ je.posting_date,
+ je.remark.as_("remarks"),
+ jea.name.as_("reference_row"),
+ jea[dr_or_cr].as_("amount"),
+ jea.is_advance,
+ jea.exchange_rate,
+ jea.account_currency.as_("currency"),
+ jea.cost_center.as_("cost_center"),
+ )
+ .where(
+ (je.docstatus == 1)
+ & (jea.party_type == self.party_type)
+ & (jea.party == self.party)
+ & (jea.account == self.receivable_payable_account)
+ & (
+ (jea.reference_type == "")
+ | (jea.reference_type.isnull())
+ | (jea.reference_type.isin(("Sales Order", "Purchase Order")))
+ )
+ )
+ .where(Criterion.all(conditions))
+ .orderby(je.posting_date)
)
- limit = f"limit {self.payment_limit}" if self.payment_limit else " "
+ if self.payment_limit:
+ journal_query = journal_query.limit(self.payment_limit)
- # nosemgrep
- journal_entries = frappe.db.sql(
- """
- select
- "Journal Entry" as reference_type, t1.name as reference_name,
- t1.posting_date, t1.remark as remarks, t2.name as reference_row,
- {dr_or_cr} as amount, t2.is_advance, t2.exchange_rate,
- t2.account_currency as currency, t2.cost_center as cost_center
- from
- `tabJournal Entry` t1, `tabJournal Entry Account` t2
- where
- t1.name = t2.parent and t1.docstatus = 1 and t2.docstatus = 1
- and t2.party_type = %(party_type)s and t2.party = %(party)s
- and t2.account = %(account)s and {dr_or_cr} > 0 {condition}
- and (t2.reference_type is null or t2.reference_type = '' or
- (t2.reference_type in ('Sales Order', 'Purchase Order')
- and t2.reference_name is not null and t2.reference_name != ''))
- and (CASE
- WHEN t1.voucher_type in ('Debit Note', 'Credit Note')
- THEN 1=1
- ELSE {bank_account_condition}
- END)
- order by t1.posting_date
- {limit}
- """.format(
- **{
- "dr_or_cr": dr_or_cr,
- "bank_account_condition": bank_account_condition,
- "condition": condition,
- "limit": limit,
- }
- ),
- {
- "party_type": self.party_type,
- "party": self.party,
- "account": self.receivable_payable_account,
- "bank_cash_account": "%%%s%%" % self.bank_cash_account,
- },
- as_dict=1,
- )
+ journal_entries = journal_query.run(as_dict=True)
return list(journal_entries)
@@ -298,6 +309,7 @@
min_outstanding=-(self.minimum_payment_amount) if self.minimum_payment_amount else None,
max_outstanding=-(self.maximum_payment_amount) if self.maximum_payment_amount else None,
get_payments=True,
+ accounting_dimensions=self.accounting_dimension_filter_conditions,
)
for inv in return_outstanding:
@@ -447,8 +459,15 @@
row = self.append("allocation", {})
row.update(entry)
+ def update_dimension_values_in_allocated_entries(self, res):
+ for x in self.dimensions:
+ dimension = x.fieldname
+ if self.get(dimension):
+ res[dimension] = self.get(dimension)
+ return res
+
def get_allocated_entry(self, pay, inv, allocated_amount):
- return frappe._dict(
+ res = frappe._dict(
{
"reference_type": pay.get("reference_type"),
"reference_name": pay.get("reference_name"),
@@ -464,6 +483,9 @@
}
)
+ res = self.update_dimension_values_in_allocated_entries(res)
+ return res
+
def reconcile_allocations(self, skip_ref_details_update_for_pe=False):
adjust_allocations_for_taxes(self)
dr_or_cr = (
@@ -486,10 +508,10 @@
reconciled_entry.append(payment_details)
if entry_list:
- reconcile_against_document(entry_list, skip_ref_details_update_for_pe)
+ reconcile_against_document(entry_list, skip_ref_details_update_for_pe, self.dimensions)
if dr_or_cr_notes:
- reconcile_dr_cr_note(dr_or_cr_notes, self.company)
+ reconcile_dr_cr_note(dr_or_cr_notes, self.company, self.dimensions)
@frappe.whitelist()
def reconcile(self):
@@ -518,7 +540,7 @@
self.get_unreconciled_entries()
def get_payment_details(self, row, dr_or_cr):
- return frappe._dict(
+ payment_details = frappe._dict(
{
"voucher_type": row.get("reference_type"),
"voucher_no": row.get("reference_name"),
@@ -541,6 +563,12 @@
}
)
+ for x in self.dimensions:
+ if row.get(x.fieldname):
+ payment_details[x.fieldname] = row.get(x.fieldname)
+
+ return payment_details
+
def check_mandatory_to_fetch(self):
for fieldname in ["company", "party_type", "party", "receivable_payable_account"]:
if not self.get(fieldname):
@@ -648,6 +676,13 @@
if not invoices_to_reconcile:
frappe.throw(_("No records found in Allocation table"))
+ def build_dimensions_filter_conditions(self):
+ ple = qb.DocType("Payment Ledger Entry")
+ for x in self.dimensions:
+ dimension = x.fieldname
+ if self.get(dimension):
+ self.accounting_dimension_filter_conditions.append(ple[dimension] == self.get(dimension))
+
def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=False):
self.common_filter_conditions.clear()
self.accounting_dimension_filter_conditions.clear()
@@ -671,40 +706,30 @@
if self.to_payment_date:
self.ple_posting_date_filter.append(ple.posting_date.lte(self.to_payment_date))
- def get_conditions(self, get_payments=False):
- condition = " and company = '{0}' ".format(self.company)
+ self.build_dimensions_filter_conditions()
- if self.get("cost_center") and get_payments:
- condition = " and cost_center = '{0}' ".format(self.cost_center)
+ def get_journal_filter_conditions(self):
+ conditions = []
+ je = qb.DocType("Journal Entry")
+ jea = qb.DocType("Journal Entry Account")
+ conditions.append(je.company == self.company)
- condition += (
- " and posting_date >= {0}".format(frappe.db.escape(self.from_payment_date))
- if self.from_payment_date
- else ""
- )
- condition += (
- " and posting_date <= {0}".format(frappe.db.escape(self.to_payment_date))
- if self.to_payment_date
- else ""
- )
+ if self.from_payment_date:
+ conditions.append(je.posting_date.gte(self.from_payment_date))
+
+ if self.to_payment_date:
+ conditions.append(je.posting_date.lte(self.to_payment_date))
if self.minimum_payment_amount:
- condition += (
- " and unallocated_amount >= {0}".format(flt(self.minimum_payment_amount))
- if get_payments
- else " and total_debit >= {0}".format(flt(self.minimum_payment_amount))
- )
+ conditions.append(je.total_debit.gte(self.minimum_payment_amount))
+
if self.maximum_payment_amount:
- condition += (
- " and unallocated_amount <= {0}".format(flt(self.maximum_payment_amount))
- if get_payments
- else " and total_debit <= {0}".format(flt(self.maximum_payment_amount))
- )
+ conditions.append(je.total_debit.lte(self.maximum_payment_amount))
- return condition
+ return conditions
-def reconcile_dr_cr_note(dr_cr_notes, company):
+def reconcile_dr_cr_note(dr_cr_notes, company, active_dimensions=None):
for inv in dr_cr_notes:
voucher_type = "Credit Note" if inv.voucher_type == "Sales Invoice" else "Debit Note"
@@ -754,6 +779,15 @@
}
)
+ # Credit Note(JE) will inherit the same dimension values as payment
+ dimensions_dict = frappe._dict()
+ if active_dimensions:
+ for dim in active_dimensions:
+ dimensions_dict[dim.fieldname] = inv.get(dim.fieldname)
+
+ jv.accounts[0].update(dimensions_dict)
+ jv.accounts[1].update(dimensions_dict)
+
jv.flags.ignore_mandatory = True
jv.flags.ignore_exchange_rate = True
jv.remark = None
@@ -787,9 +821,27 @@
inv.against_voucher,
None,
inv.cost_center,
+ dimensions_dict,
)
@erpnext.allow_regional
def adjust_allocations_for_taxes(doc):
pass
+
+
+@frappe.whitelist()
+def get_queries_for_dimension_filters(company: str = None):
+ dimensions_with_filters = []
+ for d in get_dimensions()[0]:
+ filters = {}
+ meta = frappe.get_meta(d.document_type)
+ if meta.has_field("company") and company:
+ filters.update({"company": company})
+
+ if meta.is_tree:
+ filters.update({"is_group": 0})
+
+ dimensions_with_filters.append({"fieldname": d.fieldname, "filters": filters})
+
+ return dimensions_with_filters
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 491c678..3f85b21 100644
--- a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json
+++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json
@@ -24,7 +24,9 @@
"difference_account",
"exchange_rate",
"currency",
- "cost_center"
+ "accounting_dimensions_section",
+ "cost_center",
+ "dimension_col_break"
],
"fields": [
{
@@ -157,12 +159,21 @@
"fieldname": "gain_loss_posting_date",
"fieldtype": "Date",
"label": "Difference Posting Date"
+ },
+ {
+ "fieldname": "accounting_dimensions_section",
+ "fieldtype": "Section Break",
+ "label": "Accounting Dimensions"
+ },
+ {
+ "fieldname": "dimension_col_break",
+ "fieldtype": "Column Break"
}
],
"is_virtual": 1,
"istable": 1,
"links": [],
- "modified": "2023-11-17 17:33:38.612615",
+ "modified": "2023-12-14 13:38:26.104150",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation Allocation",
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 9772b94..839348a 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -169,6 +169,13 @@
elif self.payment_channel == "Phone":
self.request_phone_payment()
+ advance_payment_doctypes = frappe.get_hooks(
+ "advance_payment_customer_doctypes"
+ ) + frappe.get_hooks("advance_payment_supplier_doctypes")
+ if self.reference_doctype in advance_payment_doctypes:
+ # set advance payment status
+ ref_doc.set_total_advance_paid()
+
def request_phone_payment(self):
controller = _get_payment_gateway_controller(self.payment_gateway)
request_amount = self.get_request_amount()
@@ -207,6 +214,14 @@
self.check_if_payment_entry_exists()
self.set_as_cancelled()
+ ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
+ advance_payment_doctypes = frappe.get_hooks(
+ "advance_payment_customer_doctypes"
+ ) + frappe.get_hooks("advance_payment_supplier_doctypes")
+ if self.reference_doctype in advance_payment_doctypes:
+ # set advance payment status
+ ref_doc.set_total_advance_paid()
+
def make_invoice(self):
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
if hasattr(ref_doc, "order_type") and getattr(ref_doc, "order_type") == "Shopping Cart":
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index e542d3c..ca031f0 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -371,7 +371,7 @@
if d.get("qty") > 0:
frappe.throw(
_(
- "Row #{}: You cannot add postive quantities in a return invoice. Please remove item {} to complete the return."
+ "Row #{}: You cannot add positive quantities in a return invoice. Please remove item {} to complete the return."
).format(d.idx, frappe.bold(d.item_code)),
title=_("Invalid Item"),
)
@@ -793,7 +793,7 @@
invoices = json.loads(invoices)
if len(invoices) == 0:
- frappe.throw(_("Atleast one invoice has to be selected."))
+ frappe.throw(_("At least one invoice has to be selected."))
merge_log = frappe.new_doc("POS Invoice Merge Log")
merge_log.posting_date = getdate(nowdate())
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.py b/erpnext/accounts/doctype/pos_profile/pos_profile.py
index 30f3e0c..c1add57 100644
--- a/erpnext/accounts/doctype/pos_profile/pos_profile.py
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile.py
@@ -132,7 +132,7 @@
if len(customer_groups) != len(set(customer_groups)):
frappe.throw(
- _("Duplicate customer group found in the cutomer group table"),
+ _("Duplicate customer group found in the customer group table"),
title=_("Duplicate Customer Group"),
)
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
index e8e8044..61c01a4 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
@@ -339,7 +339,7 @@
{
"fieldname": "valid_upto",
"fieldtype": "Date",
- "label": "Valid Upto"
+ "label": "Valid Up To"
},
{
"fieldname": "col_break1",
@@ -608,7 +608,7 @@
"icon": "fa fa-gift",
"idx": 1,
"links": [],
- "modified": "2023-02-14 04:53:34.887358",
+ "modified": "2024-01-24 02:20:26.145996",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
index ca70490..300692f 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
@@ -193,7 +193,7 @@
def validate_applicable_for_selling_or_buying(self):
if not self.selling and not self.buying:
- throw(_("Atleast one of the Selling or Buying must be selected"))
+ throw(_("At least one of the Selling or Buying must be selected"))
if not self.selling and self.applicable_for in [
"Customer",
diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.json b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.json
index 1d68b23..7fdfdcd 100644
--- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.json
+++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.json
@@ -232,7 +232,7 @@
{
"fieldname": "valid_upto",
"fieldtype": "Date",
- "label": "Valid Upto"
+ "label": "Valid Up To"
},
{
"fieldname": "column_break_26",
@@ -278,7 +278,7 @@
}
],
"links": [],
- "modified": "2021-05-06 16:20:22.039078",
+ "modified": "2024-01-24 02:20:26.145996",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Promotional Scheme",
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 9cf4e4f..26984d9 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -64,6 +64,7 @@
"warehouse",
"from_warehouse",
"quality_inspection",
+ "add_serial_batch_bundle",
"serial_and_batch_bundle",
"serial_no",
"col_br_wh",
@@ -913,12 +914,18 @@
"fieldtype": "Link",
"label": "WIP Composite Asset",
"options": "Asset"
+ },
+ {
+ "depends_on": "eval:parent.update_stock === 1",
+ "fieldname": "add_serial_batch_bundle",
+ "fieldtype": "Button",
+ "label": "Add Serial / Batch No"
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2023-12-25 22:00:28.043555",
+ "modified": "2024-01-21 19:46:25.537861",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/doctype/subscription/subscription.json b/erpnext/accounts/doctype/subscription/subscription.json
index 97fd4d0..afa8bcb 100644
--- a/erpnext/accounts/doctype/subscription/subscription.json
+++ b/erpnext/accounts/doctype/subscription/subscription.json
@@ -51,7 +51,7 @@
"fieldtype": "Select",
"label": "Status",
"no_copy": 1,
- "options": "\nTrialling\nActive\nPast Due Date\nCancelled\nUnpaid\nCompleted",
+ "options": "\nTrialing\nActive\nPast Due Date\nCancelled\nUnpaid\nCompleted",
"read_only": 1
},
{
@@ -267,7 +267,7 @@
"link_fieldname": "subscription"
}
],
- "modified": "2023-12-28 17:20:42.687789",
+ "modified": "2024-01-24 02:20:26.145996",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription",
diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py
index 6d27806..9f19366 100644
--- a/erpnext/accounts/doctype/subscription/subscription.py
+++ b/erpnext/accounts/doctype/subscription/subscription.py
@@ -78,9 +78,7 @@
purchase_tax_template: DF.Link | None
sales_tax_template: DF.Link | None
start_date: DF.Date | None
- status: DF.Literal[
- "", "Trialling", "Active", "Past Due Date", "Cancelled", "Unpaid", "Completed"
- ]
+ status: DF.Literal["", "Trialing", "Active", "Past Due Date", "Cancelled", "Unpaid", "Completed"]
submit_invoice: DF.Check
trial_period_end: DF.Date | None
trial_period_start: DF.Date | None
@@ -233,7 +231,7 @@
Sets the status of the `Subscription`
"""
if self.is_trialling():
- self.status = "Trialling"
+ self.status = "Trialing"
elif (
self.status == "Active" and self.end_date and getdate(posting_date) > getdate(self.end_date)
):
diff --git a/erpnext/accounts/doctype/subscription/subscription_list.js b/erpnext/accounts/doctype/subscription/subscription_list.js
index 6490ff3..ea48b53 100644
--- a/erpnext/accounts/doctype/subscription/subscription_list.js
+++ b/erpnext/accounts/doctype/subscription/subscription_list.js
@@ -1,7 +1,7 @@
frappe.listview_settings['Subscription'] = {
get_indicator: function(doc) {
- if(doc.status === 'Trialling') {
- return [__("Trialling"), "green"];
+ if(doc.status === 'Trialing') {
+ return [__("Trialing"), "green"];
} else if(doc.status === 'Active') {
return [__("Active"), "green"];
} else if(doc.status === 'Completed') {
diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py
index a46642a..89be543 100644
--- a/erpnext/accounts/doctype/subscription/test_subscription.py
+++ b/erpnext/accounts/doctype/subscription/test_subscription.py
@@ -46,7 +46,7 @@
get_date_str(subscription.current_invoice_end),
)
self.assertEqual(subscription.invoices, [])
- self.assertEqual(subscription.status, "Trialling")
+ self.assertEqual(subscription.status, "Trialing")
def test_create_subscription_without_trial_with_correct_period(self):
subscription = create_subscription()
diff --git a/erpnext/accounts/form_tour/accounts_settings/accounts_settings.json b/erpnext/accounts/form_tour/accounts_settings/accounts_settings.json
index e2bf50d..b41012c 100644
--- a/erpnext/accounts/form_tour/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/form_tour/accounts_settings/accounts_settings.json
@@ -4,7 +4,7 @@
"doctype": "Form Tour",
"idx": 0,
"is_standard": 1,
- "modified": "2021-06-29 17:00:26.145996",
+ "modified": "2024-01-24 02:20:26.145996",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
@@ -82,7 +82,7 @@
"label": "Accounts Frozen Till Date",
"parent_field": "",
"position": "Right",
- "title": "Accounts Frozen Upto"
+ "title": "Accounts Frozen Up To"
},
{
"description": "Users with this Role are allowed to set frozen accounts and create/modify accounting entries against frozen accounts.",
diff --git a/erpnext/accounts/report/account_balance/account_balance.js b/erpnext/accounts/report/account_balance/account_balance.js
index 5681be9..ab5dce8 100644
--- a/erpnext/accounts/report/account_balance/account_balance.js
+++ b/erpnext/accounts/report/account_balance/account_balance.js
@@ -39,7 +39,7 @@
{ "value": "Asset Received But Not Billed", "label": __("Asset Received But Not Billed") },
{ "value": "Bank", "label": __("Bank") },
{ "value": "Cash", "label": __("Cash") },
- { "value": "Chargeble", "label": __("Chargeble") },
+ { "value": "Chargeable", "label": __("Chargeable") },
{ "value": "Capital Work in Progress", "label": __("Capital Work in Progress") },
{ "value": "Cost of Goods Sold", "label": __("Cost of Goods Sold") },
{ "value": "Depreciation", "label": __("Depreciation") },
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html
index ed3b991..7d8d33c 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html
@@ -10,10 +10,8 @@
<h2 class="text-center" style="margin-top:0">{%= __(report.report_name) %}</h2>
<h4 class="text-center">
- {% if (filters.customer_name) { %}
- {%= filters.customer_name %}
- {% } else { %}
- {%= filters.customer || filters.supplier %}
+ {% if (filters.party) { %}
+ {%= __(filters.party) %}
{% } %}
</h4>
<h6 class="text-center">
@@ -141,7 +139,7 @@
<th style="width: 24%">{%= __("Reference") %}</th>
{% } %}
{% if(!filters.show_future_payments) { %}
- <th style="width: 20%">{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}</th>
+ <th style="width: 20%">{%= (filters.party) ? __("Remarks"): __("Party") %}</th>
{% } %}
<th style="width: 10%; text-align: right">{%= __("Invoiced Amount") %}</th>
{% if(!filters.show_future_payments) { %}
@@ -158,7 +156,7 @@
<th style="width: 10%">{%= __("Remaining Balance") %}</th>
{% } %}
{% } else { %}
- <th style="width: 40%">{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}</th>
+ <th style="width: 40%">{%= (filters.party) ? __("Remarks"): __("Party") %}</th>
<th style="width: 15%">{%= __("Total Invoiced Amount") %}</th>
<th style="width: 15%">{%= __("Total Paid Amount") %}</th>
<th style="width: 15%">{%= report.report_name === "Accounts Receivable Summary" ? __('Credit Note Amount') : __('Debit Note Amount') %}</th>
@@ -187,7 +185,7 @@
{% if(!filters.show_future_payments) { %}
<td>
- {% if(!(filters.customer || filters.supplier)) { %}
+ {% if(!(filters.party)) { %}
{%= data[i]["party"] %}
{% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %}
<br> {%= data[i]["customer_name"] %}
@@ -260,7 +258,7 @@
{% if(data[i]["party"]|| " ") { %}
{% if(!data[i]["is_total_row"]) { %}
<td>
- {% if(!(filters.customer || filters.supplier)) { %}
+ {% if(!(filters.party)) { %}
{%= data[i]["party"] %}
{% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %}
<br> {%= data[i]["customer_name"] %}
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 19095bc..65b3aba 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -453,7 +453,19 @@
return cc.name
-def reconcile_against_document(args, skip_ref_details_update_for_pe=False): # nosemgrep
+def _build_dimensions_dict_for_exc_gain_loss(
+ entry: dict | object = None, active_dimensions: list = None
+):
+ dimensions_dict = frappe._dict()
+ if entry and active_dimensions:
+ for dim in active_dimensions:
+ dimensions_dict[dim.fieldname] = entry.get(dim.fieldname)
+ return dimensions_dict
+
+
+def reconcile_against_document(
+ args, skip_ref_details_update_for_pe=False, active_dimensions=None
+): # nosemgrep
"""
Cancel PE or JV, Update against document, split if required and resubmit
"""
@@ -482,6 +494,8 @@
check_if_advance_entry_modified(entry)
validate_allocated_amount(entry)
+ dimensions_dict = _build_dimensions_dict_for_exc_gain_loss(entry, active_dimensions)
+
# update ref in advance entry
if voucher_type == "Journal Entry":
referenced_row = update_reference_in_journal_entry(entry, doc, do_not_save=False)
@@ -489,10 +503,14 @@
# amount and account in args
# referenced_row is used to deduplicate gain/loss journal
entry.update({"referenced_row": referenced_row})
- doc.make_exchange_gain_loss_journal([entry])
+ doc.make_exchange_gain_loss_journal([entry], dimensions_dict)
else:
referenced_row = update_reference_in_payment_entry(
- entry, doc, do_not_save=True, skip_ref_details_update_for_pe=skip_ref_details_update_for_pe
+ entry,
+ doc,
+ do_not_save=True,
+ skip_ref_details_update_for_pe=skip_ref_details_update_for_pe,
+ dimensions_dict=dimensions_dict,
)
doc.save(ignore_permissions=True)
@@ -600,7 +618,10 @@
jv_detail = journal_entry.get("accounts", {"name": d["voucher_detail_no"]})[0]
# Update Advance Paid in SO/PO since they might be getting unlinked
- if jv_detail.get("reference_type") in ("Sales Order", "Purchase Order"):
+ advance_payment_doctypes = frappe.get_hooks(
+ "advance_payment_customer_doctypes"
+ ) + frappe.get_hooks("advance_payment_supplier_doctypes")
+ if jv_detail.get("reference_type") in advance_payment_doctypes:
frappe.get_doc(jv_detail.reference_type, jv_detail.reference_name).set_total_advance_paid()
if flt(d["unadjusted_amount"]) - flt(d["allocated_amount"]) != 0:
@@ -654,7 +675,7 @@
def update_reference_in_payment_entry(
- d, payment_entry, do_not_save=False, skip_ref_details_update_for_pe=False
+ d, payment_entry, do_not_save=False, skip_ref_details_update_for_pe=False, dimensions_dict=None
):
reference_details = {
"reference_doctype": d.against_voucher_type,
@@ -667,13 +688,17 @@
else payment_entry.get_exchange_rate(),
"exchange_gain_loss": d.difference_amount,
"account": d.account,
+ "dimensions": d.dimensions,
}
if d.voucher_detail_no:
existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0]
# Update Advance Paid in SO/PO since they are getting unlinked
- if existing_row.get("reference_doctype") in ("Sales Order", "Purchase Order"):
+ advance_payment_doctypes = frappe.get_hooks(
+ "advance_payment_customer_doctypes"
+ ) + frappe.get_hooks("advance_payment_supplier_doctypes")
+ if existing_row.get("reference_doctype") in advance_payment_doctypes:
frappe.get_doc(
existing_row.reference_doctype, existing_row.reference_name
).set_total_advance_paid()
@@ -699,8 +724,9 @@
if not skip_ref_details_update_for_pe:
payment_entry.set_missing_ref_details()
payment_entry.set_amounts()
+
payment_entry.make_exchange_gain_loss_journal(
- frappe._dict({"difference_posting_date": d.difference_posting_date})
+ frappe._dict({"difference_posting_date": d.difference_posting_date}), dimensions_dict
)
if not do_not_save:
@@ -2042,6 +2068,7 @@
ref2_dn,
ref2_detail_no,
cost_center,
+ dimensions,
) -> str:
journal_entry = frappe.new_doc("Journal Entry")
journal_entry.voucher_type = "Exchange Gain Or Loss"
@@ -2075,7 +2102,8 @@
dr_or_cr + "_in_account_currency": 0,
}
)
-
+ if dimensions:
+ journal_account.update(dimensions)
journal_entry.append("accounts", journal_account)
journal_account = frappe._dict(
@@ -2091,7 +2119,8 @@
reverse_dr_or_cr: abs(exc_gain_loss),
}
)
-
+ if dimensions:
+ journal_account.update(dimensions)
journal_entry.append("accounts", journal_account)
journal_entry.save()
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 7357249..cc23d9d 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -1011,7 +1011,7 @@
assets = json.loads(assets)
if len(assets) == 0:
- frappe.throw(_("Atleast one asset has to be selected."))
+ frappe.throw(_("At least one asset has to be selected."))
asset_movement = frappe.new_doc("Asset Movement")
asset_movement.quantity = len(assets)
diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
index 063fe99..780f61f 100644
--- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
+++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
@@ -40,7 +40,7 @@
if getdate(task.next_due_date) < getdate(nowdate()):
task.maintenance_status = "Overdue"
if not task.assign_to and self.docstatus == 0:
- throw(_("Row #{}: Please asign task to a member.").format(task.idx))
+ throw(_("Row #{}: Please assign task to a member.").format(task.idx))
def on_update(self):
for task in self.get("asset_maintenance_tasks"):
diff --git a/erpnext/assets/onboarding_step/asset_category/asset_category.json b/erpnext/assets/onboarding_step/asset_category/asset_category.json
index 58f322e..a1b68ba 100644
--- a/erpnext/assets/onboarding_step/asset_category/asset_category.json
+++ b/erpnext/assets/onboarding_step/asset_category/asset_category.json
@@ -2,14 +2,14 @@
"action": "Show Form Tour",
"action_label": "Let's review existing Asset Category",
"creation": "2021-08-13 14:26:18.656303",
- "description": "# Asset Category\n\nAn Asset Category classifies different assets of a Company.\n\nYou can create an Asset Category based on the type of assets. For example, all your desktops and laptops can be part of an Asset Category named \"Electronic Equipments\". Create a separate category for furniture. Also, you can update default properties for each category, like:\n - Depreciation type and duration\n - Fixed asset account\n - Depreciation account\n",
+ "description": "# Asset Category\n\nAn Asset Category classifies different assets of a Company.\n\nYou can create an Asset Category based on the type of assets. For example, all your desktops and laptops can be part of an Asset Category named \"Electronic Equipment\". Create a separate category for furniture. Also, you can update default properties for each category, like:\n - Depreciation type and duration\n - Fixed asset account\n - Depreciation account\n",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
"is_single": 0,
"is_skipped": 0,
- "modified": "2021-11-23 10:02:03.242127",
+ "modified": "2024-01-24 02:20:26.145996",
"modified_by": "Administrator",
"name": "Asset Category",
"owner": "Administrator",
diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
index 45811a9..e689b05 100644
--- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
+++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
@@ -202,7 +202,7 @@
"values": [flt(d.get("asset_value"), 2) for d in labels_values_map.values()],
},
{
- "name": _("Depreciatied Amount"),
+ "name": _("Depreciated Amount"),
"values": [flt(d.get("depreciated_amount"), 2) for d in labels_values_map.values()],
},
],
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index f74df66..9da49a7 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -134,6 +134,7 @@
"more_info_tab",
"tracking_section",
"status",
+ "advance_payment_status",
"column_break_75",
"per_billed",
"per_received",
@@ -1269,13 +1270,25 @@
"fieldtype": "Tab Break",
"label": "Connections",
"show_dashboard": 1
+ },
+ {
+ "fieldname": "advance_payment_status",
+ "fieldtype": "Select",
+ "hidden": 1,
+ "in_standard_filter": 1,
+ "label": "Advance Payment Status",
+ "no_copy": 1,
+ "oldfieldname": "status",
+ "oldfieldtype": "Select",
+ "options": "Not Initiated\nInitiated\nPartially Paid\nFully Paid",
+ "print_hide": 1
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
- "modified": "2023-10-01 20:58:07.851037",
+ "modified": "2023-10-10 13:37:40.158761",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",
@@ -1330,4 +1343,4 @@
"timeline_field": "supplier",
"title_field": "supplier_name",
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index b830e7d..4efbb27 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -215,6 +215,10 @@
self.validate_fg_item_for_subcontracting()
self.set_received_qty_for_drop_ship_items()
+
+ if not self.advance_payment_status:
+ self.advance_payment_status = "Not Initiated"
+
validate_inter_company_party(
self.doctype, self.supplier, self.company, self.inter_company_order_reference
)
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order_list.js b/erpnext/buying/doctype/purchase_order/purchase_order_list.js
index 6594746..d39d7f9 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order_list.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order_list.js
@@ -1,6 +1,6 @@
frappe.listview_settings['Purchase Order'] = {
add_fields: ["base_grand_total", "company", "currency", "supplier",
- "supplier_name", "per_received", "per_billed", "status"],
+ "supplier_name", "per_received", "per_billed", "status", "advance_payment_status"],
get_indicator: function (doc) {
if (doc.status === "Closed") {
return [__("Closed"), "green", "status,=,Closed"];
@@ -8,6 +8,8 @@
return [__("On Hold"), "orange", "status,=,On Hold"];
} else if (doc.status === "Delivered") {
return [__("Delivered"), "green", "status,=,Closed"];
+ } else if (doc.advance_payment_status == "Initiated") {
+ return [__("To Pay"), "gray", "advance_payment_status,=,Initiated"];
} else if (flt(doc.per_received, 2) < 100 && doc.status !== "Closed") {
if (flt(doc.per_billed, 2) < 100) {
return [__("To Receive and Bill"), "orange",
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 9b382bb..5405799 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -1021,6 +1021,33 @@
self.assertTrue(frappe.db.get_value("Subcontracting Order", {"purchase_order": po.name}))
+ def test_purchase_order_advance_payment_status(self):
+ from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
+ from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
+
+ po = create_purchase_order()
+ self.assertEqual(
+ frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Not Initiated"
+ )
+
+ pr = make_payment_request(dt=po.doctype, dn=po.name, submit_doc=True, return_doc=True)
+ self.assertEqual(frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Initiated")
+
+ pe = get_payment_entry(po.doctype, po.name).save().submit()
+ self.assertEqual(
+ frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Fully Paid"
+ )
+
+ pe.reload()
+ pe.cancel()
+ self.assertEqual(frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Initiated")
+
+ pr.reload()
+ pr.cancel()
+ self.assertEqual(
+ frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Not Initiated"
+ )
+
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js
index 91506c0..3bf4f2b 100644
--- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js
+++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js
@@ -54,7 +54,7 @@
"fieldtype": "MultiSelectList",
"width": "80",
get_data: function(txt) {
- let status = ["To Bill", "To Receive", "To Receive and Bill", "Completed"]
+ let status = ["To Pay", "To Bill", "To Receive", "To Receive and Bill", "Completed"]
let options = []
for (let option of status){
options.push({
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 0c554f2..afbea61 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -7,6 +7,7 @@
import frappe
from frappe import _, bold, qb, throw
from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied
+from frappe.query_builder import Criterion
from frappe.query_builder.custom import ConstantColumn
from frappe.query_builder.functions import Abs, Sum
from frappe.utils import (
@@ -27,6 +28,7 @@
import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
+ get_dimensions,
)
from erpnext.accounts.doctype.pricing_rule.utils import (
apply_pricing_rule_for_free_items,
@@ -1216,7 +1218,9 @@
return True
return False
- def make_exchange_gain_loss_journal(self, args: dict = None) -> None:
+ def make_exchange_gain_loss_journal(
+ self, args: dict = None, dimensions_dict: dict = None
+ ) -> None:
"""
Make Exchange Gain/Loss journal for Invoices and Payments
"""
@@ -1271,6 +1275,7 @@
self.name,
arg.get("referenced_row"),
arg.get("cost_center"),
+ dimensions_dict,
)
frappe.msgprint(
_("Exchange Gain/Loss amount has been booked through {0}").format(
@@ -1351,6 +1356,7 @@
self.name,
d.idx,
self.cost_center,
+ dimensions_dict,
)
frappe.msgprint(
_("Exchange Gain/Loss amount has been booked through {0}").format(
@@ -1415,7 +1421,13 @@
if lst:
from erpnext.accounts.utils import reconcile_against_document
- reconcile_against_document(lst)
+ # pass dimension values to utility method
+ active_dimensions = get_dimensions()[0]
+ for x in lst:
+ for dim in active_dimensions:
+ if self.get(dim.fieldname):
+ x.update({dim.fieldname: self.get(dim.fieldname)})
+ reconcile_against_document(lst, active_dimensions=active_dimensions)
def on_cancel(self):
from erpnext.accounts.doctype.bank_transaction.bank_transaction import (
@@ -1749,7 +1761,10 @@
def set_total_advance_paid(self):
ple = frappe.qb.DocType("Payment Ledger Entry")
- party = self.customer if self.doctype == "Sales Order" else self.supplier
+ if self.doctype in frappe.get_hooks("advance_payment_customer_doctypes"):
+ party = self.customer
+ if self.doctype in frappe.get_hooks("advance_payment_supplier_doctypes"):
+ party = self.supplier
advance = (
frappe.qb.from_(ple)
.select(ple.account_currency, Abs(Sum(ple.amount_in_account_currency)).as_("amount"))
@@ -1763,6 +1778,8 @@
.run(as_dict=True)
)
+ advance_paid, order_total = None, None
+
if advance:
advance = advance[0]
@@ -1791,7 +1808,38 @@
).format(formatted_advance_paid, self.name, formatted_order_total)
)
- frappe.db.set_value(self.doctype, self.name, "advance_paid", advance_paid)
+ self.db_set("advance_paid", advance_paid)
+
+ self.set_advance_payment_status(advance_paid, order_total)
+
+ def set_advance_payment_status(
+ self, advance_paid: float | None = None, order_total: float | None = None
+ ):
+ new_status = None
+ # if money is paid set the paid states
+ if advance_paid:
+ new_status = "Partially Paid" if advance_paid < order_total else "Fully Paid"
+
+ if not new_status:
+ prs = frappe.db.count(
+ "Payment Request",
+ {
+ "reference_doctype": self.doctype,
+ "reference_name": self.name,
+ "docstatus": 1,
+ },
+ )
+ if self.doctype in frappe.get_hooks("advance_payment_customer_doctypes"):
+ new_status = "Requested" if prs else "Not Requested"
+ if self.doctype in frappe.get_hooks("advance_payment_supplier_doctypes"):
+ new_status = "Initiated" if prs else "Not Initiated"
+
+ if new_status == self.advance_payment_status:
+ return
+
+ self.db_set("advance_payment_status", new_status)
+ self.set_status(update=True)
+ self.notify_update()
@property
def company_abbr(self):
@@ -2684,47 +2732,37 @@
q = q.select((payment_entry.target_exchange_rate).as_("exchange_rate"))
if condition:
- if condition.get("name", None):
- q = q.where(payment_entry.name.like(f"%{condition.get('name')}%"))
+ # conditions should be built as an array and passed as Criterion
+ common_filter_conditions = []
- q = q.where(payment_entry.company == condition["company"])
- q = (
- q.where(payment_entry.posting_date >= condition["from_payment_date"])
- if condition.get("from_payment_date")
- else q
- )
- q = (
- q.where(payment_entry.posting_date <= condition["to_payment_date"])
- if condition.get("to_payment_date")
- else q
- )
+ common_filter_conditions.append(payment_entry.company == condition["company"])
+ if condition.get("name", None):
+ common_filter_conditions.append(payment_entry.name.like(f"%{condition.get('name')}%"))
+
+ if condition.get("from_payment_date"):
+ common_filter_conditions.append(payment_entry.posting_date.gte(condition["from_payment_date"]))
+
+ if condition.get("to_payment_date"):
+ common_filter_conditions.append(payment_entry.posting_date.lte(condition["to_payment_date"]))
+
if condition.get("get_payments") == True:
- q = (
- q.where(payment_entry.cost_center == condition["cost_center"])
- if condition.get("cost_center")
- else q
- )
- q = (
- q.where(payment_entry.unallocated_amount >= condition["minimum_payment_amount"])
- if condition.get("minimum_payment_amount")
- else q
- )
- q = (
- q.where(payment_entry.unallocated_amount <= condition["maximum_payment_amount"])
- if condition.get("maximum_payment_amount")
- else q
- )
- else:
- q = (
- q.where(payment_entry.total_debit >= condition["minimum_payment_amount"])
- if condition.get("minimum_payment_amount")
- else q
- )
- q = (
- q.where(payment_entry.total_debit <= condition["maximum_payment_amount"])
- if condition.get("maximum_payment_amount")
- else q
- )
+ if condition.get("cost_center"):
+ common_filter_conditions.append(payment_entry.cost_center == condition["cost_center"])
+
+ if condition.get("accounting_dimensions"):
+ for field, val in condition.get("accounting_dimensions").items():
+ common_filter_conditions.append(payment_entry[field] == val)
+
+ if condition.get("minimum_payment_amount"):
+ common_filter_conditions.append(
+ payment_entry.unallocated_amount.gte(condition["minimum_payment_amount"])
+ )
+
+ if condition.get("maximum_payment_amount"):
+ common_filter_conditions.append(
+ payment_entry.unallocated_amount.lte(condition["maximum_payment_amount"])
+ )
+ q = q.where(Criterion.all(common_filter_conditions))
q = q.orderby(payment_entry.posting_date)
q = q.limit(limit) if limit else q
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 6e50279..800e756 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -141,7 +141,7 @@
items_returned = True
if not items_returned:
- frappe.throw(_("Atleast one item should be entered with negative quantity in return document"))
+ frappe.throw(_("At least one item should be entered with negative quantity in return document"))
def validate_quantity(doc, args, ref, valid_items, already_returned_items):
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index 297f8c2..ac8c88f 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -54,6 +54,10 @@
"eval:self.per_delivered < 100 and self.per_billed >= 100 and self.docstatus == 1 and not self.skip_delivery_note",
],
[
+ "To Pay",
+ "eval:self.advance_payment_status == 'Requested' and self.docstatus == 1",
+ ],
+ [
"Completed",
"eval:(self.per_delivered >= 100 or self.skip_delivery_note) and self.per_billed >= 100 and self.docstatus == 1",
],
@@ -63,16 +67,20 @@
],
"Purchase Order": [
["Draft", None],
- [
- "To Receive and Bill",
- "eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1",
- ],
["To Bill", "eval:self.per_received >= 100 and self.per_billed < 100 and self.docstatus == 1"],
[
"To Receive",
"eval:self.per_received < 100 and self.per_billed == 100 and self.docstatus == 1",
],
[
+ "To Receive and Bill",
+ "eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1",
+ ],
+ [
+ "To Pay",
+ "eval:self.advance_payment_status == 'Initiated' and self.docstatus == 1",
+ ],
+ [
"Completed",
"eval:self.per_received >= 100 and self.per_billed == 100 and self.docstatus == 1",
],
diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py
index 97d3c5c..fad216d 100644
--- a/erpnext/controllers/tests/test_accounts_controller.py
+++ b/erpnext/controllers/tests/test_accounts_controller.py
@@ -56,6 +56,7 @@
20 series - Sales Invoice against Journals
30 series - Sales Invoice against Credit Notes
40 series - Company default Cost center is unset
+ 50 series - Dimension inheritence
"""
def setUp(self):
@@ -1255,3 +1256,214 @@
)
frappe.db.set_value("Company", self.company, "cost_center", cc)
+
+ def setup_dimensions(self):
+ # create dimension
+ from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
+ create_dimension,
+ )
+
+ create_dimension()
+ # make it non-mandatory
+ loc = frappe.get_doc("Accounting Dimension", "Location")
+ for x in loc.dimension_defaults:
+ x.mandatory_for_bs = False
+ x.mandatory_for_pl = False
+ loc.save()
+
+ def test_50_dimensions_filter(self):
+ """
+ Test workings of dimension filters
+ """
+ self.setup_dimensions()
+ rate_in_account_currency = 1
+
+ # Invoices
+ si1 = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True)
+ si1.department = "Management"
+ si1.save().submit()
+
+ si2 = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True)
+ si2.department = "Operations"
+ si2.save().submit()
+
+ # Payments
+ cr_note1 = self.create_sales_invoice(qty=-1, conversion_rate=75, rate=1, do_not_save=True)
+ cr_note1.department = "Management"
+ cr_note1.is_return = 1
+ cr_note1.save().submit()
+
+ cr_note2 = self.create_sales_invoice(qty=-1, conversion_rate=75, rate=1, do_not_save=True)
+ cr_note2.department = "Legal"
+ cr_note2.is_return = 1
+ cr_note2.save().submit()
+
+ pe1 = get_payment_entry(si1.doctype, si1.name)
+ pe1.references = []
+ pe1.department = "Research & Development"
+ pe1.save().submit()
+
+ pe2 = get_payment_entry(si1.doctype, si1.name)
+ pe2.references = []
+ pe2.department = "Management"
+ pe2.save().submit()
+
+ je1 = self.create_journal_entry(
+ acc1=self.debit_usd,
+ acc1_exc_rate=75,
+ acc2=self.cash,
+ acc1_amount=-1,
+ acc2_amount=-75,
+ acc2_exc_rate=1,
+ )
+ je1.accounts[0].party_type = "Customer"
+ je1.accounts[0].party = self.customer
+ je1.accounts[0].department = "Management"
+ je1.save().submit()
+
+ # assert dimension filter's result
+ pr = self.create_payment_reconciliation()
+ pr.get_unreconciled_entries()
+ self.assertEqual(len(pr.invoices), 2)
+ self.assertEqual(len(pr.payments), 5)
+
+ pr.department = "Legal"
+ pr.get_unreconciled_entries()
+ self.assertEqual(len(pr.invoices), 0)
+ self.assertEqual(len(pr.payments), 1)
+
+ pr.department = "Management"
+ pr.get_unreconciled_entries()
+ self.assertEqual(len(pr.invoices), 1)
+ self.assertEqual(len(pr.payments), 3)
+
+ pr.department = "Research & Development"
+ pr.get_unreconciled_entries()
+ self.assertEqual(len(pr.invoices), 0)
+ self.assertEqual(len(pr.payments), 1)
+
+ def test_51_cr_note_should_inherit_dimension(self):
+ self.setup_dimensions()
+ rate_in_account_currency = 1
+
+ # Invoice
+ si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True)
+ si.department = "Management"
+ si.save().submit()
+
+ # Payment
+ cr_note = self.create_sales_invoice(qty=-1, conversion_rate=75, rate=1, do_not_save=True)
+ cr_note.department = "Management"
+ cr_note.is_return = 1
+ cr_note.save().submit()
+
+ pr = self.create_payment_reconciliation()
+ pr.department = "Management"
+ pr.get_unreconciled_entries()
+ self.assertEqual(len(pr.invoices), 1)
+ self.assertEqual(len(pr.payments), 1)
+ invoices = [x.as_dict() for x in pr.invoices]
+ payments = [x.as_dict() for x in pr.payments]
+ pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+ pr.reconcile()
+ self.assertEqual(len(pr.invoices), 0)
+ self.assertEqual(len(pr.payments), 0)
+
+ # There should be 2 journals, JE(Cr Note) and JE(Exchange Gain/Loss)
+ exc_je_for_si = self.get_journals_for(si.doctype, si.name)
+ exc_je_for_cr_note = self.get_journals_for(cr_note.doctype, cr_note.name)
+ self.assertNotEqual(exc_je_for_si, [])
+ self.assertEqual(len(exc_je_for_si), 2)
+ self.assertEqual(len(exc_je_for_cr_note), 2)
+ self.assertEqual(exc_je_for_si, exc_je_for_cr_note)
+
+ for x in exc_je_for_si + exc_je_for_cr_note:
+ with self.subTest(x=x):
+ self.assertEqual(
+ [cr_note.department, cr_note.department],
+ frappe.db.get_all("Journal Entry Account", filters={"parent": x.parent}, pluck="department"),
+ )
+
+ def test_52_dimension_inhertiance_exc_gain_loss(self):
+ # Sales Invoice in Foreign Currency
+ self.setup_dimensions()
+ rate = 80
+ rate_in_account_currency = 1
+ dpt = "Research & Development"
+
+ si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_save=True)
+ si.department = dpt
+ si.save().submit()
+
+ pe = self.create_payment_entry(amount=1, source_exc_rate=82).save()
+ pe.department = dpt
+ pe = pe.save().submit()
+
+ pr = self.create_payment_reconciliation()
+ pr.department = dpt
+ pr.get_unreconciled_entries()
+ self.assertEqual(len(pr.invoices), 1)
+ self.assertEqual(len(pr.payments), 1)
+ invoices = [x.as_dict() for x in pr.invoices]
+ payments = [x.as_dict() for x in pr.payments]
+ pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+ pr.reconcile()
+ self.assertEqual(len(pr.invoices), 0)
+ self.assertEqual(len(pr.payments), 0)
+
+ # Exc Gain/Loss journals should inherit dimension from parent
+ journals = self.get_journals_for(si.doctype, si.name)
+ self.assertEqual(
+ [dpt, dpt],
+ frappe.db.get_all(
+ "Journal Entry Account",
+ filters={"parent": ("in", [x.parent for x in journals])},
+ pluck="department",
+ ),
+ )
+
+ def test_53_dimension_inheritance_on_advance(self):
+ self.setup_dimensions()
+ dpt = "Research & Development"
+
+ adv = self.create_payment_entry(amount=1, source_exc_rate=85)
+ adv.department = dpt
+ adv.save().submit()
+ adv.reload()
+
+ # Sales Invoices in different exchange rates
+ si = self.create_sales_invoice(qty=1, conversion_rate=82, rate=1, do_not_submit=True)
+ si.department = dpt
+ advances = si.get_advance_entries()
+ self.assertEqual(len(advances), 1)
+ self.assertEqual(advances[0].reference_name, adv.name)
+ si.append(
+ "advances",
+ {
+ "doctype": "Sales Invoice Advance",
+ "reference_type": advances[0].reference_type,
+ "reference_name": advances[0].reference_name,
+ "reference_row": advances[0].reference_row,
+ "advance_amount": 1,
+ "allocated_amount": 1,
+ "ref_exchange_rate": advances[0].exchange_rate,
+ "remarks": advances[0].remarks,
+ },
+ )
+ si = si.save().submit()
+
+ # Outstanding in both currencies should be '0'
+ adv.reload()
+ self.assertEqual(si.outstanding_amount, 0)
+ self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0)
+
+ # Exc Gain/Loss journals should inherit dimension from parent
+ journals = self.get_journals_for(si.doctype, si.name)
+ self.assertEqual(
+ [dpt, dpt],
+ frappe.db.get_all(
+ "Journal Entry Account",
+ filters={"parent": ("in", [x.parent for x in journals])},
+ pluck="department",
+ ),
+ )
diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
index ba1fae9..8cba24a 100644
--- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
+++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
@@ -155,7 +155,7 @@
except RecursionError:
self.log(
_(
- "Error occured while parsing Chart of Accounts: Please make sure that no two accounts have the same name"
+ "Error occurred while parsing Chart of Accounts: Please make sure that no two accounts have the same name"
)
)
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 6efb893..f33fff0 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -481,7 +481,8 @@
communication_doctypes = ["Customer", "Supplier"]
-advance_payment_doctypes = ["Sales Order", "Purchase Order"]
+advance_payment_customer_doctypes = ["Sales Order"]
+advance_payment_supplier_doctypes = ["Purchase Order"]
invoice_doctypes = ["Sales Invoice", "Purchase Invoice"]
@@ -538,6 +539,8 @@
"Account Closing Balance",
"Supplier Quotation",
"Supplier Quotation Item",
+ "Payment Reconciliation",
+ "Payment Reconciliation Allocation",
]
get_matching_queries = (
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
index ceb4406..75d890c 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
+++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
@@ -222,7 +222,7 @@
def validate_maintenance_detail(self):
if not self.get("items"):
- throw(_("Please enter Maintaince Details first"))
+ throw(_("Please enter Maintenance Details first"))
for d in self.get("items"):
if not d.item_code:
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index aa5db57..f6e9a07 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -998,12 +998,6 @@
make_job_card(wo_order.name, operations)
job_card = frappe.db.get_value("Job Card", {"work_order": wo_order.name, "docstatus": 0}, "name")
- update_job_card(job_card, 10, 2)
-
- stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
- for row in stock_entry.items:
- if row.is_scrap_item:
- self.assertEqual(row.qty, 2)
def test_close_work_order(self):
items = [
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 0acc2b1..aa7bc5b 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -1520,7 +1520,7 @@
if row.get("qty") > row.get("pending_qty"):
frappe.throw(
- _("For operation {0}: Quantity ({1}) can not be greter than pending quantity({2})").format(
+ _("For operation {0}: Quantity ({1}) can not be greater than pending quantity({2})").format(
frappe.bold(row.get("operation")),
frappe.bold(row.get("qty")),
frappe.bold(row.get("pending_qty")),
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 1110617..4ead7e7 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -352,6 +352,7 @@
execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency", "Each Transaction")
execute:frappe.db.set_default("date_format", frappe.db.get_single_value("System Settings", "date_format"))
erpnext.patches.v14_0.update_total_asset_cost_field
+erpnext.patches.v15_0.create_advance_payment_status
# below migration patch should always run last
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20
diff --git a/erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py b/erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py
index 72c8c07..95b5bc5 100644
--- a/erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py
+++ b/erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py
@@ -188,4 +188,4 @@
raise err
else:
break
- print(f"{processed} records have been sucessfully migrated")
+ print(f"{processed} records have been successfully migrated")
diff --git a/erpnext/patches/v15_0/create_advance_payment_status.py b/erpnext/patches/v15_0/create_advance_payment_status.py
new file mode 100644
index 0000000..18ab9fa
--- /dev/null
+++ b/erpnext/patches/v15_0/create_advance_payment_status.py
@@ -0,0 +1,54 @@
+import frappe
+
+
+def execute():
+ """
+ Description:
+ Calculate the new Advance Payment Statuse column in SO & PO
+ """
+
+ if frappe.reload_doc("selling", "doctype", "Sales Order"):
+ so = frappe.qb.DocType("Sales Order")
+ frappe.qb.update(so).set(so.advance_payment_status, "Not Requested").where(
+ so.docstatus == 1
+ ).where(so.advance_paid == 0.0).run()
+
+ frappe.qb.update(so).set(so.advance_payment_status, "Partially Paid").where(
+ so.docstatus == 1
+ ).where(so.advance_payment_status.isnull()).where(
+ so.advance_paid < (so.rounded_total or so.grand_total)
+ ).run()
+
+ frappe.qb.update(so).set(so.advance_payment_status, "Fully Paid").where(so.docstatus == 1).where(
+ so.advance_payment_status.isnull()
+ ).where(so.advance_paid == (so.rounded_total or so.grand_total)).run()
+
+ pr = frappe.qb.DocType("Payment Request")
+ frappe.qb.update(so).join(pr).on(so.name == pr.reference_name).set(
+ so.advance_payment_status, "Requested"
+ ).where(so.docstatus == 1).where(pr.docstatus == 1).where(
+ so.advance_payment_status == "Not Requested"
+ ).run()
+
+ if frappe.reload_doc("buying", "doctype", "Purchase Order"):
+ po = frappe.qb.DocType("Purchase Order")
+ frappe.qb.update(po).set(po.advance_payment_status, "Not Initiated").where(
+ po.docstatus == 1
+ ).where(po.advance_paid == 0.0).run()
+
+ frappe.qb.update(po).set(po.advance_payment_status, "Partially Paid").where(
+ po.docstatus == 1
+ ).where(po.advance_payment_status.isnull()).where(
+ po.advance_paid < (po.rounded_total or po.grand_total)
+ ).run()
+
+ frappe.qb.update(po).set(po.advance_payment_status, "Fully Paid").where(po.docstatus == 1).where(
+ po.advance_payment_status.isnull()
+ ).where(po.advance_paid == (po.rounded_total or po.grand_total)).run()
+
+ pr = frappe.qb.DocType("Payment Request")
+ frappe.qb.update(po).join(pr).on(po.name == pr.reference_name).set(
+ po.advance_payment_status, "Initiated"
+ ).where(po.docstatus == 1).where(pr.docstatus == 1).where(
+ po.advance_payment_status == "Not Initiated"
+ ).run()
diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js
index cf7fab8..aacab0f 100644
--- a/erpnext/public/js/utils/barcode_scanner.js
+++ b/erpnext/public/js/utils/barcode_scanner.js
@@ -105,32 +105,47 @@
this.frm.has_items = false;
}
- if (serial_no && this.is_duplicate_serial_no(row, item_code, serial_no)) {
- this.clean_up();
- reject();
- return;
+ if (serial_no) {
+ this.is_duplicate_serial_no(row, item_code, serial_no)
+ .then((is_duplicate) => {
+ if (!is_duplicate) {
+ this.run_serially_tasks(row, data, resolve);
+ } else {
+ this.clean_up();
+ reject();
+ return;
+ }
+ });
+ } else {
+ this.run_serially_tasks(row, data, resolve);
}
- frappe.run_serially([
- () => this.set_serial_and_batch(row, item_code, serial_no, batch_no),
- () => this.set_barcode(row, barcode),
- () => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => {
- this.show_scan_message(row.idx, row.item_code, qty);
- }),
- () => this.set_barcode_uom(row, uom),
- () => this.clean_up(),
- () => resolve(row),
- () => {
- if (row.serial_and_batch_bundle && !this.frm.is_new()) {
- this.frm.save();
- }
- frappe.flags.trigger_from_barcode_scanner = false;
- }
- ]);
});
}
+ run_serially_tasks(row, data, resolve) {
+ const {item_code, barcode, batch_no, serial_no, uom} = data;
+
+ frappe.run_serially([
+ () => this.set_serial_and_batch(row, item_code, serial_no, batch_no),
+ () => this.set_barcode(row, barcode),
+ () => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => {
+ this.show_scan_message(row.idx, row.item_code, qty);
+ }),
+ () => this.set_barcode_uom(row, uom),
+ () => this.clean_up(),
+ () => {
+ if (row.serial_and_batch_bundle && !this.frm.is_new()) {
+ this.frm.save();
+ }
+
+ frappe.flags.trigger_from_barcode_scanner = false;
+ },
+ () => resolve(row),
+ ]);
+ }
+
set_item(row, item_code, barcode, batch_no, serial_no) {
return new Promise(resolve => {
const increment = async (value = 1) => {
@@ -475,26 +490,32 @@
}
}
- is_duplicate_serial_no(row, item_code, serial_no) {
- if (this.frm.is_new() || !row.serial_and_batch_bundle) {
- let is_duplicate = this.check_duplicate_serial_no_in_localstorage(item_code, serial_no);
- if (is_duplicate) {
- this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
- }
-
- return is_duplicate;
- } else if (row.serial_and_batch_bundle) {
- this.check_duplicate_serial_no_in_db(row, serial_no, (r) => {
- if (r.message) {
+ async is_duplicate_serial_no(row, item_code, serial_no) {
+ let is_duplicate = false;
+ const promise = new Promise((resolve, reject) => {
+ if (this.frm.is_new() || !row.serial_and_batch_bundle) {
+ is_duplicate = this.check_duplicate_serial_no_in_localstorage(item_code, serial_no);
+ if (is_duplicate) {
this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
}
- return r.message;
- })
- }
+ resolve(is_duplicate);
+ } else if (row.serial_and_batch_bundle) {
+ this.check_duplicate_serial_no_in_db(row, serial_no, (r) => {
+ if (r.message) {
+ this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
+ }
+
+ is_duplicate = r.message;
+ resolve(is_duplicate);
+ })
+ }
+ });
+
+ return await promise;
}
- async check_duplicate_serial_no_in_db(row, serial_no, response) {
+ check_duplicate_serial_no_in_db(row, serial_no, response) {
frappe.call({
method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.is_duplicate_serial_no",
args: {
@@ -504,7 +525,7 @@
callback(r) {
response(r);
}
- })
+ });
}
check_duplicate_serial_no_in_localstorage(item_code, serial_no) {
diff --git a/erpnext/public/js/utils/dimension_tree_filter.js b/erpnext/public/js/utils/dimension_tree_filter.js
index 3f70c09..27d00ba 100644
--- a/erpnext/public/js/utils/dimension_tree_filter.js
+++ b/erpnext/public/js/utils/dimension_tree_filter.js
@@ -25,6 +25,10 @@
},
setup_filters(frm, doctype) {
+ if (doctype == 'Payment Entry' && this.accounting_dimensions) {
+ frm.dimension_filters = this.accounting_dimensions
+ }
+
if (this.accounting_dimensions) {
this.accounting_dimensions.forEach((dimension) => {
frappe.model.with_doctype(dimension['document_type'], () => {
diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js
index b92b02e..b8ec77f 100644
--- a/erpnext/public/js/utils/sales_common.js
+++ b/erpnext/public/js/utils/sales_common.js
@@ -22,6 +22,15 @@
}
};
});
+
+ this.frm.set_query('project', function(doc) {
+ return {
+ query: "erpnext.controllers.queries.get_project_name",
+ filters: {
+ 'customer': doc.customer
+ }
+ }
+ });
}
setup_queries() {
@@ -439,4 +448,4 @@
}
});
}
-}
\ No newline at end of file
+}
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index bf362e3..44a4957 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -135,7 +135,7 @@
filters: this.get_serial_no_filters()
};
},
- onchange: () => this.update_serial_batch_no()
+ onchange: () => this.scan_barcode_data()
});
}
@@ -145,7 +145,7 @@
options: 'Barcode',
fieldname: 'scan_batch_no',
label: __('Scan Batch No'),
- onchange: () => this.update_serial_batch_no()
+ onchange: () => this.scan_barcode_data()
});
}
@@ -179,11 +179,54 @@
label = __('Serial Nos / Batch Nos');
}
- return [
+ let fields = [
{
fieldtype: 'Section Break',
label: __('{0} {1} via CSV File', [primary_label, label])
- },
+ }
+ ]
+
+ if (this.item?.has_serial_no) {
+ fields = [...fields,
+ {
+ fieldtype: 'Check',
+ label: __('Import Using CSV file'),
+ fieldname: 'import_using_csv_file',
+ default: 0,
+ },
+ {
+ fieldtype: 'Section Break',
+ label: __('{0} {1} Manually', [primary_label, label]),
+ depends_on: 'eval:doc.import_using_csv_file === 0',
+ },
+ {
+ fieldtype: 'Small Text',
+ label: __('Enter Serial Nos'),
+ fieldname: 'upload_serial_nos',
+ depends_on: 'eval:doc.import_using_csv_file === 0',
+ description: __('Enter each serial no in a new line'),
+ },
+ {
+ fieldtype: 'Column Break',
+ depends_on: 'eval:doc.import_using_csv_file === 0',
+ },
+ {
+ fieldtype: 'Button',
+ fieldname: 'make_serial_nos',
+ label: __('Create Serial Nos'),
+ depends_on: 'eval:doc.import_using_csv_file === 0',
+ click: () => {
+ this.create_serial_nos();
+ }
+ },
+ {
+ fieldtype: 'Section Break',
+ depends_on: 'eval:doc.import_using_csv_file === 1',
+ }
+ ];
+ }
+
+ fields = [...fields,
{
fieldtype: 'Button',
fieldname: 'download_csv',
@@ -199,7 +242,32 @@
label: __('Attach CSV File'),
onchange: () => this.upload_csv_file()
}
- ]
+ ];
+
+ return fields;
+ }
+
+ create_serial_nos() {
+ let {upload_serial_nos} = this.dialog.get_values();
+
+ if (!upload_serial_nos) {
+ frappe.throw(__('Please enter Serial Nos'));
+ }
+
+ frappe.call({
+ method: 'erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.create_serial_nos',
+ args: {
+ item_code: this.item.item_code,
+ serial_nos: upload_serial_nos
+ },
+ callback: (r) => {
+ if (r.message) {
+ this.dialog.fields_dict.entries.df.data = [];
+ this.set_data(r.message);
+ this.update_bundle_entries();
+ }
+ }
+ });
}
download_csv_file() {
@@ -374,6 +442,26 @@
}
}
+ scan_barcode_data() {
+ const { scan_serial_no, scan_batch_no } = this.dialog.get_values();
+
+ if (scan_serial_no || scan_batch_no) {
+ frappe.call({
+ method: 'erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.is_serial_batch_no_exists',
+ args: {
+ item_code: this.item.item_code,
+ type_of_transaction: this.item.type_of_transaction,
+ serial_no: scan_serial_no,
+ batch_no: scan_batch_no,
+ },
+ callback: (r) => {
+ this.update_serial_batch_no();
+ }
+
+ })
+ }
+ }
+
update_serial_batch_no() {
const { scan_serial_no, scan_batch_no } = this.dialog.get_values();
diff --git a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json
index d332b4e..ecc198a 100644
--- a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json
+++ b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json
@@ -64,7 +64,7 @@
{
"fieldname": "valid_upto",
"fieldtype": "Date",
- "label": "Valid Upto",
+ "label": "Valid Up To",
"reqd": 1
},
{
@@ -135,7 +135,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2023-04-18 08:25:35.302081",
+ "modified": "2024-01-24 02:20:26.145996",
"modified_by": "Administrator",
"module": "Regional",
"name": "Lower Deduction Certificate",
diff --git a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py
index 72b3a49..6982ad1 100644
--- a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py
+++ b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py
@@ -37,7 +37,7 @@
def validate_dates(self):
if getdate(self.valid_upto) < getdate(self.valid_from):
- frappe.throw(_("Valid Upto date cannot be before Valid From date"))
+ frappe.throw(_("Valid Up To date cannot be before Valid From date"))
fiscal_year = get_fiscal_year(fiscal_year=self.fiscal_year, as_dict=True)
@@ -45,7 +45,7 @@
frappe.throw(_("Valid From date not in Fiscal Year {0}").format(frappe.bold(self.fiscal_year)))
if not (fiscal_year.year_start_date <= getdate(self.valid_upto) <= fiscal_year.year_end_date):
- frappe.throw(_("Valid Upto date not in Fiscal Year {0}").format(frappe.bold(self.fiscal_year)))
+ frappe.throw(_("Valid Up To date not in Fiscal Year {0}").format(frappe.bold(self.fiscal_year)))
def validate_supplier_against_tax_category(self):
duplicate_certificate = frappe.db.get_value(
diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py
index 29dbd4f..47153a8 100644
--- a/erpnext/selling/doctype/customer/test_customer.py
+++ b/erpnext/selling/doctype/customer/test_customer.py
@@ -427,11 +427,11 @@
if not allowed_to_interact_with:
allowed_to_interact_with = represents_company
- exisiting_representative = frappe.db.get_value(
+ existing_representative = frappe.db.get_value(
"Customer", {"represents_company": represents_company}
)
- if exisiting_representative:
- return exisiting_representative
+ if existing_representative:
+ return existing_representative
if not frappe.db.exists("Customer", customer_name):
customer = frappe.get_doc(
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index ab74f7f..654f297 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -127,7 +127,8 @@
def validate(self):
super(Quotation, self).validate()
self.set_status()
- self.validate_uom_is_integer("stock_uom", "qty")
+ self.validate_uom_is_integer("stock_uom", "stock_qty")
+ self.validate_uom_is_integer("uom", "qty")
self.validate_valid_till()
self.set_customer_name()
if self.items:
diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py
index ecb7d09..2a4855e 100644
--- a/erpnext/selling/doctype/quotation/test_quotation.py
+++ b/erpnext/selling/doctype/quotation/test_quotation.py
@@ -593,6 +593,22 @@
quotation.reload()
self.assertEqual(quotation.status, "Ordered")
+ def test_uom_validation(self):
+ from erpnext.stock.doctype.item.test_item import make_item
+
+ item = "_Test Item FOR UOM Validation"
+ make_item(item, {"is_stock_item": 1})
+
+ if not frappe.db.exists("UOM", "lbs"):
+ frappe.get_doc({"doctype": "UOM", "uom_name": "lbs", "must_be_whole_number": 1}).insert()
+ else:
+ frappe.db.set_value("UOM", "lbs", "must_be_whole_number", 1)
+
+ quotation = make_quotation(item_code=item, qty=1, rate=100, do_not_submit=1)
+ quotation.items[0].uom = "lbs"
+ quotation.items[0].conversion_factor = 2.23
+ self.assertRaises(frappe.ValidationError, quotation.save)
+
test_records = frappe.get_test_records("Quotation")
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index 56c745c..2bb093d 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -144,15 +144,6 @@
};
});
- frm.set_query('project', function(doc, cdt, cdn) {
- return {
- query: "erpnext.controllers.queries.get_project_name",
- filters: {
- 'customer': doc.customer
- }
- }
- });
-
frm.set_query('warehouse', 'items', function(doc, cdt, cdn) {
let row = locals[cdt][cdn];
let query = {
diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json
index 01d047c..3c516d0 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.json
+++ b/erpnext/selling/doctype/sales_order/sales_order.json
@@ -131,6 +131,7 @@
"per_billed",
"per_picked",
"billing_status",
+ "advance_payment_status",
"sales_team_section_break",
"sales_partner",
"column_break7",
@@ -1269,7 +1270,7 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "\nDraft\nOn Hold\nTo Deliver and Bill\nTo Bill\nTo Deliver\nCompleted\nCancelled\nClosed",
+ "options": "\nDraft\nOn Hold\nTo Pay\nTo Deliver and Bill\nTo Bill\nTo Deliver\nCompleted\nCancelled\nClosed",
"print_hide": 1,
"read_only": 1,
"reqd": 1,
@@ -1638,6 +1639,18 @@
"no_copy": 1,
"print_hide": 1,
"report_hide": 1
+ },
+ {
+ "fieldname": "advance_payment_status",
+ "fieldtype": "Select",
+ "hidden": 1,
+ "hide_days": 1,
+ "hide_seconds": 1,
+ "in_standard_filter": 1,
+ "label": "Advance Payment Status",
+ "no_copy": 1,
+ "options": "Not Requested\nRequested\nPartially Paid\nFully Paid",
+ "print_hide": 1
}
],
"icon": "fa fa-file-text",
@@ -1722,4 +1735,4 @@
"title_field": "customer_name",
"track_changes": 1,
"track_seen": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 5ef2c50..79f24d1 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -223,6 +223,8 @@
self.billing_status = "Not Billed"
if not self.delivery_status:
self.delivery_status = "Not Delivered"
+ if not self.advance_payment_status:
+ self.advance_payment_status = "Not Requested"
self.reset_default_field_value("set_warehouse", "items", "warehouse")
@@ -641,7 +643,7 @@
if not frappe.get_cached_value("Item", item.item_code, "has_serial_no"):
frappe.throw(
_(
- "Item {0} has no Serial No. Only serilialized items can have delivery based on Serial No"
+ "Item {0} has no Serial No. Only serialized items can have delivery based on Serial No"
).format(item.item_code)
)
if not frappe.db.exists("BOM", {"item": item.item_code, "is_active": 1}):
diff --git a/erpnext/selling/doctype/sales_order/sales_order_list.js b/erpnext/selling/doctype/sales_order/sales_order_list.js
index 518f018..37686a8 100644
--- a/erpnext/selling/doctype/sales_order/sales_order_list.js
+++ b/erpnext/selling/doctype/sales_order/sales_order_list.js
@@ -1,6 +1,6 @@
frappe.listview_settings['Sales Order'] = {
add_fields: ["base_grand_total", "customer_name", "currency", "delivery_date",
- "per_delivered", "per_billed", "status", "order_type", "name", "skip_delivery_note"],
+ "per_delivered", "per_billed", "status", "advance_payment_status", "order_type", "name", "skip_delivery_note"],
get_indicator: function (doc) {
if (doc.status === "Closed") {
// Closed
@@ -10,6 +10,8 @@
return [__("On Hold"), "orange", "status,=,On Hold"];
} else if (doc.status === "Completed") {
return [__("Completed"), "green", "status,=,Completed"];
+ } else if (doc.advance_payment_status === "Requested") {
+ return [__("To Pay"), "gray", "advance_payment_status,=,Requested"];
} else if (!doc.skip_delivery_note && flt(doc.per_delivered, 2) < 100) {
if (frappe.datetime.get_diff(doc.delivery_date) < 0) {
// not delivered & overdue
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index ac7fdb1..5ae48ee 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -1996,6 +1996,33 @@
self.assertEqual(so.items[0].rate, scenario.get("expected_rate"))
self.assertEqual(so.packed_items[0].rate, scenario.get("expected_rate"))
+ def test_sales_order_advance_payment_status(self):
+ from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
+ from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
+
+ so = make_sales_order(qty=1, rate=100)
+ self.assertEqual(
+ frappe.db.get_value(so.doctype, so.name, "advance_payment_status"), "Not Requested"
+ )
+
+ pr = make_payment_request(dt=so.doctype, dn=so.name, submit_doc=True, return_doc=True)
+ self.assertEqual(frappe.db.get_value(so.doctype, so.name, "advance_payment_status"), "Requested")
+
+ pe = get_payment_entry(so.doctype, so.name).save().submit()
+ self.assertEqual(
+ frappe.db.get_value(so.doctype, so.name, "advance_payment_status"), "Fully Paid"
+ )
+
+ pe.reload()
+ pe.cancel()
+ self.assertEqual(frappe.db.get_value(so.doctype, so.name, "advance_payment_status"), "Requested")
+
+ pr.reload()
+ pr.cancel()
+ self.assertEqual(
+ frappe.db.get_value(so.doctype, so.name, "advance_payment_status"), "Not Requested"
+ )
+
def automatically_fetch_payment_terms(enable=1):
accounts_settings = frappe.get_doc("Accounts Settings")
diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js
index feecd9c..80e1c20 100644
--- a/erpnext/selling/page/point_of_sale/pos_controller.js
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -360,7 +360,7 @@
this.order_summary.load_summary_of(this.frm.doc, true);
frappe.show_alert({
indicator: 'green',
- message: __('POS invoice {0} created succesfully', [r.doc.name])
+ message: __('POS invoice {0} created successfully', [r.doc.name])
});
});
}
diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py
index 3682c5f..00acc80 100644
--- a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py
+++ b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py
@@ -209,7 +209,7 @@
)
.where(
(so.docstatus == 1)
- & (so.status.isin(["To Deliver and Bill", "To Bill"]))
+ & (so.status.isin(["To Deliver and Bill", "To Bill", "To Pay"]))
& (so.payment_terms_template != "NULL")
& (so.company == conditions.company)
& (so.transaction_date[conditions.start_date : conditions.end_date])
diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js
index ac3d3db..fc685e0 100644
--- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js
+++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js
@@ -56,7 +56,7 @@
"fieldtype": "MultiSelectList",
"width": "80",
get_data: function(txt) {
- let status = ["To Bill", "To Deliver", "To Deliver and Bill", "Completed"]
+ let status = ["To Pay", "To Bill", "To Deliver", "To Deliver and Bill", "Completed"]
let options = []
for (let option of status){
options.push({
diff --git a/erpnext/setup/doctype/authorization_control/authorization_control.py b/erpnext/setup/doctype/authorization_control/authorization_control.py
index 9446fb4..27313ae 100644
--- a/erpnext/setup/doctype/authorization_control/authorization_control.py
+++ b/erpnext/setup/doctype/authorization_control/authorization_control.py
@@ -54,7 +54,7 @@
if not has_common(appr_roles, frappe.get_roles()) and not has_common(
appr_users, [session["user"]]
):
- frappe.msgprint(_("Not authroized since {0} exceeds limits").format(_(based_on)))
+ frappe.msgprint(_("Not authorized since {0} exceeds limits").format(_(based_on)))
frappe.throw(_("Can be approved by {0}").format(comma_or(appr_roles + appr_users)))
def validate_auth_rule(self, doctype_name, total, based_on, cond, company, master_name=""):
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index 1bd469b..340a917 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -140,38 +140,48 @@
},
delete_company_transactions: function(frm) {
- frappe.verify_password(function() {
- var d = frappe.prompt({
- fieldtype:"Data",
- fieldname: "company_name",
- label: __("Please enter the company name to confirm"),
- reqd: 1,
- description: __("Please make sure you really want to delete all the transactions for this company. Your master data will remain as it is. This action cannot be undone.")
+ frappe.call({
+ method: "erpnext.setup.doctype.company.company.is_deletion_job_running",
+ args: {
+ company: frm.doc.name
},
- function(data) {
- if(data.company_name !== frm.doc.name) {
- frappe.msgprint(__("Company name not same"));
- return;
+ freeze: true,
+ callback: function(r) {
+ if(!r.exc) {
+ frappe.verify_password(function() {
+ var d = frappe.prompt({
+ fieldtype:"Data",
+ fieldname: "company_name",
+ label: __("Please enter the company name to confirm"),
+ reqd: 1,
+ description: __("Please make sure you really want to delete all the transactions for this company. Your master data will remain as it is. This action cannot be undone.")
+ },
+ function(data) {
+ if(data.company_name !== frm.doc.name) {
+ frappe.msgprint(__("Company name not same"));
+ return;
+ }
+ frappe.call({
+ method: "erpnext.setup.doctype.company.company.create_transaction_deletion_request",
+ args: {
+ company: data.company_name
+ },
+ freeze: true,
+ callback: function(r, rt) { },
+ onerror: function() {
+ frappe.msgprint(__("Wrong Password"));
+ }
+ });
+ },
+ __("Delete all the Transactions for this Company"), __("Delete")
+ );
+ d.get_primary_btn().addClass("btn-danger");
+ });
}
- frappe.call({
- method: "erpnext.setup.doctype.company.company.create_transaction_deletion_request",
- args: {
- company: data.company_name
- },
- freeze: true,
- callback: function(r, rt) {
- if(!r.exc)
- frappe.msgprint(__("Successfully deleted all transactions related to this company!"));
- },
- onerror: function() {
- frappe.msgprint(__("Wrong Password"));
- }
- });
+
},
- __("Delete all the Transactions for this Company"), __("Delete")
- );
- d.get_primary_btn().addClass("btn-danger");
});
+
}
});
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index ec953b8..68a3854 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -11,7 +11,8 @@
from frappe.contacts.address_and_contact import load_address_and_contact
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.desk.page.setup_wizard.setup_wizard import make_records
-from frappe.utils import cint, formatdate, get_timestamp, today
+from frappe.utils import cint, formatdate, get_link_to_form, get_timestamp, today
+from frappe.utils.background_jobs import get_job, is_job_enqueued
from frappe.utils.nestedset import NestedSet, rebuild_tree
from erpnext.accounts.doctype.account.account import get_account_currency
@@ -900,8 +901,37 @@
return None
+def generate_id_for_deletion_job(company):
+ return "delete_company_transactions_" + company
+
+
+@frappe.whitelist()
+def is_deletion_job_running(company):
+ job_id = generate_id_for_deletion_job(company)
+ job_name = get_job(job_id).get_id() # job name will have site prefix
+ if is_job_enqueued(job_id):
+ frappe.throw(
+ _("A Transaction Deletion Job: {0} is already running for {1}").format(
+ frappe.bold(get_link_to_form("RQ Job", job_name)), frappe.bold(company)
+ )
+ )
+
+
@frappe.whitelist()
def create_transaction_deletion_request(company):
+ is_deletion_job_running(company)
+ job_id = generate_id_for_deletion_job(company)
+
tdr = frappe.get_doc({"doctype": "Transaction Deletion Record", "company": company})
tdr.insert()
- tdr.submit()
+
+ frappe.enqueue(
+ "frappe.utils.background_jobs.run_doc_method",
+ doctype=tdr.doctype,
+ name=tdr.name,
+ doc_method="submit",
+ job_id=job_id,
+ queue="long",
+ enqueue_after_commit=True,
+ )
+ frappe.msgprint(_("A Transaction Deletion Job is triggered for {0}").format(frappe.bold(company)))
diff --git a/erpnext/setup/doctype/employee/employee.json b/erpnext/setup/doctype/employee/employee.json
index daf2df5..fc1fc9b 100644
--- a/erpnext/setup/doctype/employee/employee.json
+++ b/erpnext/setup/doctype/employee/employee.json
@@ -441,13 +441,13 @@
{
"fieldname": "prefered_contact_email",
"fieldtype": "Select",
- "label": "Prefered Contact Email",
+ "label": "Preferred Contact Email",
"options": "\nCompany Email\nPersonal Email\nUser ID"
},
{
"fieldname": "prefered_email",
"fieldtype": "Data",
- "label": "Prefered Email",
+ "label": "Preferred Email",
"options": "Email",
"read_only": 1
},
@@ -524,7 +524,7 @@
{
"fieldname": "valid_upto",
"fieldtype": "Date",
- "label": "Valid Upto"
+ "label": "Valid Up To"
},
{
"fieldname": "place_of_issue",
@@ -824,7 +824,7 @@
"image_field": "image",
"is_tree": 1,
"links": [],
- "modified": "2024-01-03 17:36:20.984421",
+ "modified": "2024-01-24 02:20:26.145996",
"modified_by": "Administrator",
"module": "Setup",
"name": "Employee",
diff --git a/erpnext/stock/doctype/batch/batch.js b/erpnext/stock/doctype/batch/batch.js
index 7bf7a1f..f4a935a 100644
--- a/erpnext/stock/doctype/batch/batch.js
+++ b/erpnext/stock/doctype/batch/batch.js
@@ -52,7 +52,7 @@
// sort by qty
r.message.sort(function(a, b) { a.qty > b.qty ? 1 : -1 });
- var rows = $('<div></div>').appendTo(section);
+ const rows = $('<div></div>').appendTo(section);
// show
(r.message || []).forEach(function(d) {
@@ -76,7 +76,7 @@
// move - ask for target warehouse and make stock entry
rows.find('.btn-move').on('click', function() {
- var $btn = $(this);
+ const $btn = $(this);
const fields = [
{
fieldname: 'to_warehouse',
@@ -115,7 +115,7 @@
// split - ask for new qty and batch ID (optional)
// and make stock entry via batch.batch_split
rows.find('.btn-split').on('click', function() {
- var $btn = $(this);
+ const $btn = $(this);
frappe.prompt([{
fieldname: 'qty',
label: __('New Batch Qty'),
@@ -128,19 +128,16 @@
fieldtype: 'Data',
}],
(data) => {
- frappe.call({
- method: 'erpnext.stock.doctype.batch.batch.split_batch',
- args: {
+ frappe.xcall(
+ 'erpnext.stock.doctype.batch.batch.split_batch',
+ {
item_code: frm.doc.item,
batch_no: frm.doc.name,
qty: data.qty,
warehouse: $btn.attr('data-warehouse'),
new_batch_id: data.new_batch_id
- },
- callback: (r) => {
- frm.refresh();
- },
- });
+ }
+ ).then(() => frm.reload_doc());
},
__('Split Batch'),
__('Split')
diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index 7b23f9e..e8e94fd 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -9,7 +9,7 @@
from frappe.model.document import Document
from frappe.model.naming import make_autoname, revert_series_if_last
from frappe.query_builder.functions import CurDate, Sum
-from frappe.utils import cint, flt, get_link_to_form, nowtime, today
+from frappe.utils import cint, flt, get_link_to_form
from frappe.utils.data import add_days
from frappe.utils.jinja import render_template
@@ -248,8 +248,9 @@
@frappe.whitelist()
-def split_batch(batch_no, item_code, warehouse, qty, new_batch_id=None):
-
+def split_batch(
+ batch_no: str, item_code: str, warehouse: str, qty: float, new_batch_id: str | None = None
+):
"""Split the batch into a new batch"""
batch = frappe.get_doc(dict(doctype="Batch", item=item_code, batch_id=new_batch_id)).insert()
qty = flt(qty)
@@ -257,29 +258,21 @@
company = frappe.db.get_value("Warehouse", warehouse, "company")
from_bundle_id = make_batch_bundle(
- frappe._dict(
- {
- "item_code": item_code,
- "warehouse": warehouse,
- "batches": frappe._dict({batch_no: qty}),
- "company": company,
- "type_of_transaction": "Outward",
- "qty": qty,
- }
- )
+ item_code=item_code,
+ warehouse=warehouse,
+ batches=frappe._dict({batch_no: qty}),
+ company=company,
+ type_of_transaction="Outward",
+ qty=qty,
)
to_bundle_id = make_batch_bundle(
- frappe._dict(
- {
- "item_code": item_code,
- "warehouse": warehouse,
- "batches": frappe._dict({batch.name: qty}),
- "company": company,
- "type_of_transaction": "Inward",
- "qty": qty,
- }
- )
+ item_code=item_code,
+ warehouse=warehouse,
+ batches=frappe._dict({batch.name: qty}),
+ company=company,
+ type_of_transaction="Inward",
+ qty=qty,
)
stock_entry = frappe.get_doc(
@@ -304,21 +297,30 @@
return batch.name
-def make_batch_bundle(kwargs):
+def make_batch_bundle(
+ item_code: str,
+ warehouse: str,
+ batches: dict[str, float],
+ company: str,
+ type_of_transaction: str,
+ qty: float,
+):
+ from frappe.utils import nowtime, today
+
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
return (
SerialBatchCreation(
{
- "item_code": kwargs.item_code,
- "warehouse": kwargs.warehouse,
+ "item_code": item_code,
+ "warehouse": warehouse,
"posting_date": today(),
"posting_time": nowtime(),
"voucher_type": "Stock Entry",
- "qty": flt(kwargs.qty),
- "type_of_transaction": kwargs.type_of_transaction,
- "company": kwargs.company,
- "batches": kwargs.batches,
+ "qty": qty,
+ "type_of_transaction": type_of_transaction,
+ "company": company,
+ "batches": batches,
"do_not_submit": True,
}
)
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js
index ec68549..14aedca 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.js
@@ -31,15 +31,6 @@
});
erpnext.queries.setup_warehouse_query(frm);
- frm.set_query('project', function(doc) {
- return {
- query: "erpnext.controllers.queries.get_project_name",
- filters: {
- 'customer': doc.customer
- }
- }
- })
-
frm.set_query('transporter', function() {
return {
filters: {
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index 7d7b0cd..58990d4 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -796,36 +796,36 @@
updated_dn = []
for dnd in dn_details:
- billed_amt_agianst_dn = 0
+ billed_amt_against_dn = 0
# If delivered against Sales Invoice
if dnd.si_detail:
- billed_amt_agianst_dn = flt(dnd.amount)
- billed_against_so -= billed_amt_agianst_dn
+ billed_amt_against_dn = flt(dnd.amount)
+ billed_against_so -= billed_amt_against_dn
else:
# Get billed amount directly against Delivery Note
- billed_amt_agianst_dn = frappe.db.sql(
+ billed_amt_against_dn = frappe.db.sql(
"""select sum(amount) from `tabSales Invoice Item`
where dn_detail=%s and docstatus=1""",
dnd.name,
)
- billed_amt_agianst_dn = billed_amt_agianst_dn and billed_amt_agianst_dn[0][0] or 0
+ billed_amt_against_dn = billed_amt_against_dn and billed_amt_against_dn[0][0] or 0
# Distribute billed amount directly against SO between DNs based on FIFO
- if billed_against_so and billed_amt_agianst_dn < dnd.amount:
- pending_to_bill = flt(dnd.amount) - billed_amt_agianst_dn
+ if billed_against_so and billed_amt_against_dn < dnd.amount:
+ pending_to_bill = flt(dnd.amount) - billed_amt_against_dn
if pending_to_bill <= billed_against_so:
- billed_amt_agianst_dn += pending_to_bill
+ billed_amt_against_dn += pending_to_bill
billed_against_so -= pending_to_bill
else:
- billed_amt_agianst_dn += billed_against_so
+ billed_amt_against_dn += billed_against_so
billed_against_so = 0
frappe.db.set_value(
"Delivery Note Item",
dnd.name,
"billed_amt",
- billed_amt_agianst_dn,
+ billed_amt_against_dn,
update_modified=update_modified,
)
diff --git a/erpnext/stock/doctype/item_price/item_price.json b/erpnext/stock/doctype/item_price/item_price.json
index f4d9bb0..2390ee2 100644
--- a/erpnext/stock/doctype/item_price/item_price.json
+++ b/erpnext/stock/doctype/item_price/item_price.json
@@ -191,7 +191,7 @@
{
"fieldname": "valid_upto",
"fieldtype": "Date",
- "label": "Valid Upto"
+ "label": "Valid Up To"
},
{
"fieldname": "section_break_24",
@@ -220,7 +220,7 @@
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2022-11-15 08:26:04.041861",
+ "modified": "2024-01-24 02:20:26.145996",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item Price",
diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py
index 89a130a..de2add6 100644
--- a/erpnext/stock/doctype/item_price/item_price.py
+++ b/erpnext/stock/doctype/item_price/item_price.py
@@ -59,7 +59,7 @@
def validate_dates(self):
if self.valid_from and self.valid_upto:
if getdate(self.valid_from) > getdate(self.valid_upto):
- frappe.throw(_("Valid From Date must be lesser than Valid Upto Date."))
+ frappe.throw(_("Valid From Date must be lesser than Valid Up To Date."))
def update_price_list_details(self):
if self.price_list:
diff --git a/erpnext/stock/doctype/item_price/test_item_price.py b/erpnext/stock/doctype/item_price/test_item_price.py
index 8fd4938..63d717c 100644
--- a/erpnext/stock/doctype/item_price/test_item_price.py
+++ b/erpnext/stock/doctype/item_price/test_item_price.py
@@ -64,7 +64,7 @@
# Enter invalid dates valid_from >= valid_upto
doc.valid_from = "2017-04-20"
doc.valid_upto = "2017-04-17"
- # Valid Upto Date can not be less/equal than Valid From Date
+ # Valid Up To Date can not be less/equal than Valid From Date
self.assertRaises(frappe.ValidationError, doc.save)
def test_price_in_a_qty(self):
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index ad9b34c..e784b70 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -776,7 +776,7 @@
)
else:
msgprint(
- _("The {0} {1} created sucessfully").format(frappe.bold(_("Work Order")), work_orders_list[0])
+ _("The {0} {1} created successfully").format(frappe.bold(_("Work Order")), work_orders_list[0])
)
if errors:
diff --git a/erpnext/stock/doctype/material_request/material_request_list.js b/erpnext/stock/doctype/material_request/material_request_list.js
index de7a3d0..c85bd71 100644
--- a/erpnext/stock/doctype/material_request/material_request_list.js
+++ b/erpnext/stock/doctype/material_request/material_request_list.js
@@ -24,7 +24,7 @@
} else if (doc.material_request_type == "Purchase") {
return [__("Ordered"), "green", "per_ordered,=,100"];
} else if (doc.material_request_type == "Material Transfer") {
- return [__("Transfered"), "green", "per_ordered,=,100"];
+ return [__("Transferred"), "green", "per_ordered,=,100"];
} else if (doc.material_request_type == "Material Issue") {
return [__("Issued"), "green", "per_ordered,=,100"];
} else if (doc.material_request_type == "Customer Provided") {
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index fcb7a6d..bf6080b 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -951,32 +951,32 @@
billed_against_po = flt(po_billed_amt_details.get(pr_item.purchase_order_item))
# Get billed amount directly against Purchase Receipt
- billed_amt_agianst_pr = flt(pr_items_billed_amount.get(pr_item.name, 0))
+ billed_amt_against_pr = flt(pr_items_billed_amount.get(pr_item.name, 0))
# Distribute billed amount directly against PO between PRs based on FIFO
- if billed_against_po and billed_amt_agianst_pr < pr_item.amount:
- pending_to_bill = flt(pr_item.amount) - billed_amt_agianst_pr
+ if billed_against_po and billed_amt_against_pr < pr_item.amount:
+ pending_to_bill = flt(pr_item.amount) - billed_amt_against_pr
if pending_to_bill <= billed_against_po:
- billed_amt_agianst_pr += pending_to_bill
+ billed_amt_against_pr += pending_to_bill
billed_against_po -= pending_to_bill
else:
- billed_amt_agianst_pr += billed_against_po
+ billed_amt_against_pr += billed_against_po
billed_against_po = 0
po_billed_amt_details[pr_item.purchase_order_item] = billed_against_po
- if pr_item.billed_amt != billed_amt_agianst_pr:
+ if pr_item.billed_amt != billed_amt_against_pr:
# update existing doc if possible
if pr_doc and pr_item.parent == pr_doc.name:
pr_item = next((item for item in pr_doc.items if item.name == pr_item.name), None)
- pr_item.db_set("billed_amt", billed_amt_agianst_pr, update_modified=update_modified)
+ pr_item.db_set("billed_amt", billed_amt_against_pr, update_modified=update_modified)
else:
frappe.db.set_value(
"Purchase Receipt Item",
pr_item.name,
"billed_amt",
- billed_amt_agianst_pr,
+ billed_amt_against_pr,
update_modified=update_modified,
)
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js
index 9f01ee9..91b7430 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js
@@ -74,7 +74,7 @@
let fields = [
{
- "label": __("Using CSV File"),
+ "label": __("Import Using CSV file"),
"fieldname": "using_csv_file",
"default": 1,
"fieldtype": "Check",
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 2b87fcd..63cc938 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
@@ -999,9 +999,25 @@
make_serial_nos(item_code, serial_nos)
+ if kwargs.get("_has_serial_nos"):
+ return serial_nos
+
return serial_nos, batch_nos
+@frappe.whitelist()
+def create_serial_nos(item_code, serial_nos):
+ serial_nos = get_serial_batch_from_data(
+ item_code,
+ {
+ "serial_nos": serial_nos,
+ "_has_serial_nos": True,
+ },
+ )
+
+ return serial_nos
+
+
def make_serial_nos(item_code, serial_nos):
item = frappe.get_cached_value("Item", item_code, ["description", "item_code"], as_dict=1)
@@ -2080,5 +2096,34 @@
@frappe.whitelist()
+def is_serial_batch_no_exists(item_code, type_of_transaction, serial_no=None, batch_no=None):
+ if serial_no and not frappe.db.exists("Serial No", serial_no):
+ if type_of_transaction != "Inward":
+ frappe.throw(_("Serial No {0} does not exists").format(serial_no))
+
+ make_serial_no(serial_no, item_code)
+
+ if batch_no and frappe.db.exists("Batch", batch_no):
+ if type_of_transaction != "Inward":
+ frappe.throw(_("Batch No {0} does not exists").format(batch_no))
+
+ make_batch_no(batch_no, item_code)
+
+
+def make_serial_no(serial_no, item_code):
+ serial_no_doc = frappe.new_doc("Serial No")
+ serial_no_doc.serial_no = serial_no
+ serial_no_doc.item_code = item_code
+ serial_no_doc.save(ignore_permissions=True)
+
+
+def make_batch_no(batch_no, item_code):
+ batch_doc = frappe.new_doc("Batch")
+ batch_doc.batch_id = batch_no
+ batch_doc.item = item_code
+ batch_doc.save(ignore_permissions=True)
+
+
+@frappe.whitelist()
def is_duplicate_serial_no(bundle_id, serial_no):
return frappe.db.exists("Serial and Batch Entry", {"parent": bundle_id, "serial_no": serial_no})
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 96c249f..c371b70 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -640,7 +640,7 @@
frappe.throw(_("Source and target warehouse cannot be same for row {0}").format(d.idx))
if not (d.s_warehouse or d.t_warehouse):
- frappe.throw(_("Atleast one warehouse is mandatory"))
+ frappe.throw(_("At least one warehouse is mandatory"))
def validate_work_order(self):
if self.purpose in (
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json
index 1228290..f84456a 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.json
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.json
@@ -176,7 +176,7 @@
"description": "No stock transactions can be created or modified before this date.",
"fieldname": "stock_frozen_upto",
"fieldtype": "Date",
- "label": "Stock Frozen Upto"
+ "label": "Stock Frozen Up To"
},
{
"description": "Stock transactions that are older than the mentioned days cannot be modified.",
@@ -427,7 +427,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2023-10-18 12:35:30.068799",
+ "modified": "2024-01-24 02:20:26.145996",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Settings",
diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py
index 31fb99a..276531a 100644
--- a/erpnext/stock/reorder_item.py
+++ b/erpnext/stock/reorder_item.py
@@ -285,7 +285,7 @@
_("Dear System Manager,")
+ "<br>"
+ _(
- "An error occured for certain Items while creating Material Requests based on Re-order level. Please rectify these issues :"
+ "An error occurred for certain Items while creating Material Requests based on Re-order level. Please rectify these issues :"
)
+ "<br>"
)
diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
index 810dc46..3f5216b 100644
--- a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
+++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
@@ -22,9 +22,8 @@
{"label": _("Posting Time"), "fieldtype": "Time", "fieldname": "posting_time", "width": 90},
{
"label": _("Voucher Type"),
- "fieldtype": "Link",
+ "fieldtype": "Data",
"fieldname": "voucher_type",
- "options": "DocType",
"width": 160,
},
{
diff --git a/erpnext/utilities/activation.py b/erpnext/utilities/activation.py
index 4c8379e..581b53d 100644
--- a/erpnext/utilities/activation.py
+++ b/erpnext/utilities/activation.py
@@ -124,7 +124,7 @@
doctype="Timesheet",
title=_("Add Timesheets"),
description=_(
- "Timesheets help keep track of time, cost and billing for activites done by your team"
+ "Timesheets help keep track of time, cost and billing for activities done by your team"
),
action=_("Create Timesheet"),
route="List/Timesheet",
diff --git a/erpnext/utilities/web_form/addresses/addresses.json b/erpnext/utilities/web_form/addresses/addresses.json
index 2f5e180..4e2d8e3 100644
--- a/erpnext/utilities/web_form/addresses/addresses.json
+++ b/erpnext/utilities/web_form/addresses/addresses.json
@@ -8,26 +8,29 @@
"allow_print": 0,
"amount": 0.0,
"amount_based_on_field": 0,
+ "anonymous": 0,
+ "apply_document_permissions": 1,
+ "condition_json": "[]",
"creation": "2016-06-24 15:50:33.196990",
"doc_type": "Address",
"docstatus": 0,
"doctype": "Web Form",
"idx": 0,
"is_standard": 1,
+ "list_columns": [],
+ "list_title": "",
"login_required": 1,
"max_attachment_size": 0,
- "modified": "2019-10-15 06:55:30.405119",
- "modified_by": "Administrator",
+ "modified": "2024-01-24 10:28:35.026064",
+ "modified_by": "rohitw1991@gmail.com",
"module": "Utilities",
"name": "addresses",
"owner": "Administrator",
"published": 1,
"route": "address",
- "route_to_success_link": 0,
"show_attachments": 0,
- "show_in_grid": 0,
+ "show_list": 1,
"show_sidebar": 0,
- "sidebar_items": [],
"success_url": "/addresses",
"title": "Address",
"web_form_fields": [