Merge pull request #33318 from frappe/version-14-hotfix

chore: release v14
This commit is contained in:
Ankush Menat
2022-12-13 17:59:30 +05:30
committed by GitHub
25 changed files with 213 additions and 63 deletions

View File

@@ -25,7 +25,8 @@
"in_list_view": 1, "in_list_view": 1,
"label": "Type", "label": "Type",
"options": "DocType", "options": "DocType",
"reqd": 1 "reqd": 1,
"search_index": 1
}, },
{ {
"columns": 2, "columns": 2,
@@ -35,7 +36,8 @@
"in_list_view": 1, "in_list_view": 1,
"label": "Name", "label": "Name",
"options": "reference_doctype", "options": "reference_doctype",
"reqd": 1 "reqd": 1,
"search_index": 1
}, },
{ {
"fieldname": "due_date", "fieldname": "due_date",
@@ -104,7 +106,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-09-26 17:06:55.597389", "modified": "2022-12-12 12:31:44.919895",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Entry Reference", "name": "Payment Entry Reference",
@@ -113,5 +115,6 @@
"quick_entry": 1, "quick_entry": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@@ -256,7 +256,7 @@ def apply_pricing_rule(args, doc=None):
for item in item_list: for item in item_list:
args_copy = copy.deepcopy(args) args_copy = copy.deepcopy(args)
args_copy.update(item) args_copy.update(item)
data = get_pricing_rule_for_item(args_copy, item.get("price_list_rate"), doc=doc) data = get_pricing_rule_for_item(args_copy, doc=doc)
out.append(data) out.append(data)
if ( if (
@@ -293,7 +293,7 @@ def update_pricing_rule_uom(pricing_rule, args):
pricing_rule.uom = row.uom pricing_rule.uom = row.uom
def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False): def get_pricing_rule_for_item(args, doc=None, for_validate=False):
from erpnext.accounts.doctype.pricing_rule.utils import ( from erpnext.accounts.doctype.pricing_rule.utils import (
get_applied_pricing_rules, get_applied_pricing_rules,
get_pricing_rule_items, get_pricing_rule_items,

View File

@@ -997,7 +997,7 @@ def make_pricing_rule(**args):
"apply_on": args.apply_on or "Item Code", "apply_on": args.apply_on or "Item Code",
"applicable_for": args.applicable_for, "applicable_for": args.applicable_for,
"selling": args.selling or 0, "selling": args.selling or 0,
"currency": "USD", "currency": "INR",
"apply_discount_on_rate": args.apply_discount_on_rate or 0, "apply_discount_on_rate": args.apply_discount_on_rate or 0,
"buying": args.buying or 0, "buying": args.buying or 0,
"min_qty": args.min_qty or 0.0, "min_qty": args.min_qty or 0.0,

View File

@@ -244,6 +244,17 @@ def get_other_conditions(conditions, values, args):
and ifnull(`tabPricing Rule`.valid_upto, '2500-12-31')""" and ifnull(`tabPricing Rule`.valid_upto, '2500-12-31')"""
values["transaction_date"] = args.get("transaction_date") values["transaction_date"] = args.get("transaction_date")
if args.get("doctype") in [
"Quotation",
"Sales Order",
"Delivery Note",
"Sales Invoice",
"POS Invoice",
]:
conditions += """ and ifnull(`tabPricing Rule`.selling, 0) = 1"""
else:
conditions += """ and ifnull(`tabPricing Rule`.buying, 0) = 1"""
return conditions return conditions
@@ -663,7 +674,7 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
item_details.free_item_data.append(free_item_data_args) item_details.free_item_data.append(free_item_data_args)
def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False): def apply_pricing_rule_for_free_items(doc, pricing_rule_args):
if pricing_rule_args: if pricing_rule_args:
items = tuple((d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item) items = tuple((d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item)

View File

@@ -533,12 +533,13 @@ def get_accounts(root_type, companies):
], ],
filters={"company": company, "root_type": root_type}, filters={"company": company, "root_type": root_type},
): ):
if account.account_name not in added_accounts: if account.account_number:
account_key = account.account_number + "-" + account.account_name
else:
account_key = account.account_name
if account_key not in added_accounts:
accounts.append(account) accounts.append(account)
if account.account_number:
account_key = account.account_number + "-" + account.account_name
else:
account_key = account.account_name
added_accounts.append(account_key) added_accounts.append(account_key)
return accounts return accounts

View File

@@ -234,8 +234,11 @@ def modify_report_columns(doctype, field, column):
if field in ["item_tax_rate", "base_net_amount"]: if field in ["item_tax_rate", "base_net_amount"]:
return None return None
if doctype == "GL Entry" and field in ["debit", "credit"]: if doctype == "GL Entry":
column.update({"label": _("Amount"), "fieldname": "amount"}) if field in ["debit", "credit"]:
column.update({"label": _("Amount"), "fieldname": "amount"})
elif field == "voucher_type":
column.update({"fieldtype": "Data", "options": ""})
if field == "taxes_and_charges": if field == "taxes_and_charges":
column.update({"label": _("Taxes and Charges Template")}) column.update({"label": _("Taxes and Charges Template")})

View File

@@ -136,6 +136,10 @@ frappe.ui.form.on('Asset', {
}, __("Manage")); }, __("Manage"));
} }
if (frm.doc.depr_entry_posting_status === "Failed") {
frm.trigger("set_depr_posting_failure_alert");
}
frm.trigger("setup_chart"); frm.trigger("setup_chart");
} }
@@ -146,6 +150,19 @@ frappe.ui.form.on('Asset', {
} }
}, },
set_depr_posting_failure_alert: function (frm) {
const alert = `
<div class="row">
<div class="col-xs-12 col-sm-6">
<span class="indicator whitespace-nowrap red">
<span>Failed to post depreciation entries</span>
</span>
</div>
</div>`;
frm.dashboard.set_headline_alert(alert);
},
toggle_reference_doc: function(frm) { toggle_reference_doc: function(frm) {
if (frm.doc.purchase_receipt && frm.doc.purchase_invoice && frm.doc.docstatus === 1) { if (frm.doc.purchase_receipt && frm.doc.purchase_invoice && frm.doc.docstatus === 1) {
frm.set_df_property('purchase_invoice', 'read_only', 1); frm.set_df_property('purchase_invoice', 'read_only', 1);

View File

@@ -70,6 +70,7 @@
"column_break_51", "column_break_51",
"purchase_receipt_amount", "purchase_receipt_amount",
"default_finance_book", "default_finance_book",
"depr_entry_posting_status",
"amended_from" "amended_from"
], ],
"fields": [ "fields": [
@@ -488,6 +489,16 @@
"fieldtype": "Int", "fieldtype": "Int",
"label": "Asset Quantity", "label": "Asset Quantity",
"read_only_depends_on": "eval:!doc.is_existing_asset" "read_only_depends_on": "eval:!doc.is_existing_asset"
},
{
"fieldname": "depr_entry_posting_status",
"fieldtype": "Select",
"hidden": 1,
"label": "Depreciation Entry Posting Status",
"no_copy": 1,
"options": "\nSuccessful\nFailed",
"print_hide": 1,
"read_only": 1
} }
], ],
"idx": 72, "idx": 72,
@@ -510,7 +521,7 @@
"link_fieldname": "asset" "link_fieldname": "asset"
} }
], ],
"modified": "2022-07-20 10:15:12.887372", "modified": "2022-12-05 16:21:30.024060",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Asset", "name": "Asset",

