Merge pull request #44741 from frappe/version-15-hotfix

chore: release v15
This commit is contained in:
ruthra kumar
2024-12-18 11:01:28 +05:30
committed by GitHub
22 changed files with 241 additions and 91 deletions

View File

@@ -6,7 +6,7 @@ cd ~ || exit
sudo apt update sudo apt update
sudo apt remove mysql-server mysql-client sudo apt remove mysql-server mysql-client
sudo apt install libcups2-dev redis-server mariadb-client-10.6 sudo apt install libcups2-dev redis-server mariadb-client
pip install frappe-bench pip install frappe-bench
@@ -44,13 +44,9 @@ fi
install_whktml() { install_whktml() {
if [ "$(lsb_release -rs)" = "22.04" ]; then wget -O /tmp/wkhtmltox.deb https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-2/wkhtmltox_0.12.6.1-2.jammy_amd64.deb
wget -O /tmp/wkhtmltox.deb https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-2/wkhtmltox_0.12.6.1-2.jammy_amd64.deb sudo apt install /tmp/wkhtmltox.deb
sudo apt install /tmp/wkhtmltox.deb
else
echo "Please update this script to support wkhtmltopdf for $(lsb_release -ds)"
exit 1
fi
} }
install_whktml & install_whktml &
wkpid=$! wkpid=$!

View File

