1use serde::{Deserialize, Serialize};
4
5use super::{
6 shared::{
7 traits::{
8 builder::JQuantsBuilder,
9 pagination::{HasPaginationKey, MergePage, Paginatable},
10 },
11 types::{
12 amount_per_share::AmountPerShare,
13 dividend::{
14 DevidendStatucCode, DividendCommemorativeSpecialCode, DividendForecastResultCode,
15 DividendInterimFinalCode,
16 },
17 payable_date::PayableDate,
18 },
19 },
20 JQuantsApiClient, JQuantsPlanClient,
21};
22
23#[derive(Clone, Serialize)]
25pub struct CashDividendDataBuilder {
26 #[serde(skip)]
27 client: JQuantsApiClient,
28
29 #[serde(skip_serializing_if = "Option::is_none")]
31 code: Option<String>,
32
33 #[serde(skip_serializing_if = "Option::is_none")]
35 date: Option<String>,
36
37 #[serde(skip_serializing_if = "Option::is_none")]
39 from: Option<String>,
40
41 #[serde(skip_serializing_if = "Option::is_none")]
43 to: Option<String>,
44
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pagination_key: Option<String>,
48}
49
50impl JQuantsBuilder<CashDividendDataResponse> for CashDividendDataBuilder {
51 async fn send(self) -> Result<CashDividendDataResponse, crate::JQuantsError> {
52 self.send_ref().await
53 }
54
55 async fn send_ref(&self) -> Result<CashDividendDataResponse, crate::JQuantsError> {
56 self.client.inner.get("fins/dividend", self).await
57 }
58}
59
60impl Paginatable<CashDividendDataResponse> for CashDividendDataBuilder {
61 fn pagination_key(mut self, pagination_key: impl Into<String>) -> Self {
62 self.pagination_key = Some(pagination_key.into());
63 self
64 }
65}
66
67impl CashDividendDataBuilder {
68 pub(crate) fn new(client: JQuantsApiClient) -> Self {
70 Self {
71 client,
72 code: None,
73 date: None,
74 from: None,
75 to: None,
76 pagination_key: None,
77 }
78 }
79
80 pub fn code(mut self, code: impl Into<String>) -> Self {
82 self.code = Some(code.into());
83 self
84 }
85
86 pub fn date(mut self, date: impl Into<String>) -> Self {
88 self.date = Some(date.into());
89 self
90 }
91
92 pub fn from(mut self, from: impl Into<String>) -> Self {
94 self.from = Some(from.into());
95 self
96 }
97
98 pub fn to(mut self, to: impl Into<String>) -> Self {
100 self.to = Some(to.into());
101 self
102 }
103}
104
105pub trait CashDividendDataApi: JQuantsPlanClient {
107 fn get_cash_dividend_data(&self) -> CashDividendDataBuilder {
111 CashDividendDataBuilder::new(self.get_api_client().clone())
112 }
113}
114
115#[derive(Debug, Clone, PartialEq, Deserialize)]
119pub struct CashDividendDataResponse {
120 pub dividend: Vec<CashDividendItem>,
122 pub pagination_key: Option<String>,
124}
125
126impl HasPaginationKey for CashDividendDataResponse {
127 fn get_pagination_key(&self) -> Option<&str> {
128 self.pagination_key.as_deref()
129 }
130}
131
132impl MergePage for CashDividendDataResponse {
133 fn merge_page(
134 page: Result<Vec<Self>, crate::JQuantsError>,
135 ) -> Result<Self, crate::JQuantsError> {
136 let mut page = page?;
137 let mut merged = page.pop().unwrap();
138 for p in page {
139 merged.dividend.extend(p.dividend);
140 }
141 merged.pagination_key = None;
142
143 Ok(merged)
144 }
145}
146
147#[derive(Debug, Clone, PartialEq, Deserialize)]
149pub struct CashDividendItem {
150 #[serde(rename = "AnnouncementDate")]
152 pub announcement_date: String,
153
154 #[serde(rename = "AnnouncementTime")]
156 pub announcement_time: String,
157
158 #[serde(rename = "Code")]
160 pub code: String,
161
162 #[serde(rename = "ReferenceNumber")]
164 pub reference_number: String,
165
166 #[serde(rename = "StatusCode")]
168 pub status_code: DevidendStatucCode,
169
170 #[serde(rename = "BoardMeetingDate")]
172 pub board_meeting_date: String,
173
174 #[serde(rename = "InterimFinalCode")]
176 pub interim_final_code: DividendInterimFinalCode,
177
178 #[serde(rename = "ForecastResultCode")]
180 pub forecast_result_code: DividendForecastResultCode,
181
182 #[serde(rename = "InterimFinalTerm")]
184 pub interim_final_term: String,
185
186 #[serde(rename = "GrossDividendRate")]
188 pub gross_dividend_rate: AmountPerShare,
189
190 #[serde(rename = "RecordDate")]
192 pub record_date: String,
193
194 #[serde(rename = "ExDate")]
196 pub ex_date: String,
197
198 #[serde(rename = "ActualRecordDate")]
200 pub actual_record_date: String,
201
202 #[serde(rename = "PayableDate")]
204 pub payable_date: PayableDate,
205
206 #[serde(rename = "CAReferenceNumber")]
208 pub ca_reference_number: String,
209
210 #[serde(rename = "DistributionAmount")]
212 pub distribution_amount: AmountPerShare,
213
214 #[serde(rename = "RetainedEarnings")]
216 pub retained_earnings: AmountPerShare,
217
218 #[serde(rename = "DeemedDividend")]
220 pub deemed_dividend: AmountPerShare,
221
222 #[serde(rename = "DeemedCapitalGains")]
224 pub deemed_capital_gains: AmountPerShare,
225
226 #[serde(rename = "NetAssetDecreaseRatio")]
228 pub net_asset_decrease_ratio: AmountPerShare,
229
230 #[serde(rename = "CommemorativeSpecialCode")]
232 pub commemorative_special_code: DividendCommemorativeSpecialCode,
233
234 #[serde(rename = "CommemorativeDividendRate")]
236 pub commemorative_dividend_rate: AmountPerShare,
237
238 #[serde(rename = "SpecialDividendRate")]
240 pub special_dividend_rate: AmountPerShare,
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 #[test]
248 fn test_deserialize_cash_dividend_data_response() {
249 let json_data = r#"
250 {
251 "dividend": [
252 {
253 "AnnouncementDate": "2014-02-24",
254 "AnnouncementTime": "09:21",
255 "Code": "15550",
256 "ReferenceNumber": "201402241B00002",
257 "StatusCode": "1",
258 "BoardMeetingDate": "2014-02-24",
259 "InterimFinalCode": "2",
260 "ForecastResultCode": "2",
261 "InterimFinalTerm": "2014-03",
262 "GrossDividendRate": "-",
263 "RecordDate": "2014-03-10",
264 "ExDate": "2014-03-06",
265 "ActualRecordDate": "2014-03-10",
266 "PayableDate": "-",
267 "CAReferenceNumber": "201402241B00002",
268 "DistributionAmount": "",
269 "RetainedEarnings": "",
270 "DeemedDividend": "",
271 "DeemedCapitalGains": "",
272 "NetAssetDecreaseRatio": "",
273 "CommemorativeSpecialCode": "0",
274 "CommemorativeDividendRate": "",
275 "SpecialDividendRate": ""
276 }
277 ],
278 "pagination_key": "value1.value2."
279 }
280 "#;
281
282 let response: CashDividendDataResponse = serde_json::from_str(json_data).unwrap();
283
284 let expected_dividend = vec![CashDividendItem {
285 announcement_date: "2014-02-24".to_string(),
286 announcement_time: "09:21".to_string(),
287 code: "15550".to_string(),
288 reference_number: "201402241B00002".to_string(),
289 status_code: DevidendStatucCode::New,
290 board_meeting_date: "2014-02-24".to_string(),
291 interim_final_code: DividendInterimFinalCode::Final,
292 forecast_result_code: DividendForecastResultCode::Forecast,
293 interim_final_term: "2014-03".to_string(),
294 gross_dividend_rate: AmountPerShare::Undetermined,
295 record_date: "2014-03-10".to_string(),
296 ex_date: "2014-03-06".to_string(),
297 actual_record_date: "2014-03-10".to_string(),
298 payable_date: PayableDate::Undetermined,
299 ca_reference_number: "201402241B00002".to_string(),
300 distribution_amount: AmountPerShare::NotApplicable,
301 retained_earnings: AmountPerShare::NotApplicable,
302 deemed_dividend: AmountPerShare::NotApplicable,
303 deemed_capital_gains: AmountPerShare::NotApplicable,
304 net_asset_decrease_ratio: AmountPerShare::NotApplicable,
305 commemorative_special_code: DividendCommemorativeSpecialCode::Normal,
306 commemorative_dividend_rate: AmountPerShare::NotApplicable,
307 special_dividend_rate: AmountPerShare::NotApplicable,
308 }];
309
310 let expected_response = CashDividendDataResponse {
311 dividend: expected_dividend,
312 pagination_key: Some("value1.value2.".to_string()),
313 };
314
315 pretty_assertions::assert_eq!(response, expected_response);
316 }
317
318 #[test]
319 fn test_deserialize_cash_dividend_data_response_no_pagination_key() {
320 let json_data = r#"
321 {
322 "dividend": [
323 {
324 "AnnouncementDate": "2014-02-24",
325 "AnnouncementTime": "09:21",
326 "Code": "15550",
327 "ReferenceNumber": "201402241B00002",
328 "StatusCode": "1",
329 "BoardMeetingDate": "2014-02-24",
330 "InterimFinalCode": "2",
331 "ForecastResultCode": "2",
332 "InterimFinalTerm": "2014-03",
333 "GrossDividendRate": "-",
334 "RecordDate": "2014-03-10",
335 "ExDate": "2014-03-06",
336 "ActualRecordDate": "2014-03-10",
337 "PayableDate": "-",
338 "CAReferenceNumber": "201402241B00002",
339 "DistributionAmount": "",
340 "RetainedEarnings": "",
341 "DeemedDividend": "",
342 "DeemedCapitalGains": "",
343 "NetAssetDecreaseRatio": "",
344 "CommemorativeSpecialCode": "0",
345 "CommemorativeDividendRate": "",
346 "SpecialDividendRate": ""
347 }
348 ]
349 }
350 "#;
351
352 let response: CashDividendDataResponse = serde_json::from_str(json_data).unwrap();
353
354 let expected_dividend = vec![CashDividendItem {
355 announcement_date: "2014-02-24".to_string(),
356 announcement_time: "09:21".to_string(),
357 code: "15550".to_string(),
358 reference_number: "201402241B00002".to_string(),
359 status_code: DevidendStatucCode::New,
360 board_meeting_date: "2014-02-24".to_string(),
361 interim_final_code: DividendInterimFinalCode::Final,
362 forecast_result_code: DividendForecastResultCode::Forecast,
363 interim_final_term: "2014-03".to_string(),
364 gross_dividend_rate: AmountPerShare::Undetermined,
365 record_date: "2014-03-10".to_string(),
366 ex_date: "2014-03-06".to_string(),
367 actual_record_date: "2014-03-10".to_string(),
368 payable_date: PayableDate::Undetermined,
369 ca_reference_number: "201402241B00002".to_string(),
370 distribution_amount: AmountPerShare::NotApplicable,
371 retained_earnings: AmountPerShare::NotApplicable,
372 deemed_dividend: AmountPerShare::NotApplicable,
373 deemed_capital_gains: AmountPerShare::NotApplicable,
374 net_asset_decrease_ratio: AmountPerShare::NotApplicable,
375 commemorative_special_code: DividendCommemorativeSpecialCode::Normal,
376 commemorative_dividend_rate: AmountPerShare::NotApplicable,
377 special_dividend_rate: AmountPerShare::NotApplicable,
378 }];
379
380 let expected_response = CashDividendDataResponse {
381 dividend: expected_dividend,
382 pagination_key: None,
383 };
384
385 pretty_assertions::assert_eq!(response, expected_response);
386 }
387
388 #[test]
389 fn test_deserialize_cash_dividend_data_response_multiple_items() {
390 let json_data = r#"
391 {
392 "dividend": [
393 {
394 "AnnouncementDate": "2023-03-06",
395 "AnnouncementTime": "10:00",
396 "Code": "86970",
397 "ReferenceNumber": "1",
398 "StatusCode": "1",
399 "BoardMeetingDate": "2023-03-06",
400 "InterimFinalCode": "1",
401 "ForecastResultCode": "1",
402 "InterimFinalTerm": "2023-04",
403 "GrossDividendRate": "100",
404 "RecordDate": "2023-03-10",
405 "ExDate": "2023-03-05",
406 "ActualRecordDate": "2023-03-10",
407 "PayableDate": "2023-03-15",
408 "CAReferenceNumber": "1",
409 "DistributionAmount": "100",
410 "RetainedEarnings": "50",
411 "DeemedDividend": "0",
412 "DeemedCapitalGains": "0",
413 "NetAssetDecreaseRatio": "0.05",
414 "CommemorativeSpecialCode": "0",
415 "CommemorativeDividendRate": "-",
416 "SpecialDividendRate": "-"
417 },
418 {
419 "AnnouncementDate": "2023-03-07",
420 "AnnouncementTime": "11:00",
421 "Code": "86970",
422 "ReferenceNumber": "2",
423 "StatusCode": "2",
424 "BoardMeetingDate": "2023-03-07",
425 "InterimFinalCode": "2",
426 "ForecastResultCode": "1",
427 "InterimFinalTerm": "2023-04",
428 "GrossDividendRate": "110",
429 "RecordDate": "2023-03-12",
430 "ExDate": "2023-03-07",
431 "ActualRecordDate": "2023-03-12",
432 "PayableDate": "2023-03-17",
433 "CAReferenceNumber": "1",
434 "DistributionAmount": "110",
435 "RetainedEarnings": "55",
436 "DeemedDividend": "0",
437 "DeemedCapitalGains": "0",
438 "NetAssetDecreaseRatio": "0.055",
439 "CommemorativeSpecialCode": "1",
440 "CommemorativeDividendRate": "10",
441 "SpecialDividendRate": "-"
442 }
443 ],
444 "pagination_key": "value3.value4."
445 }
446 "#;
447
448 let response: CashDividendDataResponse = serde_json::from_str(json_data).unwrap();
449
450 let expected_dividend = vec![
451 CashDividendItem {
452 announcement_date: "2023-03-06".to_string(),
453 announcement_time: "10:00".to_string(),
454 code: "86970".to_string(),
455 reference_number: "1".to_string(),
456 status_code: DevidendStatucCode::New,
457 board_meeting_date: "2023-03-06".to_string(),
458 interim_final_code: DividendInterimFinalCode::Interim,
459 forecast_result_code: DividendForecastResultCode::Determined,
460 interim_final_term: "2023-04".to_string(),
461 gross_dividend_rate: AmountPerShare::Number(100.0),
462 record_date: "2023-03-10".to_string(),
463 ex_date: "2023-03-05".to_string(),
464 actual_record_date: "2023-03-10".to_string(),
465 payable_date: PayableDate::Date("2023-03-15".to_string()),
466 ca_reference_number: "1".to_string(),
467 distribution_amount: AmountPerShare::Number(100.0),
468 retained_earnings: AmountPerShare::Number(50.0),
469 deemed_dividend: AmountPerShare::Number(0.0),
470 deemed_capital_gains: AmountPerShare::Number(0.0),
471 net_asset_decrease_ratio: AmountPerShare::Number(0.05),
472 commemorative_special_code: DividendCommemorativeSpecialCode::Normal,
473 commemorative_dividend_rate: AmountPerShare::Undetermined,
474 special_dividend_rate: AmountPerShare::Undetermined,
475 },
476 CashDividendItem {
477 announcement_date: "2023-03-07".to_string(),
478 announcement_time: "11:00".to_string(),
479 code: "86970".to_string(),
480 reference_number: "2".to_string(),
481 status_code: DevidendStatucCode::Revised,
482 board_meeting_date: "2023-03-07".to_string(),
483 interim_final_code: DividendInterimFinalCode::Final,
484 forecast_result_code: DividendForecastResultCode::Determined,
485 interim_final_term: "2023-04".to_string(),
486 gross_dividend_rate: AmountPerShare::Number(110.0),
487 record_date: "2023-03-12".to_string(),
488 ex_date: "2023-03-07".to_string(),
489 actual_record_date: "2023-03-12".to_string(),
490 payable_date: PayableDate::Date("2023-03-17".to_string()),
491 ca_reference_number: "1".to_string(),
492 distribution_amount: AmountPerShare::Number(110.0),
493 retained_earnings: AmountPerShare::Number(55.0),
494 deemed_dividend: AmountPerShare::Number(0.0),
495 deemed_capital_gains: AmountPerShare::Number(0.0),
496 net_asset_decrease_ratio: AmountPerShare::Number(0.055),
497 commemorative_special_code: DividendCommemorativeSpecialCode::Commemorative,
498 commemorative_dividend_rate: AmountPerShare::Number(10.0),
499 special_dividend_rate: AmountPerShare::Undetermined,
500 },
501 ];
502
503 let expected_response = CashDividendDataResponse {
504 dividend: expected_dividend,
505 pagination_key: Some("value3.value4.".to_string()),
506 };
507
508 pretty_assertions::assert_eq!(response, expected_response);
509 }
510
511 #[test]
512 fn test_deserialize_cash_dividend_data_response_no_data() {
513 let json_data = r#"
514 {
515 "dividend": []
516 }
517 "#;
518
519 let response: CashDividendDataResponse = serde_json::from_str(json_data).unwrap();
520 let expected_response = CashDividendDataResponse {
521 dividend: vec![],
522 pagination_key: None,
523 };
524
525 pretty_assertions::assert_eq!(response, expected_response);
526 }
527}