Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a2b07a2a3 | ||
|
|
86ff1545ce | ||
|
|
e0b8c2788b | ||
|
|
c1a1346e2a | ||
|
|
1e02d06424 | ||
|
|
2812eae589 | ||
|
|
a94e3cd433 | ||
|
|
91f6393104 | ||
|
|
7d724d7647 | ||
|
|
e4b811be23 | ||
|
|
ccd7992e99 | ||
|
|
86ff3e0c10 | ||
|
|
96fe3f4a2c | ||
|
|
d7583e3993 | ||
|
|
5533592b2b | ||
|
|
ba19e06e64 | ||
|
|
1988c23539 | ||
|
|
70cac3186b | ||
|
|
bed6dabecb | ||
|
|
c4103d26be | ||
|
|
2061c7ca46 | ||
|
|
84bd0c8c50 | ||
|
|
f884cf8a5e | ||
|
|
54782d41be | ||
|
|
d7caa8d51b | ||
|
|
9e8150554f | ||
|
|
df0dac5610 | ||
|
|
cbce4e72d9 | ||
|
|
2c2a204a06 | ||
|
|
da5b57e4af | ||
|
|
de5b253215 | ||
|
|
a8ef90b40c | ||
|
|
7e847f27dd | ||
|
|
eb809847f1 | ||
|
|
9baaaca924 | ||
|
|
c911a70a84 | ||
|
|
e2728db5c2 | ||
|
|
f6f1052a80 | ||
|
|
8529eb4573 | ||
|
|
a73fedbaca | ||
|
|
147eb047aa | ||
|
|
9ae04dfed3 | ||
|
|
78ab44ce1a |
@@ -3,7 +3,7 @@ import inspect
|
||||
|
||||
import frappe
|
||||
|
||||
__version__ = "14.77.3"
|
||||
__version__ = "14.78.1"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -165,6 +165,10 @@ frappe.ui.form.on('Payment Entry', {
|
||||
filters: {
|
||||
reference_doctype: row.reference_doctype,
|
||||
reference_name: row.reference_name,
|
||||
company: doc.company,
|
||||
status: ["!=", "Paid"],
|
||||
outstanding_amount: [">", 0], // for compatibility with old data
|
||||
docstatus: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -2605,6 +2605,7 @@ def get_open_payment_requests_for_references(references=None):
|
||||
.where(Tuple(PR.reference_doctype, PR.reference_name).isin(list(refs)))
|
||||
.where(PR.status != "Paid")
|
||||
.where(PR.docstatus == 1)
|
||||
.where(PR.outstanding_amount > 0) # to avoid old PRs with 0 outstanding amount
|
||||
.orderby(Coalesce(PR.transaction_date, PR.creation), order=frappe.qb.asc)
|
||||
).run(as_dict=True)
|
||||
|
||||
|
||||
@@ -848,12 +848,7 @@ def get_open_payment_requests_query(doctype, txt, searchfield, start, page_len,
|
||||
|
||||
open_payment_requests = frappe.get_list(
|
||||
"Payment Request",
|
||||
filters={
|
||||
**filters,
|
||||
"status": ["!=", "Paid"],
|
||||
"outstanding_amount": ["!=", 0], # for compatibility with old data
|
||||
"docstatus": 1,
|
||||
},
|
||||
filters=filters,
|
||||
fields=["name", "grand_total", "outstanding_amount"],
|
||||
order_by="transaction_date ASC,creation ASC",
|
||||
)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
from frappe import _
|
||||
|
||||
|
||||
def get_data():
|
||||
return {
|
||||
"fieldname": "payment_request",
|
||||
"internal_links": {
|
||||
"Payment Entry": ["references", "payment_request"],
|
||||
"Payment Order": ["references", "payment_order"],
|
||||
},
|
||||
"transactions": [
|
||||
{"label": _("Payment"), "items": ["Payment Entry", "Payment Order"]},
|
||||
],
|
||||
}
|
||||
@@ -1,19 +1,18 @@
|
||||
const INDICATORS = {
|
||||
"Partially Paid": "orange",
|
||||
Cancelled: "red",
|
||||
Draft: "gray",
|
||||
Failed: "red",
|
||||
Initiated: "green",
|
||||
Paid: "blue",
|
||||
Requested: "green",
|
||||
};
|
||||
|
||||
frappe.listview_settings["Payment Request"] = {
|
||||
add_fields: ["status"],
|
||||
get_indicator: function (doc) {
|
||||
if (doc.status == "Draft") {
|
||||
return [__("Draft"), "gray", "status,=,Draft"];
|
||||
}
|
||||
if (doc.status == "Requested") {
|
||||
return [__("Requested"), "green", "status,=,Requested"];
|
||||
} else if (doc.status == "Initiated") {
|
||||
return [__("Initiated"), "green", "status,=,Initiated"];
|
||||
} else if (doc.status == "Partially Paid") {
|
||||
return [__("Partially Paid"), "orange", "status,=,Partially Paid"];
|
||||
} else if (doc.status == "Paid") {
|
||||
return [__("Paid"), "blue", "status,=,Paid"];
|
||||
} else if (doc.status == "Cancelled") {
|
||||
return [__("Cancelled"), "red", "status,=,Cancelled"];
|
||||
}
|
||||
if (!doc.status || !INDICATORS[doc.status]) return;
|
||||
|
||||
return [__(doc.status), INDICATORS[doc.status], `status,=,${doc.status}`];
|
||||
},
|
||||
};
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"shipping_address",
|
||||
"company_address",
|
||||
"company_address_display",
|
||||
"company_contact_person",
|
||||
"currency_and_price_list",
|
||||
"currency",
|
||||
"conversion_rate",
|
||||
@@ -1557,12 +1558,19 @@
|
||||
"fieldname": "update_billed_amount_in_delivery_note",
|
||||
"fieldtype": "Check",
|
||||
"label": "Update Billed Amount in Delivery Note"
|
||||
},
|
||||
{
|
||||
"fieldname": "company_contact_person",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company Contact Person",
|
||||
"options": "Contact",
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-20 16:00:34.268756",
|
||||
"modified": "2024-11-26 13:10:50.309570",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice",
|
||||
|
||||
@@ -1643,6 +1643,30 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
|
||||
frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 1)
|
||||
|
||||
# Cost of Item is zero in Purchase Receipt
|
||||
pr = make_purchase_receipt(qty=1, rate=0)
|
||||
|
||||
stock_value_difference = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
|
||||
"stock_value_difference",
|
||||
)
|
||||
self.assertEqual(stock_value_difference, 0)
|
||||
|
||||
pi = create_purchase_invoice_from_receipt(pr.name)
|
||||
for row in pi.items:
|
||||
row.rate = 150
|
||||
|
||||
pi.save()
|
||||
pi.submit()
|
||||
|
||||
stock_value_difference = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
|
||||
"stock_value_difference",
|
||||
)
|
||||
self.assertEqual(stock_value_difference, 150)
|
||||
|
||||
# Increase the cost of the item
|
||||
|
||||
pr = make_purchase_receipt(qty=1, rate=100)
|
||||
|
||||
@@ -160,8 +160,9 @@
|
||||
"dispatch_address",
|
||||
"company_address_section",
|
||||
"company_address",
|
||||
"company_addr_col_break",
|
||||
"company_address_display",
|
||||
"company_addr_col_break",
|
||||
"company_contact_person",
|
||||
"terms_tab",
|
||||
"payment_schedule_section",
|
||||
"ignore_default_payment_terms_template",
|
||||
@@ -2171,6 +2172,13 @@
|
||||
"label": "Update Outstanding for Self",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "company_contact_person",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company Contact Person",
|
||||
"options": "Contact",
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
@@ -2183,7 +2191,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2024-07-18 15:30:39.428519",
|
||||
"modified": "2024-11-26 12:34:09.110690",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -1556,8 +1556,12 @@ class SalesInvoice(SellingController):
|
||||
)
|
||||
|
||||
def update_project(self):
|
||||
if self.project:
|
||||
project = frappe.get_doc("Project", self.project)
|
||||
unique_projects = list(set([d.project for d in self.get("items") if d.project]))
|
||||
if self.project and self.project not in unique_projects:
|
||||
unique_projects.append(self.project)
|
||||
|
||||
for p in unique_projects:
|
||||
project = frappe.get_doc("Project", p)
|
||||
project.update_billed_amount()
|
||||
project.db_update()
|
||||
|
||||
|
||||
@@ -3760,6 +3760,102 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
self.assertTrue(jv)
|
||||
self.assertEqual(jv[0], si.grand_total)
|
||||
|
||||
@change_settings("Accounts Settings", {"enable_common_party_accounting": True})
|
||||
def test_common_party_with_different_currency_in_debtor_and_creditor(self):
|
||||
from erpnext.accounts.doctype.account.test_account import create_account
|
||||
from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import (
|
||||
make_customer,
|
||||
)
|
||||
from erpnext.accounts.doctype.party_link.party_link import create_party_link
|
||||
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
||||
from erpnext.setup.utils import get_exchange_rate
|
||||
|
||||
creditors = create_account(
|
||||
account_name="Creditors INR",
|
||||
parent_account="Accounts Payable - _TC",
|
||||
company="_Test Company",
|
||||
account_currency="INR",
|
||||
account_type="Payable",
|
||||
)
|
||||
debtors = create_account(
|
||||
account_name="Debtors USD",
|
||||
parent_account="Accounts Receivable - _TC",
|
||||
company="_Test Company",
|
||||
account_currency="USD",
|
||||
account_type="Receivable",
|
||||
)
|
||||
|
||||
# create a customer
|
||||
customer = make_customer(customer="_Test Common Party USD")
|
||||
cust_doc = frappe.get_doc("Customer", customer)
|
||||
cust_doc.default_currency = "USD"
|
||||
test_account_details = {
|
||||
"company": "_Test Company",
|
||||
"account": debtors,
|
||||
}
|
||||
cust_doc.append("accounts", test_account_details)
|
||||
cust_doc.save()
|
||||
|
||||
# create a supplier
|
||||
supplier = create_supplier(supplier_name="_Test Common Party INR").name
|
||||
supp_doc = frappe.get_doc("Supplier", supplier)
|
||||
supp_doc.default_currency = "INR"
|
||||
test_account_details = {
|
||||
"company": "_Test Company",
|
||||
"account": creditors,
|
||||
}
|
||||
supp_doc.append("accounts", test_account_details)
|
||||
supp_doc.save()
|
||||
|
||||
# create a party link between customer & supplier
|
||||
create_party_link("Supplier", supplier, customer)
|
||||
|
||||
# create a sales invoice
|
||||
si = create_sales_invoice(
|
||||
customer=customer,
|
||||
currency="USD",
|
||||
conversion_rate=get_exchange_rate("USD", "INR"),
|
||||
debit_to=debtors,
|
||||
do_not_save=1,
|
||||
)
|
||||
si.party_account_currency = "USD"
|
||||
si.save()
|
||||
si.submit()
|
||||
|
||||
# check outstanding of sales invoice
|
||||
si.reload()
|
||||
self.assertEqual(si.status, "Paid")
|
||||
self.assertEqual(flt(si.outstanding_amount), 0.0)
|
||||
|
||||
# check creation of journal entry
|
||||
jv = frappe.get_all(
|
||||
"Journal Entry Account",
|
||||
{
|
||||
"account": si.debit_to,
|
||||
"party_type": "Customer",
|
||||
"party": si.customer,
|
||||
"reference_type": si.doctype,
|
||||
"reference_name": si.name,
|
||||
},
|
||||
pluck="credit_in_account_currency",
|
||||
)
|
||||
self.assertTrue(jv)
|
||||
self.assertEqual(jv[0], si.grand_total)
|
||||
|
||||
def test_total_billed_amount(self):
|
||||
si = create_sales_invoice(do_not_submit=True)
|
||||
|
||||
project = frappe.new_doc("Project")
|
||||
project.project_name = "Test Total Billed Amount"
|
||||
project.save()
|
||||
|
||||
si.project = project.name
|
||||
si.save()
|
||||
si.submit()
|
||||
|
||||
doc = frappe.get_doc("Project", project.name)
|
||||
self.assertEqual(doc.total_billed_amount, si.grand_total)
|
||||
|
||||
|
||||
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
||||
gl_entries = frappe.db.sql(
|
||||
|
||||
@@ -29,6 +29,12 @@ from erpnext.accounts.utils import get_fiscal_year
|
||||
from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen
|
||||
from erpnext.utilities.regional import temporary_flag
|
||||
|
||||
try:
|
||||
from frappe.contacts.doctype.address.address import render_address as _render_address
|
||||
except ImportError:
|
||||
# Older frappe versions where this function is not available
|
||||
from frappe.contacts.doctype.address.address import get_address_display as _render_address
|
||||
|
||||
PURCHASE_TRANSACTION_TYPES = {
|
||||
"Supplier Quotation",
|
||||
"Purchase Order",
|
||||
@@ -985,10 +991,4 @@ def add_party_account(party_type, party, company, account):
|
||||
|
||||
|
||||
def render_address(address, check_permissions=True):
|
||||
try:
|
||||
from frappe.contacts.doctype.address.address import render_address as _render
|
||||
except ImportError:
|
||||
# Older frappe versions where this function is not available
|
||||
from frappe.contacts.doctype.address.address import get_address_display as _render
|
||||
|
||||
return frappe.call(_render, address, check_permissions=check_permissions)
|
||||
return frappe.call(_render_address, address, check_permissions=check_permissions)
|
||||
|
||||
@@ -1004,7 +1004,7 @@ class ReceivablePayableReport:
|
||||
|
||||
def get_columns(self):
|
||||
self.columns = []
|
||||
self.add_column(_("Posting Date"), fieldtype="Date")
|
||||
self.add_column(_("Posting Date"), fieldname="posting_date", fieldtype="Date")
|
||||
self.add_column(
|
||||
label=_("Party Type"),
|
||||
fieldname="party_type",
|
||||
@@ -1018,8 +1018,15 @@ class ReceivablePayableReport:
|
||||
options="party_type",
|
||||
width=180,
|
||||
)
|
||||
if self.account_type == "Receivable":
|
||||
label = _("Receivable Account")
|
||||
elif self.account_type == "Payable":
|
||||
label = _("Payable Account")
|
||||
else:
|
||||
label = _("Party Account")
|
||||
|
||||
self.add_column(
|
||||
label=self.account_type + " Account",
|
||||
label=label,
|
||||
fieldname="party_account",
|
||||
fieldtype="Link",
|
||||
options="Account",
|
||||
@@ -1057,7 +1064,7 @@ class ReceivablePayableReport:
|
||||
width=180,
|
||||
)
|
||||
|
||||
self.add_column(label=_("Due Date"), fieldtype="Date")
|
||||
self.add_column(label=_("Due Date"), fieldname="due_date", fieldtype="Date")
|
||||
|
||||
if self.account_type == "Payable":
|
||||
self.add_column(label=_("Bill No"), fieldname="bill_no", fieldtype="Data")
|
||||
|
||||
@@ -7,6 +7,7 @@ from frappe import _
|
||||
from frappe.utils import cint, flt
|
||||
|
||||
from erpnext.accounts.report.financial_statements import (
|
||||
compute_growth_view_data,
|
||||
get_columns,
|
||||
get_data,
|
||||
get_filtered_list_for_consolidated_report,
|
||||
@@ -101,6 +102,9 @@ def execute(filters=None):
|
||||
period_list, asset, liability, equity, provisional_profit_loss, currency, filters
|
||||
)
|
||||
|
||||
if filters.get("selected_view") == "Growth":
|
||||
compute_growth_view_data(data, period_list)
|
||||
|
||||
return columns, data, message, chart, report_summary, primitive_summary
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
import copy
|
||||
import functools
|
||||
import math
|
||||
import re
|
||||
@@ -653,3 +654,67 @@ def get_filtered_list_for_consolidated_report(filters, period_list):
|
||||
filtered_summary_list.append(period)
|
||||
|
||||
return filtered_summary_list
|
||||
|
||||
|
||||
def compute_growth_view_data(data, columns):
|
||||
data_copy = copy.deepcopy(data)
|
||||
|
||||
for row_idx in range(len(data_copy)):
|
||||
for column_idx in range(1, len(columns)):
|
||||
previous_period_key = columns[column_idx - 1].get("key")
|
||||
current_period_key = columns[column_idx].get("key")
|
||||
current_period_value = data_copy[row_idx].get(current_period_key)
|
||||
previous_period_value = data_copy[row_idx].get(previous_period_key)
|
||||
annual_growth = 0
|
||||
|
||||
if current_period_value is None:
|
||||
data[row_idx][current_period_key] = None
|
||||
continue
|
||||
|
||||
if previous_period_value == 0 and current_period_value > 0:
|
||||
annual_growth = 1
|
||||
|
||||
elif previous_period_value > 0:
|
||||
annual_growth = (current_period_value - previous_period_value) / previous_period_value
|
||||
|
||||
growth_percent = round(annual_growth * 100, 2)
|
||||
|
||||
data[row_idx][current_period_key] = growth_percent
|
||||
|
||||
|
||||
def compute_margin_view_data(data, columns, accumulated_values):
|
||||
if not columns:
|
||||
return
|
||||
|
||||
if not accumulated_values:
|
||||
columns.append({"key": "total"})
|
||||
|
||||
data_copy = copy.deepcopy(data)
|
||||
|
||||
base_row = None
|
||||
for row in data_copy:
|
||||
if row.get("account_name") == _("Income"):
|
||||
base_row = row
|
||||
break
|
||||
|
||||
if not base_row:
|
||||
return
|
||||
|
||||
for row_idx in range(len(data_copy)):
|
||||
# Taking the total income from each column (for all the financial years) as the base (100%)
|
||||
row = data_copy[row_idx]
|
||||
if not row:
|
||||
continue
|
||||
|
||||
for column in columns:
|
||||
curr_period = column.get("key")
|
||||
base_value = base_row[curr_period]
|
||||
curr_value = row[curr_period]
|
||||
|
||||
if curr_value is None or base_value <= 0:
|
||||
data[row_idx][curr_period] = None
|
||||
continue
|
||||
|
||||
margin_percent = round((curr_value / base_value) * 100, 2)
|
||||
|
||||
data[row_idx][curr_period] = margin_percent
|
||||
|
||||
@@ -402,10 +402,10 @@ class GrossProfitGenerator:
|
||||
self.load_invoice_items()
|
||||
self.get_delivery_notes()
|
||||
|
||||
self.load_product_bundle()
|
||||
if filters.group_by == "Invoice":
|
||||
self.group_items_by_invoice()
|
||||
|
||||
self.load_product_bundle()
|
||||
self.load_non_stock_items()
|
||||
self.get_returned_invoice_items()
|
||||
self.process()
|
||||
@@ -617,6 +617,7 @@ class GrossProfitGenerator:
|
||||
if packed_item.get("parent_detail_docname") == row.item_row:
|
||||
packed_item_row = row.copy()
|
||||
packed_item_row.warehouse = packed_item.warehouse
|
||||
packed_item_row.qty = packed_item.total_qty * -1
|
||||
buying_amount += self.get_buying_amount(packed_item_row, packed_item.item_code)
|
||||
|
||||
return flt(buying_amount, self.currency_precision)
|
||||
@@ -649,7 +650,9 @@ class GrossProfitGenerator:
|
||||
else:
|
||||
my_sle = self.get_stock_ledger_entries(item_code, row.warehouse)
|
||||
if (row.update_stock or row.dn_detail) and my_sle:
|
||||
parenttype, parent = row.parenttype, row.parent
|
||||
parenttype = row.parenttype
|
||||
parent = row.invoice or row.parent
|
||||
|
||||
if row.dn_detail:
|
||||
parenttype, parent = "Delivery Note", row.delivery_note
|
||||
|
||||
@@ -797,6 +800,7 @@ class GrossProfitGenerator:
|
||||
`tabSales Invoice`.project, `tabSales Invoice`.update_stock,
|
||||
`tabSales Invoice`.customer, `tabSales Invoice`.customer_group,
|
||||
`tabSales Invoice`.territory, `tabSales Invoice Item`.item_code,
|
||||
`tabSales Invoice`.base_net_total as "invoice_base_net_total",
|
||||
`tabSales Invoice Item`.item_name, `tabSales Invoice Item`.description,
|
||||
`tabSales Invoice Item`.warehouse, `tabSales Invoice Item`.item_group,
|
||||
`tabSales Invoice Item`.brand, `tabSales Invoice Item`.so_detail,
|
||||
@@ -857,6 +861,7 @@ class GrossProfitGenerator:
|
||||
"""
|
||||
|
||||
grouped = OrderedDict()
|
||||
product_bundles = self.product_bundles.get("Sales Invoice", {})
|
||||
|
||||
for row in self.si_list:
|
||||
# initialize list with a header row for each new parent
|
||||
@@ -867,8 +872,7 @@ class GrossProfitGenerator:
|
||||
)
|
||||
|
||||
# if item is a bundle, add it's components as seperate rows
|
||||
if frappe.db.exists("Product Bundle", row.item_code):
|
||||
bundled_items = self.get_bundle_items(row)
|
||||
if bundled_items := product_bundles.get(row.parent, {}).get(row.item_code):
|
||||
for x in bundled_items:
|
||||
bundle_item = self.get_bundle_item_row(row, x)
|
||||
grouped.get(row.parent).append(bundle_item)
|
||||
@@ -904,47 +908,40 @@ class GrossProfitGenerator:
|
||||
"item_row": None,
|
||||
"is_return": row.is_return,
|
||||
"cost_center": row.cost_center,
|
||||
"base_net_amount": frappe.db.get_value("Sales Invoice", row.parent, "base_net_total"),
|
||||
"base_net_amount": row.invoice_base_net_total,
|
||||
}
|
||||
)
|
||||
|
||||
def get_bundle_items(self, product_bundle):
|
||||
return frappe.get_all(
|
||||
"Product Bundle Item", filters={"parent": product_bundle.item_code}, fields=["item_code", "qty"]
|
||||
)
|
||||
|
||||
def get_bundle_item_row(self, product_bundle, item):
|
||||
item_name, description, item_group, brand = self.get_bundle_item_details(item.item_code)
|
||||
|
||||
def get_bundle_item_row(self, row, item):
|
||||
return frappe._dict(
|
||||
{
|
||||
"parent_invoice": product_bundle.item_code,
|
||||
"indent": product_bundle.indent + 1,
|
||||
"parent_invoice": row.item_code,
|
||||
"parenttype": row.parenttype,
|
||||
"indent": row.indent + 1,
|
||||
"parent": None,
|
||||
"invoice_or_item": item.item_code,
|
||||
"posting_date": product_bundle.posting_date,
|
||||
"posting_time": product_bundle.posting_time,
|
||||
"project": product_bundle.project,
|
||||
"customer": product_bundle.customer,
|
||||
"customer_group": product_bundle.customer_group,
|
||||
"posting_date": row.posting_date,
|
||||
"posting_time": row.posting_time,
|
||||
"project": row.project,
|
||||
"customer": row.customer,
|
||||
"customer_group": row.customer_group,
|
||||
"item_code": item.item_code,
|
||||
"item_name": item_name,
|
||||
"description": description,
|
||||
"warehouse": product_bundle.warehouse,
|
||||
"item_group": item_group,
|
||||
"brand": brand,
|
||||
"dn_detail": product_bundle.dn_detail,
|
||||
"delivery_note": product_bundle.delivery_note,
|
||||
"qty": (flt(product_bundle.qty) * flt(item.qty)),
|
||||
"item_row": None,
|
||||
"is_return": product_bundle.is_return,
|
||||
"cost_center": product_bundle.cost_center,
|
||||
"item_name": item.item_name,
|
||||
"description": item.description,
|
||||
"warehouse": item.warehouse or row.warehouse,
|
||||
"update_stock": row.update_stock,
|
||||
"item_group": "",
|
||||
"brand": "",
|
||||
"dn_detail": row.dn_detail,
|
||||
"delivery_note": row.delivery_note,
|
||||
"qty": item.total_qty * -1,
|
||||
"item_row": row.item_row,
|
||||
"is_return": row.is_return,
|
||||
"cost_center": row.cost_center,
|
||||
"invoice": row.parent,
|
||||
}
|
||||
)
|
||||
|
||||
def get_bundle_item_details(self, item_code):
|
||||
return frappe.db.get_value("Item", item_code, ["item_name", "description", "item_group", "brand"])
|
||||
|
||||
def get_stock_ledger_entries(self, item_code, warehouse):
|
||||
if item_code and warehouse:
|
||||
if (item_code, warehouse) not in self.sle:
|
||||
|
||||
@@ -7,6 +7,8 @@ from frappe import _
|
||||
from frappe.utils import flt
|
||||
|
||||
from erpnext.accounts.report.financial_statements import (
|
||||
compute_growth_view_data,
|
||||
compute_margin_view_data,
|
||||
get_columns,
|
||||
get_data,
|
||||
get_filtered_list_for_consolidated_report,
|
||||
@@ -68,6 +70,12 @@ def execute(filters=None):
|
||||
period_list, filters.periodicity, income, expense, net_profit_loss, currency, filters
|
||||
)
|
||||
|
||||
if filters.get("selected_view") == "Growth":
|
||||
compute_growth_view_data(data, period_list)
|
||||
|
||||
if filters.get("selected_view") == "Margin":
|
||||
compute_margin_view_data(data, period_list, filters.accumulated_values)
|
||||
|
||||
return columns, data, None, chart, report_summary, primitive_summary
|
||||
|
||||
|
||||
|
||||
@@ -2313,6 +2313,12 @@ class AccountsController(TransactionBase):
|
||||
secondary_account = get_party_account(secondary_party_type, secondary_party, self.company)
|
||||
primary_account_currency = get_account_currency(primary_account)
|
||||
secondary_account_currency = get_account_currency(secondary_account)
|
||||
default_currency = erpnext.get_company_currency(self.company)
|
||||
|
||||
# Determine if multi-currency journal entry is needed
|
||||
multi_currency = (
|
||||
primary_account_currency != default_currency or secondary_account_currency != default_currency
|
||||
)
|
||||
|
||||
jv = frappe.new_doc("Journal Entry")
|
||||
jv.voucher_type = "Journal Entry"
|
||||
@@ -2337,7 +2343,7 @@ class AccountsController(TransactionBase):
|
||||
advance_entry.cost_center = self.cost_center or erpnext.get_default_cost_center(self.company)
|
||||
advance_entry.is_advance = "Yes"
|
||||
|
||||
# update dimesions
|
||||
# Update dimensions
|
||||
dimensions_dict = frappe._dict()
|
||||
active_dimensions = get_dimensions()[0]
|
||||
for dim in active_dimensions:
|
||||
@@ -2346,17 +2352,58 @@ class AccountsController(TransactionBase):
|
||||
reconcilation_entry.update(dimensions_dict)
|
||||
advance_entry.update(dimensions_dict)
|
||||
|
||||
if self.doctype == "Sales Invoice":
|
||||
reconcilation_entry.credit_in_account_currency = self.outstanding_amount
|
||||
advance_entry.debit_in_account_currency = self.outstanding_amount
|
||||
# Calculate exchange rates if necessary
|
||||
if multi_currency:
|
||||
# Exchange rates for primary and secondary accounts
|
||||
exc_rate_primary_to_default = (
|
||||
1
|
||||
if primary_account_currency == default_currency
|
||||
else get_exchange_rate(primary_account_currency, default_currency, self.posting_date)
|
||||
)
|
||||
exc_rate_secondary_to_default = (
|
||||
1
|
||||
if secondary_account_currency == default_currency
|
||||
else get_exchange_rate(secondary_account_currency, default_currency, self.posting_date)
|
||||
)
|
||||
exc_rate_secondary_to_primary = (
|
||||
1
|
||||
if secondary_account_currency == primary_account_currency
|
||||
else get_exchange_rate(
|
||||
secondary_account_currency, primary_account_currency, self.posting_date
|
||||
)
|
||||
)
|
||||
|
||||
# Convert outstanding amount from secondary to primary account currency, if needed
|
||||
|
||||
os_in_default_currency = self.outstanding_amount * exc_rate_secondary_to_default
|
||||
os_in_primary_currency = self.outstanding_amount * exc_rate_secondary_to_primary
|
||||
|
||||
if self.doctype == "Sales Invoice":
|
||||
# Calculate credit and debit values for reconciliation and advance entries
|
||||
reconcilation_entry.credit_in_account_currency = self.outstanding_amount
|
||||
reconcilation_entry.credit = os_in_default_currency
|
||||
|
||||
advance_entry.debit_in_account_currency = os_in_primary_currency
|
||||
advance_entry.debit = os_in_default_currency
|
||||
else:
|
||||
advance_entry.credit_in_account_currency = os_in_primary_currency
|
||||
advance_entry.credit = os_in_default_currency
|
||||
|
||||
reconcilation_entry.debit_in_account_currency = self.outstanding_amount
|
||||
reconcilation_entry.debit = os_in_default_currency
|
||||
|
||||
# Set exchange rates for entries
|
||||
reconcilation_entry.exchange_rate = exc_rate_secondary_to_default
|
||||
advance_entry.exchange_rate = exc_rate_primary_to_default
|
||||
else:
|
||||
advance_entry.credit_in_account_currency = self.outstanding_amount
|
||||
reconcilation_entry.debit_in_account_currency = self.outstanding_amount
|
||||
|
||||
default_currency = erpnext.get_company_currency(self.company)
|
||||
if primary_account_currency != default_currency or secondary_account_currency != default_currency:
|
||||
jv.multi_currency = 1
|
||||
if self.doctype == "Sales Invoice":
|
||||
reconcilation_entry.credit_in_account_currency = self.outstanding_amount
|
||||
advance_entry.debit_in_account_currency = self.outstanding_amount
|
||||
else:
|
||||
advance_entry.credit_in_account_currency = self.outstanding_amount
|
||||
reconcilation_entry.debit_in_account_currency = self.outstanding_amount
|
||||
|
||||
jv.multi_currency = multi_currency
|
||||
jv.append("accounts", reconcilation_entry)
|
||||
jv.append("accounts", advance_entry)
|
||||
|
||||
|
||||
@@ -68,19 +68,13 @@ class SellingController(StockController):
|
||||
if customer:
|
||||
from erpnext.accounts.party import _get_party_details
|
||||
|
||||
fetch_payment_terms_template = False
|
||||
if self.get("__islocal") or self.company != frappe.db.get_value(
|
||||
self.doctype, self.name, "company"
|
||||
):
|
||||
fetch_payment_terms_template = True
|
||||
|
||||
party_details = _get_party_details(
|
||||
customer,
|
||||
ignore_permissions=self.flags.ignore_permissions,
|
||||
doctype=self.doctype,
|
||||
company=self.company,
|
||||
posting_date=self.get("posting_date"),
|
||||
fetch_payment_terms_template=fetch_payment_terms_template,
|
||||
fetch_payment_terms_template=self.has_value_changed("company"),
|
||||
party_address=self.customer_address,
|
||||
shipping_address=self.shipping_address_name,
|
||||
company_address=self.get("company_address"),
|
||||
@@ -365,12 +359,32 @@ class SellingController(StockController):
|
||||
return il
|
||||
|
||||
def has_product_bundle(self, item_code):
|
||||
product_bundle = frappe.qb.DocType("Product Bundle")
|
||||
return (
|
||||
frappe.qb.from_(product_bundle)
|
||||
.select(product_bundle.name)
|
||||
.where((product_bundle.new_item_code == item_code) & (product_bundle.disabled == 0))
|
||||
).run()
|
||||
product_bundle_items = getattr(self, "_product_bundle_items", None)
|
||||
if product_bundle_items is None:
|
||||
self._product_bundle_items = product_bundle_items = {}
|
||||
|
||||
if item_code not in product_bundle_items:
|
||||
self._fetch_product_bundle_items(item_code)
|
||||
|
||||
return product_bundle_items[item_code]
|
||||
|
||||
def _fetch_product_bundle_items(self, item_code):
|
||||
product_bundle_items = self._product_bundle_items
|
||||
items_to_fetch = {row.item_code for row in self.items if row.item_code not in product_bundle_items}
|
||||
# fetch for requisite item_code even if it is not in items
|
||||
items_to_fetch.add(item_code)
|
||||
|
||||
items_with_product_bundle = {
|
||||
row.new_item_code
|
||||
for row in frappe.get_all(
|
||||
"Product Bundle",
|
||||
filters={"new_item_code": ("in", items_to_fetch), "disabled": 0},
|
||||
fields="new_item_code",
|
||||
)
|
||||
}
|
||||
|
||||
for item_code in items_to_fetch:
|
||||
product_bundle_items[item_code] = item_code in items_with_product_bundle
|
||||
|
||||
def get_already_delivered_qty(self, current_docname, so, so_detail):
|
||||
delivered_via_dn = frappe.db.sql(
|
||||
|
||||
@@ -89,10 +89,18 @@ class WorkOrder(Document):
|
||||
self.status = self.get_status()
|
||||
self.validate_workstation_type()
|
||||
|
||||
if self.source_warehouse:
|
||||
self.set_warehouses()
|
||||
|
||||
validate_uom_is_integer(self, "stock_uom", ["qty", "produced_qty"])
|
||||
|
||||
self.set_required_items(reset_only_qty=len(self.get("required_items")))
|
||||
|
||||
def set_warehouses(self):
|
||||
for row in self.required_items:
|
||||
if not row.source_warehouse:
|
||||
row.source_warehouse = self.source_warehouse
|
||||
|
||||
def validate_workstation_type(self):
|
||||
for row in self.operations:
|
||||
if not row.workstation and not row.workstation_type:
|
||||
|
||||
@@ -421,7 +421,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
setup_sms() {
|
||||
var me = this;
|
||||
let blacklist = ['Purchase Invoice', 'BOM'];
|
||||
if(this.frm.doc.docstatus===1 && !["Lost", "Stopped", "Closed"].includes(this.frm.doc.status)
|
||||
if(frappe.boot.sms_gateway_enabled && this.frm.doc.docstatus===1 && !["Lost", "Stopped", "Closed"].includes(this.frm.doc.status)
|
||||
&& !blacklist.includes(this.frm.doctype)) {
|
||||
this.frm.page.add_menu_item(__('Send SMS'), function() { me.send_sms(); });
|
||||
}
|
||||
@@ -990,7 +990,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
|
||||
apply_discount_on_item(doc, cdt, cdn, field) {
|
||||
var item = frappe.get_doc(cdt, cdn);
|
||||
if(!item.price_list_rate) {
|
||||
if(item && !item.price_list_rate) {
|
||||
item[field] = 0.0;
|
||||
} else {
|
||||
this.price_list_rate(doc, cdt, cdn);
|
||||
|
||||
@@ -9,40 +9,29 @@ erpnext.financial_statements = {
|
||||
data &&
|
||||
column.colIndex >= 3
|
||||
) {
|
||||
//Assuming that the first three columns are s.no, account name and the very first year of the accounting values, to calculate the relative percentage values of the successive columns.
|
||||
const lastAnnualValue = row[column.colIndex - 1].content;
|
||||
const currentAnnualvalue = data[column.fieldname];
|
||||
if (currentAnnualvalue == undefined) return "NA"; //making this not applicable for undefined/null values
|
||||
let annualGrowth = 0;
|
||||
if (lastAnnualValue == 0 && currentAnnualvalue > 0) {
|
||||
//If the previous year value is 0 and the current value is greater than 0
|
||||
annualGrowth = 1;
|
||||
} else if (lastAnnualValue > 0) {
|
||||
annualGrowth = (currentAnnualvalue - lastAnnualValue) / lastAnnualValue;
|
||||
}
|
||||
const growthPercent = data[column.fieldname];
|
||||
|
||||
const growthPercent = Math.round(annualGrowth * 10000) / 100; //calculating the rounded off percentage
|
||||
if (growthPercent == undefined) return "NA"; //making this not applicable for undefined/null values
|
||||
|
||||
value = $(`<span>${(growthPercent >= 0 ? "+" : "") + growthPercent + "%"}</span>`);
|
||||
if (growthPercent < 0) {
|
||||
value = $(value).addClass("text-danger");
|
||||
if (column.fieldname === "total") {
|
||||
value = $(`<span>${growthPercent}</span>`);
|
||||
} else {
|
||||
value = $(value).addClass("text-success");
|
||||
value = $(`<span>${(growthPercent >= 0 ? "+" : "") + growthPercent + "%"}</span>`);
|
||||
|
||||
if (growthPercent < 0) {
|
||||
value = $(value).addClass("text-danger");
|
||||
} else {
|
||||
value = $(value).addClass("text-success");
|
||||
}
|
||||
}
|
||||
value = $(value).wrap("<p></p>").parent().html();
|
||||
|
||||
return value;
|
||||
} else if (frappe.query_report.get_filter_value("selected_view") == "Margin" && data) {
|
||||
if (column.fieldname == "account" && data.account_name == __("Income")) {
|
||||
//Taking the total income from each column (for all the financial years) as the base (100%)
|
||||
this.baseData = row;
|
||||
}
|
||||
if (column.colIndex >= 2) {
|
||||
//Assuming that the first two columns are s.no and account name, to calculate the relative percentage values of the successive columns.
|
||||
const currentAnnualvalue = data[column.fieldname];
|
||||
const baseValue = this.baseData[column.colIndex].content;
|
||||
if (currentAnnualvalue == undefined || baseValue <= 0) return "NA";
|
||||
const marginPercent = Math.round((currentAnnualvalue / baseValue) * 10000) / 100;
|
||||
const marginPercent = data[column.fieldname];
|
||||
|
||||
if (marginPercent == undefined) return "NA"; //making this not applicable for undefined/null values
|
||||
|
||||
value = $(`<span>${marginPercent + "%"}</span>`);
|
||||
if (marginPercent < 0) value = $(value).addClass("text-danger");
|
||||
|
||||
@@ -64,6 +64,17 @@ $.extend(erpnext.queries, {
|
||||
}
|
||||
},
|
||||
|
||||
company_contact_query: function (doc) {
|
||||
if (!doc.company) {
|
||||
frappe.throw(__("Please set {0}", [__(frappe.meta.get_label(doc.doctype, "company", doc.name))]));
|
||||
}
|
||||
|
||||
return {
|
||||
query: "frappe.contacts.doctype.contact.contact.contact_query",
|
||||
filters: { link_doctype: "Company", link_name: doc.company },
|
||||
};
|
||||
},
|
||||
|
||||
address_query: function (doc) {
|
||||
if (frappe.dynamic_link) {
|
||||
if (!doc[frappe.dynamic_link.fieldname]) {
|
||||
|
||||
@@ -95,8 +95,9 @@
|
||||
"shipping_address",
|
||||
"company_address_section",
|
||||
"company_address",
|
||||
"column_break_87",
|
||||
"company_address_display",
|
||||
"column_break_87",
|
||||
"company_contact_person",
|
||||
"terms_tab",
|
||||
"payment_schedule_section",
|
||||
"payment_terms_template",
|
||||
@@ -1066,13 +1067,20 @@
|
||||
"fieldname": "named_place",
|
||||
"fieldtype": "Data",
|
||||
"label": "Named Place"
|
||||
},
|
||||
{
|
||||
"fieldname": "company_contact_person",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company Contact Person",
|
||||
"options": "Contact",
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-shopping-cart",
|
||||
"idx": 82,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-20 16:04:21.567847",
|
||||
"modified": "2024-11-26 12:43:29.293637",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Quotation",
|
||||
|
||||
@@ -455,7 +455,9 @@ def _make_customer(source_name, ignore_permissions=False):
|
||||
raise
|
||||
except frappe.MandatoryError as e:
|
||||
mandatory_fields = e.args[0].split(":")[1].split(",")
|
||||
mandatory_fields = [customer.meta.get_label(field.strip()) for field in mandatory_fields]
|
||||
mandatory_fields = [
|
||||
_(customer.meta.get_label(field.strip())) for field in mandatory_fields
|
||||
]
|
||||
|
||||
frappe.local.message_log = []
|
||||
lead_link = frappe.utils.get_link_to_form("Lead", lead_name)
|
||||
|
||||
@@ -112,8 +112,9 @@
|
||||
"dispatch_address",
|
||||
"col_break46",
|
||||
"company_address",
|
||||
"column_break_92",
|
||||
"company_address_display",
|
||||
"column_break_92",
|
||||
"company_contact_person",
|
||||
"payment_schedule_section",
|
||||
"payment_terms_section",
|
||||
"payment_terms_template",
|
||||
@@ -1626,13 +1627,20 @@
|
||||
"fieldname": "named_place",
|
||||
"fieldtype": "Data",
|
||||
"label": "Named Place"
|
||||
},
|
||||
{
|
||||
"fieldname": "company_contact_person",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company Contact Person",
|
||||
"options": "Contact",
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-05-23 16:35:54.905804",
|
||||
"modified": "2024-11-26 12:42:06.872527",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Sales Order",
|
||||
@@ -1711,4 +1719,4 @@
|
||||
"title_field": "customer_name",
|
||||
"track_changes": 1,
|
||||
"track_seen": 1
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
|
||||
me.frm.set_query('shipping_address_name', erpnext.queries.address_query);
|
||||
me.frm.set_query('dispatch_address_name', erpnext.queries.dispatch_address_query);
|
||||
me.frm.set_query('company_address', erpnext.queries.company_address_query);
|
||||
me.frm.set_query('company_contact_person', erpnext.queries.company_contact_query);
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(me.frm, me.frm.doctype);
|
||||
|
||||
|
||||
@@ -109,8 +109,9 @@
|
||||
"dispatch_address",
|
||||
"company_address_section",
|
||||
"company_address",
|
||||
"column_break_101",
|
||||
"company_address_display",
|
||||
"column_break_101",
|
||||
"company_contact_person",
|
||||
"terms_tab",
|
||||
"tc_name",
|
||||
"terms",
|
||||
@@ -1395,13 +1396,20 @@
|
||||
"fieldname": "named_place",
|
||||
"fieldtype": "Data",
|
||||
"label": "Named Place"
|
||||
},
|
||||
{
|
||||
"fieldname": "company_contact_person",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company Contact Person",
|
||||
"options": "Contact",
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-truck",
|
||||
"idx": 146,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-20 16:05:02.854990",
|
||||
"modified": "2024-11-26 12:44:28.258215",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Note",
|
||||
|
||||
@@ -907,7 +907,7 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
|
||||
|
||||
if adjust_incoming_rate:
|
||||
adjusted_amt = 0.0
|
||||
if item.billed_amt and item.amount:
|
||||
if item.billed_amt is not None and item.amount is not None:
|
||||
adjusted_amt = flt(item.billed_amt) - flt(item.amount)
|
||||
|
||||
item.db_set("rate_difference_with_purchase_invoice", adjusted_amt, update_modified=False)
|
||||
|
||||
@@ -1727,6 +1727,46 @@ class TestStockEntry(FrappeTestCase):
|
||||
mr.cancel()
|
||||
mr.delete()
|
||||
|
||||
def test_auto_reorder_level_with_lead_time_days(self):
|
||||
from erpnext.stock.reorder_item import reorder_item
|
||||
|
||||
item_doc = make_item(
|
||||
"Test Auto Reorder Item - 002",
|
||||
properties={"stock_uom": "Kg", "purchase_uom": "Nos", "is_stock_item": 1, "lead_time_days": 2},
|
||||
uoms=[{"uom": "Nos", "conversion_factor": 5}],
|
||||
)
|
||||
|
||||
if not frappe.db.exists("Item Reorder", {"parent": item_doc.name}):
|
||||
item_doc.append(
|
||||
"reorder_levels",
|
||||
{
|
||||
"warehouse_reorder_level": 0,
|
||||
"warehouse_reorder_qty": 10,
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"material_request_type": "Purchase",
|
||||
},
|
||||
)
|
||||
|
||||
item_doc.save(ignore_permissions=True)
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "auto_indent", 1)
|
||||
|
||||
mr_list = reorder_item()
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "auto_indent", 0)
|
||||
mrs = frappe.get_all(
|
||||
"Material Request Item",
|
||||
fields=["schedule_date"],
|
||||
filters={"item_code": item_doc.name, "uom": "Nos"},
|
||||
)
|
||||
|
||||
for mri in mrs:
|
||||
self.assertEqual(getdate(mri.schedule_date), getdate(add_days(today(), 2)))
|
||||
|
||||
for mr in mr_list:
|
||||
mr.cancel()
|
||||
mr.delete()
|
||||
|
||||
def test_stock_entry_for_same_posting_date_and_time(self):
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
item_code = "Test Stock Entry For Same Posting Datetime 1"
|
||||
|
||||
@@ -98,6 +98,7 @@ def _reorder_item():
|
||||
"description": d.description,
|
||||
"stock_uom": d.stock_uom,
|
||||
"purchase_uom": d.purchase_uom,
|
||||
"lead_time_days": d.lead_time_days,
|
||||
}
|
||||
),
|
||||
)
|
||||
@@ -129,6 +130,7 @@ def get_items_for_reorder() -> dict[str, list]:
|
||||
item_table.brand,
|
||||
item_table.variant_of,
|
||||
item_table.has_variants,
|
||||
item_table.lead_time_days,
|
||||
)
|
||||
.where(
|
||||
(item_table.disabled == 0)
|
||||
|
||||
@@ -194,11 +194,11 @@ def validate_uom_is_integer(doc, uom_field, qty_fields, child_dt=None):
|
||||
if isinstance(qty_fields, str):
|
||||
qty_fields = [qty_fields]
|
||||
|
||||
distinct_uoms = list(set(d.get(uom_field) for d in doc.get_all_children()))
|
||||
integer_uoms = list(
|
||||
filter(
|
||||
lambda uom: frappe.db.get_value("UOM", uom, "must_be_whole_number", cache=True) or None,
|
||||
distinct_uoms,
|
||||
distinct_uoms = tuple(set(uom for uom in (d.get(uom_field) for d in doc.get_all_children()) if uom))
|
||||
integer_uoms = set(
|
||||
d[0]
|
||||
for d in frappe.db.get_values(
|
||||
"UOM", (("name", "in", distinct_uoms), ("must_be_whole_number", "=", 1)), cache=True
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user