View File

@@ -5,6 +5,8 @@
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import add_months, cint, flt, getdate, nowdate, today from frappe.utils import add_months, cint, flt, getdate, nowdate, today
from frappe.utils.data import get_link_to_form
from frappe.utils.user import get_users_with_role
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts, get_checks_for_pl_and_bs_accounts,
@@ -12,7 +14,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
def post_depreciation_entries(date=None, commit=True): def post_depreciation_entries(date=None):
# Return if automatic booking of asset depreciation is disabled # Return if automatic booking of asset depreciation is disabled
if not cint( if not cint(
frappe.db.get_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically") frappe.db.get_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically")
@@ -21,10 +23,22 @@ def post_depreciation_entries(date=None, commit=True):
if not date: if not date:
date = today() date = today()
for asset in get_depreciable_assets(date):
make_depreciation_entry(asset, date) failed_asset_names = []
if commit:
for asset_name in get_depreciable_assets(date):
try:
make_depreciation_entry(asset_name, date)
frappe.db.commit() frappe.db.commit()
except Exception as e:
frappe.db.rollback()
failed_asset_names.append(asset_name)
if failed_asset_names:
set_depr_entry_posting_status_for_failed_assets(failed_asset_names)
notify_depr_entry_posting_error(failed_asset_names)
frappe.db.commit()
def get_depreciable_assets(date): def get_depreciable_assets(date):
@@ -123,6 +137,8 @@ def make_depreciation_entry(asset_name, date=None):
finance_books.value_after_depreciation -= d.depreciation_amount finance_books.value_after_depreciation -= d.depreciation_amount
finance_books.db_update() finance_books.db_update()
frappe.db.set_value("Asset", asset_name, "depr_entry_posting_status", "Successful")
asset.set_status() asset.set_status()
return asset return asset
@@ -186,6 +202,42 @@ def get_credit_and_debit_accounts(accumulated_depreciation_account, depreciation
return credit_account, debit_account return credit_account, debit_account
def set_depr_entry_posting_status_for_failed_assets(failed_asset_names):
for asset_name in failed_asset_names:
frappe.db.set_value("Asset", asset_name, "depr_entry_posting_status", "Failed")
def notify_depr_entry_posting_error(failed_asset_names):
recipients = get_users_with_role("Accounts Manager")
if not recipients:
recipients = get_users_with_role("System Manager")
subject = _("Error while posting depreciation entries")
asset_links = get_comma_separated_asset_links(failed_asset_names)
message = (
_("Hi,")
+ "<br>"
+ _("The following assets have failed to post depreciation entries: {0}").format(asset_links)
+ "."
)
frappe.sendmail(recipients=recipients, subject=subject, message=message)
def get_comma_separated_asset_links(asset_names):
asset_links = []
for asset_name in asset_names:
asset_links.append(get_link_to_form("Asset", asset_name))
asset_links = ", ".join(asset_links)
return asset_links
@frappe.whitelist() @frappe.whitelist()
def scrap_asset(asset_name): def scrap_asset(asset_name):
asset = frappe.get_doc("Asset", asset_name) asset = frappe.get_doc("Asset", asset_name)

View File

@@ -1478,6 +1478,7 @@ def create_asset(**args):
"asset_owner": args.asset_owner or "Company", "asset_owner": args.asset_owner or "Company",
"is_existing_asset": args.is_existing_asset or 1, "is_existing_asset": args.is_existing_asset or 1,
"asset_quantity": args.get("asset_quantity") or 1, "asset_quantity": args.get("asset_quantity") or 1,
"depr_entry_posting_status": args.depr_entry_posting_status or "",
} }
) )

