Skip to content

Commit 2542b32

Browse files
chore: cookbook example
1 parent 7aa1ef0 commit 2542b32

File tree

4 files changed

+181
-1
lines changed

4 files changed

+181
-1
lines changed
Loading
Loading

src/components/SafeGlobalsTable.astro

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ const props: Props = Astro.props;
2424
<tbody>
2525
{Object.entries(props.globals).map(([name, value]) => (
2626
<tr>
27-
<td>{name}</td>
27+
<a href=""></a>
28+
<td>
29+
<a href={`#${name}`}></a>
30+
{name}
31+
</td>
2832
<td>{value.type}</td>
2933
<td>{value.docs}</td>
3034
<td>{value.parent}</td>
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
---
2+
title: Child Table Data in Columns
3+
description: An example of a report that shows the child table values in a single row with the parent data. Also, columns can be dynamic.
4+
---
5+
6+
## Introduction
7+
8+
An example of a report that shows the child table values in a single row with the parent data. Also, columns can be dynamic.
9+
10+
## Use Case
11+
12+
Suppose we have a demo DocType named **Credit Card EMI** which has the following fields:
13+
14+
1. Customer Name
15+
1. Total Amount
16+
1. Scheme: 3/6/12 Months
17+
1. EMIs: Child table with EMIs based on the scheme
18+
19+
Here is a screenshot showing the form view:
20+
21+
![Screenshot of Credit Card EMI Doctype form view](../../../../../assets/images/cookbook/reports/credit_card_emi_form_view.png)
22+
23+
## The Report
24+
25+
We want to create a report that has this EMIs in different columns, like shown below:
26+
27+
![Credit Card EMI Summary Report](../../../../../assets/images/cookbook/reports/cc_report.png)
28+
29+
## The Script
30+
31+
```py
32+
33+
import frappe
34+
35+
# maximum number of EMIs for a Credit Card EMI
36+
# case when EMI is paid in 1 year
37+
NUM_MAX_EMI = 12
38+
39+
40+
def execute(filters=None):
41+
columns = get_columns()
42+
data = get_data(filters)
43+
44+
return columns, data
45+
46+
47+
def get_columns():
48+
columns = [
49+
{
50+
"label": "Credit Card EMI",
51+
"fieldname": "credit_card_emi",
52+
"fieldtype": "Link",
53+
"options": "Credit Card EMI",
54+
},
55+
{
56+
"label": "Customer Name",
57+
"fieldname": "customer_name",
58+
"fieldtype": "Data",
59+
},
60+
{
61+
"label": "Scheme",
62+
"fieldname": "scheme",
63+
"fieldtype": "Data",
64+
},
65+
]
66+
67+
# 1 column for each EMI
68+
for i in range(1, NUM_MAX_EMI + 1):
69+
columns.append(
70+
{
71+
"label": "EMI {}".format(i),
72+
"fieldname": "emi_{}".format(i),
73+
"fieldtype": "Data",
74+
"options": "EMI",
75+
}
76+
)
77+
78+
return columns
79+
80+
81+
def get_data(filters=None):
82+
data = []
83+
84+
credit_card_emis = frappe.get_all(
85+
"Credit Card EMI", fields=["name", "customer_name", "scheme"]
86+
)
87+
88+
for credit_card_emi in credit_card_emis:
89+
row = {
90+
"credit_card_emi": credit_card_emi.name,
91+
"customer_name": credit_card_emi.customer_name,
92+
"scheme": credit_card_emi.scheme,
93+
}
94+
95+
# get all EMIs (child items) for the Credit Card EMI
96+
emis = frappe.get_all(
97+
"EMI",
98+
fields=["amount", "due_date", "paid", "idx"],
99+
filters={"parent": credit_card_emi.name},
100+
)
101+
102+
# e.g. emi_1 = 1000, emi_2 = 2000, emi_3 = 3000, etc.
103+
for emi in emis:
104+
row["emi_{}".format(emi.idx)] = format_currency(emi.amount)
105+
106+
# set rest of the EMI columns to "-"
107+
for i in range(1, MAX_EMI + 1):
108+
if "emi_{}".format(i) not in row:
109+
row["emi_{}".format(i)] = "-"
110+
111+
data.append(row)
112+
113+
return data
114+
115+
116+
def format_currency(value, currency="INR"):
117+
return frappe.format_value(value, df={"fieldtype": "Currency"}, currency=currency)
118+
```
119+
120+
## Optimizing The Data Fetching
121+
122+
If you observe the `get_data()` method, you will see we are calling the `frappe.get_all` method to get child items for each **Credit Card EMI** doc, which means 1 DB call per document (`O(n)` in computer *sciency* terms).
123+
124+
We can use the Query Builder to reduce this to just one database call (line 141):
125+
126+
```py {6-19}
127+
def get_data_with_qb(filters=None):
128+
data = []
129+
credit_card_emi = frappe.qb.DocType("Credit Card EMI")
130+
emi = frappe.qb.DocType("EMI")
131+
132+
query = (
133+
frappe.qb.from_(emi)
134+
.join(credit_card_emi)
135+
.on(emi.parent == credit_card_emi.name)
136+
.select(
137+
credit_card_emi.name.as_("credit_card_emi"),
138+
credit_card_emi.customer_name,
139+
credit_card_emi.scheme,
140+
emi.amount,
141+
emi.due_date,
142+
emi.paid,
143+
emi.idx,
144+
)
145+
)
146+
147+
emi_items = query.run(as_dict=True)
148+
149+
# group EMI items by Credit Card EMI
150+
credit_card_emi_items = {}
151+
for emi_item in emi_items:
152+
credit_card_emi_items.setdefault(emi_item.credit_card_emi, []).append(emi_item)
153+
154+
# create a row for each Credit Card EMI
155+
for credit_card_emi, emi_items in credit_card_emi_items.items():
156+
row = {
157+
"credit_card_emi": credit_card_emi,
158+
"customer_name": emi_items[0].customer_name,
159+
"scheme": emi_items[0].scheme,
160+
}
161+
162+
# create a column for each EMI
163+
for emi_item in emi_items:
164+
row["emi_{}".format(emi_item.idx)] = format_currency(emi_item.amount)
165+
166+
# set rest of the EMI columns to "-"
167+
for i in range(1, MAX_EMI + 1):
168+
if "emi_{}".format(i) not in row:
169+
row["emi_{}".format(i)] = "-"
170+
171+
data.append(row)
172+
173+
return data
174+
```
175+
176+
We had to add more data processing on the Python side though.

0 commit comments

Comments
 (0)