1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6use super::{
7 shared::{
8 traits::{
9 builder::JQuantsBuilder,
10 pagination::{HasPaginationKey, MergePage, Paginatable},
11 },
12 types::type_of_document::TypeOfDocument,
13 },
14 JQuantsApiClient, JQuantsPlanClient,
15};
16
17#[derive(Clone, Serialize)]
19pub struct FinancialStatementDetailsBuilder {
20 #[serde(skip)]
21 client: JQuantsApiClient,
22
23 #[serde(skip_serializing_if = "Option::is_none")]
25 code: Option<String>,
26 #[serde(skip_serializing_if = "Option::is_none")]
28 date: Option<String>,
29
30 #[serde(skip_serializing_if = "Option::is_none")]
32 pagination_key: Option<String>,
33}
34
35impl JQuantsBuilder<FinancialStatementDetailsResponse> for FinancialStatementDetailsBuilder {
36 async fn send(self) -> Result<FinancialStatementDetailsResponse, crate::JQuantsError> {
37 self.send_ref().await
38 }
39
40 async fn send_ref(&self) -> Result<FinancialStatementDetailsResponse, crate::JQuantsError> {
41 self.client.inner.get("fins/fs_details", self).await
42 }
43}
44
45impl Paginatable<FinancialStatementDetailsResponse> for FinancialStatementDetailsBuilder {
46 fn pagination_key(mut self, pagination_key: impl Into<String>) -> Self {
47 self.pagination_key = Some(pagination_key.into());
48 self
49 }
50}
51
52impl FinancialStatementDetailsBuilder {
53 pub(crate) fn new(client: JQuantsApiClient) -> Self {
55 Self {
56 client,
57 code: None,
58 date: None,
59 pagination_key: None,
60 }
61 }
62
63 pub fn code(mut self, code: impl Into<String>) -> Self {
65 self.code = Some(code.into());
66 self
67 }
68
69 pub fn date(mut self, date: impl Into<String>) -> Self {
71 self.date = Some(date.into());
72 self
73 }
74}
75
76pub trait FinancialStatementDetailsApi: JQuantsPlanClient {
78 fn get_financial_statement_details(&self) -> FinancialStatementDetailsBuilder {
82 FinancialStatementDetailsBuilder::new(self.get_api_client().clone())
83 }
84}
85
86#[derive(Debug, Clone, PartialEq, Deserialize)]
90pub struct FinancialStatementDetailsResponse {
91 pub fs_details: Vec<FinancialStatementDetailItem>,
93 pub pagination_key: Option<String>,
95}
96
97impl HasPaginationKey for FinancialStatementDetailsResponse {
98 fn get_pagination_key(&self) -> Option<&str> {
99 self.pagination_key.as_deref()
100 }
101}
102
103impl MergePage for FinancialStatementDetailsResponse {
104 fn merge_page(
105 page: Result<Vec<Self>, crate::JQuantsError>,
106 ) -> Result<Self, crate::JQuantsError> {
107 let mut page = page?;
108 let mut merged = page.pop().unwrap();
109 for p in page {
110 merged.fs_details.extend(p.fs_details);
111 }
112 merged.pagination_key = None;
113
114 Ok(merged)
115 }
116}
117
118#[derive(Debug, Clone, PartialEq, Deserialize)]
120pub struct FinancialStatementDetailItem {
121 #[serde(rename = "DisclosedDate")]
123 pub disclosed_date: String,
124
125 #[serde(rename = "DisclosedTime")]
127 pub disclosed_time: String,
128
129 #[serde(rename = "LocalCode")]
131 pub local_code: String,
132
133 #[serde(rename = "DisclosureNumber")]
137 pub disclosure_number: String,
138
139 #[serde(rename = "TypeOfDocument")]
141 pub type_of_document: TypeOfDocument,
142
143 #[serde(rename = "FinancialStatement")]
147 pub financial_statement: HashMap<String, String>,
148}
149
150#[cfg(test)]
151mod tests {
152 use maplit::hashmap;
153
154 use super::*;
155
156 #[test]
157 fn test_deserialize_financial_statement_details_response() {
158 let json_data = r#"
159 {
160 "fs_details": [
161 {
162 "DisclosedDate": "2023-01-30",
163 "DisclosedTime": "12:00:00",
164 "LocalCode": "86970",
165 "DisclosureNumber": "20230127594871",
166 "TypeOfDocument": "3QFinancialStatements_Consolidated_IFRS",
167 "FinancialStatement": {
168 "Goodwill (IFRS)": "67374000000",
169 "Retained earnings (IFRS)": "263894000000",
170 "Operating profit (loss) (IFRS)": "51765000000.0",
171 "Previous fiscal year end date, DEI": "2022-03-31",
172 "Basic earnings (loss) per share (IFRS)": "66.76",
173 "Document type, DEI": "四半期第3号参考様式 [IFRS](連結)",
174 "Current period end date, DEI": "2022-12-31",
175 "Revenue - 2 (IFRS)": "100987000000.0",
176 "Industry code when consolidated financial statements are prepared in accordance with industry specific regulations, DEI": "CTE",
177 "Profit (loss) attributable to owners of parent (IFRS)": "35175000000.0",
178 "Other current liabilities - CL (IFRS)": "8904000000",
179 "Share of profit (loss) of investments accounted for using equity method (IFRS)": "1042000000.0",
180 "Current liabilities (IFRS)": "78852363000000",
181 "Equity attributable to owners of parent (IFRS)": "311103000000",
182 "Whether consolidated financial statements are prepared, DEI": "true",
183 "Non-current liabilities (IFRS)": "33476000000",
184 "Other expenses (IFRS)": "58000000.0",
185 "Income taxes payable - CL (IFRS)": "5245000000",
186 "Filer name in English, DEI": "Japan Exchange Group, Inc.",
187 "Non-controlling interests (IFRS)": "8918000000",
188 "Capital surplus (IFRS)": "38844000000",
189 "Finance costs (IFRS)": "71000000.0",
190 "Other current assets - CA (IFRS)": "4217000000",
191 "Property, plant and equipment (IFRS)": "11277000000",
192 "Deferred tax liabilities (IFRS)": "419000000",
193 "Other components of equity (IFRS)": "422000000",
194 "Current fiscal year start date, DEI": "2022-04-01",
195 "Type of current period, DEI": "Q3",
196 "Cash and cash equivalents (IFRS)": "91135000000",
197 "Share capital (IFRS)": "11500000000",
198 "Retirement benefit asset - NCA (IFRS)": "9028000000",
199 "Number of submission, DEI": "1",
200 "Trade and other receivables - CA (IFRS)": "18837000000",
201 "Liabilities and equity (IFRS)": "79205861000000",
202 "EDINET code, DEI": "E03814",
203 "Equity (IFRS)": "320021000000",
204 "Security code, DEI": "86970",
205 "Other financial assets - CA (IFRS)": "112400000000",
206 "Other financial assets - NCA (IFRS)": "2898000000",
207 "Income taxes receivable - CA (IFRS)": "5529000000",
208 "Investments accounted for using equity method (IFRS)": "18362000000",
209 "Other non-current assets - NCA (IFRS)": "6240000000",
210 "Previous fiscal year start date, DEI": "2021-04-01",
211 "Filer name in Japanese, DEI": "株式会社日本取引所グループ",
212 "Deferred tax assets (IFRS)": "2862000000",
213 "Trade and other payables - CL (IFRS)": "5037000000",
214 "Bonds and borrowings - CL (IFRS)": "33000000000",
215 "Current fiscal year end date, DEI": "2023-03-31",
216 "XBRL amendment flag, DEI": "false",
217 "Non-current assets (IFRS)": "182317000000",
218 "Retirement benefit liability - NCL (IFRS)": "9214000000",
219 "Amendment flag, DEI": "false",
220 "Assets (IFRS)": "79205861000000",
221 "Income tax expense (IFRS)": "15841000000.0",
222 "Report amendment flag, DEI": "false",
223 "Profit (loss) (IFRS)": "35894000000.0",
224 "Operating expenses (IFRS)": "50206000000.0",
225 "Intangible assets (IFRS)": "36324000000",
226 "Profit (loss) before tax from continuing operations (IFRS)": "51736000000.0",
227 "Liabilities (IFRS)": "78885839000000",
228 "Accounting standards, DEI": "IFRS",
229 "Bonds and borrowings - NCL (IFRS)": "19972000000",
230 "Finance income (IFRS)": "43000000.0",
231 "Profit (loss) attributable to non-controlling interests (IFRS)": "719000000.0",
232 "Comparative period end date, DEI": "2021-12-31",
233 "Current assets (IFRS)": "79023543000000",
234 "Other non-current liabilities - NCL (IFRS)": "3870000000",
235 "Other income (IFRS)": "458000000.0",
236 "Treasury shares (IFRS)": "-3556000000"
237 }
238 }
239 ],
240 "pagination_key": "value1.value2."
241 }
242 "#;
243
244 let response: FinancialStatementDetailsResponse = serde_json::from_str(json_data).unwrap();
245 let financial_statement_map: HashMap<&str, &str> = hashmap! {
246 "Goodwill (IFRS)" => "67374000000",
247 "Retained earnings (IFRS)" => "263894000000",
248 "Operating profit (loss) (IFRS)" => "51765000000.0",
249 "Previous fiscal year end date, DEI" => "2022-03-31",
250 "Basic earnings (loss) per share (IFRS)" => "66.76",
251 "Document type, DEI" => "四半期第3号参考様式 [IFRS](連結)",
252 "Current period end date, DEI" => "2022-12-31",
253 "Revenue - 2 (IFRS)" => "100987000000.0",
254 "Industry code when consolidated financial statements are prepared in accordance with industry specific regulations, DEI" => "CTE",
255 "Profit (loss) attributable to owners of parent (IFRS)" => "35175000000.0",
256 "Other current liabilities - CL (IFRS)" => "8904000000",
257 "Share of profit (loss) of investments accounted for using equity method (IFRS)" => "1042000000.0",
258 "Current liabilities (IFRS)" => "78852363000000",
259 "Equity attributable to owners of parent (IFRS)" => "311103000000",
260 "Whether consolidated financial statements are prepared, DEI" => "true",
261 "Non-current liabilities (IFRS)" => "33476000000",
262 "Other expenses (IFRS)" => "58000000.0",
263 "Income taxes payable - CL (IFRS)" => "5245000000",
264 "Filer name in English, DEI" => "Japan Exchange Group, Inc.",
265 "Non-controlling interests (IFRS)" => "8918000000",
266 "Capital surplus (IFRS)" => "38844000000",
267 "Finance costs (IFRS)" => "71000000.0",
268 "Other current assets - CA (IFRS)" => "4217000000",
269 "Property, plant and equipment (IFRS)" => "11277000000",
270 "Deferred tax liabilities (IFRS)" => "419000000",
271 "Other components of equity (IFRS)" => "422000000",
272 "Current fiscal year start date, DEI" => "2022-04-01",
273 "Type of current period, DEI" => "Q3",
274 "Cash and cash equivalents (IFRS)" => "91135000000",
275 "Share capital (IFRS)" => "11500000000",
276 "Retirement benefit asset - NCA (IFRS)" => "9028000000",
277 "Number of submission, DEI" => "1",
278 "Trade and other receivables - CA (IFRS)" => "18837000000",
279 "Liabilities and equity (IFRS)" => "79205861000000",
280 "EDINET code, DEI" => "E03814",
281 "Equity (IFRS)" => "320021000000",
282 "Security code, DEI" => "86970",
283 "Other financial assets - CA (IFRS)" => "112400000000",
284 "Other financial assets - NCA (IFRS)" => "2898000000",
285 "Income taxes receivable - CA (IFRS)" => "5529000000",
286 "Investments accounted for using equity method (IFRS)" => "18362000000",
287 "Other non-current assets - NCA (IFRS)" => "6240000000",
288 "Previous fiscal year start date, DEI" => "2021-04-01",
289 "Filer name in Japanese, DEI" => "株式会社日本取引所グループ",
290 "Deferred tax assets (IFRS)" => "2862000000",
291 "Trade and other payables - CL (IFRS)" => "5037000000",
292 "Bonds and borrowings - CL (IFRS)" => "33000000000",
293 "Current fiscal year end date, DEI" => "2023-03-31",
294 "XBRL amendment flag, DEI" => "false",
295 "Non-current assets (IFRS)" => "182317000000",
296 "Retirement benefit liability - NCL (IFRS)" => "9214000000",
297 "Amendment flag, DEI" => "false",
298 "Assets (IFRS)" => "79205861000000",
299 "Income tax expense (IFRS)" => "15841000000.0",
300 "Report amendment flag, DEI" => "false",
301 "Profit (loss) (IFRS)" => "35894000000.0",
302 "Operating expenses (IFRS)" => "50206000000.0",
303 "Intangible assets (IFRS)" => "36324000000",
304 "Profit (loss) before tax from continuing operations (IFRS)" => "51736000000.0",
305 "Liabilities (IFRS)" => "78885839000000",
306 "Accounting standards, DEI" => "IFRS",
307 "Bonds and borrowings - NCL (IFRS)" => "19972000000",
308 "Finance income (IFRS)" => "43000000.0",
309 "Profit (loss) attributable to non-controlling interests (IFRS)" => "719000000.0",
310 "Comparative period end date, DEI" => "2021-12-31",
311 "Current assets (IFRS)" => "79023543000000",
312 "Other non-current liabilities - NCL (IFRS)" => "3870000000",
313 "Other income (IFRS)" => "458000000.0",
314 "Treasury shares (IFRS)" => "-3556000000",
315 };
316
317 let expected_response = FinancialStatementDetailsResponse {
318 fs_details: vec![FinancialStatementDetailItem {
319 disclosed_date: "2023-01-30".to_string(),
320 disclosed_time: "12:00:00".to_string(),
321 local_code: "86970".to_string(),
322 disclosure_number: "20230127594871".to_string(),
323 type_of_document: TypeOfDocument::Q3FinancialStatementsConsolidatedIFRS,
324 financial_statement: financial_statement_map
325 .into_iter()
326 .map(|(k, v)| (k.to_string(), v.to_string()))
327 .collect(),
328 }],
329 pagination_key: Some("value1.value2.".to_string()),
330 };
331
332 pretty_assertions::assert_eq!(response, expected_response);
333 }
334
335 #[test]
336 fn test_deserialize_financial_statement_details_response_no_pagination_key() {
337 let json_data = r#"
338 {
339 "fs_details": [
340 {
341 "DisclosedDate": "2023-01-30",
342 "DisclosedTime": "12:00:00",
343 "LocalCode": "86970",
344 "DisclosureNumber": "20230127594871",
345 "TypeOfDocument": "3QFinancialStatements_Consolidated_IFRS",
346 "FinancialStatement": {
347 "Goodwill (IFRS)": "67374000000",
348 "Retained earnings (IFRS)": "263894000000"
349 }
350 }
351 ]
352 }
353 "#;
354
355 let response: FinancialStatementDetailsResponse = serde_json::from_str(json_data).unwrap();
356 let financial_statement_map: HashMap<&str, &str> = hashmap! {
357 "Goodwill (IFRS)" => "67374000000",
358 "Retained earnings (IFRS)" => "263894000000"
359 };
360
361 let expected_response = FinancialStatementDetailsResponse {
362 fs_details: vec![FinancialStatementDetailItem {
363 disclosed_date: "2023-01-30".to_string(),
364 disclosed_time: "12:00:00".to_string(),
365 local_code: "86970".to_string(),
366 disclosure_number: "20230127594871".to_string(),
367 type_of_document: TypeOfDocument::Q3FinancialStatementsConsolidatedIFRS,
368 financial_statement: financial_statement_map
369 .into_iter()
370 .map(|(k, v)| (k.to_string(), v.to_string()))
371 .collect(),
372 }],
373 pagination_key: None,
374 };
375
376 pretty_assertions::assert_eq!(response, expected_response);
377 }
378
379 #[test]
380 fn test_deserialize_financial_statement_details_response_multiple_items() {
381 let json_data = r#"
382 {
383 "fs_details": [
384 {
385 "DisclosedDate": "2023-01-30",
386 "DisclosedTime": "12:00:00",
387 "LocalCode": "86970",
388 "DisclosureNumber": "20230127594871",
389 "TypeOfDocument": "3QFinancialStatements_Consolidated_IFRS",
390 "FinancialStatement": {
391 "Goodwill (IFRS)": "67374000000",
392 "Retained earnings (IFRS)": "263894000000"
393 }
394 },
395 {
396 "DisclosedDate": "2023-02-15",
397 "DisclosedTime": "14:30:00",
398 "LocalCode": "86971",
399 "DisclosureNumber": "20230227594872",
400 "TypeOfDocument": "OtherPeriodFinancialStatements_Consolidated_IFRS",
401 "FinancialStatement": {
402 "Goodwill (IFRS)": "70000000000",
403 "Retained earnings (IFRS)": "280000000000"
404 }
405 }
406 ],
407 "pagination_key": "value3.value4."
408 }
409 "#;
410
411 let response: FinancialStatementDetailsResponse = serde_json::from_str(json_data).unwrap();
412
413 let fs_map1: HashMap<&str, &str> = hashmap! {
414 "Goodwill (IFRS)" => "67374000000",
415 "Retained earnings (IFRS)" => "263894000000",
416 };
418
419 let fs_map2: HashMap<&str, &str> = hashmap! {
420 "Goodwill (IFRS)" => "70000000000",
421 "Retained earnings (IFRS)" => "280000000000"
422 };
424
425 let expected_response = FinancialStatementDetailsResponse {
426 fs_details: vec![
427 FinancialStatementDetailItem {
428 disclosed_date: "2023-01-30".to_string(),
429 disclosed_time: "12:00:00".to_string(),
430 local_code: "86970".to_string(),
431 disclosure_number: "20230127594871".to_string(),
432 type_of_document: TypeOfDocument::Q3FinancialStatementsConsolidatedIFRS,
433 financial_statement: fs_map1
434 .into_iter()
435 .map(|(k, v)| (k.to_string(), v.to_string()))
436 .collect(),
437 },
438 FinancialStatementDetailItem {
439 disclosed_date: "2023-02-15".to_string(),
440 disclosed_time: "14:30:00".to_string(),
441 local_code: "86971".to_string(),
442 disclosure_number: "20230227594872".to_string(),
443 type_of_document:
444 TypeOfDocument::OtherPeriodFinancialStatementsConsolidatedIFRS,
445 financial_statement: fs_map2
446 .into_iter()
447 .map(|(k, v)| (k.to_string(), v.to_string()))
448 .collect(),
449 },
450 ],
451 pagination_key: Some("value3.value4.".to_string()),
452 };
453
454 pretty_assertions::assert_eq!(response, expected_response);
455 }
456
457 #[test]
458 fn test_deserialize_financial_statement_details_response_no_data() {
459 let json_data = r#"
460 {
461 "fs_details": []
462 }
463 "#;
464
465 let response: FinancialStatementDetailsResponse = serde_json::from_str(json_data).unwrap();
466 let expected_response = FinancialStatementDetailsResponse {
467 fs_details: vec![],
468 pagination_key: None,
469 };
470
471 pretty_assertions::assert_eq!(response, expected_response);
472 }
473}