receipt.rs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. use chrono::{DateTime, Utc};
  2. use serde::Deserialize;
  3. use std::fmt;
  4. pub struct Purchase {
  5. sum: i64,
  6. date: DateTime<Utc>,
  7. pub items: Vec<Item>,
  8. }
  9. impl Purchase {
  10. pub fn total_sum(&self) -> i64 {
  11. self.sum
  12. }
  13. pub fn date(&self) -> DateTime<Utc> {
  14. self.date
  15. }
  16. }
  17. #[derive(Deserialize, Debug, Clone)]
  18. pub struct Item {
  19. pub name: String,
  20. pub sum: i64,
  21. }
  22. impl fmt::Display for Item {
  23. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  24. write!(f, "{}:{}", self.name, self.sum)
  25. }
  26. }
  27. #[allow(dead_code)]
  28. #[allow(non_snake_case)]
  29. #[derive(Deserialize, Debug, Clone)]
  30. struct Receipt {
  31. totalSum: i64,
  32. #[serde(with = "custom_date_format")]
  33. dateTime: DateTime<Utc>,
  34. pub items: Vec<Item>,
  35. }
  36. #[allow(dead_code)]
  37. #[derive(Deserialize)]
  38. struct Document {
  39. receipt: Receipt,
  40. }
  41. #[allow(dead_code)]
  42. #[derive(Deserialize)]
  43. struct Ticket {
  44. document: Document,
  45. }
  46. mod custom_date_format {
  47. use chrono::{DateTime, NaiveDateTime, Utc};
  48. use serde::{self, Deserialize, Deserializer};
  49. /// The format seems alike to RFC3339 but is not compliant
  50. const FORMAT: &str = "%Y-%m-%dT%H:%M:%S";
  51. /// Custom deserializer for format in our json
  52. pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
  53. where
  54. D: Deserializer<'de>,
  55. {
  56. let s = String::deserialize(deserializer)?;
  57. let naive_dt =
  58. NaiveDateTime::parse_from_str(&s, FORMAT).map_err(serde::de::Error::custom)?;
  59. // Associate the NaiveDateTime with the Utc timezone
  60. Ok(DateTime::<Utc>::from_naive_utc_and_offset(naive_dt, Utc))
  61. }
  62. }
  63. #[allow(dead_code)]
  64. #[derive(Deserialize)]
  65. struct Input {
  66. ticket: Ticket,
  67. }
  68. pub fn parse_purchase(line: &str) -> Purchase {
  69. // TODO: Check if several receipts are possible
  70. let receipt: Vec<Input> = serde_json::from_str(line).unwrap();
  71. Purchase {
  72. sum: receipt[0].ticket.document.receipt.totalSum,
  73. date: receipt[0].ticket.document.receipt.dateTime,
  74. items: receipt[0].ticket.document.receipt.items.clone(),
  75. }
  76. }
  77. #[cfg(test)]
  78. mod receipttest {
  79. use super::*;
  80. use chrono::Datelike;
  81. #[test]
  82. fn item() {
  83. let line = String::from(
  84. r#"
  85. {
  86. "quantity" : 1,
  87. "nds" : 1,
  88. "price" : 5549,
  89. "paymentType": 4,
  90. "productType": 1,
  91. "name" : "ХРЕН РУССКИЙ 170Г",
  92. "sum" : 5549
  93. }
  94. "#,
  95. );
  96. let testit: Item = serde_json::from_str(&line).unwrap();
  97. assert_eq!(testit.name, "ХРЕН РУССКИЙ 170Г");
  98. assert_eq!(testit.sum, 5549);
  99. }
  100. #[test]
  101. fn receipt() {
  102. let line = String::from(
  103. r#"
  104. {
  105. "totalSum" : 548702,
  106. "userInn" : "7703270067",
  107. "operator" : "Теpминал 24",
  108. "dateTime": "2021-03-24T17:42:00",
  109. "items" : [
  110. {
  111. "quantity" : 1,
  112. "ndsRate" : 1,
  113. "price" : 5549,
  114. "calculationSubjectSign" : 1,
  115. "calculationTypeSign" : 4,
  116. "name" : "ХРЕН РУССКИЙ 170Г",
  117. "sum" : 5549
  118. },
  119. {
  120. "quantity" : 1,
  121. "ndsRate" : 1,
  122. "price" : 20599,
  123. "calculationSubjectSign" : 1,
  124. "calculationTypeSign" : 4,
  125. "name" : "СОУС ОСТР.380Г КИНТО",
  126. "sum" : 20599
  127. }
  128. ]
  129. }
  130. "#,
  131. );
  132. let testit: Receipt = serde_json::from_str(&line).unwrap();
  133. assert_eq!(testit.totalSum, 548702);
  134. assert_eq!(testit.items.len(), 2);
  135. assert_eq!(testit.items[0].sum, 5549);
  136. assert_eq!(testit.items[1].sum, 20599);
  137. assert_eq!(testit.dateTime.day(), 24);
  138. assert_eq!(testit.dateTime.month(), 3);
  139. assert_eq!(testit.dateTime.year(), 2021);
  140. }
  141. #[test]
  142. fn input() {
  143. let line = String::from(
  144. r#"
  145. {
  146. "ticket": {
  147. "document" : {
  148. "receipt" : {
  149. "totalSum" : 548702,
  150. "userInn" : "7703270067",
  151. "operator" : "Теpминал 24",
  152. "dateTime": "2021-03-24T17:42:00",
  153. "items" : [
  154. {
  155. "quantity" : 1,
  156. "ndsRate" : 1,
  157. "price" : 5549,
  158. "calculationSubjectSign" : 1,
  159. "calculationTypeSign" : 4,
  160. "name" : "ХРЕН РУССКИЙ 170Г",
  161. "sum" : 5549
  162. },
  163. {
  164. "quantity" : 1,
  165. "ndsRate" : 1,
  166. "price" : 20599,
  167. "calculationSubjectSign" : 1,
  168. "calculationTypeSign" : 4,
  169. "name" : "СОУС ОСТР.380Г КИНТО",
  170. "sum" : 20599
  171. }
  172. ]
  173. }
  174. }
  175. }
  176. }
  177. "#,
  178. );
  179. let testit: Input = serde_json::from_str(&line).unwrap();
  180. assert_eq!(testit.ticket.document.receipt.totalSum, 548702);
  181. assert_eq!(testit.ticket.document.receipt.items.len(), 2);
  182. assert_eq!(testit.ticket.document.receipt.items[0].sum, 5549);
  183. assert_eq!(testit.ticket.document.receipt.items[1].sum, 20599);
  184. assert_eq!(testit.ticket.document.receipt.dateTime.day(), 24);
  185. assert_eq!(testit.ticket.document.receipt.dateTime.month(), 3);
  186. assert_eq!(testit.ticket.document.receipt.dateTime.year(), 2021);
  187. }
  188. #[test]
  189. fn display() {
  190. let it = Item {
  191. name: "test".to_string(),
  192. sum: 1000,
  193. };
  194. let line = it.to_string();
  195. assert_eq!(line, "test:1000");
  196. }
  197. }