1use crate::client::ConcurClient;
6use crate::error::Result;
7use crate::models::{
8 CustomData, ExchangeRate, ExpenseType, Link, Location, Money, PaymentType, Vendor,
9};
10use reqwest::Method;
11use serde::{Deserialize, Serialize};
12
13pub struct ReportsApi<'a> {
15 client: &'a ConcurClient,
16}
17
18impl<'a> ReportsApi<'a> {
19 pub fn new(client: &'a ConcurClient) -> Self {
20 Self { client }
21 }
22
23 fn base(&self, user_id: &str, context: &str) -> String {
24 format!(
25 "/expensereports/v4/users/{}/context/{}/reports",
26 user_id, context
27 )
28 }
29
30 pub async fn get(&self, user_id: &str, context: &str, report_id: &str) -> Result<Report> {
32 self.client
33 .request(
34 Method::GET,
35 &format!("{}/{}", self.base(user_id, context), report_id),
36 )
37 .await?
38 .send()
39 .await?
40 .json()
41 .await
42 .map_err(Into::into)
43 }
44
45 pub async fn create(
47 &self,
48 user_id: &str,
49 context: &str,
50 report: &CreateReport,
51 ) -> Result<CreateReportResponse> {
52 let req = self
53 .client
54 .request(Method::POST, &self.base(user_id, context))
55 .await?;
56 let req = ConcurClient::with_json(req, report)?;
57 self.client.execute(req).await
58 }
59
60 pub async fn update(
62 &self,
63 user_id: &str,
64 context: &str,
65 report_id: &str,
66 patch: &CreateReport,
67 ) -> Result<()> {
68 let req = self
69 .client
70 .request(
71 Method::PATCH,
72 &format!("{}/{}", self.base(user_id, context), report_id),
73 )
74 .await?;
75 let req = ConcurClient::with_json(req, patch)?;
76 self.client.execute_unit(req).await
77 }
78
79 pub async fn delete(&self, user_id: &str, context: &str, report_id: &str) -> Result<()> {
81 self.client
82 .execute_unit(
83 self.client
84 .request(
85 Method::DELETE,
86 &format!("{}/{}", self.base(user_id, context), report_id),
87 )
88 .await?,
89 )
90 .await
91 }
92
93 pub async fn form_fields(
95 &self,
96 report_id: &str,
97 policy_id: Option<&str>,
98 ) -> Result<Vec<FormField>> {
99 let mut path = format!("/expensereports/v4/reports/{}/formFields", report_id);
100 if let Some(pid) = policy_id {
101 path.push_str(&format!("?policyId={}", urlencoding::encode(pid)));
102 }
103 self.client
104 .request(Method::GET, &path)
105 .await?
106 .send()
107 .await?
108 .json()
109 .await
110 .map_err(Into::into)
111 }
112}
113
114pub struct ExpensesApi<'a> {
116 client: &'a ConcurClient,
117}
118
119impl<'a> ExpensesApi<'a> {
120 pub fn new(client: &'a ConcurClient) -> Self {
121 Self { client }
122 }
123
124 fn base(&self, user_id: &str, context: &str, report_id: &str) -> String {
125 format!(
126 "/expensereports/v4/users/{}/context/{}/reports/{}/expenses",
127 user_id, context, report_id
128 )
129 }
130
131 pub async fn list(
133 &self,
134 user_id: &str,
135 context: &str,
136 report_id: &str,
137 ) -> Result<Vec<Expense>> {
138 self.client
139 .request(Method::GET, &self.base(user_id, context, report_id))
140 .await?
141 .send()
142 .await?
143 .json()
144 .await
145 .map_err(Into::into)
146 }
147
148 pub async fn get(
150 &self,
151 user_id: &str,
152 context: &str,
153 report_id: &str,
154 expense_id: &str,
155 ) -> Result<Expense> {
156 self.client
157 .request(
158 Method::GET,
159 &format!("{}/{}", self.base(user_id, context, report_id), expense_id),
160 )
161 .await?
162 .send()
163 .await?
164 .json()
165 .await
166 .map_err(Into::into)
167 }
168
169 pub async fn create(
171 &self,
172 user_id: &str,
173 context: &str,
174 report_id: &str,
175 expense: &CreateExpense,
176 ) -> Result<CreateExpenseResponse> {
177 let req = self
178 .client
179 .request(Method::POST, &self.base(user_id, context, report_id))
180 .await?;
181 let req = ConcurClient::with_json(req, expense)?;
182 self.client.execute(req).await
183 }
184
185 pub async fn update(
187 &self,
188 user_id: &str,
189 context: &str,
190 report_id: &str,
191 expense_id: &str,
192 patch: &CreateExpense,
193 ) -> Result<()> {
194 let req = self
195 .client
196 .request(
197 Method::PATCH,
198 &format!("{}/{}", self.base(user_id, context, report_id), expense_id),
199 )
200 .await?;
201 let req = ConcurClient::with_json(req, patch)?;
202 self.client.execute_unit(req).await
203 }
204
205 pub async fn delete(
207 &self,
208 user_id: &str,
209 context: &str,
210 report_id: &str,
211 expense_id: &str,
212 ) -> Result<()> {
213 self.client
214 .execute_unit(
215 self.client
216 .request(
217 Method::DELETE,
218 &format!("{}/{}", self.base(user_id, context, report_id), expense_id),
219 )
220 .await?,
221 )
222 .await
223 }
224
225 pub async fn itemizations(
227 &self,
228 user_id: &str,
229 context: &str,
230 report_id: &str,
231 expense_id: &str,
232 ) -> Result<Vec<serde_json::Value>> {
233 self.client
234 .request(
235 Method::GET,
236 &format!(
237 "{}/{}/itemizations",
238 self.base(user_id, context, report_id),
239 expense_id
240 ),
241 )
242 .await?
243 .send()
244 .await?
245 .json()
246 .await
247 .map_err(Into::into)
248 }
249}
250
251pub struct WorkflowApi<'a> {
253 client: &'a ConcurClient,
254}
255
256impl<'a> WorkflowApi<'a> {
257 pub fn new(client: &'a ConcurClient) -> Self {
258 Self { client }
259 }
260
261 pub async fn action(
263 &self,
264 report_id: &str,
265 action: ReportAction,
266 request: &WorkflowRequest,
267 user_id: Option<&str>,
268 context: Option<&str>,
269 ) -> Result<()> {
270 let path = match (user_id, context) {
271 (Some(uid), Some(ctx)) => format!(
272 "/expensereports/v4/users/{}/context/{}/reports/{}/{}",
273 uid,
274 ctx,
275 report_id,
276 action.path()
277 ),
278 _ => format!("/expensereports/v4/reports/{}/{}", report_id, action.path()),
279 };
280 let req = self.client.request(Method::PATCH, &path).await?;
281 let req = ConcurClient::with_json(req, request)?;
282 self.client.execute_unit(req).await
283 }
284
285 pub async fn submit(&self, user_id: &str, report_id: &str) -> Result<()> {
287 self.client
288 .execute_unit(
289 self.client
290 .request(
291 Method::PATCH,
292 &format!(
293 "/expensereports/v4/users/{}/reports/{}/submit",
294 user_id, report_id
295 ),
296 )
297 .await?,
298 )
299 .await
300 }
301
302 pub async fn cost_objects(&self, user_id: &str, report_id: &str) -> Result<Vec<CostObject>> {
304 self.client
305 .request(
306 Method::GET,
307 &format!(
308 "/expensereports/v4/users/{}/reports/{}/costObjectsForApprover",
309 user_id, report_id
310 ),
311 )
312 .await?
313 .send()
314 .await?
315 .json()
316 .await
317 .map_err(Into::into)
318 }
319}
320
321#[derive(Debug, Clone, Copy)]
322pub enum ReportAction {
323 Approve,
324 SendBack,
325 Recall,
326}
327
328impl ReportAction {
329 fn path(&self) -> &'static str {
330 match self {
331 ReportAction::Approve => "approve",
332 ReportAction::SendBack => "sendBack",
333 ReportAction::Recall => "recall",
334 }
335 }
336}
337
338#[derive(Debug, Clone, Default, Serialize, Deserialize)]
339pub struct WorkflowRequest {
340 #[serde(skip_serializing_if = "Option::is_none")]
341 pub comment: Option<String>,
342 #[serde(
343 rename = "expenseRejectedComment",
344 skip_serializing_if = "Option::is_none"
345 )]
346 pub expense_rejected_comment: Option<String>,
347 #[serde(rename = "expectedStepCode", skip_serializing_if = "Option::is_none")]
348 pub expected_step_code: Option<String>,
349 #[serde(
350 rename = "expectedStepSequence",
351 skip_serializing_if = "Option::is_none"
352 )]
353 pub expected_step_sequence: Option<String>,
354 #[serde(rename = "statusId", skip_serializing_if = "Option::is_none")]
355 pub status_id: Option<String>,
356}
357
358#[derive(Debug, Clone, Serialize, Deserialize)]
359pub struct Report {
360 #[serde(rename = "reportId")]
361 pub report_id: String,
362 pub name: Option<String>,
363 #[serde(rename = "businessPurpose")]
364 pub business_purpose: Option<String>,
365 #[serde(rename = "approvalStatus")]
366 pub approval_status: Option<String>,
367 #[serde(rename = "approvalStatusId")]
368 pub approval_status_id: Option<String>,
369 #[serde(rename = "paymentStatus")]
370 pub payment_status: Option<String>,
371 #[serde(rename = "paymentStatusId")]
372 pub payment_status_id: Option<String>,
373 #[serde(rename = "reportDate")]
374 pub report_date: Option<String>,
375 #[serde(rename = "startDate")]
376 pub start_date: Option<String>,
377 #[serde(rename = "endDate")]
378 pub end_date: Option<String>,
379 #[serde(rename = "policyId")]
380 pub policy_id: Option<String>,
381 #[serde(rename = "policy")]
382 pub policy_name: Option<String>,
383 #[serde(rename = "countryCode")]
384 pub country_code: Option<String>,
385 #[serde(rename = "countrySubDivisionCode")]
386 pub country_sub_division_code: Option<String>,
387 #[serde(rename = "reportTotal")]
388 pub report_total: Option<Money>,
389 #[serde(rename = "claimedAmount")]
390 pub claimed_amount: Option<Money>,
391 #[serde(rename = "approvedAmount")]
392 pub approved_amount: Option<Money>,
393 #[serde(rename = "customData")]
394 pub custom_data: Option<Vec<CustomData>>,
395 pub links: Option<Vec<Link>>,
396 #[serde(rename = "creationDate")]
397 pub creation_date: Option<String>,
398 #[serde(rename = "submitDate")]
399 pub submit_date: Option<String>,
400 #[serde(rename = "userId")]
401 pub user_id: Option<String>,
402}
403
404#[derive(Debug, Clone, Default, Serialize, Deserialize)]
405pub struct CreateReport {
406 pub name: Option<String>,
407 #[serde(rename = "businessPurpose")]
408 pub business_purpose: Option<String>,
409 pub comment: Option<String>,
410 #[serde(rename = "countryCode")]
411 pub country_code: Option<String>,
412 #[serde(rename = "countrySubDivisionCode")]
413 pub country_sub_division_code: Option<String>,
414 #[serde(rename = "policyId")]
415 pub policy_id: Option<String>,
416 #[serde(rename = "reportSource")]
417 pub report_source: Option<String>,
418 #[serde(rename = "customData")]
419 pub custom_data: Option<Vec<CustomData>>,
420}
421
422#[derive(Debug, Clone, Deserialize)]
423pub struct CreateReportResponse {
424 pub uri: String,
425}
426
427#[derive(Debug, Clone, Serialize, Deserialize)]
428pub struct Expense {
429 #[serde(rename = "expenseId")]
430 pub expense_id: String,
431 #[serde(rename = "businessPurpose")]
432 pub business_purpose: Option<String>,
433 #[serde(rename = "transactionDate")]
434 pub transaction_date: Option<String>,
435 #[serde(rename = "transactionAmount")]
436 pub transaction_amount: Option<Money>,
437 #[serde(rename = "postedAmount")]
438 pub posted_amount: Option<Money>,
439 #[serde(rename = "approvedAmount")]
440 pub approved_amount: Option<Money>,
441 #[serde(rename = "claimedAmount")]
442 pub claimed_amount: Option<Money>,
443 #[serde(rename = "expenseType")]
444 pub expense_type: Option<ExpenseType>,
445 #[serde(rename = "paymentType")]
446 pub payment_type: Option<PaymentType>,
447 pub location: Option<Location>,
448 pub vendor: Option<Vendor>,
449 #[serde(rename = "receiptImageId")]
450 pub receipt_image_id: Option<String>,
451 #[serde(rename = "ereceiptImageId")]
452 pub ereceipt_image_id: Option<String>,
453 #[serde(rename = "exchangeRate")]
454 pub exchange_rate: Option<ExchangeRate>,
455 #[serde(rename = "attendeeCount")]
456 pub attendee_count: Option<i32>,
457 #[serde(rename = "isPersonalExpense")]
458 pub is_personal_expense: Option<bool>,
459 #[serde(rename = "customData")]
460 pub custom_data: Option<Vec<CustomData>>,
461 pub links: Option<Vec<Link>>,
462}
463
464#[derive(Debug, Clone, Default, Serialize, Deserialize)]
465pub struct CreateExpense {
466 #[serde(rename = "expenseTypeId")]
467 pub expense_type_id: Option<String>,
468 #[serde(rename = "transactionDate")]
469 pub transaction_date: Option<String>,
470 #[serde(rename = "transactionAmount")]
471 pub transaction_amount: Option<Money>,
472 #[serde(rename = "businessPurpose")]
473 pub business_purpose: Option<String>,
474 #[serde(rename = "paymentTypeId")]
475 pub payment_type_id: Option<String>,
476 #[serde(rename = "locationId")]
477 pub location_id: Option<String>,
478 pub vendor: Option<Vendor>,
479 pub comment: Option<String>,
480 #[serde(rename = "customData")]
481 pub custom_data: Option<Vec<CustomData>>,
482}
483
484#[derive(Debug, Clone, Deserialize)]
485pub struct CreateExpenseResponse {
486 pub uri: String,
487}
488
489#[derive(Debug, Clone, Serialize, Deserialize)]
490pub struct FormField {
491 #[serde(rename = "fieldId")]
492 pub field_id: String,
493 #[serde(rename = "fieldName")]
494 pub field_name: Option<String>,
495 #[serde(rename = "controlType")]
496 pub control_type: Option<String>,
497 #[serde(rename = "dataType")]
498 pub data_type: Option<String>,
499 #[serde(rename = "fieldAccess")]
500 pub field_access: Option<String>,
501 #[serde(rename = "isRequired")]
502 pub is_required: Option<bool>,
503 #[serde(rename = "listId")]
504 pub list_id: Option<String>,
505}
506
507#[derive(Debug, Clone, Serialize, Deserialize)]
508pub struct CostObject {
509 pub name: String,
510 #[serde(rename = "approvedAmount")]
511 pub approved_amount: Money,
512 #[serde(rename = "claimedAmount")]
513 pub claimed_amount: Money,
514 #[serde(rename = "approverId")]
515 pub approver_id: String,
516 pub expenses: Vec<CostObjectExpense>,
517 #[serde(rename = "isOwnedByCaller")]
518 pub is_owned_by_caller: bool,
519 #[serde(rename = "isFullyApproved")]
520 pub is_fully_approved: bool,
521}
522
523#[derive(Debug, Clone, Serialize, Deserialize)]
524pub struct CostObjectExpense {
525 #[serde(rename = "approvedAmount")]
526 pub approved_amount: Money,
527 #[serde(rename = "postedAmount")]
528 pub posted_amount: Money,
529 pub id: String,
530}