View File

@@ -197,7 +197,7 @@ class AccountsController(TransactionBase):
validate_einvoice_fields(self) validate_einvoice_fields(self)
if self.doctype != "Material Request": if self.doctype != "Material Request" and not self.ignore_pricing_rule:
apply_pricing_rule_on_transaction(self) apply_pricing_rule_on_transaction(self)
def before_cancel(self): def before_cancel(self):

View File

@@ -349,7 +349,7 @@ class StatusUpdater(Document):
def warn_about_bypassing_with_role(self, item, qty_or_amount, role): def warn_about_bypassing_with_role(self, item, qty_or_amount, role):
action = _("Over Receipt/Delivery") if qty_or_amount == "qty" else _("Overbilling") action = _("Over Receipt/Delivery") if qty_or_amount == "qty" else _("Overbilling")
msg = _("{} of {} {} ignored for item {} because you have {} role.").format( msg = _("{0} of {1} {2} ignored for item {3} because you have {4} role.").format(
action, action,
_(item["target_ref_field"].title()), _(item["target_ref_field"].title()),
frappe.bold(item["reduce_by"]), frappe.bold(item["reduce_by"]),

View File

@@ -4,7 +4,7 @@
import frappe import frappe
from frappe import _ from frappe import _
from frappe.query_builder.functions import Floor, Sum from frappe.query_builder.functions import Sum
from pypika.terms import ExistsCriterion from pypika.terms import ExistsCriterion
@@ -58,9 +58,9 @@ def get_bom_stock(filters):
bom_item.description, bom_item.description,
bom_item.stock_qty, bom_item.stock_qty,
bom_item.stock_uom, bom_item.stock_uom,
bom_item.stock_qty * qty_to_produce / bom.quantity, (bom_item.stock_qty / bom.quantity) * qty_to_produce,
Sum(bin.actual_qty).as_("actual_qty"), Sum(bin.actual_qty),
Sum(Floor(bin.actual_qty / (bom_item.stock_qty * qty_to_produce / bom.quantity))), Sum(bin.actual_qty) / (bom_item.stock_qty / bom.quantity),
) )
.where((bom_item.parent == filters.get("bom")) & (bom_item.parenttype == "BOM")) .where((bom_item.parent == filters.get("bom")) & (bom_item.parenttype == "BOM"))
.groupby(bom_item.item_code) .groupby(bom_item.item_code)

