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