@@ -1328,6 +1328,24 @@ frappe.ui.form.on("Payment Entry", {
if (r.message) { if (r.message) {
if (!frm.doc.mode_of_payment) { if (!frm.doc.mode_of_payment) {
frm.set_value(field, r.message.account); frm.set_value(field, r.message.account);
} else {
frappe.call({
method: "frappe.client.get_value",
args: {
doctype: "Mode of Payment Account",
filters: {
parent: frm.doc.mode_of_payment,
company: frm.doc.company,
},
fieldname: "default_account",
parent: "Mode of Payment",
},
callback: function (res) {
if (!res.message.default_account) {
frm.set_value(field, r.message.account);
}
},
});
} }
frm.set_value("bank", r.message.bank); frm.set_value("bank", r.message.bank);
frm.set_value("bank_account_no", r.message.bank_account_no); frm.set_value("bank_account_no", r.message.bank_account_no);

View File

@@ -117,12 +117,12 @@ class POSInvoiceMergeLog(Document):
sales = [d for d in pos_invoice_docs if d.get("is_return") == 0] sales = [d for d in pos_invoice_docs if d.get("is_return") == 0]
sales_invoice, credit_note = "", "" sales_invoice, credit_note = "", ""
if returns:
credit_note = self.process_merging_into_credit_note(returns)
if sales: if sales:
sales_invoice = self.process_merging_into_sales_invoice(sales) sales_invoice = self.process_merging_into_sales_invoice(sales)
if returns:
credit_note = self.process_merging_into_credit_note(returns, sales_invoice)
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note) self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note)
@@ -132,6 +132,7 @@ class POSInvoiceMergeLog(Document):
self.update_pos_invoices(pos_invoice_docs) self.update_pos_invoices(pos_invoice_docs)
self.serial_and_batch_bundle_reference_for_pos_invoice() self.serial_and_batch_bundle_reference_for_pos_invoice()
self.cancel_linked_invoices() self.cancel_linked_invoices()
self.delink_serial_and_batch_bundle()
def process_merging_into_sales_invoice(self, data): def process_merging_into_sales_invoice(self, data):
sales_invoice = self.get_new_sales_invoice() sales_invoice = self.get_new_sales_invoice()
@@ -139,8 +140,13 @@ class POSInvoiceMergeLog(Document):
sales_invoice.is_consolidated = 1 sales_invoice.is_consolidated = 1
sales_invoice.set_posting_time = 1 sales_invoice.set_posting_time = 1
sales_invoice.posting_date = getdate(self.posting_date)
sales_invoice.posting_time = get_time(self.posting_time) if not sales_invoice.posting_date:
sales_invoice.posting_date = getdate(self.posting_date)
if not sales_invoice.posting_time:
sales_invoice.posting_time = get_time(self.posting_time)
sales_invoice.save() sales_invoice.save()
sales_invoice.submit() sales_invoice.submit()
@@ -148,12 +154,14 @@ class POSInvoiceMergeLog(Document):
return sales_invoice.name return sales_invoice.name
def process_merging_into_credit_note(self, data): def process_merging_into_credit_note(self, data, sales_invoice):
credit_note = self.get_new_sales_invoice() credit_note = self.get_new_sales_invoice()
credit_note.is_return = 1 credit_note.is_return = 1
credit_note = self.merge_pos_invoice_into(credit_note, data) credit_note = self.merge_pos_invoice_into(credit_note, data)
credit_note.return_against = sales_invoice
credit_note.is_consolidated = 1 credit_note.is_consolidated = 1
credit_note.set_posting_time = 1 credit_note.set_posting_time = 1
credit_note.posting_date = getdate(self.posting_date) credit_note.posting_date = getdate(self.posting_date)
@@ -180,6 +188,10 @@ class POSInvoiceMergeLog(Document):
for doc in data: for doc in data:
map_doc(doc, invoice, table_map={"doctype": invoice.doctype}) map_doc(doc, invoice, table_map={"doctype": invoice.doctype})
if doc.get("posting_date"):
invoice.posting_date = getdate(doc.posting_date)
invoice.posting_time = get_time(doc.posting_time)
if doc.redeem_loyalty_points: if doc.redeem_loyalty_points:
invoice.loyalty_redemption_account = doc.loyalty_redemption_account invoice.loyalty_redemption_account = doc.loyalty_redemption_account
invoice.loyalty_redemption_cost_center = doc.loyalty_redemption_cost_center invoice.loyalty_redemption_cost_center = doc.loyalty_redemption_cost_center
@@ -297,6 +309,8 @@ class POSInvoiceMergeLog(Document):
sales_invoice = frappe.new_doc("Sales Invoice") sales_invoice = frappe.new_doc("Sales Invoice")
sales_invoice.customer = self.customer sales_invoice.customer = self.customer
sales_invoice.is_pos = 1 sales_invoice.is_pos = 1
sales_invoice.posting_date = None
sales_invoice.posting_time = None
return sales_invoice return sales_invoice
@@ -319,6 +333,38 @@ class POSInvoiceMergeLog(Document):
for table_name in ["items", "packed_items"]: for table_name in ["items", "packed_items"]:
pos_invoice.set_serial_and_batch_bundle(table_name) pos_invoice.set_serial_and_batch_bundle(table_name)
def delink_serial_and_batch_bundle(self):
bundles = self.get_serial_and_batch_bundles()
if not bundles:
return
sle_table = frappe.qb.DocType("Stock Ledger Entry")
query = (
frappe.qb.update(sle_table)
.set(sle_table.serial_and_batch_bundle, None)
.where(sle_table.serial_and_batch_bundle.isin(bundles) & sle_table.is_cancelled == 1)
)
query.run()
def get_serial_and_batch_bundles(self):
pos_invoices = []
for d in self.pos_invoices:
pos_invoices.append(d.pos_invoice)
if pos_invoices:
return frappe.get_all(
"POS Invoice Item",
filters={
"docstatus": 1,
"parent": ["in", pos_invoices],
"serial_and_batch_bundle": ["is", "set"],
},
pluck="serial_and_batch_bundle",
)
return []
def cancel_linked_invoices(self): def cancel_linked_invoices(self):
for si_name in [self.consolidated_invoice, self.consolidated_credit_note]: for si_name in [self.consolidated_invoice, self.consolidated_credit_note]:
if not si_name: if not si_name:
@@ -503,6 +549,9 @@ def cancel_merge_logs(merge_logs, closing_entry=None):
try: try:
for log in merge_logs: for log in merge_logs:
merge_log = frappe.get_doc("POS Invoice Merge Log", log) merge_log = frappe.get_doc("POS Invoice Merge Log", log)
if merge_log.docstatus == 2:
continue
merge_log.flags.ignore_permissions = True merge_log.flags.ignore_permissions = True
merge_log.cancel() merge_log.cancel()

View File

@@ -415,6 +415,8 @@ def get_pricing_rule_for_item(args, doc=None, for_validate=False):
"parent": args.parent, "parent": args.parent,
"parenttype": args.parenttype, "parenttype": args.parenttype,
"child_docname": args.get("child_docname"), "child_docname": args.get("child_docname"),
"discount_percentage": 0.0,
"discount_amount": 0,
} }
) )