View File

@@ -49,7 +49,7 @@ class ProductionPlanReport(object):
parent.bom_no, parent.bom_no,
parent.fg_warehouse.as_("warehouse"), parent.fg_warehouse.as_("warehouse"),
) )
.where(parent.status.notin(["Completed", "Stopped"])) .where(parent.status.notin(["Completed", "Stopped", "Closed"]))
) )
if order_by == "Planned Start Date": if order_by == "Planned Start Date":
@@ -79,10 +79,11 @@ class ProductionPlanReport(object):
query = query.where(child.parent.isin(self.filters.docnames)) query = query.where(child.parent.isin(self.filters.docnames))
if doctype == "Sales Order": if doctype == "Sales Order":
query = query.select( query = query.select(child.delivery_date, parent.base_grand_total,).where(
child.delivery_date, (child.stock_qty > child.produced_qty)
parent.base_grand_total, & (parent.per_delivered < 100.0)
).where((child.stock_qty > child.produced_qty) & (parent.per_delivered < 100.0)) & (parent.status.notin(["Completed", "Closed"]))
)
if order_by == "Delivery Date": if order_by == "Delivery Date":
query = query.orderby(child.delivery_date, order=Order.asc) query = query.orderby(child.delivery_date, order=Order.asc)
@@ -91,7 +92,9 @@ class ProductionPlanReport(object):
elif doctype == "Material Request": elif doctype == "Material Request":
query = query.select(child.schedule_date,).where( query = query.select(child.schedule_date,).where(
(parent.per_ordered < 100) & (parent.material_request_type == "Manufacture") (parent.per_ordered < 100)
& (parent.material_request_type == "Manufacture")
& (parent.status != "Stopped")
) )
if order_by == "Required Date": if order_by == "Required Date":

View File

