mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-28 09:24:45 +00:00
Merge pull request #51078 from Abdeali099/custom-financial-statement-pdf-export
This commit is contained in:
committed by
GitHub
parent
7bb0ec836f
commit
4632ddc497
@@ -71,7 +71,9 @@ class PeriodValue:
|
|||||||
class AccountData:
|
class AccountData:
|
||||||
"""Account data across all periods"""
|
"""Account data across all periods"""
|
||||||
|
|
||||||
account_name: str
|
account: str # docname
|
||||||
|
account_name: str = "" # account name
|
||||||
|
account_number: str = ""
|
||||||
period_values: dict[str, PeriodValue] = field(default_factory=dict)
|
period_values: dict[str, PeriodValue] = field(default_factory=dict)
|
||||||
|
|
||||||
def add_period(self, period_value: PeriodValue) -> None:
|
def add_period(self, period_value: PeriodValue) -> None:
|
||||||
@@ -103,7 +105,11 @@ class AccountData:
|
|||||||
# movement is unaccumulated by default
|
# movement is unaccumulated by default
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
copied = AccountData(account_name=self.account_name)
|
copied = AccountData(
|
||||||
|
account=self.account,
|
||||||
|
account_name=self.account_name,
|
||||||
|
account_number=self.account_number,
|
||||||
|
)
|
||||||
copied.period_values = {k: v.copy() for k, v in self.period_values.items()}
|
copied.period_values = {k: v.copy() for k, v in self.period_values.items()}
|
||||||
return copied
|
return copied
|
||||||
|
|
||||||
@@ -329,12 +335,10 @@ class DataCollector:
|
|||||||
self.account_fields = {field.fieldname for field in frappe.get_meta("Account").fields}
|
self.account_fields = {field.fieldname for field in frappe.get_meta("Account").fields}
|
||||||
|
|
||||||
def add_account_request(self, row):
|
def add_account_request(self, row):
|
||||||
accounts = self._parse_account_filter(self.company, row)
|
|
||||||
|
|
||||||
self.account_requests.append(
|
self.account_requests.append(
|
||||||
{
|
{
|
||||||
"row": row,
|
"row": row,
|
||||||
"accounts": accounts,
|
"accounts": self._parse_account_filter(self.company, row),
|
||||||
"balance_type": row.balance_type,
|
"balance_type": row.balance_type,
|
||||||
"reference_code": row.reference_code,
|
"reference_code": row.reference_code,
|
||||||
"reverse_sign": row.reverse_sign,
|
"reverse_sign": row.reverse_sign,
|
||||||
@@ -345,12 +349,12 @@ class DataCollector:
|
|||||||
if not self.account_requests:
|
if not self.account_requests:
|
||||||
return {"account_data": {}, "summary": {}, "account_details": {}}
|
return {"account_data": {}, "summary": {}, "account_details": {}}
|
||||||
|
|
||||||
# Get all unique accounts
|
# Get all accounts
|
||||||
all_accounts = set()
|
all_accounts = []
|
||||||
for request in self.account_requests:
|
|
||||||
all_accounts.update(request["accounts"])
|
for request in self.account_requests:
|
||||||
|
all_accounts.extend(request["accounts"])
|
||||||
|
|
||||||
all_accounts = list(all_accounts)
|
|
||||||
if not all_accounts:
|
if not all_accounts:
|
||||||
return {"account_data": {}, "summary": {}, "account_details": {}}
|
return {"account_data": {}, "summary": {}, "account_details": {}}
|
||||||
|
|
||||||
@@ -373,7 +377,9 @@ class DataCollector:
|
|||||||
total_values = [0.0] * len(self.periods)
|
total_values = [0.0] * len(self.periods)
|
||||||
request_account_details = {}
|
request_account_details = {}
|
||||||
|
|
||||||
for account_name in accounts:
|
for account in accounts:
|
||||||
|
account_name = account.name
|
||||||
|
|
||||||
if account_name not in account_data:
|
if account_name not in account_data:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -396,20 +402,21 @@ class DataCollector:
|
|||||||
return {"account_data": account_data, "summary": summary, "account_details": account_details}
|
return {"account_data": account_data, "summary": summary, "account_details": account_details}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse_account_filter(company, report_row) -> list[str]:
|
def _parse_account_filter(company, report_row) -> list[dict]:
|
||||||
"""
|
"""
|
||||||
Find accounts matching filter criteria.
|
Find accounts matching filter criteria.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
Input: '["account_type", "=", "Cash"]'
|
|
||||||
Output: ["Cash - COMP", "Petty Cash - COMP", "Bank - COMP"]
|
- Input: '["account_type", "=", "Cash"]'
|
||||||
|
- Output: [{"name": "Cash - COMP", "account_name": "Cash", "account_number": "1001"}]
|
||||||
"""
|
"""
|
||||||
filter_parser = FilterExpressionParser()
|
filter_parser = FilterExpressionParser()
|
||||||
|
|
||||||
account = frappe.qb.DocType("Account")
|
account = frappe.qb.DocType("Account")
|
||||||
query = (
|
query = (
|
||||||
frappe.qb.from_(account)
|
frappe.qb.from_(account)
|
||||||
.select(account.name)
|
.select(account.name, account.account_name, account.account_number)
|
||||||
.where(account.disabled == 0)
|
.where(account.disabled == 0)
|
||||||
.where(account.is_group == 0)
|
.where(account.is_group == 0)
|
||||||
)
|
)
|
||||||
@@ -423,8 +430,8 @@ class DataCollector:
|
|||||||
|
|
||||||
query = query.where(where_condition)
|
query = query.where(where_condition)
|
||||||
query = query.orderby(account.name)
|
query = query.orderby(account.name)
|
||||||
result = query.run(as_dict=True)
|
|
||||||
return [row.name for row in result]
|
return query.run(as_dict=True)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_filtered_accounts(company: str, account_rows: list) -> list[str]:
|
def get_filtered_accounts(company: str, account_rows: list) -> list[str]:
|
||||||
@@ -456,17 +463,35 @@ class FinancialQueryBuilder:
|
|||||||
self.filters = filters
|
self.filters = filters
|
||||||
self.periods = periods
|
self.periods = periods
|
||||||
self.company = filters.get("company")
|
self.company = filters.get("company")
|
||||||
|
self.account_meta = {} # {name: {account_name, account_number}}
|
||||||
|
|
||||||
def fetch_account_balances(self, accounts: list[str]) -> dict[str, AccountData]:
|
def fetch_account_balances(self, accounts: list[dict]) -> dict[str, AccountData]:
|
||||||
"""
|
"""
|
||||||
Fetch account balances for all periods with optimization.
|
Fetch account balances for all periods with optimization.
|
||||||
Steps: get opening balances → fetch GL entries → calculate running totals
|
Steps: get opening balances → fetch GL entries → calculate running totals
|
||||||
|
|
||||||
|
- accounts: list of accounts with details
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"name": "Cash - COMP",
|
||||||
|
"account_name": "Cash",
|
||||||
|
"account_number": "1001",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: {account: AccountData}
|
dict: {account: AccountData}
|
||||||
"""
|
"""
|
||||||
balances_data = self._get_opening_balances(accounts)
|
account_names = list({acc.name for acc in accounts})
|
||||||
gl_data = self._get_gl_movements(accounts)
|
# NOTE: do not change accounts list as it is used in caller function
|
||||||
|
self.account_meta = {
|
||||||
|
acc.name: {"account_name": acc.account_name, "account_number": acc.account_number}
|
||||||
|
for acc in accounts
|
||||||
|
}
|
||||||
|
|
||||||
|
balances_data = self._get_opening_balances(account_names)
|
||||||
|
gl_data = self._get_gl_movements(account_names)
|
||||||
self._calculate_running_balances(balances_data, gl_data)
|
self._calculate_running_balances(balances_data, gl_data)
|
||||||
self._handle_balance_accumulation(balances_data)
|
self._handle_balance_accumulation(balances_data)
|
||||||
|
|
||||||
@@ -543,7 +568,8 @@ class FinancialQueryBuilder:
|
|||||||
gap_movement = gap_movements.get(account, 0.0)
|
gap_movement = gap_movements.get(account, 0.0)
|
||||||
opening_balance = closing_balance + gap_movement
|
opening_balance = closing_balance + gap_movement
|
||||||
|
|
||||||
account_data = AccountData(account)
|
account_data = AccountData(account=account, **self._get_account_meta(account))
|
||||||
|
|
||||||
account_data.add_period(PeriodValue(first_period_key, opening_balance, 0, 0))
|
account_data.add_period(PeriodValue(first_period_key, opening_balance, 0, 0))
|
||||||
balances_data[account] = account_data
|
balances_data[account] = account_data
|
||||||
|
|
||||||
@@ -613,7 +639,7 @@ class FinancialQueryBuilder:
|
|||||||
for row in gl_data:
|
for row in gl_data:
|
||||||
account = row["account"]
|
account = row["account"]
|
||||||
if account not in balances_data:
|
if account not in balances_data:
|
||||||
balances_data[account] = AccountData(account)
|
balances_data[account] = AccountData(account=account, **self._get_account_meta(account))
|
||||||
|
|
||||||
account_data: AccountData = balances_data[account]
|
account_data: AccountData = balances_data[account]
|
||||||
|
|
||||||
@@ -714,6 +740,9 @@ class FinancialQueryBuilder:
|
|||||||
|
|
||||||
return query.run(as_dict=True)
|
return query.run(as_dict=True)
|
||||||
|
|
||||||
|
def _get_account_meta(self, account: str) -> dict[str, Any]:
|
||||||
|
return self.account_meta.get(account, {})
|
||||||
|
|
||||||
|
|
||||||
class FilterExpressionParser:
|
class FilterExpressionParser:
|
||||||
"""Direct filter expression to SQL condition builder"""
|
"""Direct filter expression to SQL condition builder"""
|
||||||
@@ -1544,20 +1573,29 @@ class RowFormatterBase(ABC):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def _get_values(self, row_data: RowData) -> dict[str, Any]:
|
def _get_values(self, row_data: RowData) -> dict[str, Any]:
|
||||||
# TODO: can be commonify COA? @abdeali
|
def _get_row_data(key: str, default: Any = "") -> Any:
|
||||||
|
return getattr(row_data.row, key, default) or default
|
||||||
|
|
||||||
|
def _get_filter_value(key: str, default: Any = "") -> Any:
|
||||||
|
return getattr(self.context.filters, key, default) or default
|
||||||
|
|
||||||
child_accounts = []
|
child_accounts = []
|
||||||
|
|
||||||
if row_data.account_details:
|
if row_data.account_details:
|
||||||
child_accounts = list(row_data.account_details.keys())
|
child_accounts = list(row_data.account_details.keys())
|
||||||
|
|
||||||
|
display_name = _get_row_data("display_name", "")
|
||||||
|
|
||||||
values = {
|
values = {
|
||||||
|
"account": _get_row_data("account", "") or display_name,
|
||||||
|
"account_name": display_name,
|
||||||
|
"acc_name": _get_row_data("account_name", ""),
|
||||||
|
"acc_number": _get_row_data("account_number", ""),
|
||||||
"child_accounts": child_accounts,
|
"child_accounts": child_accounts,
|
||||||
"account": getattr(row_data.row, "display_name", "") or "",
|
|
||||||
"indent": getattr(row_data.row, "indentation_level", 0),
|
|
||||||
"account_name": getattr(row_data.row, "account", "") or "",
|
|
||||||
"currency": self.context.currency or "",
|
"currency": self.context.currency or "",
|
||||||
"period_start_date": getattr(self.context.filters, "period_start_date", "") or "",
|
"indent": _get_row_data("indentation_level", 0),
|
||||||
"period_end_date": getattr(self.context.filters, "period_end_date", "") or "",
|
"period_start_date": _get_filter_value("period_start_date", ""),
|
||||||
|
"period_end_date": _get_filter_value("period_end_date", ""),
|
||||||
"total": 0,
|
"total": 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1670,8 +1708,8 @@ class DetailRowBuilder:
|
|||||||
detail_rows = []
|
detail_rows = []
|
||||||
parent_row = self.parent_row_data.row
|
parent_row = self.parent_row_data.row
|
||||||
|
|
||||||
for account_name, account_data in self.parent_row_data.account_details.items():
|
for account_data in self.parent_row_data.account_details.values():
|
||||||
detail_row = self._create_detail_row_object(account_name, parent_row)
|
detail_row = self._create_detail_row_object(account_data, parent_row)
|
||||||
|
|
||||||
balance_type = getattr(parent_row, "balance_type", "Closing Balance")
|
balance_type = getattr(parent_row, "balance_type", "Closing Balance")
|
||||||
values = account_data.get_values_by_type(balance_type)
|
values = account_data.get_values_by_type(balance_type)
|
||||||
@@ -1687,16 +1725,20 @@ class DetailRowBuilder:
|
|||||||
|
|
||||||
return detail_rows
|
return detail_rows
|
||||||
|
|
||||||
def _create_detail_row_object(self, account_name: str, parent_row):
|
def _create_detail_row_object(self, account_data: AccountData, parent_row):
|
||||||
short_name = account_name.rsplit(" - ", 1)[0].strip()
|
acc_name = account_data.account_name or ""
|
||||||
|
acc_number = account_data.account_number or ""
|
||||||
|
|
||||||
|
display_name = f"{_(acc_number)} - {_(acc_name)}" if acc_number else _(acc_name)
|
||||||
|
|
||||||
return type(
|
return type(
|
||||||
"DetailRow",
|
"DetailRow",
|
||||||
(),
|
(),
|
||||||
{
|
{
|
||||||
"display_name": short_name,
|
"account": account_data.account,
|
||||||
"account": account_name,
|
"display_name": display_name,
|
||||||
"account_name": short_name,
|
"account_name": acc_name,
|
||||||
|
"account_number": acc_number,
|
||||||
"data_source": "Account Detail",
|
"data_source": "Account Detail",
|
||||||
"indentation_level": getattr(parent_row, "indentation_level", 0) + 1,
|
"indentation_level": getattr(parent_row, "indentation_level", 0) + 1,
|
||||||
"fieldtype": getattr(parent_row, "fieldtype", None),
|
"fieldtype": getattr(parent_row, "fieldtype", None),
|
||||||
|
|||||||
@@ -44,3 +44,5 @@ frappe.query_reports[CF_REPORT_NAME]["filters"].push(
|
|||||||
fieldtype: "Check",
|
fieldtype: "Check",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
frappe.query_reports[CF_REPORT_NAME]["export_hidden_cols"] = true;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.financial-statements-blank-row td {
|
.financial-statements-blank-row td {
|
||||||
height: 37px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@@ -25,30 +25,37 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<h3 class="text-center">{%= filters.fiscal_year %}</h3>
|
<h3 class="text-center">{%= filters.fiscal_year %}</h3>
|
||||||
|
|
||||||
<h5 class="text-center">
|
<h5 class="text-center">
|
||||||
{%= __("Currency") %} : {%= filters.presentation_currency || erpnext.get_currency(filters.company) %}
|
<strong>{%= __("Currency") %}:</strong> {%= filters.presentation_currency || erpnext.get_currency(filters.company) %}
|
||||||
</h5>
|
</h5>
|
||||||
|
|
||||||
{% if (filters.from_date) { %}
|
{% if (filters.from_date) { %}
|
||||||
<h5 class="text-center">
|
<h5 class="text-center">
|
||||||
{%= frappe.datetime.str_to_user(filters.from_date) %} - {%= frappe.datetime.str_to_user(filters.to_date) %}
|
{%= frappe.datetime.str_to_user(filters.from_date) %} - {%= frappe.datetime.str_to_user(filters.to_date) %}
|
||||||
</h5>
|
</h5>
|
||||||
{% } %}
|
{% } %}
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<div class="show-filters">
|
|
||||||
{% if subtitle %}
|
{% if subtitle %}
|
||||||
{{ subtitle }}
|
<div class="show-filters">
|
||||||
<hr>
|
{{ subtitle }}
|
||||||
{% endif %}
|
<hr>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<table class="table table-bordered">
|
<table class="table table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: {%= 100 - (report_columns.length - 1) * 13 %}%"></th>
|
<th style="width: {%= 100 - (report_columns.length - 1) * 16 %}%">{%= report_columns[0].label %}</th>
|
||||||
|
|
||||||
{% for (let i=1, l=report_columns.length; i<l; i++) { %}
|
{% for (let i=1, l=report_columns.length; i<l; i++) { %}
|
||||||
<th class="text-right">{%= report_columns[i].label %}</th>
|
<th class="text-right">{%= report_columns[i].label %}</th>
|
||||||
{% } %}
|
{% } %}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for(let j=0, k=data.length; j<k; j++) { %}
|
{% for(let j=0, k=data.length; j<k; j++) { %}
|
||||||
{%
|
{%
|
||||||
@@ -60,6 +67,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<span style="padding-left: {%= cint(data[j].indent) * 2 %}em">{%= row.account_name || row.section %}</span>
|
<span style="padding-left: {%= cint(data[j].indent) * 2 %}em">{%= row.account_name || row.section %}</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
{% for(let i=1, l=report_columns.length; i<l; i++) { %}
|
{% for(let i=1, l=report_columns.length; i<l; i++) { %}
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{% const fieldname = report_columns[i].fieldname; %}
|
{% const fieldname = report_columns[i].fieldname; %}
|
||||||
@@ -72,6 +80,7 @@
|
|||||||
{% } %}
|
{% } %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p class="text-right text-muted">
|
<p class="text-right text-muted">
|
||||||
{%= __("Printed on {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}
|
{%= __("Printed on {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -3,15 +3,18 @@ frappe.provide("erpnext.financial_statements");
|
|||||||
erpnext.financial_statements = {
|
erpnext.financial_statements = {
|
||||||
filters: get_filters(),
|
filters: get_filters(),
|
||||||
baseData: null,
|
baseData: null,
|
||||||
|
|
||||||
|
get_pdf_format: function (report, custom_format) {
|
||||||
|
// If report template is selected, use default pdf formatting
|
||||||
|
return report.get_filter_value("report_template") ? null : custom_format;
|
||||||
|
},
|
||||||
|
|
||||||
formatter: function (value, row, column, data, default_formatter, filter) {
|
formatter: function (value, row, column, data, default_formatter, filter) {
|
||||||
const report_params = [value, row, column, data, default_formatter, filter];
|
const report_params = [value, row, column, data, default_formatter, filter];
|
||||||
// Growth/Margin
|
// Growth/Margin
|
||||||
if (erpnext.financial_statements._is_special_view(column, data))
|
if (erpnext.financial_statements._is_special_view(column, data))
|
||||||
return erpnext.financial_statements._format_special_view(...report_params);
|
return erpnext.financial_statements._format_special_view(...report_params);
|
||||||
|
|
||||||
if (frappe.query_report.get_filter_value("report_template"))
|
|
||||||
return erpnext.financial_statements._format_custom_report(...report_params);
|
|
||||||
|
|
||||||
if (frappe.query_report.get_filter_value("report_template"))
|
if (frappe.query_report.get_filter_value("report_template"))
|
||||||
return erpnext.financial_statements._format_custom_report(...report_params);
|
return erpnext.financial_statements._format_custom_report(...report_params);
|
||||||
else return erpnext.financial_statements._format_standard_report(...report_params);
|
else return erpnext.financial_statements._format_standard_report(...report_params);
|
||||||
@@ -56,7 +59,7 @@ erpnext.financial_statements = {
|
|||||||
const isPeriodColumn = periodKeys.includes(baseName);
|
const isPeriodColumn = periodKeys.includes(baseName);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isAccount: baseName === "account",
|
isAccount: baseName === erpnext.financial_statements.name_field,
|
||||||
isPeriod: isPeriodColumn,
|
isPeriod: isPeriodColumn,
|
||||||
segmentIndex: valueMatch && valueMatch[1] ? parseInt(valueMatch[1]) : null,
|
segmentIndex: valueMatch && valueMatch[1] ? parseInt(valueMatch[1]) : null,
|
||||||
fieldname: baseName,
|
fieldname: baseName,
|
||||||
@@ -74,15 +77,26 @@ erpnext.financial_statements = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
_format_custom_account_column: function (value, data, formatting, column, default_formatter, row) {
|
_format_custom_account_column: function (value, data, formatting, column, default_formatter, row) {
|
||||||
|
// account name to display in the report
|
||||||
|
// 1. section_name for sections
|
||||||
|
// 2. account_name for accounts
|
||||||
|
// 3. formatting.account_name for segments
|
||||||
|
// 4. value as last fallback
|
||||||
|
value = data.section_name || data.account_name || formatting.account_name || value;
|
||||||
|
|
||||||
if (!value) return "";
|
if (!value) return "";
|
||||||
|
|
||||||
// Link to open ledger
|
// Link to open ledger
|
||||||
const should_link_to_ledger =
|
const should_link_to_ledger =
|
||||||
formatting.is_detail || (formatting.account_filters && formatting.child_accounts);
|
formatting.is_detail ||
|
||||||
|
(formatting.account_filters && formatting.child_accounts && formatting.child_accounts.length);
|
||||||
|
|
||||||
if (should_link_to_ledger) {
|
if (should_link_to_ledger) {
|
||||||
const glData = {
|
const glData = {
|
||||||
account: formatting.account_name || formatting.child_accounts || value,
|
account:
|
||||||
|
Array.isArray(formatting.child_accounts) && formatting.child_accounts.length
|
||||||
|
? formatting.child_accounts
|
||||||
|
: formatting.account ?? value,
|
||||||
from_date: formatting.from_date || formatting.period_start_date,
|
from_date: formatting.from_date || formatting.period_start_date,
|
||||||
to_date: formatting.to_date || formatting.period_end_date,
|
to_date: formatting.to_date || formatting.period_end_date,
|
||||||
account_type: formatting.account_type,
|
account_type: formatting.account_type,
|
||||||
@@ -125,16 +139,41 @@ erpnext.financial_statements = {
|
|||||||
return erpnext.financial_statements._style_custom_value(formattedValue, formatting, value);
|
return erpnext.financial_statements._style_custom_value(formattedValue, formatting, value);
|
||||||
},
|
},
|
||||||
|
|
||||||
_style_custom_value(formattedValue, formatting, value) {
|
_style_custom_value(formatted_value, formatting, value) {
|
||||||
let $element = $(`<span>${formattedValue}</span>`);
|
const styles = [];
|
||||||
|
|
||||||
if (formatting.bold) $element.css("font-weight", "bold");
|
if (formatting.bold) styles.push("font-weight: bold");
|
||||||
if (formatting.italic) $element.css("font-style", "italic");
|
if (formatting.italic) styles.push("font-style: italic");
|
||||||
if (formatting.warn_if_negative && typeof value === "number" && value < 0)
|
|
||||||
$element.addClass("text-danger");
|
|
||||||
if (formatting.color) $element.css("color", formatting.color);
|
|
||||||
|
|
||||||
return $element.wrap("<p></p>").parent().html();
|
if (formatting.warn_if_negative && typeof value === "number" && value < 0) {
|
||||||
|
styles.push("color: #dc3545"); // text-danger
|
||||||
|
} else if (formatting.color) {
|
||||||
|
styles.push(`color: ${formatting.color}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (styles.length === 0) return formatted_value;
|
||||||
|
|
||||||
|
const style_string = styles.join("; ");
|
||||||
|
|
||||||
|
// formatted value contains HTML tags/elements
|
||||||
|
if (/<[^>]+>/.test(formatted_value)) {
|
||||||
|
const temp_div = document.createElement("div");
|
||||||
|
temp_div.innerHTML = formatted_value;
|
||||||
|
|
||||||
|
// parse HTML and inject styles into the first element
|
||||||
|
const first_element = temp_div.querySelector("*");
|
||||||
|
|
||||||
|
if (first_element) {
|
||||||
|
const existing_style = first_element.getAttribute("style") || "";
|
||||||
|
first_element.setAttribute(
|
||||||
|
"style",
|
||||||
|
existing_style ? `${existing_style}; ${style_string}` : style_string
|
||||||
|
);
|
||||||
|
return temp_div.innerHTML;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `<span style="${style_string}">${formatted_value}</span>`;
|
||||||
},
|
},
|
||||||
|
|
||||||
_format_special_view: function (value, row, column, data, default_formatter) {
|
_format_special_view: function (value, row, column, data, default_formatter) {
|
||||||
|
|||||||
Reference in New Issue
Block a user