View File

@@ -244,9 +244,9 @@
}, },
{ {
"fieldname": "cc_to", "fieldname": "cc_to",
"fieldtype": "Link", "fieldtype": "Table MultiSelect",
"label": "CC To", "label": "CC To",
"options": "User" "options": "Process Statement Of Accounts CC"
}, },
{ {
"default": "1", "default": "1",
@@ -400,7 +400,7 @@
} }
], ],
"links": [], "links": [],
"modified": "2024-10-18 17:51:39.108481", "modified": "2024-12-11 12:11:13.543134",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Process Statement Of Accounts", "name": "Process Statement Of Accounts",

View File

@@ -31,6 +31,9 @@ class ProcessStatementOfAccounts(Document):
if TYPE_CHECKING: if TYPE_CHECKING:
from frappe.types import DF from frappe.types import DF
from erpnext.accounts.doctype.process_statement_of_accounts_cc.process_statement_of_accounts_cc import (
ProcessStatementOfAccountsCC,
)
from erpnext.accounts.doctype.process_statement_of_accounts_customer.process_statement_of_accounts_customer import ( from erpnext.accounts.doctype.process_statement_of_accounts_customer.process_statement_of_accounts_customer import (
ProcessStatementOfAccountsCustomer, ProcessStatementOfAccountsCustomer,
) )
@@ -41,7 +44,7 @@ class ProcessStatementOfAccounts(Document):
ageing_based_on: DF.Literal["Due Date", "Posting Date"] ageing_based_on: DF.Literal["Due Date", "Posting Date"]
based_on_payment_terms: DF.Check based_on_payment_terms: DF.Check
body: DF.TextEditor | None body: DF.TextEditor | None
cc_to: DF.Link | None cc_to: DF.TableMultiSelect[ProcessStatementOfAccountsCC]
collection_name: DF.DynamicLink | None collection_name: DF.DynamicLink | None
company: DF.Link company: DF.Link
cost_center: DF.TableMultiSelect[PSOACostCenter] cost_center: DF.TableMultiSelect[PSOACostCenter]
@@ -324,7 +327,7 @@ def get_recipients_and_cc(customer, doc):
cc = [] cc = []
if doc.cc_to != "": if doc.cc_to != "":
try: try:
cc = [frappe.get_value("User", doc.cc_to, "email")] cc = [frappe.get_value("User", user.cc, "email") for user in doc.cc_to]
except Exception: except Exception:
pass pass

View File