@@ -58,7 +58,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
if ( if (
in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype) in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype)
&& this.frm.doc.s_pos && this.frm.doc.is_pos
&& this.frm.doc.is_return && this.frm.doc.is_return
) { ) {
this.set_total_amount_to_default_mop(); this.set_total_amount_to_default_mop();

View File

@@ -1130,10 +1130,13 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
qty(doc, cdt, cdn) { qty(doc, cdt, cdn) {
let item = frappe.get_doc(cdt, cdn); let item = frappe.get_doc(cdt, cdn);
item.pricing_rules = '' // item.pricing_rules = ''
this.conversion_factor(doc, cdt, cdn, true); frappe.run_serially([
this.calculate_stock_uom_rate(doc, cdt, cdn); () => this.remove_pricing_rule(item),
this.apply_pricing_rule(item, true); () => this.conversion_factor(doc, cdt, cdn, true),
() => this.calculate_stock_uom_rate(doc, cdt, cdn),
() => this.apply_pricing_rule(item, true)
]);
} }
calculate_stock_uom_rate(doc, cdt, cdn) { calculate_stock_uom_rate(doc, cdt, cdn) {
@@ -1357,16 +1360,21 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
var item_list = []; var item_list = [];
$.each(this.frm.doc["items"] || [], function(i, d) { $.each(this.frm.doc["items"] || [], function(i, d) {
if (d.item_code && !d.is_free_item) { if (d.item_code) {
item_list.push({ if (d.is_free_item) {
"doctype": d.doctype, // Simply remove free items
"name": d.name, me.frm.get_field("items").grid.grid_rows[i].remove();
"item_code": d.item_code, } else {
"pricing_rules": d.pricing_rules, item_list.push({
"parenttype": d.parenttype, "doctype": d.doctype,
"parent": d.parent, "name": d.name,
"price_list_rate": d.price_list_rate "item_code": d.item_code,
}) "pricing_rules": d.pricing_rules,
"parenttype": d.parenttype,
"parent": d.parent,
"price_list_rate": d.price_list_rate
})
}
} }
}); });
return this.frm.call({ return this.frm.call({

View File

@@ -6,7 +6,7 @@ import json
import frappe import frappe
import frappe.defaults import frappe.defaults
from frappe import _, msgprint from frappe import _, msgprint, qb
from frappe.contacts.address_and_contact import ( from frappe.contacts.address_and_contact import (
delete_contact_and_address, delete_contact_and_address,
load_address_and_contact, load_address_and_contact,
@@ -732,12 +732,15 @@ def make_address(args, is_primary_address=1):
@frappe.validate_and_sanitize_search_inputs @frappe.validate_and_sanitize_search_inputs
def get_customer_primary_contact(doctype, txt, searchfield, start, page_len, filters): def get_customer_primary_contact(doctype, txt, searchfield, start, page_len, filters):
customer = filters.get("customer") customer = filters.get("customer")
return frappe.db.sql(
""" con = qb.DocType("Contact")
select `tabContact`.name from `tabContact`, `tabDynamic Link` dlink = qb.DocType("Dynamic Link")
where `tabContact`.name = `tabDynamic Link`.parent and `tabDynamic Link`.link_name = %(customer)s
and `tabDynamic Link`.link_doctype = 'Customer' return (
and `tabContact`.name like %(txt)s qb.from_(con)
""", .join(dlink)
{"customer": customer, "txt": "%%%s%%" % txt}, .on(con.name == dlink.parent)
.select(con.name, con.full_name, con.email_id)
.where((dlink.link_name == customer) & (con.name.like(f"%{txt}%")))
.run()
) )

View File

@@ -30,6 +30,24 @@ class TestQuotation(FrappeTestCase):
self.assertTrue(sales_order.get("payment_schedule")) self.assertTrue(sales_order.get("payment_schedule"))
def test_maintain_rate_in_sales_cycle_is_enforced(self):
from erpnext.selling.doctype.quotation.quotation import make_sales_order
maintain_rate = frappe.db.get_single_value("Selling Settings", "maintain_same_sales_rate")
frappe.db.set_single_value("Selling Settings", "maintain_same_sales_rate", 1)
quotation = frappe.copy_doc(test_records[0])
quotation.transaction_date = nowdate()
quotation.valid_till = add_months(quotation.transaction_date, 1)
quotation.insert()
quotation.submit()
sales_order = make_sales_order(quotation.name)
sales_order.items[0].rate = 1
self.assertRaises(frappe.ValidationError, sales_order.save)
frappe.db.set_single_value("Selling Settings", "maintain_same_sales_rate", maintain_rate)
def test_make_sales_order_with_different_currency(self): def test_make_sales_order_with_different_currency(self):
from erpnext.selling.doctype.quotation.quotation import make_sales_order from erpnext.selling.doctype.quotation.quotation import make_sales_order

View File

@@ -194,7 +194,7 @@ class SalesOrder(SellingController):
) )
if cint(frappe.db.get_single_value("Selling Settings", "maintain_same_sales_rate")): if cint(frappe.db.get_single_value("Selling Settings", "maintain_same_sales_rate")):
self.validate_rate_with_reference_doc([["Quotation", "prev_docname", "quotation_item"]]) self.validate_rate_with_reference_doc([["Quotation", "prevdoc_docname", "quotation_item"]])
def update_enquiry_status(self, prevdoc, flag): def update_enquiry_status(self, prevdoc, flag):
enq = frappe.db.sql( enq = frappe.db.sql(

View File

@@ -12,7 +12,10 @@ def get_data():
"Auto Repeat": "reference_document", "Auto Repeat": "reference_document",
"Maintenance Visit": "prevdoc_docname", "Maintenance Visit": "prevdoc_docname",
}, },
"internal_links": {"Quotation": ["items", "prevdoc_docname"]}, "internal_links": {
"Quotation": ["items", "prevdoc_docname"],
"Material Request": ["items", "material_request"],
},
"transactions": [ "transactions": [
{ {
"label": _("Fulfillment"), "label": _("Fulfillment"),

View File

@@ -152,7 +152,7 @@ def get_parent_item_groups(item_group_name, from_item=False):
if from_item and frappe.request.environ.get("HTTP_REFERER"): if from_item and frappe.request.environ.get("HTTP_REFERER"):
# base page after 'Home' will vary on Item page # base page after 'Home' will vary on Item page
last_page = frappe.request.environ["HTTP_REFERER"].split("/")[-1] last_page = frappe.request.environ["HTTP_REFERER"].split("/")[-1].split("?")[0]
if last_page and last_page in ("shop-by-category", "all-products"): if last_page and last_page in ("shop-by-category", "all-products"):
base_nav_page_title = " ".join(last_page.split("-")).title() base_nav_page_title = " ".join(last_page.split("-")).title()
base_nav_page = {"name": _(base_nav_page_title), "route": "/" + last_page} base_nav_page = {"name": _(base_nav_page_title), "route": "/" + last_page}

View File

@@ -113,7 +113,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
if args.get(key) is None: if args.get(key) is None:
args[key] = value args[key] = value
data = get_pricing_rule_for_item(args, out.price_list_rate, doc, for_validate=for_validate) data = get_pricing_rule_for_item(args, doc=doc, for_validate=for_validate)
out.update(data) out.update(data)
@@ -1305,7 +1305,7 @@ def apply_price_list_on_item(args):
item_doc = frappe.db.get_value("Item", args.item_code, ["name", "variant_of"], as_dict=1) item_doc = frappe.db.get_value("Item", args.item_code, ["name", "variant_of"], as_dict=1)
item_details = get_price_list_rate(args, item_doc) item_details = get_price_list_rate(args, item_doc)
item_details.update(get_pricing_rule_for_item(args, item_details.price_list_rate)) item_details.update(get_pricing_rule_for_item(args))
return item_details return item_details

View File

@@ -45,7 +45,7 @@ def get_warehouses(report_filters: StockBalanceFilter):
return frappe.get_all( return frappe.get_all(
"Warehouse", "Warehouse",
fields=["name", "parent_warehouse", "is_group"], fields=["name", "parent_warehouse", "is_group"],
filters={"company": report_filters.company, "disabled": 0}, filters={"company": report_filters.company},
order_by="lft", order_by="lft",
) )
@@ -55,9 +55,10 @@ def get_data(filters: StockBalanceFilter):
warehouses = get_warehouses(filters) warehouses = get_warehouses(filters)
for warehouse in warehouses: for warehouse in warehouses:
warehouse["stock_balance"] = warehouse_balance.get(warehouse.name, 0) warehouse.stock_balance = warehouse_balance.get(warehouse.name, 0) or 0.0
update_indent(warehouses) update_indent(warehouses)
set_balance_in_parent(warehouses)
return warehouses return warehouses
@@ -69,13 +70,26 @@ def update_indent(warehouses):
warehouse.indent = indent warehouse.indent = indent
for child in warehouses: for child in warehouses:
if child.parent_warehouse == warehouse.name: if child.parent_warehouse == warehouse.name:
warehouse.stock_balance += child.stock_balance
add_indent(child, indent + 1) add_indent(child, indent + 1)
if warehouse.is_group: if warehouse.is_group:
add_indent(warehouse, warehouse.indent or 0) add_indent(warehouse, warehouse.indent or 0)
def set_balance_in_parent(warehouses):
# sort warehouses by indent in descending order
warehouses = sorted(warehouses, key=lambda x: x.get("indent", 0), reverse=1)
for warehouse in warehouses:
def update_balance(warehouse, balance):
for parent in warehouses:
if warehouse.parent_warehouse == parent.name:
parent.stock_balance += balance
update_balance(warehouse, warehouse.stock_balance)
def get_columns(): def get_columns():
return [ return [
{ {

View File

@@ -9916,3 +9916,4 @@ Cost and Freight,Kosten und Fracht,
Delivered at Place,Geliefert benannter Ort, Delivered at Place,Geliefert benannter Ort,
Delivered at Place Unloaded,Geliefert benannter Ort entladen, Delivered at Place Unloaded,Geliefert benannter Ort entladen,
Delivered Duty Paid,Geliefert verzollt, Delivered Duty Paid,Geliefert verzollt,
{0} of {1} {2} ignored for item {3} because you have {4} role,"{0} von Artikel {3} mit {1} {2} wurde ignoriert, weil Sie die Rolle {4} haben."
Can't render this file because it is too large.

View File

@@ -110,6 +110,7 @@ def get_price(item_code, price_list, customer_group, company, qty=1):
"conversion_rate": 1, "conversion_rate": 1,
"for_shopping_cart": True, "for_shopping_cart": True,
"currency": frappe.db.get_value("Price List", price_list, "currency"), "currency": frappe.db.get_value("Price List", price_list, "currency"),
"doctype": "Quotation",
} }
) )