1use 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 fn require_session(&self) -> Result<&str, YukiError> {
31 self.soap.session_id().ok_or_else(|| {
32 YukiError::AuthFailed("not authenticated — call authenticate() first".to_string())
33 })
34 }
35
36 pub async fn authenticate(&mut self, api_key: &str) -> Result<String, YukiError> {
38 self.soap.authenticate(api_key).await
39 }
40
41 pub async fn get_sales_items(&self) -> Result<Vec<SalesItem>, YukiError> {
43 let session = self.require_session()?;
44 let envelope = SoapEnvelope::new("GetSalesItems").session(session).build();
45 let body = self.soap.call("GetSalesItems", envelope).await?;
46 Self::parse_sales_items(&body)
47 }
48
49 pub fn parse_sales_items(xml: &str) -> Result<Vec<SalesItem>, YukiError> {
53 let mut reader = Reader::from_str(xml);
54 reader.config_mut().trim_text(true);
55
56 let mut items = Vec::new();
57 let mut in_item = false;
58 let mut field: Option<String> = None;
59 let mut current = SalesItem {
60 id: String::new(),
61 description: String::new(),
62 };
63 let mut buf = Vec::new();
64
65 loop {
66 match reader.read_event_into(&mut buf) {
67 Ok(Event::Start(ref e)) => {
68 let local = local_name(e.name().as_ref()).to_string();
69 match local.as_str() {
70 "SalesItem" => {
71 in_item = true;
72 current = SalesItem {
73 id: String::new(),
74 description: String::new(),
75 };
76 }
77 "id" | "description" if in_item => {
78 field = Some(local);
79 }
80 _ => {}
81 }
82 }
83 Ok(Event::Text(ref e)) => {
84 if let Some(ref f) = field {
85 let text = e
86 .unescape()
87 .map_err(|e| YukiError::Xml(e.to_string()))?
88 .trim()
89 .to_string();
90 match f.as_str() {
91 "id" => current.id = text,
92 "description" => current.description = text,
93 _ => {}
94 }
95 }
96 }
97 Ok(Event::End(ref e)) => {
98 let local = local_name(e.name().as_ref()).to_string();
99 match local.as_str() {
100 "id" | "description" => {
101 field = None;
102 }
103 "SalesItem" if in_item => {
104 items.push(current.clone());
105 in_item = false;
106 }
107 _ => {}
108 }
109 }
110 Ok(Event::Eof) => break,
111 Err(e) => return Err(YukiError::Xml(e.to_string())),
112 _ => {}
113 }
114 buf.clear();
115 }
116
117 Ok(items)
118 }
119}
120
121impl Default for SalesClient {
122 fn default() -> Self {
123 Self::new()
124 }
125}