1use anyhow::{Result, anyhow, bail};
2use prost::Message;
3use serde::Serialize;
4use tabled::Tabled;
5
6use crate::common::{connect_gateway, parse_symbol};
7use crate::output::OutputFormat;
8
9use super::{display_opt, display_opt_f64, display_opt_i32, display_opt_i64};
10
11#[derive(Tabled)]
12struct CompanyProfileRow {
13 #[tabled(rename = "Name")]
14 name: String,
15 #[tabled(rename = "Value")]
16 value: String,
17 #[tabled(rename = "Field Type")]
18 field_type: String,
19}
20
21#[derive(Serialize)]
22struct CompanyProfileJson {
23 symbol: String,
24 item_list: Vec<CompanyProfileItemJson>,
25}
26
27#[derive(Serialize)]
28struct CompanyProfileItemJson {
29 name: Option<String>,
30 value: Option<String>,
31 field_type: Option<i32>,
32}
33
34pub async fn run_company_profile(gateway: &str, symbol: &str, format: OutputFormat) -> Result<()> {
35 let sec = parse_symbol(symbol)?;
36 let (client, _rx) = connect_gateway(gateway, "futucli-company-profile").await?;
37
38 let req = futu_proto::qot_get_company_profile::Request {
39 c2s: futu_proto::qot_get_company_profile::C2s {
40 security: futu_proto::qot_common::Security {
41 market: sec.market as i32,
42 code: sec.code.clone(),
43 },
44 },
45 };
46 let body = req.encode_to_vec();
47 let frame = client
48 .request(futu_core::proto_id::QOT_GET_COMPANY_PROFILE, body)
49 .await?;
50 let resp = futu_proto::qot_get_company_profile::Response::decode(frame.body.as_ref())
51 .map_err(|e| anyhow!("decode company_profile: {e}"))?;
52 if resp.ret_type != 0 {
53 bail!(
54 "company_profile ret_type={} msg={:?}",
55 resp.ret_type,
56 resp.ret_msg
57 );
58 }
59 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
60
61 let rows: Vec<CompanyProfileRow> = s2c
62 .item_list
63 .iter()
64 .map(|item| CompanyProfileRow {
65 name: display_opt(&item.name),
66 value: display_opt(&item.value),
67 field_type: item.field_type.map(|v| v.to_string()).unwrap_or_default(),
68 })
69 .collect();
70 let json = CompanyProfileJson {
71 symbol: symbol.to_string(),
72 item_list: s2c
73 .item_list
74 .iter()
75 .map(|item| CompanyProfileItemJson {
76 name: item.name.clone(),
77 value: item.value.clone(),
78 field_type: item.field_type,
79 })
80 .collect(),
81 };
82 format.print_rows(&rows, &[json])?;
83 Ok(())
84}
85
86#[derive(Tabled)]
87struct CompanyExecutiveRow {
88 #[tabled(rename = "Name")]
89 name: String,
90 #[tabled(rename = "Position")]
91 position: String,
92 #[tabled(rename = "Begin")]
93 begin_date: String,
94 #[tabled(rename = "Salary")]
95 annual_salary: String,
96}
97
98#[derive(Serialize)]
99struct CompanyExecutivesJson {
100 symbol: String,
101 director_list: Vec<DirectorJson>,
102}
103
104#[derive(Serialize)]
105struct DirectorJson {
106 display_leader_name: Option<String>,
107 leader_name: Option<String>,
108 position_name: Option<String>,
109 begin_date: Option<u64>,
110 begin_date_str: Option<String>,
111 leader_gender: Option<String>,
112 leader_age: Option<String>,
113 highest_education: Option<String>,
114 annual_salary: Option<u64>,
115 issue_date: Option<u64>,
116 issue_date_str: Option<String>,
117}
118
119pub async fn run_company_executives(
120 gateway: &str,
121 symbol: &str,
122 format: OutputFormat,
123) -> Result<()> {
124 let sec = parse_symbol(symbol)?;
125 let (client, _rx) = connect_gateway(gateway, "futucli-company-executives").await?;
126
127 let req = futu_proto::qot_get_company_executives::Request {
128 c2s: futu_proto::qot_get_company_executives::C2s {
129 security: futu_proto::qot_common::Security {
130 market: sec.market as i32,
131 code: sec.code.clone(),
132 },
133 },
134 };
135 let body = req.encode_to_vec();
136 let frame = client
137 .request(futu_core::proto_id::QOT_GET_COMPANY_EXECUTIVES, body)
138 .await?;
139 let resp = futu_proto::qot_get_company_executives::Response::decode(frame.body.as_ref())
140 .map_err(|e| anyhow!("decode company_executives: {e}"))?;
141 if resp.ret_type != 0 {
142 bail!(
143 "company_executives ret_type={} msg={:?}",
144 resp.ret_type,
145 resp.ret_msg
146 );
147 }
148 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
149
150 let rows: Vec<CompanyExecutiveRow> = s2c
151 .director_list
152 .iter()
153 .map(|item| CompanyExecutiveRow {
154 name: display_opt(&item.display_leader_name),
155 position: display_opt(&item.position_name),
156 begin_date: display_opt(&item.begin_date_str),
157 annual_salary: item
158 .annual_salary
159 .map(|v| v.to_string())
160 .unwrap_or_default(),
161 })
162 .collect();
163 let json = CompanyExecutivesJson {
164 symbol: symbol.to_string(),
165 director_list: s2c
166 .director_list
167 .iter()
168 .map(|item| DirectorJson {
169 display_leader_name: item.display_leader_name.clone(),
170 leader_name: item.leader_name.clone(),
171 position_name: item.position_name.clone(),
172 begin_date: item.begin_date,
173 begin_date_str: item.begin_date_str.clone(),
174 leader_gender: item.leader_gender.clone(),
175 leader_age: item.leader_age.clone(),
176 highest_education: item.highest_education.clone(),
177 annual_salary: item.annual_salary,
178 issue_date: item.issue_date,
179 issue_date_str: item.issue_date_str.clone(),
180 })
181 .collect(),
182 };
183 format.print_rows(&rows, &[json])?;
184 Ok(())
185}
186
187#[derive(Tabled)]
188struct CompanyExecutiveBackgroundRow {
189 #[tabled(rename = "Symbol")]
190 symbol: String,
191 #[tabled(rename = "Leader Name")]
192 leader_name: String,
193 #[tabled(rename = "Brief Background")]
194 brief_background: String,
195}
196
197#[derive(Serialize)]
198struct CompanyExecutiveBackgroundJson {
199 symbol: String,
200 leader_name: String,
201 brief_background: Option<String>,
202}
203
204pub async fn run_company_executive_background(
205 gateway: &str,
206 symbol: &str,
207 leader_name: &str,
208 format: OutputFormat,
209) -> Result<()> {
210 let sec = parse_symbol(symbol)?;
211 let (client, _rx) = connect_gateway(gateway, "futucli-company-executive-background").await?;
212
213 let req = futu_proto::qot_get_company_executive_background::Request {
214 c2s: futu_proto::qot_get_company_executive_background::C2s {
215 security: futu_proto::qot_common::Security {
216 market: sec.market as i32,
217 code: sec.code.clone(),
218 },
219 leader_name: Some(leader_name.to_string()),
220 },
221 };
222 let body = req.encode_to_vec();
223 let frame = client
224 .request(
225 futu_core::proto_id::QOT_GET_COMPANY_EXECUTIVE_BACKGROUND,
226 body,
227 )
228 .await?;
229 let resp =
230 futu_proto::qot_get_company_executive_background::Response::decode(frame.body.as_ref())
231 .map_err(|e| anyhow!("decode company_executive_background: {e}"))?;
232 if resp.ret_type != 0 {
233 bail!(
234 "company_executive_background ret_type={} msg={:?}",
235 resp.ret_type,
236 resp.ret_msg
237 );
238 }
239 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
240
241 let json = CompanyExecutiveBackgroundJson {
242 symbol: symbol.to_string(),
243 leader_name: leader_name.to_string(),
244 brief_background: s2c.brief_background.clone(),
245 };
246 let rows = [CompanyExecutiveBackgroundRow {
247 symbol: symbol.to_string(),
248 leader_name: leader_name.to_string(),
249 brief_background: display_opt(&s2c.brief_background),
250 }];
251 format.print_rows(&rows, &[json])?;
252 Ok(())
253}
254
255#[derive(Tabled)]
256struct CompanyOperationalEfficiencyRow {
257 #[tabled(rename = "Fiscal Year")]
258 fiscal_year: String,
259 #[tabled(rename = "Period")]
260 period: String,
261 #[tabled(rename = "End Date")]
262 end_date: String,
263 #[tabled(rename = "Employees")]
264 employee_num: String,
265 #[tabled(rename = "Income/Capita")]
266 income_per_capita: String,
267 #[tabled(rename = "Profit/Capita")]
268 profit_per_capita: String,
269 #[tabled(rename = "Net Profit/Capita")]
270 net_profit_per_capita: String,
271}
272
273#[derive(Serialize)]
274struct CompanyOperationalEfficiencyJson {
275 symbol: String,
276 item_list: Vec<OperationalEfficiencyItemJson>,
277 next_key: Option<String>,
278 currency_code: Option<String>,
279}
280
281#[derive(Serialize)]
282struct OperationalEfficiencyItemJson {
283 fiscal_year: Option<i32>,
284 financial_type: Option<i32>,
285 period_text: Option<String>,
286 end_date: Option<i64>,
287 end_date_str: Option<String>,
288 employee_num: Option<i64>,
289 employee_num_yoy: Option<f64>,
290 income_per_capita: Option<f64>,
291 income_per_capita_yoy: Option<f64>,
292 profit_per_capita: Option<f64>,
293 profit_per_capita_yoy: Option<f64>,
294 net_profit_per_capita: Option<f64>,
295 net_profit_per_capita_yoy: Option<f64>,
296}
297
298pub async fn run_company_operational_efficiency(
299 gateway: &str,
300 symbol: &str,
301 next_key: Option<&str>,
302 num: Option<i32>,
303 currency_code: Option<&str>,
304 financial_type: Option<i32>,
305 format: OutputFormat,
306) -> Result<()> {
307 let sec = parse_symbol(symbol)?;
308 let (client, _rx) = connect_gateway(gateway, "futucli-company-operational-efficiency").await?;
309
310 let req = futu_proto::qot_get_company_operational_efficiency::Request {
311 c2s: futu_proto::qot_get_company_operational_efficiency::C2s {
312 security: futu_proto::qot_common::Security {
313 market: sec.market as i32,
314 code: sec.code.clone(),
315 },
316 next_key: next_key.map(str::to_string),
317 num,
318 currency_code: currency_code.map(str::to_string),
319 financial_type,
320 },
321 };
322 let body = req.encode_to_vec();
323 let frame = client
324 .request(
325 futu_core::proto_id::QOT_GET_COMPANY_OPERATIONAL_EFFICIENCY,
326 body,
327 )
328 .await?;
329 let resp =
330 futu_proto::qot_get_company_operational_efficiency::Response::decode(frame.body.as_ref())
331 .map_err(|e| anyhow!("decode company_operational_efficiency: {e}"))?;
332 if resp.ret_type != 0 {
333 bail!(
334 "company_operational_efficiency ret_type={} msg={:?}",
335 resp.ret_type,
336 resp.ret_msg
337 );
338 }
339 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
340
341 let rows: Vec<CompanyOperationalEfficiencyRow> = s2c
342 .item_list
343 .iter()
344 .map(|item| CompanyOperationalEfficiencyRow {
345 fiscal_year: display_opt_i32(item.fiscal_year),
346 period: display_opt(&item.period_text),
347 end_date: display_opt(&item.end_date_str),
348 employee_num: display_opt_i64(item.employee_num),
349 income_per_capita: display_opt_f64(item.income_per_capita),
350 profit_per_capita: display_opt_f64(item.profit_per_capita),
351 net_profit_per_capita: display_opt_f64(item.net_profit_per_capita),
352 })
353 .collect();
354 let json = CompanyOperationalEfficiencyJson {
355 symbol: symbol.to_string(),
356 item_list: s2c
357 .item_list
358 .iter()
359 .map(|item| OperationalEfficiencyItemJson {
360 fiscal_year: item.fiscal_year,
361 financial_type: item.financial_type,
362 period_text: item.period_text.clone(),
363 end_date: item.end_date,
364 end_date_str: item.end_date_str.clone(),
365 employee_num: item.employee_num,
366 employee_num_yoy: item.employee_num_yoy,
367 income_per_capita: item.income_per_capita,
368 income_per_capita_yoy: item.income_per_capita_yoy,
369 profit_per_capita: item.profit_per_capita,
370 profit_per_capita_yoy: item.profit_per_capita_yoy,
371 net_profit_per_capita: item.net_profit_per_capita,
372 net_profit_per_capita_yoy: item.net_profit_per_capita_yoy,
373 })
374 .collect(),
375 next_key: s2c.next_key,
376 currency_code: s2c.currency_code,
377 };
378 format.print_rows(&rows, &[json])?;
379 Ok(())
380}