@@ -0,0 +1,32 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2024-12-11 12:10:04.654593",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"cc"
],
"fields": [
{
"fieldname": "cc",
"fieldtype": "Link",
"in_list_view": 1,
"label": "CC",
"options": "User"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-12-11 12:10:39.772598",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Process Statement Of Accounts CC",
"owner": "Administrator",
"permissions": [],
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,23 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class ProcessStatementOfAccountsCC(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
cc: DF.Link | None
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
# end: auto-generated types
pass

View File

@@ -551,9 +551,7 @@ class ReceivablePayableReport:
self.append_payment_term(row, d, term) self.append_payment_term(row, d, term)
def append_payment_term(self, row, d, term): def append_payment_term(self, row, d, term):
if ( if d.currency == d.party_account_currency:
self.filters.get("customer") or self.filters.get("supplier")
) and d.currency == d.party_account_currency:
invoiced = d.payment_amount invoiced = d.payment_amount
else: else:
invoiced = d.base_payment_amount invoiced = d.base_payment_amount

View File

@@ -28,15 +28,14 @@ def get_group_by_asset_category_data(filters):
for asset_category in asset_categories: for asset_category in asset_categories:
row = frappe._dict() row = frappe._dict()
# row.asset_category = asset_category
row.update(asset_category) row.update(asset_category)
row.cost_as_on_to_date = ( row.value_as_on_to_date = (
flt(row.cost_as_on_from_date) flt(row.value_as_on_from_date)
+ flt(row.cost_of_new_purchase) + flt(row.value_of_new_purchase)
- flt(row.cost_of_sold_asset) - flt(row.value_of_sold_asset)
- flt(row.cost_of_scrapped_asset) - flt(row.value_of_scrapped_asset)
- flt(row.cost_of_capitalized_asset) - flt(row.value_of_capitalized_asset)
) )
row.update( row.update(
@@ -53,11 +52,11 @@ def get_group_by_asset_category_data(filters):
- flt(row.depreciation_eliminated_during_the_period) - flt(row.depreciation_eliminated_during_the_period)
) )
row.net_asset_value_as_on_from_date = flt(row.cost_as_on_from_date) - flt( row.net_asset_value_as_on_from_date = flt(row.value_as_on_from_date) - flt(
row.accumulated_depreciation_as_on_from_date row.accumulated_depreciation_as_on_from_date
) )
row.net_asset_value_as_on_to_date = flt(row.cost_as_on_to_date) - flt( row.net_asset_value_as_on_to_date = flt(row.value_as_on_to_date) - flt(
row.accumulated_depreciation_as_on_to_date row.accumulated_depreciation_as_on_to_date
) )
@@ -85,12 +84,12 @@ def get_asset_categories_for_grouped_by_category(filters):
end end
else else
0 0
end), 0) as cost_as_on_from_date, end), 0) as value_as_on_from_date,
ifnull(sum(case when a.purchase_date >= %(from_date)s then ifnull(sum(case when a.purchase_date >= %(from_date)s then
a.gross_purchase_amount a.gross_purchase_amount
else else
0 0
end), 0) as cost_of_new_purchase, end), 0) as value_of_new_purchase,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 ifnull(sum(case when ifnull(a.disposal_date, 0) != 0
and a.disposal_date >= %(from_date)s and a.disposal_date >= %(from_date)s
and a.disposal_date <= %(to_date)s then and a.disposal_date <= %(to_date)s then
@@ -101,7 +100,7 @@ def get_asset_categories_for_grouped_by_category(filters):
end end
else else
0 0
end), 0) as cost_of_sold_asset, end), 0) as value_of_sold_asset,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 ifnull(sum(case when ifnull(a.disposal_date, 0) != 0
and a.disposal_date >= %(from_date)s and a.disposal_date >= %(from_date)s
and a.disposal_date <= %(to_date)s then and a.disposal_date <= %(to_date)s then
@@ -112,7 +111,7 @@ def get_asset_categories_for_grouped_by_category(filters):
end end
else else
0 0
end), 0) as cost_of_scrapped_asset, end), 0) as value_of_scrapped_asset,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 ifnull(sum(case when ifnull(a.disposal_date, 0) != 0
and a.disposal_date >= %(from_date)s and a.disposal_date >= %(from_date)s
and a.disposal_date <= %(to_date)s then and a.disposal_date <= %(to_date)s then
@@ -123,7 +122,7 @@ def get_asset_categories_for_grouped_by_category(filters):
end end
else else
0 0
end), 0) as cost_of_capitalized_asset end), 0) as value_of_capitalized_asset
from `tabAsset` a from `tabAsset` a
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {condition} where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {condition}
and not exists( and not exists(
@@ -164,12 +163,12 @@ def get_asset_details_for_grouped_by_category(filters):
end end
else else
0 0
end), 0) as cost_as_on_from_date, end), 0) as value_as_on_from_date,
ifnull(sum(case when a.purchase_date >= %(from_date)s then ifnull(sum(case when a.purchase_date >= %(from_date)s then
a.gross_purchase_amount a.gross_purchase_amount
else else
0 0
end), 0) as cost_of_new_purchase, end), 0) as value_of_new_purchase,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 ifnull(sum(case when ifnull(a.disposal_date, 0) != 0
and a.disposal_date >= %(from_date)s and a.disposal_date >= %(from_date)s
and a.disposal_date <= %(to_date)s then and a.disposal_date <= %(to_date)s then
@@ -180,7 +179,7 @@ def get_asset_details_for_grouped_by_category(filters):
end end
else else
0 0
end), 0) as cost_of_sold_asset, end), 0) as value_of_sold_asset,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 ifnull(sum(case when ifnull(a.disposal_date, 0) != 0
and a.disposal_date >= %(from_date)s and a.disposal_date >= %(from_date)s
and a.disposal_date <= %(to_date)s then and a.disposal_date <= %(to_date)s then
@@ -191,7 +190,7 @@ def get_asset_details_for_grouped_by_category(filters):
end end
else else
0 0
end), 0) as cost_of_scrapped_asset, end), 0) as value_of_scrapped_asset,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 ifnull(sum(case when ifnull(a.disposal_date, 0) != 0
and a.disposal_date >= %(from_date)s and a.disposal_date >= %(from_date)s
and a.disposal_date <= %(to_date)s then and a.disposal_date <= %(to_date)s then
@@ -202,7 +201,7 @@ def get_asset_details_for_grouped_by_category(filters):
end end
else else
0 0
end), 0) as cost_of_capitalized_asset end), 0) as value_of_capitalized_asset
from `tabAsset` a from `tabAsset` a
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {condition} where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {condition}
and not exists( and not exists(
@@ -232,15 +231,14 @@ def get_group_by_asset_data(filters):
for asset_detail in asset_details: for asset_detail in asset_details:
row = frappe._dict() row = frappe._dict()
# row.asset_category = asset_category
row.update(asset_detail) row.update(asset_detail)
row.cost_as_on_to_date = ( row.value_as_on_to_date = (
flt(row.cost_as_on_from_date) flt(row.value_as_on_from_date)
+ flt(row.cost_of_new_purchase) + flt(row.value_of_new_purchase)
- flt(row.cost_of_sold_asset) - flt(row.value_of_sold_asset)
- flt(row.cost_of_scrapped_asset) - flt(row.value_of_scrapped_asset)
- flt(row.cost_of_capitalized_asset) - flt(row.value_of_capitalized_asset)
) )
row.update(next(asset for asset in assets if asset["asset"] == asset_detail.get("name", ""))) row.update(next(asset for asset in assets if asset["asset"] == asset_detail.get("name", "")))
@@ -251,11 +249,11 @@ def get_group_by_asset_data(filters):
- flt(row.depreciation_eliminated_during_the_period) - flt(row.depreciation_eliminated_during_the_period)
) )
row.net_asset_value_as_on_from_date = flt(row.cost_as_on_from_date) - flt( row.net_asset_value_as_on_from_date = flt(row.value_as_on_from_date) - flt(
row.accumulated_depreciation_as_on_from_date row.accumulated_depreciation_as_on_from_date
) )
row.net_asset_value_as_on_to_date = flt(row.cost_as_on_to_date) - flt( row.net_asset_value_as_on_to_date = flt(row.value_as_on_to_date) - flt(
row.accumulated_depreciation_as_on_to_date row.accumulated_depreciation_as_on_to_date
) )
@@ -446,38 +444,38 @@ def get_columns(filters):
columns += [ columns += [
{ {
"label": _("Cost as on") + " " + formatdate(filters.day_before_from_date), "label": _("Value as on") + " " + formatdate(filters.day_before_from_date),
"fieldname": "cost_as_on_from_date", "fieldname": "value_as_on_from_date",
"fieldtype": "Currency", "fieldtype": "Currency",
"width": 140, "width": 140,
}, },
{ {
"label": _("Cost of New Purchase"), "label": _("Value of New Purchase"),
"fieldname": "cost_of_new_purchase", "fieldname": "value_of_new_purchase",
"fieldtype": "Currency", "fieldtype": "Currency",
"width": 140, "width": 140,
}, },
{ {
"label": _("Cost of Sold Asset"), "label": _("Value of Sold Asset"),
"fieldname": "cost_of_sold_asset", "fieldname": "value_of_sold_asset",
"fieldtype": "Currency", "fieldtype": "Currency",
"width": 140, "width": 140,
}, },
{ {
"label": _("Cost of Scrapped Asset"), "label": _("Value of Scrapped Asset"),
"fieldname": "cost_of_scrapped_asset", "fieldname": "value_of_scrapped_asset",
"fieldtype": "Currency", "fieldtype": "Currency",
"width": 140, "width": 140,
}, },
{ {
"label": _("Cost of New Capitalized Asset"), "label": _("Value of New Capitalized Asset"),
"fieldname": "cost_of_capitalized_asset", "fieldname": "value_of_capitalized_asset",
"fieldtype": "Currency", "fieldtype": "Currency",
"width": 140, "width": 140,
}, },
{ {
"label": _("Cost as on") + " " + formatdate(filters.to_date), "label": _("Value as on") + " " + formatdate(filters.to_date),
"fieldname": "cost_as_on_to_date", "fieldname": "value_as_on_to_date",
"fieldtype": "Currency", "fieldtype": "Currency",
"width": 140, "width": 140,
}, },

View File

@@ -527,9 +527,16 @@ def get_accounting_entries(
account_filter_query = get_account_filter_query(root_lft, root_rgt, root_type, gl_entry) account_filter_query = get_account_filter_query(root_lft, root_rgt, root_type, gl_entry)
query = query.where(ExistsCriterion(account_filter_query)) query = query.where(ExistsCriterion(account_filter_query))
entries = query.run(as_dict=True) from frappe.desk.reportview import build_match_conditions
return entries match_conditions = build_match_conditions(doctype)
if match_conditions:
query += "and" + match_conditions
query, params = query.walk()
return frappe.db.sql(query, params, as_dict=True)
def get_account_filter_query(root_lft, root_rgt, root_type, gl_entry): def get_account_filter_query(root_lft, root_rgt, root_type, gl_entry):

View File

@@ -790,7 +790,10 @@ class GrossProfitGenerator:
""" """
if self.filters.group_by == "Sales Person": if self.filters.group_by == "Sales Person":
sales_person_cols = ", sales.sales_person, sales.allocated_amount, sales.incentives" sales_person_cols = """, sales.sales_person,
sales.allocated_percentage * `tabSales Invoice Item`.base_net_amount / 100 as allocated_amount,
sales.incentives
"""
sales_team_table = "left join `tabSales Team` sales on sales.parent = `tabSales Invoice`.name" sales_team_table = "left join `tabSales Team` sales on sales.parent = `tabSales Invoice`.name"
else: else:
sales_person_cols = "" sales_person_cols = ""

View File

@@ -804,6 +804,9 @@ class Asset(AccountsController):
): ):
return args.get("rate_of_depreciation") return args.get("rate_of_depreciation")
if args.get("rate_of_depreciation") and not flt(args.get("expected_value_after_useful_life")):
return args.get("rate_of_depreciation")
if self.flags.increase_in_asset_value_due_to_repair: if self.flags.increase_in_asset_value_due_to_repair:
value = flt(args.get("expected_value_after_useful_life")) / flt( value = flt(args.get("expected_value_after_useful_life")) / flt(
args.get("value_after_depreciation") args.get("value_after_depreciation")

View File

@@ -86,7 +86,8 @@
"description": "In Percentage", "description": "In Percentage",
"fieldname": "rate_of_depreciation", "fieldname": "rate_of_depreciation",
"fieldtype": "Percent", "fieldtype": "Percent",
"label": "Rate of Depreciation" "label": "Rate of Depreciation (%)",
"mandatory_depends_on": "eval:doc.depreciation_method == 'Written Down Value'"
}, },
{ {
"fieldname": "salvage_value_percentage", "fieldname": "salvage_value_percentage",
@@ -117,7 +118,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2024-05-21 15:48:20.907250", "modified": "2024-12-13 12:11:03.743209",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Asset Finance Book", "name": "Asset Finance Book",

View File

@@ -76,16 +76,13 @@ def validate_return_against(doc):
def validate_returned_items(doc): def validate_returned_items(doc):
valid_items = frappe._dict() valid_items = frappe._dict()
select_fields = "item_code, qty, stock_qty, rate, parenttype, conversion_factor" select_fields = "item_code, qty, stock_qty, rate, parenttype, conversion_factor, name"
if doc.doctype != "Purchase Invoice": if doc.doctype != "Purchase Invoice":
select_fields += ",serial_no, batch_no" select_fields += ",serial_no, batch_no"
if doc.doctype in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]: if doc.doctype in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]:
select_fields += ",rejected_qty, received_qty" select_fields += ",rejected_qty, received_qty"
if doc.doctype in ["Purchase Receipt", "Purchase Invoice"]:
select_fields += ",name"
for d in frappe.db.sql( for d in frappe.db.sql(
f"""select {select_fields} from `tab{doc.doctype} Item` where parent = %s""", f"""select {select_fields} from `tab{doc.doctype} Item` where parent = %s""",
doc.return_against, doc.return_against,
@@ -113,11 +110,13 @@ def validate_returned_items(doc):
for d in doc.get("items"): for d in doc.get("items"):
key = d.item_code key = d.item_code
raise_exception = False raise_exception = False
if doc.doctype in ["Purchase Receipt", "Purchase Invoice"]: if doc.doctype in ["Purchase Receipt", "Purchase Invoice", "Sales Invoice"]:
field = frappe.scrub(doc.doctype) + "_item" field = frappe.scrub(doc.doctype) + "_item"
if d.get(field): if d.get(field):
key = (d.item_code, d.get(field)) key = (d.item_code, d.get(field))
raise_exception = True raise_exception = True
elif doc.doctype == "Delivery Note":
key = (d.item_code, d.get("dn_detail"))
if d.item_code and (flt(d.qty) < 0 or flt(d.get("received_qty")) < 0): if d.item_code and (flt(d.qty) < 0 or flt(d.get("received_qty")) < 0):
if key not in valid_items: if key not in valid_items:
@@ -129,7 +128,7 @@ def validate_returned_items(doc):
) )
else: else:
ref = valid_items.get(key, frappe._dict()) ref = valid_items.get(key, frappe._dict())
validate_quantity(doc, d, ref, valid_items, already_returned_items) validate_quantity(doc, key, d, ref, valid_items, already_returned_items)
if ( if (
ref.rate ref.rate
@@ -159,7 +158,7 @@ def validate_returned_items(doc):
frappe.throw(_("Atleast one item should be entered with negative quantity in return document")) frappe.throw(_("Atleast one item should be entered with negative quantity in return document"))
def validate_quantity(doc, args, ref, valid_items, already_returned_items): def validate_quantity(doc, key, args, ref, valid_items, already_returned_items):
fields = ["stock_qty"] fields = ["stock_qty"]
if doc.doctype in ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"]: if doc.doctype in ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"]:
if not args.get("return_qty_from_rejected_warehouse"): if not args.get("return_qty_from_rejected_warehouse"):
@@ -167,7 +166,7 @@ def validate_quantity(doc, args, ref, valid_items, already_returned_items):
else: else:
fields.extend(["received_qty"]) fields.extend(["received_qty"])
already_returned_data = already_returned_items.get(args.item_code) or {} already_returned_data = already_returned_items.get(key) or {}
company_currency = erpnext.get_company_currency(doc.company) company_currency = erpnext.get_company_currency(doc.company)
stock_qty_precision = get_field_precision( stock_qty_precision = get_field_precision(
@@ -253,15 +252,20 @@ def get_already_returned_items(doc):
column += """, sum(abs(child.rejected_qty) * child.conversion_factor) as rejected_qty, column += """, sum(abs(child.rejected_qty) * child.conversion_factor) as rejected_qty,
sum(abs(child.received_qty) * child.conversion_factor) as received_qty""" sum(abs(child.received_qty) * child.conversion_factor) as received_qty"""
field = (
frappe.scrub(doc.doctype) + "_item"
if doc.doctype in ["Purchase Invoice", "Purchase Receipt", "Sales Invoice"]
else "dn_detail"
)
data = frappe.db.sql( data = frappe.db.sql(
f""" f"""
select {column} select {column}, {field}
from from
`tab{doc.doctype} Item` child, `tab{doc.doctype}` par `tab{doc.doctype} Item` child, `tab{doc.doctype}` par
where where
child.parent = par.name and par.docstatus = 1 child.parent = par.name and par.docstatus = 1
and par.is_return = 1 and par.return_against = %s and par.is_return = 1 and par.return_against = %s
group by item_code group by item_code, {field}
""", """,
doc.return_against, doc.return_against,
as_dict=1, as_dict=1,
@@ -271,7 +275,7 @@ def get_already_returned_items(doc):
for d in data: for d in data:
items.setdefault( items.setdefault(
d.item_code, (d.item_code, d.get(field)),
frappe._dict( frappe._dict(
{ {
"qty": d.get("qty"), "qty": d.get("qty"),

View File

@@ -255,15 +255,6 @@ frappe.ui.form.on("BOM", {
}); });
} }
if (!skip_qty_field) {
fields.push({
fieldtype: "Check",
label: __("Use Multi-Level BOM"),
fieldname: "use_multi_level_bom",
default: 1,
});
}
if (!skip_qty_field) { if (!skip_qty_field) {
fields.push({ fields.push({
fieldtype: "Float", fieldtype: "Float",

View File

@@ -385,3 +385,4 @@ erpnext.patches.v15_0.update_sub_voucher_type_in_gl_entries
erpnext.patches.v14_0.update_stock_uom_in_work_order_item erpnext.patches.v14_0.update_stock_uom_in_work_order_item
erpnext.patches.v15_0.set_is_exchange_gain_loss_in_payment_entry_deductions erpnext.patches.v15_0.set_is_exchange_gain_loss_in_payment_entry_deductions
erpnext.patches.v15_0.enable_allow_existing_serial_no erpnext.patches.v15_0.enable_allow_existing_serial_no
erpnext.patches.v15_0.update_cc_in_process_statement_of_accounts

View File

@@ -0,0 +1,16 @@
import frappe
def execute():
process_statement_of_accounts = frappe.qb.DocType("Process Statement Of Accounts")
data = (
frappe.qb.from_(process_statement_of_accounts)
.select(process_statement_of_accounts.name, process_statement_of_accounts.cc_to)
.where(process_statement_of_accounts.cc_to.isnotnull())
).run(as_dict=True)
for d in data:
doc = frappe.get_doc("Process Statement Of Accounts", d.name)
doc.append("cc_to", {"cc": d.cc_to})
doc.save()

View File

@@ -99,9 +99,12 @@ $.extend(erpnext.queries, {
}, },
dispatch_address_query: function (doc) { dispatch_address_query: function (doc) {
var filters = { link_doctype: "Company", link_name: doc.company || "" };
var is_drop_ship = doc.items.some((item) => item.delivered_by_supplier);
if (is_drop_ship) filters = {};
return { return {
query: "frappe.contacts.doctype.address.address.address_query", query: "frappe.contacts.doctype.address.address.address_query",
filters: { link_doctype: "Company", link_name: doc.company || "" }, filters: filters,
}; };
}, },

View File

@@ -85,7 +85,7 @@ erpnext.PointOfSale.PastOrderSummary = class {
<div class="right-section"> <div class="right-section">
<div class="paid-amount">${format_currency(doc.paid_amount, doc.currency)}</div> <div class="paid-amount">${format_currency(doc.paid_amount, doc.currency)}</div>
<div class="invoice-name">${doc.name}</div> <div class="invoice-name">${doc.name}</div>
<span class="indicator-pill whitespace-nowrap ${indicator_color}"><span>${doc.status}</span></span> <span class="indicator-pill whitespace-nowrap ${indicator_color}"><span>${__(doc.status)}</span></span>
</div>`; </div>`;
} }

View File

@@ -505,9 +505,9 @@ class SerialandBatchBundle(Document):
elif (d.incoming_rate == rate) and d.qty and d.stock_value_difference: elif (d.incoming_rate == rate) and d.qty and d.stock_value_difference:
continue continue
d.incoming_rate = rate d.incoming_rate = flt(rate)
if d.qty: if d.qty:
d.stock_value_difference = d.qty * d.incoming_rate d.stock_value_difference = flt(d.qty) * d.incoming_rate
if save: if save:
d.db_set( d.db_set(
@@ -519,6 +519,8 @@ class SerialandBatchBundle(Document):
if not self.voucher_no or self.voucher_no != row.parent: if not self.voucher_no or self.voucher_no != row.parent:
values_to_set["voucher_no"] = row.parent values_to_set["voucher_no"] = row.parent
self.db_set("is_cancelled", 0)
if self.voucher_type != parent.doctype: if self.voucher_type != parent.doctype:
values_to_set["voucher_type"] = parent.doctype values_to_set["voucher_type"] = parent.doctype