1pub mod guaranteed;
16
17use std::fmt::Debug;
18use std::str::FromStr;
19
20use chrono::{DateTime, FixedOffset, NaiveDateTime};
21use chrono_tz::Europe::Kyiv;
22use itertools::Itertools;
23use scraper::{ElementRef, Html};
24use serde::{Deserialize, Serialize};
25
26use crate::data::address::Address;
27use crate::data::comment::{Comment, CommentError};
28use crate::data::internal_status::{InternalStatus, InternalStatusError};
29use crate::data::order_type::{OrderType, OrderTypeError};
30use crate::data::status::{Status, StatusError};
31use crate::data::time_constrains::{TimeConstrains, TimeConstrainsError};
32use crate::macros::match_and_set;
33use crate::serializable_int_error_kind::SerializableIntErrorKind;
34use crate::serializable_parse_error_kind::SerializableParseErrorKind;
35use crate::traits::is_it::IsIt;
36use crate::traits::prev_element_ref::PrevElementRef;
37
38#[derive(Clone, PartialEq, PartialOrd, Eq, Ord, Debug, Hash, Serialize, Deserialize, Default)]
43pub struct BriefRequest {
44 pub order_id: Option<Result<u32, SerializableIntErrorKind>>,
45 pub internal_order_id: Option<Result<u32, SerializableIntErrorKind>>,
46 pub order_type: Option<Result<OrderType, OrderTypeError>>,
47 pub creation_date: Option<Result<DateTime<FixedOffset>, SerializableParseErrorKind>>,
48 pub internal_status: Option<Result<InternalStatus, InternalStatusError>>,
49 pub address: Option<Address>,
50 pub client: Option<String>,
52 pub service: Option<String>,
55 pub pa: Option<String>,
57 pub time_constrains: Option<Result<TimeConstrains, TimeConstrainsError>>,
58 pub installers: Vec<String>,
60 pub last_comment: Option<Result<Comment, CommentError>>,
61 pub first_comment: Option<Result<Comment, CommentError>>,
62 pub status: Option<Result<Status, StatusError>>,
63 pub phones: Vec<String>,
65}
66
67impl PrevElementRef for BriefRequest {}
68
69impl BriefRequest {
70 pub fn from(html: &str) -> BriefRequest {
72 log::debug!(target: "nsg", "Processing HTML: {:?}", html);
73 let mut brief_request = BriefRequest::default();
74
75 let html_fragment = Html::parse_fragment(html);
76 brief_request.collect_fragments(&html_fragment);
77
78 brief_request
79 }
80
81 fn collect_fragments(&mut self, html_fragment: &Html) {
82 log::info!(target: "nsg", "Parsing brief request HTML...");
83
84 let elements = html_fragment
85 .root_element()
86 .descendent_elements()
87 .filter_map(|element| {
88 if element.child_elements().count() == 0 {
89 return Some(element);
90 }
91
92 if element.child_elements().count() == 1 && element.child_elements().next()?.value().name() == "a" {
93 return Some(element);
94 }
95
96 None
97 });
98 log::debug!(target: "nsg", "Defined iterator over elements without child elements");
99
100 let text_getter = |element: ElementRef<'_>| -> String { element.text().collect_vec().join(" ") };
101 log::debug!(target: "nsg", "Defined text getter");
102
103 let mut city = None;
104 let mut address_string = None;
105 let mut apartment = None;
106
107 for element in elements {
108 match_and_set!(
109 "заявка:",
110 self.internal_order_id,
111 (|| Some(u32::from_str(&cur_text).map_err(|err| err.kind().clone().into()))),
112 self,
113 element,
114 text_getter,
115 is_it,
116 cur_text
117 );
118
119 match_and_set!(
120 "наряд:",
121 self.order_id,
122 (|| Some(u32::from_str(&cur_text).map_err(|err| err.kind().clone().into()))),
123 self,
124 element,
125 text_getter,
126 is_it,
127 cur_text
128 );
129
130 match_and_set!(
131 "подтип:",
132 self.order_type,
133 (|| Some(OrderType::from_str(&cur_text))),
134 self,
135 element,
136 text_getter,
137 is_it,
138 cur_text
139 );
140
141 match_and_set!(
142 "дата создания:",
143 self.creation_date,
144 (|| Some(self.as_datetime(&cur_text))),
145 self,
146 element,
147 text_getter,
148 is_it,
149 cur_text
150 );
151
152 match_and_set!(
153 "статус:",
154 self.internal_status,
155 (|| Some(InternalStatus::from_str(&cur_text))),
156 self,
157 element,
158 text_getter,
159 is_it,
160 cur_text
161 );
162
163 match_and_set!(
164 "город:",
165 city,
166 (|| Some(cur_text)),
167 self,
168 element,
169 text_getter,
170 is_it,
171 cur_text
172 );
173
174 match_and_set!(
175 "адрес:",
176 address_string,
177 (|| Some(cur_text)),
178 self,
179 element,
180 text_getter,
181 is_it,
182 cur_text
183 );
184
185 match_and_set!(
186 "квартира:",
187 apartment,
188 (|| Some(cur_text)),
189 self,
190 element,
191 text_getter,
192 is_it,
193 cur_text
194 );
195
196 match_and_set!(
197 "клиент:",
198 self.client,
199 (|| Some(cur_text.trim().to_string())),
200 self,
201 element,
202 text_getter,
203 is_it,
204 cur_text
205 );
206 match_and_set!(
207 "пакет:",
208 self.service,
209 (|| {
210 if cur_text.is_empty() {
211 log::warn!(
212 target: "nsg",
213 "[{:?}] NOTE: intentionally ignoring self.service because it's empty",
214 element.id()
215 );
216 return None;
217 }
218
219 Some(cur_text)
220 }),
221 self,
222 element,
223 text_getter,
224 is_it,
225 cur_text
226 );
227 match_and_set!(
228 "телефон:",
229 (|| self.phones.push(cur_text.trim().to_string())),
230 self,
231 element,
232 text_getter,
233 is_it,
234 cur_text
235 );
236 match_and_set!(
237 "телефон 2:",
238 (|| self.phones.push(cur_text.trim().to_string())),
239 self,
240 element,
241 text_getter,
242 is_it,
243 cur_text
244 );
245 match_and_set!(
246 "лицевой счет:",
247 self.pa,
248 (|| Some(cur_text)),
249 self,
250 element,
251 text_getter,
252 is_it,
253 cur_text
254 );
255 match_and_set!(
256 "время подключения:",
257 self.time_constrains,
258 (|| Some(TimeConstrains::from(&cur_text))),
259 self,
260 element,
261 text_getter,
262 is_it,
263 cur_text
264 );
265 match_and_set!(
266 "монтажник:",
267 (|| self.installers.push(cur_text)),
268 self,
269 element,
270 text_getter,
271 is_it,
272 cur_text
273 );
274 match_and_set!(
275 "монтажник 2:",
276 (|| self.installers.push(cur_text)),
277 self,
278 element,
279 text_getter,
280 is_it,
281 cur_text
282 );
283 match_and_set!(
284 "статус заказчика:",
285 self.status,
286 (|| Some(Status::from_str(&cur_text))),
287 self,
288 element,
289 text_getter,
290 is_it,
291 cur_text
292 );
293
294 match_and_set!(
295 "начальный комментарий:",
296 self.first_comment,
297 (|| Some(Comment::from(&cur_text))),
298 self,
299 element,
300 text_getter,
301 is_it,
302 cur_text
303 );
304
305 match_and_set!(
306 "последний комментарий:",
307 self.last_comment,
308 (|| Some(Comment::from(&cur_text))),
309 self,
310 element,
311 text_getter,
312 is_it,
313 cur_text
314 );
315 }
316
317 if let (Some(city), Some(address_string), Some(apartment)) = (city, address_string, apartment) {
318 let mut address_iter = address_string.split(',');
319
320 self.address = Some(Address::from_parts(
322 city,
323 address_iter.next().unwrap().to_string(),
324 address_iter.next().unwrap().to_string(),
325 apartment,
326 ));
327 }
328 }
329
330 fn as_datetime(&self, input: &str) -> Result<DateTime<FixedOffset>, SerializableParseErrorKind> {
331 let naive = NaiveDateTime::parse_from_str(input, "%Y-%m-%d %H:%M:%S")?;
332 let datetime = naive
333 .and_local_timezone(Kyiv)
334 .earliest()
335 .expect("Never should have gotten a time that doesn't exist in the Kyiv time zone")
336 .fixed_offset();
337
338 Ok(datetime)
339 }
340}