yuki_client/client/
sales.rs1use quick_xml::Reader;
2use quick_xml::events::Event;
3
4use crate::error::YukiError;
5
6use super::local_name;
7use super::soap_client::{SoapClient, SoapEnvelope};
8
9const BASE_URL: &str = "https://api.yukiworks.nl/ws/Sales.asmx";
10
11#[derive(Debug, Clone)]
13pub struct SalesItem {
14 pub id: String,
15 pub description: String,
16}
17
18pub struct SalesClient {
20 soap: SoapClient,
21}
22
23impl SalesClient {
24 pub fn new() -> Self {
25 Self {
26 soap: SoapClient::new(BASE_URL),
27 }
28 }
29
30 pub fn with_client(http: reqwest::Client) -> Self {
33 Self {
34 soap: SoapClient::with_client(BASE_URL, http),
35 }
36 }
37
38 fn require_session(&self) -> Result<&str, YukiError> {
39 self.soap.session_id().ok_or_else(|| {
40 YukiError::AuthFailed("not authenticated — call authenticate() first".to_string())
41 })
42 }
43
44 pub async fn authenticate(&mut self, api_key: &str) -> Result<String, YukiError> {
46 self.soap.authenticate(api_key).await
47 }
48
49 pub async fn get_sales_items(&self) -> Result<Vec<SalesItem>, YukiError> {
51 let session = self.require_session()?;
52 let envelope = SoapEnvelope::new("GetSalesItems").session(session).build();
53 let body = self.soap.call("GetSalesItems", envelope).await?;
54 Self::parse_sales_items(&body)
55 }
56
57 pub fn parse_sales_items(xml: &str) -> Result<Vec<SalesItem>, YukiError> {
61 let mut reader = Reader::from_str(xml);
62 reader.config_mut().trim_text(true);
63
64 let mut items = Vec::new();
65 let mut in_item = false;
66 let mut field: Option<String> = None;
67 let mut current = SalesItem {
68 id: String::new(),
69 description: String::new(),
70 };
71 let mut buf = Vec::new();
72
73 loop {
74 match reader.read_event_into(&mut buf) {
75 Ok(Event::Start(ref e)) => {
76 let local = local_name(e.name().as_ref()).to_string();
77 match local.as_str() {
78 "SalesItem" => {
79 in_item = true;
80 current = SalesItem {
81 id: String::new(),
82 description: String::new(),
83 };
84 }
85 "id" | "description" if in_item => {
86 field = Some(local);
87 }
88 _ => {}
89 }
90 }
91 Ok(Event::Text(ref e)) => {
92 if let Some(ref f) = field {
93 let text = e
94 .unescape()
95 .map_err(|e| YukiError::Xml(e.to_string()))?
96 .trim()
97 .to_string();
98 match f.as_str() {
99 "id" => current.id = text,
100 "description" => current.description = text,
101 _ => {}
102 }
103 }
104 }
105 Ok(Event::End(ref e)) => {
106 let local = local_name(e.name().as_ref()).to_string();
107 match local.as_str() {
108 "id" | "description" => {
109 field = None;
110 }
111 "SalesItem" if in_item => {
112 items.push(current.clone());
113 in_item = false;
114 }
115 _ => {}
116 }
117 }
118 Ok(Event::Eof) => break,
119 Err(e) => return Err(YukiError::Xml(e.to_string())),
120 _ => {}
121 }
122 buf.clear();
123 }
124
125 Ok(items)
126 }
127}
128
129impl Default for SalesClient {
130 fn default() -> Self {
131 Self::new()
132 }
133}