Merge branch 'develop' into payment-reco-company-field
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/fiscal_year/fiscal_year.json b/erpnext/accounts/doctype/fiscal_year/fiscal_year.json
index 5ab91f2..bd2bfbd 100644
--- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.json
+++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.json
@@ -82,11 +82,11 @@
"icon": "fa fa-calendar",
"idx": 1,
"links": [],
- "modified": "2020-11-05 12:16:53.081573",
+ "modified": "2024-01-17 13:06:01.608953",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Fiscal Year",
- "owner": "Administrator",
+ "owner": "Administrator",
"permissions": [
{
"create": 1,
@@ -118,6 +118,14 @@
{
"read": 1,
"role": "Employee"
+ },
+ {
+ "read": 1,
+ "role": "Accounts Manager"
+ },
+ {
+ "read": 1,
+ "role": "Stock Manager"
}
],
"show_name_in_global_search": 1,
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 93ed5c8..ff2aa6d 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",
@@ -209,6 +211,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,
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/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index 004a929..aadd873 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -8,17 +8,7 @@
import frappe
from frappe import _
-from frappe.utils import (
- add_days,
- add_months,
- cint,
- cstr,
- flt,
- formatdate,
- get_first_day,
- getdate,
- today,
-)
+from frappe.utils import add_days, add_months, cint, cstr, flt, formatdate, get_first_day, getdate
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
@@ -53,8 +43,6 @@
year_start_date = getdate(period_start_date)
year_end_date = getdate(period_end_date)
- year_end_date = getdate(today()) if year_end_date > getdate(today()) else year_end_date
-
months_to_add = {"Yearly": 12, "Half-Yearly": 6, "Quarterly": 3, "Monthly": 1}[periodicity]
period_list = []
diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
index d045d91..4a80dd0 100644
--- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
+++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
@@ -46,12 +46,10 @@
out = []
for name, details in gle_map.items():
- tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0
- bill_no, bill_date = "", ""
- tax_withholding_category = tax_category_map.get(name)
- rate = tax_rate_map.get(tax_withholding_category)
-
for entry in details:
+ tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0
+ tax_withholding_category, rate = None, None
+ bill_no, bill_date = "", ""
party = entry.party or entry.against
posting_date = entry.posting_date
voucher_type = entry.voucher_type
@@ -61,12 +59,19 @@
if party_list:
party = party_list[0]
- if not tax_withholding_category:
- tax_withholding_category = party_map.get(party, {}).get("tax_withholding_category")
- rate = tax_rate_map.get(tax_withholding_category)
-
- if entry.account in tds_accounts:
+ if entry.account in tds_accounts.keys():
tax_amount += entry.credit - entry.debit
+ # infer tax withholding category from the account if it's the single account for this category
+ tax_withholding_category = tds_accounts.get(entry.account)
+ rate = tax_rate_map.get(tax_withholding_category)
+ # or else the consolidated value from the voucher document
+ if not tax_withholding_category:
+ # or else from the party default
+ tax_withholding_category = tax_category_map.get(name)
+ rate = tax_rate_map.get(tax_withholding_category)
+ if not tax_withholding_category:
+ tax_withholding_category = party_map.get(party, {}).get("tax_withholding_category")
+ rate = tax_rate_map.get(tax_withholding_category)
if net_total_map.get(name):
if voucher_type == "Journal Entry" and tax_amount and rate:
@@ -80,41 +85,41 @@
else:
total_amount += entry.credit
- if tax_amount:
- if party_map.get(party, {}).get("party_type") == "Supplier":
- party_name = "supplier_name"
- party_type = "supplier_type"
- else:
- party_name = "customer_name"
- party_type = "customer_type"
+ if tax_amount:
+ if party_map.get(party, {}).get("party_type") == "Supplier":
+ party_name = "supplier_name"
+ party_type = "supplier_type"
+ else:
+ party_name = "customer_name"
+ party_type = "customer_type"
- row = {
- "pan"
- if frappe.db.has_column(filters.party_type, "pan")
- else "tax_id": party_map.get(party, {}).get("pan"),
- "party": party_map.get(party, {}).get("name"),
- }
-
- if filters.naming_series == "Naming Series":
- row.update({"party_name": party_map.get(party, {}).get(party_name)})
-
- row.update(
- {
- "section_code": tax_withholding_category or "",
- "entity_type": party_map.get(party, {}).get(party_type),
- "rate": rate,
- "total_amount": total_amount,
- "grand_total": grand_total,
- "base_total": base_total,
- "tax_amount": tax_amount,
- "transaction_date": posting_date,
- "transaction_type": voucher_type,
- "ref_no": name,
- "supplier_invoice_no": bill_no,
- "supplier_invoice_date": bill_date,
+ row = {
+ "pan"
+ if frappe.db.has_column(filters.party_type, "pan")
+ else "tax_id": party_map.get(party, {}).get("pan"),
+ "party": party_map.get(party, {}).get("name"),
}
- )
- out.append(row)
+
+ if filters.naming_series == "Naming Series":
+ row.update({"party_name": party_map.get(party, {}).get(party_name)})
+
+ row.update(
+ {
+ "section_code": tax_withholding_category or "",
+ "entity_type": party_map.get(party, {}).get(party_type),
+ "rate": rate,
+ "total_amount": total_amount,
+ "grand_total": grand_total,
+ "base_total": base_total,
+ "tax_amount": tax_amount,
+ "transaction_date": posting_date,
+ "transaction_type": voucher_type,
+ "ref_no": name,
+ "supplier_invoice_no": bill_no,
+ "supplier_invoice_date": bill_date,
+ }
+ )
+ out.append(row)
out.sort(key=lambda x: x["section_code"])
@@ -282,11 +287,20 @@
journal_entry_party_map = frappe._dict()
bank_accounts = frappe.get_all("Account", {"is_group": 0, "account_type": "Bank"}, pluck="name")
- tds_accounts = frappe.get_all(
- "Tax Withholding Account", {"company": filters.get("company")}, pluck="account"
+ _tds_accounts = frappe.get_all(
+ "Tax Withholding Account",
+ {"company": filters.get("company")},
+ ["account", "parent"],
)
+ tds_accounts = {}
+ for tds_acc in _tds_accounts:
+ # if it turns out not to be the only tax withholding category, then don't include in the map
+ if tds_accounts.get(tds_acc["account"]):
+ tds_accounts[tds_acc["account"]] = None
+ else:
+ tds_accounts[tds_acc["account"]] = tds_acc["parent"]
- tds_docs = get_tds_docs_query(filters, bank_accounts, tds_accounts).run(as_dict=True)
+ tds_docs = get_tds_docs_query(filters, bank_accounts, list(tds_accounts.keys())).run(as_dict=True)
for d in tds_docs:
if d.voucher_type == "Purchase Invoice":
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..166e8c4 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -519,14 +519,11 @@
movement.cancel()
def cancel_capitalization(self):
- asset_capitalization = frappe.db.get_value(
- "Asset Capitalization",
- {"target_asset": self.name, "docstatus": 1, "entry_type": "Capitalization"},
- )
-
- if asset_capitalization:
- asset_capitalization = frappe.get_doc("Asset Capitalization", asset_capitalization)
- asset_capitalization.cancel()
+ if self.capitalized_in:
+ self.db_set("capitalized_in", None)
+ asset_capitalization = frappe.get_doc("Asset Capitalization", self.capitalized_in)
+ if asset_capitalization.docstatus == 1:
+ asset_capitalization.cancel()
def delete_depreciation_entries(self):
if self.calculate_depreciation:
@@ -1011,7 +1008,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/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index a93af94..df4593b 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -561,6 +561,8 @@
def reverse_depreciation_entry_made_after_disposal(asset, date):
for row in asset.get("finance_books"):
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active", row.finance_book)
+ if not asset_depr_schedule_doc:
+ continue
for schedule_idx, schedule in enumerate(asset_depr_schedule_doc.get("depreciation_schedule")):
if schedule.schedule_date == date:
diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
index cad74df..5e251a5 100644
--- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
+++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
@@ -146,6 +146,7 @@
def cancel_target_asset(self):
if self.entry_type == "Capitalization" and self.target_asset:
asset_doc = frappe.get_doc("Asset", self.target_asset)
+ frappe.db.set_value("Asset", self.target_asset, "capitalized_in", None)
if asset_doc.docstatus == 1:
asset_doc.cancel()
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..7dfcb30 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 (
@@ -21,12 +22,14 @@
get_link_to_form,
getdate,
nowdate,
+ parse_json,
today,
)
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,
@@ -831,6 +834,37 @@
self.extend("taxes", get_taxes_and_charges(tax_master_doctype, self.get("taxes_and_charges")))
+ def append_taxes_from_item_tax_template(self):
+ if not frappe.db.get_single_value("Accounts Settings", "add_taxes_from_item_tax_template"):
+ return
+
+ for row in self.items:
+ item_tax_rate = row.get("item_tax_rate")
+ if not item_tax_rate:
+ continue
+
+ if isinstance(item_tax_rate, str):
+ item_tax_rate = parse_json(item_tax_rate)
+
+ for account_head, rate in item_tax_rate.items():
+ row = self.get_tax_row(account_head)
+
+ if not row:
+ self.append(
+ "taxes",
+ {
+ "charge_type": "On Net Total",
+ "account_head": account_head,
+ "rate": 0,
+ "description": account_head,
+ },
+ )
+
+ def get_tax_row(self, account_head):
+ for row in self.taxes:
+ if row.account_head == account_head:
+ return row
+
def set_other_charges(self):
self.set("taxes", [])
self.set_taxes()
@@ -1216,7 +1250,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 +1307,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 +1388,7 @@
self.name,
d.idx,
self.cost_center,
+ dimensions_dict,
)
frappe.msgprint(
_("Exchange Gain/Loss amount has been booked through {0}").format(
@@ -1415,7 +1453,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 +1793,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 +1810,8 @@
.run(as_dict=True)
)
+ advance_paid, order_total = None, None
+
if advance:
advance = advance[0]
@@ -1791,7 +1840,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 +2764,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..80ade70 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -71,6 +71,10 @@
let warehouse = this.item?.type_of_transaction === "Outward" ?
(this.item.warehouse || this.item.s_warehouse) : "";
+ if (!warehouse && this.frm.doc.doctype === 'Stock Reconciliation') {
+ warehouse = this.get_warehouse();
+ }
+
return {
'item_code': this.item.item_code,
'warehouse': ["=", warehouse]
@@ -135,7 +139,7 @@
filters: this.get_serial_no_filters()
};
},
- onchange: () => this.update_serial_batch_no()
+ onchange: () => this.scan_barcode_data()
});
}
@@ -145,7 +149,7 @@
options: 'Barcode',
fieldname: 'scan_batch_no',
label: __('Scan Batch No'),
- onchange: () => this.update_serial_batch_no()
+ onchange: () => this.scan_barcode_data()
});
}
@@ -179,11 +183,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 +246,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 +446,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..86c7a72 100644
--- a/erpnext/selling/doctype/quotation/test_quotation.py
+++ b/erpnext/selling/doctype/quotation/test_quotation.py
@@ -593,6 +593,77 @@
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)
+
+ def test_item_tax_template_for_quotation(self):
+ from erpnext.stock.doctype.item.test_item import make_item
+
+ if not frappe.db.exists("Account", {"account_name": "_Test Vat", "company": "_Test Company"}):
+ frappe.get_doc(
+ {
+ "doctype": "Account",
+ "account_name": "_Test Vat",
+ "company": "_Test Company",
+ "account_type": "Tax",
+ "root_type": "Asset",
+ "is_group": 0,
+ "parent_account": "Tax Assets - _TC",
+ "tax_rate": 10,
+ }
+ ).insert()
+
+ if not frappe.db.exists("Item Tax Template", "Vat Template - _TC"):
+ doc = frappe.get_doc(
+ {
+ "doctype": "Item Tax Template",
+ "name": "Vat Template",
+ "title": "Vat Template",
+ "company": "_Test Company",
+ "taxes": [
+ {
+ "tax_type": "_Test Vat - _TC",
+ "tax_rate": 5,
+ }
+ ],
+ }
+ ).insert()
+
+ item_doc = make_item("_Test Item Tax Template QTN", {"is_stock_item": 1})
+ if not frappe.db.exists(
+ "Item Tax", {"parent": item_doc.name, "item_tax_template": "Vat Template - _TC"}
+ ):
+ item_doc.append("taxes", {"item_tax_template": "Vat Template - _TC"})
+ item_doc.save()
+
+ quotation = make_quotation(
+ item_code="_Test Item Tax Template QTN", qty=1, rate=100, do_not_submit=1
+ )
+ self.assertFalse(quotation.taxes)
+
+ quotation.append_taxes_from_item_tax_template()
+ quotation.save()
+ self.assertTrue(quotation.taxes)
+ for row in quotation.taxes:
+ self.assertEqual(row.account_head, "_Test Vat - _TC")
+ self.assertAlmostEqual(row.base_tax_amount, quotation.total * 5 / 100)
+
+ item_doc.taxes = []
+ item_doc.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..72b7fa2 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,8 +209,7 @@
)
.where(
(so.docstatus == 1)
- & (so.status.isin(["To Deliver and Bill", "To Bill"]))
- & (so.payment_terms_template != "NULL")
+ & (so.status.isin(["To Deliver and Bill", "To Bill", "To Pay"]))
& (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..876b6a4 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)
+ if is_job_enqueued(job_id):
+ job_name = get_job(job_id).get_id() # job name will have site prefix
+ 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/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py
index 319d435..844e786 100644
--- a/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py
+++ b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py
@@ -4,9 +4,10 @@
import unittest
import frappe
+from frappe.tests.utils import FrappeTestCase
-class TestTransactionDeletionRecord(unittest.TestCase):
+class TestTransactionDeletionRecord(FrappeTestCase):
def setUp(self):
create_company("Dunder Mifflin Paper Co")
@@ -14,7 +15,7 @@
frappe.db.rollback()
def test_doctypes_contain_company_field(self):
- tdr = create_transaction_deletion_request("Dunder Mifflin Paper Co")
+ tdr = create_transaction_deletion_doc("Dunder Mifflin Paper Co")
for doctype in tdr.doctypes:
contains_company = False
doctype_fields = frappe.get_meta(doctype.doctype_name).as_dict()["fields"]
@@ -27,17 +28,27 @@
def test_no_of_docs_is_correct(self):
for i in range(5):
create_task("Dunder Mifflin Paper Co")
- tdr = create_transaction_deletion_request("Dunder Mifflin Paper Co")
+ tdr = create_transaction_deletion_doc("Dunder Mifflin Paper Co")
for doctype in tdr.doctypes:
if doctype.doctype_name == "Task":
self.assertEqual(doctype.no_of_docs, 5)
def test_deletion_is_successful(self):
create_task("Dunder Mifflin Paper Co")
- create_transaction_deletion_request("Dunder Mifflin Paper Co")
+ create_transaction_deletion_doc("Dunder Mifflin Paper Co")
tasks_containing_company = frappe.get_all("Task", filters={"company": "Dunder Mifflin Paper Co"})
self.assertEqual(tasks_containing_company, [])
+ def test_company_transaction_deletion_request(self):
+ from erpnext.setup.doctype.company.company import create_transaction_deletion_request
+
+ # don't reuse below company for other test cases
+ company = "Deep Space Exploration"
+ create_company(company)
+
+ # below call should not raise any exceptions or throw errors
+ create_transaction_deletion_request(company)
+
def create_company(company_name):
company = frappe.get_doc(
@@ -46,7 +57,7 @@
company.insert(ignore_if_duplicate=True)
-def create_transaction_deletion_request(company):
+def create_transaction_deletion_doc(company):
tdr = frappe.get_doc({"doctype": "Transaction Deletion Record", "company": company})
tdr.insert()
tdr.submit()
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/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py
index 3e44049..48397a3 100644
--- a/erpnext/stock/doctype/material_request/test_material_request.py
+++ b/erpnext/stock/doctype/material_request/test_material_request.py
@@ -774,6 +774,62 @@
self.assertEqual(mr.per_ordered, 100)
self.assertEqual(existing_requested_qty, current_requested_qty)
+ def test_auto_email_users_with_company_user_permissions(self):
+ from erpnext.stock.reorder_item import get_email_list
+
+ comapnywise_users = {
+ "_Test Company": "test_auto_email_@example.com",
+ "_Test Company 1": "test_auto_email_1@example.com",
+ }
+
+ permissions = []
+
+ for company, user in comapnywise_users.items():
+ if not frappe.db.exists("User", user):
+ frappe.get_doc(
+ {
+ "doctype": "User",
+ "email": user,
+ "first_name": user,
+ "send_notifications": 0,
+ "enabled": 1,
+ "user_type": "System User",
+ "roles": [{"role": "Purchase Manager"}],
+ }
+ ).insert(ignore_permissions=True)
+
+ if not frappe.db.exists(
+ "User Permission", {"user": user, "allow": "Company", "for_value": company}
+ ):
+ perm_doc = frappe.get_doc(
+ {
+ "doctype": "User Permission",
+ "user": user,
+ "allow": "Company",
+ "for_value": company,
+ "apply_to_all_doctypes": 1,
+ }
+ ).insert(ignore_permissions=True)
+
+ permissions.append(perm_doc)
+
+ comapnywise_mr_list = frappe._dict({})
+ mr1 = make_material_request()
+ comapnywise_mr_list.setdefault(mr1.company, []).append(mr1.name)
+
+ mr2 = make_material_request(
+ company="_Test Company 1", warehouse="Stores - _TC1", cost_center="Main - _TC1"
+ )
+ comapnywise_mr_list.setdefault(mr2.company, []).append(mr2.name)
+
+ for company, mr_list in comapnywise_mr_list.items():
+ emails = get_email_list(company)
+
+ self.assertTrue(comapnywise_users[company] in emails)
+
+ for perm in permissions:
+ perm.delete()
+
def get_in_transit_warehouse(company):
if not frappe.db.exists("Warehouse Type", "Transit"):
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..9cad8f6 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
@@ -250,6 +250,7 @@
for d in self.entries:
available_qty = 0
+
if self.has_serial_no:
d.incoming_rate = abs(sn_obj.serial_no_incoming_rate.get(d.serial_no, 0.0))
else:
@@ -892,6 +893,13 @@
elif batch_nos:
self.set("entries", batch_nos)
+ def delete_serial_batch_entries(self):
+ SBBE = frappe.qb.DocType("Serial and Batch Entry")
+
+ frappe.qb.from_(SBBE).delete().where(SBBE.parent == self.name).run()
+
+ self.set("entries", [])
+
@frappe.whitelist()
def download_blank_csv_template(content):
@@ -999,9 +1007,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)
@@ -1358,10 +1382,12 @@
elif kwargs.based_on == "Expiry":
order_by = "amc_expiry_date asc"
- filters = {"item_code": kwargs.item_code, "warehouse": ("is", "set")}
+ filters = {"item_code": kwargs.item_code}
- if kwargs.warehouse:
- filters["warehouse"] = kwargs.warehouse
+ if not kwargs.get("ignore_warehouse"):
+ filters["warehouse"] = ("is", "set")
+ if kwargs.warehouse:
+ filters["warehouse"] = kwargs.warehouse
# Since SLEs are not present against Reserved Stock [POS invoices, SRE], need to ignore reserved serial nos.
ignore_serial_nos = get_reserved_serial_nos(kwargs)
@@ -2080,5 +2106,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_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 6819968..788ae0d 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -156,6 +156,7 @@
"warehouse": item.warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
+ "ignore_warehouse": 1,
}
)
)
@@ -780,7 +781,20 @@
current_qty = 0.0
if row.current_serial_and_batch_bundle:
- current_qty = self.get_qty_for_serial_and_batch_bundle(row)
+ current_qty = self.get_current_qty_for_serial_or_batch(row)
+ elif row.serial_no:
+ item_dict = get_stock_balance_for(
+ row.item_code,
+ row.warehouse,
+ self.posting_date,
+ self.posting_time,
+ voucher_no=self.name,
+ )
+
+ current_qty = item_dict.get("qty")
+ row.current_serial_no = item_dict.get("serial_nos")
+ row.current_valuation_rate = item_dict.get("rate")
+ val_rate = item_dict.get("rate")
elif row.batch_no:
current_qty = get_batch_qty_for_stock_reco(
row.item_code, row.warehouse, row.batch_no, self.posting_date, self.posting_time, self.name
@@ -788,15 +802,16 @@
precesion = row.precision("current_qty")
if flt(current_qty, precesion) != flt(row.current_qty, precesion):
- val_rate = get_valuation_rate(
- row.item_code,
- row.warehouse,
- self.doctype,
- self.name,
- company=self.company,
- batch_no=row.batch_no,
- serial_and_batch_bundle=row.current_serial_and_batch_bundle,
- )
+ if not row.serial_no:
+ val_rate = get_valuation_rate(
+ row.item_code,
+ row.warehouse,
+ self.doctype,
+ self.name,
+ company=self.company,
+ batch_no=row.batch_no,
+ serial_and_batch_bundle=row.current_serial_and_batch_bundle,
+ )
row.current_valuation_rate = val_rate
row.current_qty = current_qty
@@ -842,11 +857,56 @@
return allow_negative_stock
- def get_qty_for_serial_and_batch_bundle(self, row):
+ def get_current_qty_for_serial_or_batch(self, row):
doc = frappe.get_doc("Serial and Batch Bundle", row.current_serial_and_batch_bundle)
- precision = doc.entries[0].precision("qty")
+ current_qty = 0.0
+ if doc.has_serial_no:
+ current_qty = self.get_current_qty_for_serial_nos(doc)
+ elif doc.has_batch_no:
+ current_qty = self.get_current_qty_for_batch_nos(doc)
- current_qty = 0
+ return abs(current_qty)
+
+ def get_current_qty_for_serial_nos(self, doc):
+ serial_nos_details = get_available_serial_nos(
+ frappe._dict(
+ {
+ "item_code": doc.item_code,
+ "warehouse": doc.warehouse,
+ "posting_date": self.posting_date,
+ "posting_time": self.posting_time,
+ "voucher_no": self.name,
+ "ignore_warehouse": 1,
+ }
+ )
+ )
+
+ if not serial_nos_details:
+ return 0.0
+
+ doc.delete_serial_batch_entries()
+ current_qty = 0.0
+ for serial_no_row in serial_nos_details:
+ current_qty += 1
+ doc.append(
+ "entries",
+ {
+ "serial_no": serial_no_row.serial_no,
+ "qty": -1,
+ "warehouse": doc.warehouse,
+ "batch_no": serial_no_row.batch_no,
+ },
+ )
+
+ doc.set_incoming_rate(save=True)
+ doc.calculate_qty_and_amount(save=True)
+ doc.db_update_all()
+
+ return current_qty
+
+ def get_current_qty_for_batch_nos(self, doc):
+ current_qty = 0.0
+ precision = doc.entries[0].precision("qty")
for d in doc.entries:
qty = (
get_batch_qty(
@@ -864,7 +924,7 @@
current_qty += qty
- return abs(current_qty)
+ return current_qty
def get_batch_qty_for_stock_reco(
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index 70e9fb2..0bbfed4 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -925,6 +925,74 @@
self.assertEqual(len(serial_batch_bundle), 0)
+ def test_backdated_purchase_receipt_with_stock_reco(self):
+ item_code = self.make_item(
+ properties={
+ "is_stock_item": 1,
+ "has_serial_no": 1,
+ "serial_no_series": "TEST-SERIAL-.###",
+ }
+ ).name
+
+ warehouse = "_Test Warehouse - _TC"
+
+ # Step - 1: Create a Backdated Purchase Receipt
+
+ pr1 = make_purchase_receipt(
+ item_code=item_code, warehouse=warehouse, qty=10, rate=100, posting_date=add_days(nowdate(), -3)
+ )
+ pr1.reload()
+
+ serial_nos = sorted(get_serial_nos_from_bundle(pr1.items[0].serial_and_batch_bundle))[:5]
+
+ # Step - 2: Create a Stock Reconciliation
+ sr1 = create_stock_reconciliation(
+ item_code=item_code,
+ warehouse=warehouse,
+ qty=5,
+ serial_no=serial_nos,
+ )
+
+ data = frappe.get_all(
+ "Stock Ledger Entry",
+ fields=["serial_no", "actual_qty", "stock_value_difference"],
+ filters={"voucher_no": sr1.name, "is_cancelled": 0},
+ order_by="creation",
+ )
+
+ for d in data:
+ if d.actual_qty < 0:
+ self.assertEqual(d.actual_qty, -10.0)
+ self.assertAlmostEqual(d.stock_value_difference, -1000.0)
+ else:
+ self.assertEqual(d.actual_qty, 5.0)
+ self.assertAlmostEqual(d.stock_value_difference, 500.0)
+
+ # Step - 3: Create a Purchase Receipt before the first Purchase Receipt
+ make_purchase_receipt(
+ item_code=item_code, warehouse=warehouse, qty=10, rate=200, posting_date=add_days(nowdate(), -5)
+ )
+
+ data = frappe.get_all(
+ "Stock Ledger Entry",
+ fields=["serial_no", "actual_qty", "stock_value_difference"],
+ filters={"voucher_no": sr1.name, "is_cancelled": 0},
+ order_by="creation",
+ )
+
+ for d in data:
+ if d.actual_qty < 0:
+ self.assertEqual(d.actual_qty, -20.0)
+ self.assertAlmostEqual(d.stock_value_difference, -3000.0)
+ else:
+ self.assertEqual(d.actual_qty, 5.0)
+ self.assertAlmostEqual(d.stock_value_difference, 500.0)
+
+ active_serial_no = frappe.get_all(
+ "Serial No", filters={"status": "Active", "item_code": item_code}
+ )
+ self.assertEqual(len(active_serial_no), 5)
+
def create_batch_item_with_batch(item_name, batch_id):
batch_item_doc = create_item(item_name, is_stock_item=1)
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 4cd9cbb..276531a 100644
--- a/erpnext/stock/reorder_item.py
+++ b/erpnext/stock/reorder_item.py
@@ -145,6 +145,7 @@
mr.log_error("Unable to create material request")
+ company_wise_mr = frappe._dict({})
for request_type in material_requests:
for company in material_requests[request_type]:
try:
@@ -206,17 +207,19 @@
mr.submit()
mr_list.append(mr)
+ company_wise_mr.setdefault(company, []).append(mr)
+
except Exception:
_log_exception(mr)
- if mr_list:
+ if company_wise_mr:
if getattr(frappe.local, "reorder_email_notify", None) is None:
frappe.local.reorder_email_notify = cint(
frappe.db.get_single_value("Stock Settings", "reorder_email_notify")
)
if frappe.local.reorder_email_notify:
- send_email_notification(mr_list)
+ send_email_notification(company_wise_mr)
if exceptions_list:
notify_errors(exceptions_list)
@@ -224,20 +227,56 @@
return mr_list
-def send_email_notification(mr_list):
+def send_email_notification(company_wise_mr):
"""Notify user about auto creation of indent"""
- email_list = frappe.db.sql_list(
- """select distinct r.parent
- from `tabHas Role` r, tabUser p
- where p.name = r.parent and p.enabled = 1 and p.docstatus < 2
- and r.role in ('Purchase Manager','Stock Manager')
- and p.name not in ('Administrator', 'All', 'Guest')"""
+ for company, mr_list in company_wise_mr.items():
+ email_list = get_email_list(company)
+
+ if not email_list:
+ continue
+
+ msg = frappe.render_template("templates/emails/reorder_item.html", {"mr_list": mr_list})
+
+ frappe.sendmail(
+ recipients=email_list, subject=_("Auto Material Requests Generated"), message=msg
+ )
+
+
+def get_email_list(company):
+ users = get_comapny_wise_users(company)
+ user_table = frappe.qb.DocType("User")
+ role_table = frappe.qb.DocType("Has Role")
+
+ query = (
+ frappe.qb.from_(user_table)
+ .inner_join(role_table)
+ .on(user_table.name == role_table.parent)
+ .select(user_table.email)
+ .where(
+ (role_table.role.isin(["Purchase Manager", "Stock Manager"]))
+ & (user_table.name.notin(["Administrator", "All", "Guest"]))
+ & (user_table.enabled == 1)
+ & (user_table.docstatus < 2)
+ )
)
- msg = frappe.render_template("templates/emails/reorder_item.html", {"mr_list": mr_list})
+ if users:
+ query = query.where(user_table.name.isin(users))
- frappe.sendmail(recipients=email_list, subject=_("Auto Material Requests Generated"), message=msg)
+ emails = query.run(as_dict=True)
+
+ return list(set([email.email for email in emails]))
+
+
+def get_comapny_wise_users(company):
+ users = frappe.get_all(
+ "User Permission",
+ filters={"allow": "Company", "for_value": company, "apply_to_all_doctypes": 1},
+ fields=["user"],
+ )
+
+ return [user.user for user in users]
def notify_errors(exceptions_list):
@@ -246,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/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index a6206ac..45764f3 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -9,9 +9,18 @@
import frappe
from frappe import _, scrub
from frappe.model.meta import get_field_precision
-from frappe.query_builder import Case
from frappe.query_builder.functions import CombineDatetime, Sum
-from frappe.utils import cint, flt, get_link_to_form, getdate, now, nowdate, nowtime, parse_json
+from frappe.utils import (
+ cint,
+ cstr,
+ flt,
+ get_link_to_form,
+ getdate,
+ now,
+ nowdate,
+ nowtime,
+ parse_json,
+)
import erpnext
from erpnext.stock.doctype.bin.bin import update_qty as update_bin_qty
@@ -439,7 +448,7 @@
reposting_data = get_reposting_data(doc.reposting_data_file)
if reposting_data and reposting_data.distinct_item_and_warehouse:
- return reposting_data.distinct_item_and_warehouse
+ return parse_distinct_items_and_warehouses(reposting_data.distinct_item_and_warehouse)
distinct_item_warehouses = {}
@@ -457,6 +466,16 @@
return distinct_item_warehouses
+def parse_distinct_items_and_warehouses(distinct_items_and_warehouses):
+ new_dict = frappe._dict({})
+
+ # convert string keys to tuple
+ for k, v in distinct_items_and_warehouses.items():
+ new_dict[frappe.safe_eval(k)] = frappe._dict(v)
+
+ return new_dict
+
+
def get_affected_transactions(doc, reposting_data=None) -> Set[Tuple[str, str]]:
if not reposting_data and doc and doc.reposting_data_file:
reposting_data = get_reposting_data(doc.reposting_data_file)
@@ -702,11 +721,10 @@
if (
sle.voucher_type == "Stock Reconciliation"
- and (
- sle.batch_no or (sle.has_batch_no and sle.serial_and_batch_bundle and not sle.has_serial_no)
- )
+ and (sle.batch_no or sle.serial_no or sle.serial_and_batch_bundle)
and sle.voucher_detail_no
and not self.args.get("sle_id")
+ and sle.is_cancelled == 0
):
self.reset_actual_qty_for_stock_reco(sle)
@@ -727,6 +745,23 @@
if sle.serial_and_batch_bundle:
self.calculate_valuation_for_serial_batch_bundle(sle)
+ elif sle.serial_no and not self.args.get("sle_id"):
+ # Only run in reposting
+ self.get_serialized_values(sle)
+ self.wh_data.qty_after_transaction += flt(sle.actual_qty)
+ if sle.voucher_type == "Stock Reconciliation" and not sle.batch_no:
+ self.wh_data.qty_after_transaction = sle.qty_after_transaction
+
+ self.wh_data.stock_value = flt(self.wh_data.qty_after_transaction) * flt(
+ self.wh_data.valuation_rate
+ )
+ elif (
+ sle.batch_no
+ and frappe.db.get_value("Batch", sle.batch_no, "use_batchwise_valuation", cache=True)
+ and not self.args.get("sle_id")
+ ):
+ # Only run in reposting
+ self.update_batched_values(sle)
else:
if sle.voucher_type == "Stock Reconciliation" and not sle.batch_no and not has_dimensions:
# assert
@@ -772,6 +807,45 @@
):
self.update_outgoing_rate_on_transaction(sle)
+ def get_serialized_values(self, sle):
+ incoming_rate = flt(sle.incoming_rate)
+ actual_qty = flt(sle.actual_qty)
+ serial_nos = cstr(sle.serial_no).split("\n")
+
+ if incoming_rate < 0:
+ # wrong incoming rate
+ incoming_rate = self.wh_data.valuation_rate
+
+ stock_value_change = 0
+ if actual_qty > 0:
+ stock_value_change = actual_qty * incoming_rate
+ else:
+ # In case of delivery/stock issue, get average purchase rate
+ # of serial nos of current entry
+ if not sle.is_cancelled:
+ outgoing_value = self.get_incoming_value_for_serial_nos(sle, serial_nos)
+ stock_value_change = -1 * outgoing_value
+ else:
+ stock_value_change = actual_qty * sle.outgoing_rate
+
+ new_stock_qty = self.wh_data.qty_after_transaction + actual_qty
+
+ if new_stock_qty > 0:
+ new_stock_value = (
+ self.wh_data.qty_after_transaction * self.wh_data.valuation_rate
+ ) + stock_value_change
+ if new_stock_value >= 0:
+ # calculate new valuation rate only if stock value is positive
+ # else it remains the same as that of previous entry
+ self.wh_data.valuation_rate = new_stock_value / new_stock_qty
+
+ if not self.wh_data.valuation_rate and sle.voucher_detail_no:
+ allow_zero_rate = self.check_if_allow_zero_valuation_rate(
+ sle.voucher_type, sle.voucher_detail_no
+ )
+ if not allow_zero_rate:
+ self.wh_data.valuation_rate = self.get_fallback_rate(sle)
+
def reset_actual_qty_for_stock_reco(self, sle):
doc = frappe.get_cached_doc("Stock Reconciliation", sle.voucher_no)
doc.recalculate_current_qty(sle.voucher_detail_no, sle.creation, sle.actual_qty > 0)
@@ -785,6 +859,36 @@
if abs(sle.actual_qty) == 0.0:
sle.is_cancelled = 1
+ if sle.serial_and_batch_bundle and frappe.get_cached_value(
+ "Item", sle.item_code, "has_serial_no"
+ ):
+ self.update_serial_no_status(sle)
+
+ def update_serial_no_status(self, sle):
+ from erpnext.stock.serial_batch_bundle import get_serial_nos
+
+ serial_nos = get_serial_nos(sle.serial_and_batch_bundle)
+ if not serial_nos:
+ return
+
+ warehouse = None
+ status = "Inactive"
+
+ if sle.actual_qty > 0:
+ warehouse = sle.warehouse
+ status = "Active"
+
+ sn_table = frappe.qb.DocType("Serial No")
+
+ query = (
+ frappe.qb.update(sn_table)
+ .set(sn_table.warehouse, warehouse)
+ .set(sn_table.status, status)
+ .where(sn_table.name.isin(serial_nos))
+ )
+
+ query.run()
+
def calculate_valuation_for_serial_batch_bundle(self, sle):
doc = frappe.get_cached_doc("Serial and Batch Bundle", sle.serial_and_batch_bundle)
@@ -1161,11 +1265,12 @@
outgoing_rate = get_batch_incoming_rate(
item_code=sle.item_code,
warehouse=sle.warehouse,
- serial_and_batch_bundle=sle.serial_and_batch_bundle,
+ batch_no=sle.batch_no,
posting_date=sle.posting_date,
posting_time=sle.posting_time,
creation=sle.creation,
)
+
if outgoing_rate is None:
# This can *only* happen if qty available for the batch is zero.
# in such case fall back various other rates.
@@ -1439,11 +1544,10 @@
def get_batch_incoming_rate(
- item_code, warehouse, serial_and_batch_bundle, posting_date, posting_time, creation=None
+ item_code, warehouse, batch_no, posting_date, posting_time, creation=None
):
sle = frappe.qb.DocType("Stock Ledger Entry")
- batch_ledger = frappe.qb.DocType("Serial and Batch Entry")
timestamp_condition = CombineDatetime(sle.posting_date, sle.posting_time) < CombineDatetime(
posting_date, posting_time
@@ -1454,28 +1558,13 @@
== CombineDatetime(posting_date, posting_time)
) & (sle.creation < creation)
- batches = frappe.get_all(
- "Serial and Batch Entry", fields=["batch_no"], filters={"parent": serial_and_batch_bundle}
- )
-
batch_details = (
frappe.qb.from_(sle)
- .inner_join(batch_ledger)
- .on(sle.serial_and_batch_bundle == batch_ledger.parent)
- .select(
- Sum(
- Case()
- .when(sle.actual_qty > 0, batch_ledger.qty * batch_ledger.incoming_rate)
- .else_(batch_ledger.qty * batch_ledger.outgoing_rate * -1)
- ).as_("batch_value"),
- Sum(Case().when(sle.actual_qty > 0, batch_ledger.qty).else_(batch_ledger.qty * -1)).as_(
- "batch_qty"
- ),
- )
+ .select(Sum(sle.stock_value_difference).as_("batch_value"), Sum(sle.actual_qty).as_("batch_qty"))
.where(
(sle.item_code == item_code)
& (sle.warehouse == warehouse)
- & (batch_ledger.batch_no.isin([row.batch_no for row in batches]))
+ & (sle.batch_no == batch_no)
& (sle.is_cancelled == 0)
)
.where(timestamp_condition)
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": [