Coverage for amazonorders/entity/order.py: 96.72%
122 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-16 15:31 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-16 15:31 +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.parsable import Parsable
9from amazonorders.entity.recipient import Recipient
10from amazonorders.entity.shipment import Shipment
11from amazonorders.entity.item import Item
12from amazonorders.session import BASE_URL
14__author__ = "Alex Laird"
15__copyright__ = "Copyright 2024, Alex Laird"
16__version__ = "0.0.5"
18logger = logging.getLogger(__name__)
21class Order(Parsable):
22 def __init__(self,
23 parsed,
24 full_details=False,
25 clone=None) -> None:
26 super().__init__(parsed)
28 self.full_details = full_details
30 if clone:
31 self.shipments = clone.shipments
32 self.items = clone.items
33 self.order_details_link = clone.order_details_link
34 self.order_number = clone.order_number
35 self.grand_total = clone.grand_total
36 self.order_placed_date = clone.order_placed_date
37 self.recipient = clone.recipient
38 else:
39 self.shipments = self._parse_shipments()
40 self.items = self._parse_items()
41 self.order_details_link = self._safe_parse(self._parse_order_details_link)
42 self.order_number = self._safe_parse(self._parse_order_number)
43 self.grand_total = self._safe_parse(self._parse_grand_total)
44 self.order_placed_date = self._safe_parse(self._parse_order_placed_date)
45 self.recipient = self._safe_parse(self._parse_recipient)
47 if self.full_details:
48 self.items = self._parse_items()
49 self.payment_method = self._parse_payment_method()
50 self.payment_method_last_4 = self._parse_payment_method_last_4()
51 self.subtotal = self._parse_subtotal()
52 self.shipping_total = self._parse_shipping_total()
53 self.subscription_discount = self._parse_subscription_discount()
54 self.total_before_tax = self._parse_total_before_tax()
55 self.estimated_tax = self._parse_estimated_tax()
56 self.refund_total = self._parse_refund_total()
57 self.order_shipped_date = self._parse_order_shipping_date()
58 self.refund_completed_date = self._parse_refund_completed_date()
60 def __repr__(self) -> str:
61 return "<Order #{}: \"{}\">".format(self.order_number, self.items)
63 def __str__(self) -> str: # pragma: no cover
64 return "Order #{}: \"{}\"".format(self.order_number, self.items)
66 def _parse_shipments(self):
67 return [Shipment(x, self) for x in self.parsed.find_all("div", {"class": "shipment"})]
69 def _parse_items(self):
70 return [Item(x) for x in self.parsed.find_all("div", {"class": "yohtmlc-item"})]
72 def _parse_order_details_link(self):
73 tag = self.parsed.find("a", {"class": "yohtmlc-order-details-link"})
74 if tag:
75 return "{}{}".format(BASE_URL, tag.attrs["href"])
77 def _parse_order_number(self):
78 if self.order_details_link:
79 parsed_url = urlparse(self.order_details_link)
80 return parse_qs(parsed_url.query)["orderID"][0]
81 else:
82 tag = self.parsed.find("bdi", dir="ltr")
83 return tag.text.strip()
85 def _parse_grand_total(self):
86 tag = self.parsed.find("div", {"class": "yohtmlc-order-total"})
87 if tag:
88 tag = tag.find("span", {"class": "value"})
89 else:
90 for tag in self.parsed.find("div", id="od-subtotals").find_all("div", {"class": "a-row"}):
91 if "grand total" in tag.text.lower():
92 tag = tag.find("div", {"class": "a-span-last"})
93 return tag.text.strip().replace("$", "")
95 def _parse_order_placed_date(self):
96 tag = self.parsed.find("span", {"class": "order-date-invoice-item"})
97 if tag:
98 date_str = tag.text.split("Ordered on")[1].strip()
99 else:
100 tag = self.parsed.find("div", {"class": "a-span3"}).find_all("span")
101 date_str = tag[1].text.strip()
102 return datetime.strptime(date_str, "%B %d, %Y").date()
104 def _parse_recipient(self):
105 tag = self.parsed.find("div", {"class": "displayAddressDiv"})
106 if not tag:
107 script_id = self.parsed.find("div",
108 id=lambda value: value and value.startswith("shipToInsertionNode")).attrs[
109 "id"]
110 tag = self.parsed.find("script",
111 id="shipToData-shippingAddress-{}".format(script_id.split("-")[2]))
112 tag = BeautifulSoup(str(tag.contents[0]).strip(), "html.parser")
113 return Recipient(tag)
115 def _parse_payment_method(self):
116 tag = self.parsed.find("img", {"class": "pmts-payment-credit-card-instrument-logo"})
117 if tag:
118 return tag.attrs["alt"]
120 def _parse_payment_method_last_4(self):
121 tag = self.parsed.find("img", {"class": "pmts-payment-credit-card-instrument-logo"})
122 if tag:
123 ending_sibling = tag.find_next_siblings()[-1]
124 return ending_sibling.text.split("ending in")[1].strip()
126 def _parse_subtotal(self):
127 for tag in self.parsed.find("div", id="od-subtotals").find_all("div", {"class": "a-row"}):
128 if "subtotal" in tag.text.lower():
129 return tag.find("div", {"class": "a-span-last"}).text.strip().replace("$", "")
131 def _parse_shipping_total(self):
132 for tag in self.parsed.find("div", id="od-subtotals").find_all("div", {"class": "a-row"}):
133 if "shipping" in tag.text.lower():
134 return tag.find("div", {"class": "a-span-last"}).text.strip().replace("$", "")
136 def _parse_subscription_discount(self):
137 for tag in self.parsed.find("div", id="od-subtotals").find_all("div", {"class": "a-row"}):
138 if "subscribe" in tag.text.lower():
139 return tag.find("div", {"class": "a-span-last"}).text.strip().replace("$", "")
141 def _parse_total_before_tax(self):
142 for tag in self.parsed.find("div", id="od-subtotals").find_all("div", {"class": "a-row"}):
143 if "before tax" in tag.text.lower():
144 return tag.find("div", {"class": "a-span-last"}).text.strip().replace("$", "")
146 def _parse_estimated_tax(self):
147 for tag in self.parsed.find("div", id="od-subtotals").find_all("div", {"class": "a-row"}):
148 if "estimated tax" in tag.text.lower():
149 return tag.find("div", {"class": "a-span-last"}).text.strip().replace("$", "")
151 def _parse_refund_total(self):
152 for tag in self.parsed.find("div", id="od-subtotals").find_all("div", {"class": "a-row"}):
153 if "refund total" in tag.text.lower() and "tax refund" not in tag.text.lower():
154 return tag.find("div", {"class": "a-span-last"}).text.strip().replace("$", "")
156 def _parse_order_shipping_date(self):
157 # TODO: find a better way to do this
158 if "Items shipped:" in self.parsed.text:
159 date_str = self.parsed.text.split("Items shipped:")[1].strip().split("-")[0].strip()
160 return datetime.strptime(date_str, "%B %d, %Y").date()
162 def _parse_refund_completed_date(self):
163 # TODO: find a better way to do this
164 if "Refund: Completed" in self.parsed.text:
165 date_str = self.parsed.text.split("Refund: Completed")[1].strip().split("-")[0].strip()
166 return datetime.strptime(date_str, "%B %d, %Y").date()