Coverage for amazonorders/entity/order.py: 19.76%
167 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-12 15:20 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-12 15:20 +0000
1import logging
2from datetime import datetime
3from urllib.parse import urlparse
4from urllib.parse import parse_qs
6from bs4 import BeautifulSoup
8from amazonorders.entity.recipient import Recipient
9from amazonorders.entity.shipment import Shipment
10from amazonorders.entity.item import Item
11from amazonorders.exception import AmazonOrdersError
12from amazonorders.session import BASE_URL
14__author__ = "Alex Laird"
15__copyright__ = "Copyright 2024, Alex Laird"
16__version__ = "0.0.3"
18logger = logging.getLogger(__name__)
21class Order:
22 def __init__(self,
23 parsed,
24 full_details=False,
25 clone=None) -> None:
26 self.parsed = parsed
27 self.full_details = full_details
29 if clone:
30 self.shipments = clone.shipments
31 self.items = clone.items
32 self.order_details_link = clone.order_details_link
33 self.order_number = clone.order_number
34 self.grand_total = clone.grand_total
35 self.order_placed_date = clone.order_placed_date
36 self.recipient = clone.recipient
37 else:
38 self.shipments = self._parse_shipments()
39 self.items = self._parse_items()
40 self.order_details_link = self._parse_order_details_link()
41 self.order_number = self._parse_order_number()
42 self.grand_total = self._parse_grand_total()
43 self.order_placed_date = self._parse_order_placed_date()
44 self.recipient = self._parse_recipient()
46 if self.full_details:
47 self.items = self._parse_items()
48 self.payment_method = self._parse_payment_method()
49 self.payment_method_last_4 = self._parse_payment_method_last_4()
50 self.subtotal = self._parse_subtotal()
51 self.shipping_total = self._parse_shipping_total()
52 self.subscription_discount = self._parse_subscription_discount()
53 self.total_before_tax = self._parse_total_before_tax()
54 self.estimated_tax = self._parse_estimated_tax()
55 self.refund_total = self._parse_refund_total()
56 self.order_shipped_date = self._parse_order_shipping_date()
57 self.refund_completed_date = self._parse_refund_completed_date()
59 def __repr__(self) -> str:
60 return "<Order #{}: \"{}\">".format(self.order_number, self.items)
62 def __str__(self) -> str: # pragma: no cover
63 return "Order #{}: \"{}\"".format(self.order_number, self.items)
65 def _parse_shipments(self):
66 return [Shipment(x, self) for x in self.parsed.find_all("div", {"class": "shipment"})]
68 def _parse_items(self):
69 return [Item(x) for x in self.parsed.find_all("div", {"class": "yohtmlc-item"})]
71 def _parse_order_details_link(self):
72 try:
73 tag = self.parsed.find("a", {"class": "yohtmlc-order-details-link"})
74 if tag:
75 return "{}{}".format(BASE_URL, tag.attrs["href"])
76 except AttributeError:
77 logger.warning("When building Order, `order_details_link` could not be parsed.", exc_info=True)
79 def _parse_order_number(self):
80 try:
81 if self.order_details_link:
82 parsed_url = urlparse(self.order_details_link)
83 return parse_qs(parsed_url.query)["orderID"][0]
84 else:
85 tag = self.parsed.find("bdi", dir="ltr")
86 return tag.text.strip()
87 except (AttributeError, IndexError):
88 # TODO: refactor entities to all extend a base entity, which has this base parse function with this except
89 logger.warning("When building Order, `order_number` could not be parsed.", exc_info=True)
91 def _parse_grand_total(self):
92 try:
93 tag = self.parsed.find("div", {"class": "yohtmlc-order-total"})
94 if tag:
95 tag = tag.find("span", {"class": "value"})
96 else:
97 for tag in self.parsed.find("div", id="od-subtotals").find_all("div", {"class": "a-row"}):
98 if "grand total" in tag.text.lower():
99 tag = tag.find("div", {"class": "a-span-last"})
100 return tag.text.strip().replace("$", "")
101 except AttributeError:
102 logger.warning("When building Order, `grand_total` could not be parsed.", exc_info=True)
104 def _parse_order_placed_date(self):
105 try:
106 tag = self.parsed.find("span", {"class": "order-date-invoice-item"})
107 if tag:
108 date_str = tag.text.split("Ordered on")[1].strip()
109 else:
110 tag = self.parsed.find("div", {"class": "a-span3"}).find_all("span")
111 date_str = tag[1].text.strip()
112 return datetime.strptime(date_str, "%B %d, %Y").date()
113 except (AttributeError, IndexError):
114 logger.warning("When building Order, `order_placed_date` could not be parsed.", exc_info=True)
116 def _parse_recipient(self):
117 try:
118 tag = self.parsed.find("div", {"class": "displayAddressDiv"})
119 if not tag:
120 script_id = self.parsed.find("div",
121 id=lambda value: value and value.startswith("shipToInsertionNode")).attrs["id"]
122 tag = self.parsed.find("script",
123 id="shipToData-shippingAddress-{}".format(script_id.split("-")[2]))
124 tag = BeautifulSoup(str(tag.contents[0]).strip(), "html.parser")
125 return Recipient(tag)
126 except (AttributeError, IndexError):
127 logger.warning("When building Order, `recipient` could not be parsed.", exc_info=True)
129 def _parse_payment_method(self):
130 try:
131 tag = self.parsed.find("img", {"class": "pmts-payment-credit-card-instrument-logo"})
132 if tag:
133 return tag.attrs["alt"]
134 except (AttributeError, IndexError):
135 logger.warning("When building Order, `payment_method` could not be parsed.", exc_info=True)
137 def _parse_payment_method_last_4(self):
138 try:
139 tag = self.parsed.find("img", {"class": "pmts-payment-credit-card-instrument-logo"})
140 if tag:
141 ending_sibling = tag.find_next_siblings()[-1]
142 return ending_sibling.text.split("ending in")[1].strip()
143 except (AttributeError, IndexError):
144 logger.warning("When building Order, `payment_method_last_4` could not be parsed.", exc_info=True)
146 def _parse_subtotal(self):
147 try:
148 for tag in self.parsed.find("div", id="od-subtotals").find_all("div", {"class": "a-row"}):
149 if "subtotal" in tag.text.lower():
150 return tag.find("div", {"class": "a-span-last"}).text.strip().replace("$", "")
151 except (AttributeError, IndexError):
152 logger.warning("When building Order, `subtotal` could not be parsed.", exc_info=True)
154 def _parse_shipping_total(self):
155 try:
156 for tag in self.parsed.find("div", id="od-subtotals").find_all("div", {"class": "a-row"}):
157 if "shipping" in tag.text.lower():
158 return tag.find("div", {"class": "a-span-last"}).text.strip().replace("$", "")
159 except (AttributeError, IndexError):
160 logger.warning("When building Order, `shipping_total` could not be parsed.", exc_info=True)
162 def _parse_subscription_discount(self):
163 try:
164 for tag in self.parsed.find("div", id="od-subtotals").find_all("div", {"class": "a-row"}):
165 if "subscribe" in tag.text.lower():
166 return tag.find("div", {"class": "a-span-last"}).text.strip().replace("$", "")
167 except (AttributeError, IndexError):
168 logger.warning("When building Order, `subscription_discount` could not be parsed.", exc_info=True)
170 def _parse_total_before_tax(self):
171 try:
172 for tag in self.parsed.find("div", id="od-subtotals").find_all("div", {"class": "a-row"}):
173 if "before tax" in tag.text.lower():
174 return tag.find("div", {"class": "a-span-last"}).text.strip().replace("$", "")
175 except (AttributeError, IndexError):
176 logger.warning("When building Order, `total_before_tax` could not be parsed.", exc_info=True)
178 def _parse_estimated_tax(self):
179 try:
180 for tag in self.parsed.find("div", id="od-subtotals").find_all("div", {"class": "a-row"}):
181 if "estimated tax" in tag.text.lower():
182 return tag.find("div", {"class": "a-span-last"}).text.strip().replace("$", "")
183 except (AttributeError, IndexError):
184 logger.warning("When building Order, `estimated_tax` could not be parsed.", exc_info=True)
186 def _parse_refund_total(self):
187 try:
188 for tag in self.parsed.find("div", id="od-subtotals").find_all("div", {"class": "a-row"}):
189 if "refund total" in tag.text.lower() and "tax refund" not in tag.text.lower():
190 return tag.find("div", {"class": "a-span-last"}).text.strip().replace("$", "")
191 except (AttributeError, IndexError):
192 logger.warning("When building Order, `refund_total` could not be parsed.", exc_info=True)
194 def _parse_order_shipping_date(self):
195 try:
196 # TODO: find a better way to do this
197 if "Items shipped:" in self.parsed.text:
198 date_str = self.parsed.text.split("Items shipped:")[1].strip().split("-")[0].strip()
199 return datetime.strptime(date_str, "%B %d, %Y").date()
200 except (AttributeError, IndexError):
201 logger.warning("When building Order, `order_shipping_date` could not be parsed.", exc_info=True)
203 def _parse_refund_completed_date(self):
204 try:
205 # TODO: find a better way to do this
206 if "Refund: Completed" in self.parsed.text:
207 date_str = self.parsed.text.split("Refund: Completed")[1].strip().split("-")[0].strip()
208 return datetime.strptime(date_str, "%B %d, %Y").date()
209 except (AttributeError, IndexError):
210 logger.warning("When building Order, `refund_completed_date` could not be parsed.", exc_info=True)