yuki_client/client/
vat.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/Vat.asmx";
10
11#[derive(Debug, Clone)]
13pub struct VatReturn {
14 pub period: String,
15 pub status: String,
16 pub start_date: String,
17 pub end_date: String,
18}
19
20#[derive(Debug, Clone)]
22pub struct VatCode {
23 pub code: String,
24 pub description: String,
25}
26
27pub struct VatClient {
29 soap: SoapClient,
30}
31
32impl VatClient {
33 pub fn new() -> Self {
34 Self {
35 soap: SoapClient::new(BASE_URL),
36 }
37 }
38
39 pub fn with_client(http: reqwest::Client) -> Self {
42 Self {
43 soap: SoapClient::with_client(BASE_URL, http),
44 }
45 }
46
47 fn require_session(&self) -> Result<&str, YukiError> {
48 self.soap.session_id().ok_or_else(|| {
49 YukiError::AuthFailed("not authenticated — call authenticate() first".to_string())
50 })
51 }
52
53 pub async fn authenticate(&mut self, api_key: &str) -> Result<String, YukiError> {
55 self.soap.authenticate(api_key).await
56 }
57
58 pub async fn vat_return_list(
60 &self,
61 administration_id: &str,
62 ) -> Result<Vec<VatReturn>, YukiError> {
63 let session = self.require_session()?;
64 let envelope = SoapEnvelope::new("VATReturnList")
65 .session(session)
66 .param("administrationID", administration_id)
67 .build();
68 let body = self.soap.call("VATReturnList", envelope).await?;
69 Self::parse_vat_returns(&body)
70 }
71
72 pub async fn active_vat_codes(
74 &self,
75 administration_id: &str,
76 ) -> Result<Vec<VatCode>, YukiError> {
77 let session = self.require_session()?;
78 let envelope = SoapEnvelope::new("ActiveVATCodesList")
79 .session(session)
80 .param("administrationID", administration_id)
81 .build();
82 let body = self.soap.call("ActiveVATCodesList", envelope).await?;
83 Self::parse_vat_codes(&body)
84 }
85
86 pub fn parse_vat_returns(xml: &str) -> Result<Vec<VatReturn>, YukiError> {
88 let mut reader = Reader::from_str(xml);
89 reader.config_mut().trim_text(true);
90
91 let mut returns = Vec::new();
92 let mut in_item = false;
93 let mut field: Option<String> = None;
94 let mut current = VatReturn {
95 period: String::new(),
96 status: String::new(),
97 start_date: String::new(),
98 end_date: String::new(),
99 };
100 let mut buf = Vec::new();
101
102 loop {
103 match reader.read_event_into(&mut buf) {
104 Ok(Event::Start(ref e)) => {
105 let local = local_name(e.name().as_ref()).to_string();
106 match local.as_str() {
107 "VATReturnInfo" => {
108 in_item = true;
109 current = VatReturn {
110 period: String::new(),
111 status: String::new(),
112 start_date: String::new(),
113 end_date: String::new(),
114 };
115 }
116 "startDate" | "endDate" | "status" if in_item => {
117 field = Some(local);
118 }
119 _ => {}
120 }
121 }
122 Ok(Event::Text(ref e)) => {
123 if let Some(ref f) = field {
124 let text = e
125 .unescape()
126 .map_err(|e| YukiError::Xml(e.to_string()))?
127 .trim()
128 .to_string();
129 match f.as_str() {
130 "startDate" => current.start_date = text,
131 "endDate" => current.end_date = text,
132 "status" => current.status = text,
133 _ => {}
134 }
135 }
136 }
137 Ok(Event::End(ref e)) => {
138 let local = local_name(e.name().as_ref()).to_string();
139 match local.as_str() {
140 "startDate" | "endDate" | "status" => field = None,
141 "VATReturnInfo" if in_item => {
142 current.period = format!(
144 "{} - {}",
145 current
146 .start_date
147 .split('T')
148 .next()
149 .unwrap_or(¤t.start_date),
150 current
151 .end_date
152 .split('T')
153 .next()
154 .unwrap_or(¤t.end_date),
155 );
156 returns.push(current.clone());
157 in_item = false;
158 }
159 _ => {}
160 }
161 }
162 Ok(Event::Eof) => break,
163 Err(e) => return Err(YukiError::Xml(e.to_string())),
164 _ => {}
165 }
166 buf.clear();
167 }
168
169 Ok(returns)
170 }
171
172 pub fn parse_vat_codes(xml: &str) -> Result<Vec<VatCode>, YukiError> {
174 let mut reader = Reader::from_str(xml);
175 reader.config_mut().trim_text(true);
176
177 let mut codes = Vec::new();
178 let mut in_item = false;
179 let mut field: Option<String> = None;
180 let mut current = VatCode {
181 code: String::new(),
182 description: String::new(),
183 };
184 let mut buf = Vec::new();
185
186 loop {
187 match reader.read_event_into(&mut buf) {
188 Ok(Event::Start(ref e)) => {
189 let local = local_name(e.name().as_ref()).to_string();
190 match local.as_str() {
191 "VATCode" => {
192 in_item = true;
193 current = VatCode {
194 code: String::new(),
195 description: String::new(),
196 };
197 }
198 "type" | "description" if in_item => {
199 field = Some(local);
200 }
201 _ => {}
202 }
203 }
204 Ok(Event::Text(ref e)) => {
205 if let Some(ref f) = field {
206 let text = e
207 .unescape()
208 .map_err(|e| YukiError::Xml(e.to_string()))?
209 .trim()
210 .to_string();
211 match f.as_str() {
212 "type" => current.code = text,
213 "description" => current.description = text,
214 _ => {}
215 }
216 }
217 }
218 Ok(Event::End(ref e)) => {
219 let local = local_name(e.name().as_ref()).to_string();
220 match local.as_str() {
221 "type" | "description" => field = None,
222 "VATCode" if in_item => {
223 codes.push(current.clone());
224 in_item = false;
225 }
226 _ => {}
227 }
228 }
229 Ok(Event::Eof) => break,
230 Err(e) => return Err(YukiError::Xml(e.to_string())),
231 _ => {}
232 }
233 buf.clear();
234 }
235
236 Ok(codes)
237 }
238}
239
240impl Default for VatClient {
241 fn default() -> Self {
242 Self::new()
243 }
244}