From 2fb57b5bebf40cf9f230b2450e07d9cf9f582b63 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 3 Aug 2023 09:29:11 +0200 Subject: [PATCH 01/27] Get orders in date range --- camisatoshi_wordpress_reports/cli.py | 4 ++-- camisatoshi_wordpress_reports/controllers.py | 21 +++++++++++++++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/camisatoshi_wordpress_reports/cli.py b/camisatoshi_wordpress_reports/cli.py index f1877f1..9cc7eb5 100644 --- a/camisatoshi_wordpress_reports/cli.py +++ b/camisatoshi_wordpress_reports/cli.py @@ -11,6 +11,6 @@ def check_health(): @app.command() -def show_orders(): - controllers.show_orders() +def generate_um_report(start_date: str, end_date: str): + controllers.generate_um_report(start_date, end_date) diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index 073f941..0b6a273 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -1,4 +1,6 @@ from pathlib import Path +import datetime +from typing import List, Dict from dotenv import dotenv_values from woocommerce import API @@ -30,9 +32,18 @@ def check_health(): print("Connection successful. The API is reachable.") -def show_orders(): - print( - WC_API.get( - "orders", params={"after": "2023-07-21T18:02:23+00:00"} - ).json() +def generate_um_report(start_date: str, end_date: str): + + orders_in_date_range = WC_API.get( + endpoint="orders", + params={ + "after": datetime.datetime.strptime( + start_date, "%Y-%m-%d" + ).isoformat(), + "before": datetime.datetime.strptime( + end_date, "%Y-%m-%d" + ).isoformat(), + "per_page": 100, + "status": "processing,completed", + }, ) From ad4bc8c0dab1f82c102b06fea80df439b307ee69 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 3 Aug 2023 09:42:22 +0200 Subject: [PATCH 02/27] Some advances. --- camisatoshi_wordpress_reports/controllers.py | 67 +++++++++++++++++--- 1 file changed, 57 insertions(+), 10 deletions(-) diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index 0b6a273..25c3d9d 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -1,6 +1,7 @@ from pathlib import Path import datetime from typing import List, Dict +import logging from dotenv import dotenv_values from woocommerce import API @@ -15,9 +16,14 @@ WC_API = API( version=API_CONFIG["VERSION"], ) +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger() + def check_health(): - print(f"Connecting to the configured woocomerce at {API_CONFIG['URL']}") + logger.info( + f"Connecting to the configured woocomerce at {API_CONFIG['URL']}" + ) try: api_reported_version = WC_API.get("").json()["namespace"] @@ -26,24 +32,65 @@ def check_health(): "There was an issue connecting to the woocomerce API." ) - print(f"Informed version of the API: {API_CONFIG['VERSION']}") - print(f"Version reported by the API itself: {api_reported_version}") + logger.info(f"Informed version of the API: {API_CONFIG['VERSION']}") + logger.info(f"Version reported by the API itself: {api_reported_version}") - print("Connection successful. The API is reachable.") + logger.info("Connection successful. The API is reachable.") def generate_um_report(start_date: str, end_date: str): + start_date = datetime.datetime.strptime(start_date, "%Y-%m-%d").isoformat() + end_date = datetime.datetime.strptime(end_date, "%Y-%m-%d").isoformat() + + logger.info(f"Fetching orders between {start_date} and {end_date}.") + orders_in_date_range = WC_API.get( endpoint="orders", params={ - "after": datetime.datetime.strptime( - start_date, "%Y-%m-%d" - ).isoformat(), - "before": datetime.datetime.strptime( - end_date, "%Y-%m-%d" - ).isoformat(), + "after": start_date, + "before": end_date, "per_page": 100, "status": "processing,completed", }, + ).json() + + logger.info(f"Received {len(orders_in_date_range)} orders.") + + skus_to_keep = ["TEE-05-BBO-BLACK"] + + logger.info(f"Filtering by SKUs: {skus_to_keep}") + relevant_orders = filter_orders_by_sku( + orders_in_date_range, skus=skus_to_keep ) + + logger.info(f"Kept {len(relevant_orders)} orders.") + + # Fetch orders: + # - Between specific dates + # - That contain the hardcoded products + # - That have been paid, hence status is either processing or completed + # - That have not been settled yet (is_settled_with_um: 0) + + # Print to screen: + # - Orders that do not have the `sats_received` metadata informed + # - The unit count for each product + # - The sales sum for each product + # - The sats sum for each product + # - The corresponding payment owed to UM + # - The list of order ids that have been taken into account + + # Update orders: + # - Add metadata entry: is_settled_with_um: 1 + + +def filter_orders_by_sku(orders: List[Dict], skus: List[str]): + + filtered_orders = [] + + for order in orders: + for item in order["line_items"]: + if item["sku"] in skus: + filtered_orders.append(order) + + return filtered_orders From d8f6548626ab0f896569c3dabcebcc8cae76392d Mon Sep 17 00:00:00 2001 From: Pablo Martin Date: Thu, 3 Aug 2023 14:34:49 +0200 Subject: [PATCH 03/27] Typos --- camisatoshi_wordpress_reports/controllers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index 25c3d9d..92a7e4c 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -22,14 +22,14 @@ logger = logging.getLogger() def check_health(): logger.info( - f"Connecting to the configured woocomerce at {API_CONFIG['URL']}" + f"Connecting to the configured WooCommerce at {API_CONFIG['URL']}" ) try: api_reported_version = WC_API.get("").json()["namespace"] except: raise ConnectionError( - "There was an issue connecting to the woocomerce API." + "There was an issue connecting to the WooCommerce API." ) logger.info(f"Informed version of the API: {API_CONFIG['VERSION']}") From bca2e7b143be3e7969066c4f543bd16e583e4e13 Mon Sep 17 00:00:00 2001 From: Pablo Martin Date: Thu, 3 Aug 2023 14:50:39 +0200 Subject: [PATCH 04/27] Enforce that sats_received is in orders --- camisatoshi_wordpress_reports/controllers.py | 29 +++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index 92a7e4c..8c72d62 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -39,7 +39,6 @@ def check_health(): def generate_um_report(start_date: str, end_date: str): - start_date = datetime.datetime.strptime(start_date, "%Y-%m-%d").isoformat() end_date = datetime.datetime.strptime(end_date, "%Y-%m-%d").isoformat() @@ -63,9 +62,18 @@ def generate_um_report(start_date: str, end_date: str): relevant_orders = filter_orders_by_sku( orders_in_date_range, skus=skus_to_keep ) - logger.info(f"Kept {len(relevant_orders)} orders.") + logger.info("Checking if all orders have the sats_received entry filled in.") + orders_without_sats_received = find_orders_without_sats_received(relevant_orders) + + if orders_without_sats_received: + logger.warning( + f"There are {len(orders_without_sats_received)} orders without a properly filled sats_received entry.") + logger.warning(f"See details below.") + logger.warning(orders_without_sats_received) + raise ValueError("Not all orders have sats_received. Can't compute sats owed without that.") + # Fetch orders: # - Between specific dates # - That contain the hardcoded products @@ -84,8 +92,7 @@ def generate_um_report(start_date: str, end_date: str): # - Add metadata entry: is_settled_with_um: 1 -def filter_orders_by_sku(orders: List[Dict], skus: List[str]): - +def filter_orders_by_sku(orders: List[Dict], skus: List[str]) -> List[Dict]: filtered_orders = [] for order in orders: @@ -94,3 +101,17 @@ def filter_orders_by_sku(orders: List[Dict], skus: List[str]): filtered_orders.append(order) return filtered_orders + + +def find_orders_without_sats_received(orders: List[Dict]): + orders_without_sats_received = [] + + for order in orders: + meta_data_entries = {meta_data_entry["key"]: meta_data_entry["value"] for meta_data_entry in order["meta_data"]} + if "sats_received" not in meta_data_entries.keys(): + orders_without_sats_received.append(order) + continue + if int(meta_data_entries["sats_received"]) < 0: + orders_without_sats_received.append(order) + + return orders_without_sats_received From 7c1dba2f1beb065a830d074293758361a74fbeca Mon Sep 17 00:00:00 2001 From: Pablo Martin Date: Thu, 3 Aug 2023 14:55:41 +0200 Subject: [PATCH 05/27] Order is now a class instead of just a dict. --- camisatoshi_wordpress_reports/controllers.py | 6 +++++- camisatoshi_wordpress_reports/order.py | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 camisatoshi_wordpress_reports/order.py diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index 8c72d62..7bc8a47 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -6,6 +6,8 @@ import logging from dotenv import dotenv_values from woocommerce import API +from camisatoshi_wordpress_reports.order import Order + API_CONFIG = dotenv_values( dotenv_path=Path.home() / Path(".camisatoshi-wordpress-reports/.env") ) @@ -54,6 +56,8 @@ def generate_um_report(start_date: str, end_date: str): }, ).json() + orders_in_date_range = [Order.from_api_response(order_raw_data) for order_raw_data in orders_in_date_range] + logger.info(f"Received {len(orders_in_date_range)} orders.") skus_to_keep = ["TEE-05-BBO-BLACK"] @@ -103,7 +107,7 @@ def filter_orders_by_sku(orders: List[Dict], skus: List[str]) -> List[Dict]: return filtered_orders -def find_orders_without_sats_received(orders: List[Dict]): +def find_orders_without_sats_received(orders: List[Dict]) -> List[Dict]: orders_without_sats_received = [] for order in orders: diff --git a/camisatoshi_wordpress_reports/order.py b/camisatoshi_wordpress_reports/order.py new file mode 100644 index 0000000..18add68 --- /dev/null +++ b/camisatoshi_wordpress_reports/order.py @@ -0,0 +1,14 @@ +from typing import Dict + + +class Order: + + def __init__(self, raw_data: Dict): + self.raw_data = raw_data + + def __getitem__(self, item): + return self.raw_data[item] + + @classmethod + def from_api_response(cls, raw_data) -> "Order": + return Order(raw_data) From 2e7db68ab4a09ac7e8739d5f7192de47c4545ded Mon Sep 17 00:00:00 2001 From: Pablo Martin Date: Thu, 3 Aug 2023 14:58:26 +0200 Subject: [PATCH 06/27] Sku checking in Order class --- camisatoshi_wordpress_reports/controllers.py | 6 +++--- camisatoshi_wordpress_reports/order.py | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index 7bc8a47..ec76bfc 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -96,12 +96,12 @@ def generate_um_report(start_date: str, end_date: str): # - Add metadata entry: is_settled_with_um: 1 -def filter_orders_by_sku(orders: List[Dict], skus: List[str]) -> List[Dict]: +def filter_orders_by_sku(orders: List[Order], skus: List[str]) -> List[Order]: filtered_orders = [] for order in orders: - for item in order["line_items"]: - if item["sku"] in skus: + for sku in skus: + if order.contains_sku(sku): filtered_orders.append(order) return filtered_orders diff --git a/camisatoshi_wordpress_reports/order.py b/camisatoshi_wordpress_reports/order.py index 18add68..6b9ba49 100644 --- a/camisatoshi_wordpress_reports/order.py +++ b/camisatoshi_wordpress_reports/order.py @@ -9,6 +9,12 @@ class Order: def __getitem__(self, item): return self.raw_data[item] + def contains_sku(self, sku: str) -> bool: + for item in self["line_items"]: + if item["sku"] == sku: + return True + return False + @classmethod def from_api_response(cls, raw_data) -> "Order": return Order(raw_data) From 97d79c771bde4dce59205e79035cd6ca2a3a2923 Mon Sep 17 00:00:00 2001 From: Pablo Martin Date: Thu, 3 Aug 2023 15:01:27 +0200 Subject: [PATCH 07/27] Meta data checking now in order --- camisatoshi_wordpress_reports/controllers.py | 7 ++----- camisatoshi_wordpress_reports/order.py | 6 ++++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index ec76bfc..a991426 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -107,15 +107,12 @@ def filter_orders_by_sku(orders: List[Order], skus: List[str]) -> List[Order]: return filtered_orders -def find_orders_without_sats_received(orders: List[Dict]) -> List[Dict]: +def find_orders_without_sats_received(orders: List[Order]) -> List[Order]: orders_without_sats_received = [] for order in orders: - meta_data_entries = {meta_data_entry["key"]: meta_data_entry["value"] for meta_data_entry in order["meta_data"]} - if "sats_received" not in meta_data_entries.keys(): + if not order.contains_meta_data_entry("sats_received"): orders_without_sats_received.append(order) continue - if int(meta_data_entries["sats_received"]) < 0: - orders_without_sats_received.append(order) return orders_without_sats_received diff --git a/camisatoshi_wordpress_reports/order.py b/camisatoshi_wordpress_reports/order.py index 6b9ba49..246200f 100644 --- a/camisatoshi_wordpress_reports/order.py +++ b/camisatoshi_wordpress_reports/order.py @@ -15,6 +15,12 @@ class Order: return True return False + def contains_meta_data_entry(self, meta_data_entry_key: str) -> bool: + meta_data_entries = {meta_data_entry["key"]: meta_data_entry["value"] for meta_data_entry in self["meta_data"]} + if meta_data_entry_key in meta_data_entries.keys(): + return True + return False + @classmethod def from_api_response(cls, raw_data) -> "Order": return Order(raw_data) From c8d4583543e55f9cfbfe5215941653aa83eaf741 Mon Sep 17 00:00:00 2001 From: Pablo Martin Date: Thu, 3 Aug 2023 16:01:34 +0200 Subject: [PATCH 08/27] Log success --- camisatoshi_wordpress_reports/controllers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index a991426..2eb690a 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -78,6 +78,7 @@ def generate_um_report(start_date: str, end_date: str): logger.warning(orders_without_sats_received) raise ValueError("Not all orders have sats_received. Can't compute sats owed without that.") + logger.info("Success, all orders have sats_received filled in.") # Fetch orders: # - Between specific dates # - That contain the hardcoded products From 1dddbf5ef10798f4619a275b4c9cfd98cb3882d0 Mon Sep 17 00:00:00 2001 From: Pablo Martin Date: Thu, 3 Aug 2023 16:10:30 +0200 Subject: [PATCH 09/27] Param is a datetime instead of a string --- camisatoshi_wordpress_reports/cli.py | 10 ++++++++-- camisatoshi_wordpress_reports/controllers.py | 9 +++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/camisatoshi_wordpress_reports/cli.py b/camisatoshi_wordpress_reports/cli.py index 9cc7eb5..85a63eb 100644 --- a/camisatoshi_wordpress_reports/cli.py +++ b/camisatoshi_wordpress_reports/cli.py @@ -1,5 +1,9 @@ +import datetime + import typer +from typing_extensions import Annotated + import camisatoshi_wordpress_reports.controllers as controllers app = typer.Typer() @@ -11,6 +15,8 @@ def check_health(): @app.command() -def generate_um_report(start_date: str, end_date: str): +def generate_um_report( + start_date: Annotated[datetime.datetime, typer.Option(prompt=True)], + end_date: Annotated[datetime.datetime, typer.Option(prompt=True)], +): controllers.generate_um_report(start_date, end_date) - diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index 2eb690a..54ffe14 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -40,17 +40,14 @@ def check_health(): logger.info("Connection successful. The API is reachable.") -def generate_um_report(start_date: str, end_date: str): - start_date = datetime.datetime.strptime(start_date, "%Y-%m-%d").isoformat() - end_date = datetime.datetime.strptime(end_date, "%Y-%m-%d").isoformat() - +def generate_um_report(start_date: datetime.datetime, end_date: datetime.datetime) -> None: logger.info(f"Fetching orders between {start_date} and {end_date}.") orders_in_date_range = WC_API.get( endpoint="orders", params={ - "after": start_date, - "before": end_date, + "after": start_date.isoformat(), + "before": end_date.isoformat(), "per_page": 100, "status": "processing,completed", }, From 964682716f1ac7f7e768e23aac531735912dc0a4 Mon Sep 17 00:00:00 2001 From: Pablo Martin Date: Thu, 3 Aug 2023 16:22:20 +0200 Subject: [PATCH 10/27] Check if order is already settled. --- camisatoshi_wordpress_reports/controllers.py | 18 ++++++++++++++---- camisatoshi_wordpress_reports/order.py | 12 ++++++++++-- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index 54ffe14..e248acd 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -52,9 +52,7 @@ def generate_um_report(start_date: datetime.datetime, end_date: datetime.datetim "status": "processing,completed", }, ).json() - orders_in_date_range = [Order.from_api_response(order_raw_data) for order_raw_data in orders_in_date_range] - logger.info(f"Received {len(orders_in_date_range)} orders.") skus_to_keep = ["TEE-05-BBO-BLACK"] @@ -67,15 +65,17 @@ def generate_um_report(start_date: datetime.datetime, end_date: datetime.datetim logger.info("Checking if all orders have the sats_received entry filled in.") orders_without_sats_received = find_orders_without_sats_received(relevant_orders) - if orders_without_sats_received: logger.warning( f"There are {len(orders_without_sats_received)} orders without a properly filled sats_received entry.") logger.warning(f"See details below.") logger.warning(orders_without_sats_received) raise ValueError("Not all orders have sats_received. Can't compute sats owed without that.") - logger.info("Success, all orders have sats_received filled in.") + + logger.info("Removing settled orders.") + unsettled_orders = filter_settled_orders(relevant_orders) + logger.info(f"Kept {len(unsettled_orders)} unsettled orders.") # Fetch orders: # - Between specific dates # - That contain the hardcoded products @@ -114,3 +114,13 @@ def find_orders_without_sats_received(orders: List[Order]) -> List[Order]: continue return orders_without_sats_received + + +def filter_settled_orders(orders: List[Order]) -> List[Order]: + unsettled_orders = [] + + for order in orders: + if not order.is_settled_with_um(): + unsettled_orders.append(order) + + return unsettled_orders diff --git a/camisatoshi_wordpress_reports/order.py b/camisatoshi_wordpress_reports/order.py index 246200f..e7783ef 100644 --- a/camisatoshi_wordpress_reports/order.py +++ b/camisatoshi_wordpress_reports/order.py @@ -9,6 +9,10 @@ class Order: def __getitem__(self, item): return self.raw_data[item] + @property + def meta_data_entries(self): + return {meta_data_entry["key"]: meta_data_entry["value"] for meta_data_entry in self.raw_data["meta_data"]} + def contains_sku(self, sku: str) -> bool: for item in self["line_items"]: if item["sku"] == sku: @@ -16,11 +20,15 @@ class Order: return False def contains_meta_data_entry(self, meta_data_entry_key: str) -> bool: - meta_data_entries = {meta_data_entry["key"]: meta_data_entry["value"] for meta_data_entry in self["meta_data"]} - if meta_data_entry_key in meta_data_entries.keys(): + if meta_data_entry_key in self.meta_data_entries.keys(): return True return False + def is_settled_with_um(self): + is_settled = self.meta_data_entries.get("is_settled_with_um", None) + + return bool(is_settled) + @classmethod def from_api_response(cls, raw_data) -> "Order": return Order(raw_data) From e87854bba01aea5ab9b926d12b5f558c58dca83a Mon Sep 17 00:00:00 2001 From: Pablo Martin Date: Thu, 3 Aug 2023 16:33:53 +0200 Subject: [PATCH 11/27] Constants to stop hardcoding strings everywhere --- camisatoshi_wordpress_reports/constants.py | 11 +++++++++++ camisatoshi_wordpress_reports/order.py | 16 +++++++++++----- 2 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 camisatoshi_wordpress_reports/constants.py diff --git a/camisatoshi_wordpress_reports/constants.py b/camisatoshi_wordpress_reports/constants.py new file mode 100644 index 0000000..e622efe --- /dev/null +++ b/camisatoshi_wordpress_reports/constants.py @@ -0,0 +1,11 @@ +from types import SimpleNamespace + +order_keys = SimpleNamespace() +order_keys.meta_data = "meta_data" +order_keys.line_items = "line_items" + +order_keys.line_item_keys = SimpleNamespace() +order_keys.line_item_keys.sku = "sku" + +custom_meta_data_keys = SimpleNamespace() +custom_meta_data_keys.is_settled_with_um = "is_settled_with_um" diff --git a/camisatoshi_wordpress_reports/order.py b/camisatoshi_wordpress_reports/order.py index e7783ef..da29565 100644 --- a/camisatoshi_wordpress_reports/order.py +++ b/camisatoshi_wordpress_reports/order.py @@ -1,8 +1,9 @@ from typing import Dict +from camisatoshi_wordpress_reports.constants import order_keys, custom_meta_data_keys + class Order: - def __init__(self, raw_data: Dict): self.raw_data = raw_data @@ -11,11 +12,14 @@ class Order: @property def meta_data_entries(self): - return {meta_data_entry["key"]: meta_data_entry["value"] for meta_data_entry in self.raw_data["meta_data"]} + return { + meta_data_entry["key"]: meta_data_entry["value"] + for meta_data_entry in self.raw_data[order_keys.meta_data] + } def contains_sku(self, sku: str) -> bool: - for item in self["line_items"]: - if item["sku"] == sku: + for item in self[order_keys.line_items]: + if item[order_keys.line_item_keys.sku] == sku: return True return False @@ -25,7 +29,9 @@ class Order: return False def is_settled_with_um(self): - is_settled = self.meta_data_entries.get("is_settled_with_um", None) + is_settled = self.meta_data_entries.get( + custom_meta_data_keys.is_settled_with_um, None + ) return bool(is_settled) From 2d0df17b869e22215c4c36eab603759b4e0924d2 Mon Sep 17 00:00:00 2001 From: Pablo Martin Date: Thu, 3 Aug 2023 16:36:27 +0200 Subject: [PATCH 12/27] More constants --- camisatoshi_wordpress_reports/constants.py | 1 + camisatoshi_wordpress_reports/controllers.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/camisatoshi_wordpress_reports/constants.py b/camisatoshi_wordpress_reports/constants.py index e622efe..f6873fa 100644 --- a/camisatoshi_wordpress_reports/constants.py +++ b/camisatoshi_wordpress_reports/constants.py @@ -9,3 +9,4 @@ order_keys.line_item_keys.sku = "sku" custom_meta_data_keys = SimpleNamespace() custom_meta_data_keys.is_settled_with_um = "is_settled_with_um" +custom_meta_data_keys.sats_received = "sats_received" \ No newline at end of file diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index e248acd..d16b7d8 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -7,6 +7,7 @@ from dotenv import dotenv_values from woocommerce import API from camisatoshi_wordpress_reports.order import Order +from camisatoshi_wordpress_reports.constants import custom_meta_data_keys API_CONFIG = dotenv_values( dotenv_path=Path.home() / Path(".camisatoshi-wordpress-reports/.env") @@ -109,7 +110,7 @@ def find_orders_without_sats_received(orders: List[Order]) -> List[Order]: orders_without_sats_received = [] for order in orders: - if not order.contains_meta_data_entry("sats_received"): + if not order.contains_meta_data_entry(custom_meta_data_keys.sats_received): orders_without_sats_received.append(order) continue From 2099ddc0e05a1ca1d0ba7f9009eaf3eba1c7993f Mon Sep 17 00:00:00 2001 From: Pablo Martin Date: Thu, 3 Aug 2023 16:38:24 +0200 Subject: [PATCH 13/27] Orders class --- camisatoshi_wordpress_reports/order.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/camisatoshi_wordpress_reports/order.py b/camisatoshi_wordpress_reports/order.py index da29565..cb44b06 100644 --- a/camisatoshi_wordpress_reports/order.py +++ b/camisatoshi_wordpress_reports/order.py @@ -1,4 +1,4 @@ -from typing import Dict +from typing import Dict, Collection from camisatoshi_wordpress_reports.constants import order_keys, custom_meta_data_keys @@ -38,3 +38,9 @@ class Order: @classmethod def from_api_response(cls, raw_data) -> "Order": return Order(raw_data) + + +class Orders: + + def __init__(self, orders: Collection[Order]): + self._orders = orders \ No newline at end of file From 55b06dbbb00033c25017edb5aa56d905603fe208 Mon Sep 17 00:00:00 2001 From: Pablo Martin Date: Thu, 3 Aug 2023 16:50:40 +0200 Subject: [PATCH 14/27] Move weird functions to Orders class --- camisatoshi_wordpress_reports/controllers.py | 69 ++++++-------------- camisatoshi_wordpress_reports/order.py | 37 ++++++++++- 2 files changed, 54 insertions(+), 52 deletions(-) diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index d16b7d8..5495596 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -6,7 +6,7 @@ import logging from dotenv import dotenv_values from woocommerce import API -from camisatoshi_wordpress_reports.order import Order +from camisatoshi_wordpress_reports.order import Order, Orders from camisatoshi_wordpress_reports.constants import custom_meta_data_keys API_CONFIG = dotenv_values( @@ -24,16 +24,12 @@ logger = logging.getLogger() def check_health(): - logger.info( - f"Connecting to the configured WooCommerce at {API_CONFIG['URL']}" - ) + logger.info(f"Connecting to the configured WooCommerce at {API_CONFIG['URL']}") try: api_reported_version = WC_API.get("").json()["namespace"] except: - raise ConnectionError( - "There was an issue connecting to the WooCommerce API." - ) + raise ConnectionError("There was an issue connecting to the WooCommerce API.") logger.info(f"Informed version of the API: {API_CONFIG['VERSION']}") logger.info(f"Version reported by the API itself: {api_reported_version}") @@ -41,7 +37,9 @@ def check_health(): logger.info("Connection successful. The API is reachable.") -def generate_um_report(start_date: datetime.datetime, end_date: datetime.datetime) -> None: +def generate_um_report( + start_date: datetime.datetime, end_date: datetime.datetime +) -> None: logger.info(f"Fetching orders between {start_date} and {end_date}.") orders_in_date_range = WC_API.get( @@ -53,29 +51,34 @@ def generate_um_report(start_date: datetime.datetime, end_date: datetime.datetim "status": "processing,completed", }, ).json() - orders_in_date_range = [Order.from_api_response(order_raw_data) for order_raw_data in orders_in_date_range] + orders_in_date_range = Orders( + [ + Order.from_api_response(order_raw_data) + for order_raw_data in orders_in_date_range + ] + ) logger.info(f"Received {len(orders_in_date_range)} orders.") skus_to_keep = ["TEE-05-BBO-BLACK"] - logger.info(f"Filtering by SKUs: {skus_to_keep}") - relevant_orders = filter_orders_by_sku( - orders_in_date_range, skus=skus_to_keep - ) + relevant_orders = orders_in_date_range.filter_orders_by_skus(skus=skus_to_keep) logger.info(f"Kept {len(relevant_orders)} orders.") logger.info("Checking if all orders have the sats_received entry filled in.") - orders_without_sats_received = find_orders_without_sats_received(relevant_orders) + orders_without_sats_received = relevant_orders.filter_orders_without_sats_received() if orders_without_sats_received: logger.warning( - f"There are {len(orders_without_sats_received)} orders without a properly filled sats_received entry.") + f"There are {len(orders_without_sats_received)} orders without a properly filled sats_received entry." + ) logger.warning(f"See details below.") logger.warning(orders_without_sats_received) - raise ValueError("Not all orders have sats_received. Can't compute sats owed without that.") + raise ValueError( + "Not all orders have sats_received. Can't compute sats owed without that." + ) logger.info("Success, all orders have sats_received filled in.") logger.info("Removing settled orders.") - unsettled_orders = filter_settled_orders(relevant_orders) + unsettled_orders = relevant_orders.filter_unsettled_orders() logger.info(f"Kept {len(unsettled_orders)} unsettled orders.") # Fetch orders: # - Between specific dates @@ -93,35 +96,3 @@ def generate_um_report(start_date: datetime.datetime, end_date: datetime.datetim # Update orders: # - Add metadata entry: is_settled_with_um: 1 - - -def filter_orders_by_sku(orders: List[Order], skus: List[str]) -> List[Order]: - filtered_orders = [] - - for order in orders: - for sku in skus: - if order.contains_sku(sku): - filtered_orders.append(order) - - return filtered_orders - - -def find_orders_without_sats_received(orders: List[Order]) -> List[Order]: - orders_without_sats_received = [] - - for order in orders: - if not order.contains_meta_data_entry(custom_meta_data_keys.sats_received): - orders_without_sats_received.append(order) - continue - - return orders_without_sats_received - - -def filter_settled_orders(orders: List[Order]) -> List[Order]: - unsettled_orders = [] - - for order in orders: - if not order.is_settled_with_um(): - unsettled_orders.append(order) - - return unsettled_orders diff --git a/camisatoshi_wordpress_reports/order.py b/camisatoshi_wordpress_reports/order.py index cb44b06..5e2c436 100644 --- a/camisatoshi_wordpress_reports/order.py +++ b/camisatoshi_wordpress_reports/order.py @@ -1,4 +1,4 @@ -from typing import Dict, Collection +from typing import List, Dict, Collection from camisatoshi_wordpress_reports.constants import order_keys, custom_meta_data_keys @@ -41,6 +41,37 @@ class Order: class Orders: - def __init__(self, orders: Collection[Order]): - self._orders = orders \ No newline at end of file + self._orders = orders + + def __len__(self): + return len(self._orders) + + def filter_orders_by_skus(self, skus: List[str]) -> "Orders": + filtered_orders = [] + + for order in self._orders: + for sku in skus: + if order.contains_sku(sku): + filtered_orders.append(order) + + return Orders(filtered_orders) + + def filter_orders_without_sats_received(self) -> "Orders": + orders_without_sats_received = [] + + for order in self._orders: + if not order.contains_meta_data_entry(custom_meta_data_keys.sats_received): + orders_without_sats_received.append(order) + continue + + return Orders(orders_without_sats_received) + + def filter_unsettled_orders(self) -> "Orders": + unsettled_orders = [] + + for order in self._orders: + if not order.is_settled_with_um(): + unsettled_orders.append(order) + + return Orders(unsettled_orders) From 362549e52ecb911da94655f976093e7b2ae1e7c3 Mon Sep 17 00:00:00 2001 From: Pablo Martin Date: Thu, 3 Aug 2023 17:24:42 +0200 Subject: [PATCH 15/27] Orders are now iterable --- camisatoshi_wordpress_reports/order.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/camisatoshi_wordpress_reports/order.py b/camisatoshi_wordpress_reports/order.py index 5e2c436..639fc29 100644 --- a/camisatoshi_wordpress_reports/order.py +++ b/camisatoshi_wordpress_reports/order.py @@ -47,10 +47,22 @@ class Orders: def __len__(self): return len(self._orders) + def __iter__(self): + self._index = 0 + return self + + def __next__(self): + if self._index < len(self._orders): + next_order = self._orders[self._index] + self._index += 1 + return next_order + raise StopIteration + + def filter_orders_by_skus(self, skus: List[str]) -> "Orders": filtered_orders = [] - for order in self._orders: + for order in self: for sku in skus: if order.contains_sku(sku): filtered_orders.append(order) @@ -60,7 +72,7 @@ class Orders: def filter_orders_without_sats_received(self) -> "Orders": orders_without_sats_received = [] - for order in self._orders: + for order in self: if not order.contains_meta_data_entry(custom_meta_data_keys.sats_received): orders_without_sats_received.append(order) continue @@ -70,7 +82,7 @@ class Orders: def filter_unsettled_orders(self) -> "Orders": unsettled_orders = [] - for order in self._orders: + for order in self: if not order.is_settled_with_um(): unsettled_orders.append(order) From 0c249a8f7cb3a6632dd46ff8a41b33a6bb20271c Mon Sep 17 00:00:00 2001 From: Pablo Martin Date: Thu, 3 Aug 2023 18:13:51 +0200 Subject: [PATCH 16/27] Report kind of complete --- camisatoshi_wordpress_reports/constants.py | 13 +++++- camisatoshi_wordpress_reports/controllers.py | 46 +++++++++++++------- camisatoshi_wordpress_reports/order.py | 39 ++++++++++++++--- camisatoshi_wordpress_reports/utils.py | 2 + 4 files changed, 77 insertions(+), 23 deletions(-) create mode 100644 camisatoshi_wordpress_reports/utils.py diff --git a/camisatoshi_wordpress_reports/constants.py b/camisatoshi_wordpress_reports/constants.py index f6873fa..92b0993 100644 --- a/camisatoshi_wordpress_reports/constants.py +++ b/camisatoshi_wordpress_reports/constants.py @@ -1,12 +1,23 @@ from types import SimpleNamespace +### Order keys + order_keys = SimpleNamespace() order_keys.meta_data = "meta_data" +order_keys.total = "total" order_keys.line_items = "line_items" order_keys.line_item_keys = SimpleNamespace() order_keys.line_item_keys.sku = "sku" +order_keys.line_item_keys.quantity = "quantity" +order_keys.line_item_keys.total = "total" custom_meta_data_keys = SimpleNamespace() custom_meta_data_keys.is_settled_with_um = "is_settled_with_um" -custom_meta_data_keys.sats_received = "sats_received" \ No newline at end of file +custom_meta_data_keys.sats_received = "sats_received" + + +### Other + +um_first_agreement_percentage = 0.5 +bbo_royalty_fee = 0.2 \ No newline at end of file diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index 5495596..cc09119 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -1,13 +1,13 @@ from pathlib import Path import datetime -from typing import List, Dict import logging +import csv from dotenv import dotenv_values from woocommerce import API from camisatoshi_wordpress_reports.order import Order, Orders -from camisatoshi_wordpress_reports.constants import custom_meta_data_keys +from camisatoshi_wordpress_reports.constants import um_first_agreement_percentage API_CONFIG = dotenv_values( dotenv_path=Path.home() / Path(".camisatoshi-wordpress-reports/.env") @@ -38,7 +38,7 @@ def check_health(): def generate_um_report( - start_date: datetime.datetime, end_date: datetime.datetime + start_date: datetime.datetime, end_date: datetime.datetime ) -> None: logger.info(f"Fetching orders between {start_date} and {end_date}.") @@ -59,9 +59,9 @@ def generate_um_report( ) logger.info(f"Received {len(orders_in_date_range)} orders.") - skus_to_keep = ["TEE-05-BBO-BLACK"] - logger.info(f"Filtering by SKUs: {skus_to_keep}") - relevant_orders = orders_in_date_range.filter_orders_by_skus(skus=skus_to_keep) + relevant_sku = "TEE-05-BBO-BLACK" + logger.info(f"Filtering by SKU: {relevant_sku}") + relevant_orders = orders_in_date_range.filter_orders_by_sku(sku=relevant_sku) logger.info(f"Kept {len(relevant_orders)} orders.") logger.info("Checking if all orders have the sats_received entry filled in.") @@ -84,15 +84,29 @@ def generate_um_report( # - Between specific dates # - That contain the hardcoded products # - That have been paid, hence status is either processing or completed - # - That have not been settled yet (is_settled_with_um: 0) + # - That have not been settled yet (is_settled_with_um = 0) + logger.info("Order filtering finished.") - # Print to screen: - # - Orders that do not have the `sats_received` metadata informed - # - The unit count for each product - # - The sales sum for each product - # - The sats sum for each product - # - The corresponding payment owed to UM - # - The list of order ids that have been taken into account + logger.info(f"Relevant orders: {[order['id'] for order in unsettled_orders]}.") - # Update orders: - # - Add metadata entry: is_settled_with_um: 1 + report = [] + for order in unsettled_orders: + report.append( + { + "order_id": order["id"], + "sku": relevant_sku, + "units_sold": order.units_of_sku(relevant_sku), + "eur_income": order.sales_of_sku(relevant_sku), + "sats_income": order.sats_received_for_sku(relevant_sku), + "sats_owed_to_um": order.sats_received_for_sku(relevant_sku) * um_first_agreement_percentage, + } + ) + logger.info("Report generated.") + logger.info(report) + + keys = report[0].keys() + + with open('report.csv', 'w', newline='') as output_file: + dict_writer = csv.DictWriter(output_file, keys) + dict_writer.writeheader() + dict_writer.writerows(report) diff --git a/camisatoshi_wordpress_reports/order.py b/camisatoshi_wordpress_reports/order.py index 639fc29..8610e6f 100644 --- a/camisatoshi_wordpress_reports/order.py +++ b/camisatoshi_wordpress_reports/order.py @@ -1,6 +1,7 @@ -from typing import List, Dict, Collection +from typing import Dict, Collection from camisatoshi_wordpress_reports.constants import order_keys, custom_meta_data_keys +from camisatoshi_wordpress_reports.utils import safe_zero_division class Order: @@ -17,6 +18,34 @@ class Order: for meta_data_entry in self.raw_data[order_keys.meta_data] } + def units_of_sku(self, sku: str) -> int: + units = 0 + for line in self[order_keys.line_items]: + if line[order_keys.line_item_keys.sku] == sku: + units += line[order_keys.line_item_keys.quantity] + return units + + def sales_of_sku(self, sku: str) -> float: + sales = 0 + for line in self[order_keys.line_items]: + if line[order_keys.line_item_keys.sku] == sku: + sales += float(line[order_keys.line_item_keys.total]) + return sales + + def sats_received_for_sku(self, sku: str) -> float: + total_order_eur = float(self[order_keys.total]) + eur_of_sku = self.sales_of_sku(sku) + + monetary_weight_of_sku_in_order = safe_zero_division(eur_of_sku, total_order_eur) + total_order_sats_received = float( + self.meta_data_entries[custom_meta_data_keys.sats_received] + ) + sats_received_for_sku = ( + monetary_weight_of_sku_in_order * total_order_sats_received + ) + + return sats_received_for_sku + def contains_sku(self, sku: str) -> bool: for item in self[order_keys.line_items]: if item[order_keys.line_item_keys.sku] == sku: @@ -58,14 +87,12 @@ class Orders: return next_order raise StopIteration - - def filter_orders_by_skus(self, skus: List[str]) -> "Orders": + def filter_orders_by_sku(self, sku: str) -> "Orders": filtered_orders = [] for order in self: - for sku in skus: - if order.contains_sku(sku): - filtered_orders.append(order) + if order.contains_sku(sku): + filtered_orders.append(order) return Orders(filtered_orders) diff --git a/camisatoshi_wordpress_reports/utils.py b/camisatoshi_wordpress_reports/utils.py new file mode 100644 index 0000000..68e9830 --- /dev/null +++ b/camisatoshi_wordpress_reports/utils.py @@ -0,0 +1,2 @@ +def safe_zero_division(n, d): + return n / d if d else 0 From 1ca5de8aedee55b0b1d536941e8fc5245d358ff7 Mon Sep 17 00:00:00 2001 From: Pablo Martin Date: Thu, 3 Aug 2023 18:15:07 +0200 Subject: [PATCH 17/27] Summarize open issues --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1f61f03..46d5094 100644 --- a/README.md +++ b/README.md @@ -7,4 +7,11 @@ This repository hosts a Python CLI app that can be used to generate reports by f 1. Install the package 2. In the home directory of the running user, create a folder named `.camisatoshi-wordpress-reports`. -3. Copy the provided `.env-example` file in that directory and fill it with the required params from Woocomerce. \ No newline at end of file +3. Copy the provided `.env-example` file in that directory and fill it with the required params from Woocomerce. + + +# Open issues +- Clean up controller +- Better output +- Update readme with instructions +- Sit down and write process in camisatoshi docs to clarify how much of this process should be done by the script and what belongs to the operator. \ No newline at end of file From 102c4ae31cf26993632d744a5c96553b744137c7 Mon Sep 17 00:00:00 2001 From: Pablo Martin Date: Thu, 3 Aug 2023 22:33:34 +0200 Subject: [PATCH 18/27] Reports and stuff --- camisatoshi_wordpress_reports/cli.py | 8 ++ camisatoshi_wordpress_reports/controllers.py | 135 ++++++++++++++++++- 2 files changed, 140 insertions(+), 3 deletions(-) diff --git a/camisatoshi_wordpress_reports/cli.py b/camisatoshi_wordpress_reports/cli.py index 85a63eb..dd6ea12 100644 --- a/camisatoshi_wordpress_reports/cli.py +++ b/camisatoshi_wordpress_reports/cli.py @@ -14,6 +14,14 @@ def check_health(): controllers.check_health() +@app.command() +def generate_sku_report( + start_date: Annotated[datetime.datetime, typer.Option(prompt=True)], + end_date: Annotated[datetime.datetime, typer.Option(prompt=True)], + sku: Annotated[str, typer.Option(prompt=True)] +): + controllers.generate_sku_report(start_date, end_date, sku) + @app.command() def generate_um_report( start_date: Annotated[datetime.datetime, typer.Option(prompt=True)], diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index cc09119..38bac4b 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -38,7 +38,7 @@ def check_health(): def generate_um_report( - start_date: datetime.datetime, end_date: datetime.datetime + start_date: datetime.datetime, end_date: datetime.datetime ) -> None: logger.info(f"Fetching orders between {start_date} and {end_date}.") @@ -98,7 +98,8 @@ def generate_um_report( "units_sold": order.units_of_sku(relevant_sku), "eur_income": order.sales_of_sku(relevant_sku), "sats_income": order.sats_received_for_sku(relevant_sku), - "sats_owed_to_um": order.sats_received_for_sku(relevant_sku) * um_first_agreement_percentage, + "sats_owed_to_um": order.sats_received_for_sku(relevant_sku) + * um_first_agreement_percentage, } ) logger.info("Report generated.") @@ -106,7 +107,135 @@ def generate_um_report( keys = report[0].keys() - with open('report.csv', 'w', newline='') as output_file: + with open("report.csv", "w", newline="") as output_file: + dict_writer = csv.DictWriter(output_file, keys) + dict_writer.writeheader() + dict_writer.writerows(report) + + +def generate_um_report( + start_date: datetime.datetime, end_date: datetime.datetime +) -> None: + logger.info(f"Fetching orders between {start_date} and {end_date}.") + + orders_in_date_range = WC_API.get( + endpoint="orders", + params={ + "after": start_date.isoformat(), + "before": end_date.isoformat(), + "per_page": 100, + "status": "processing,completed", + }, + ).json() + orders_in_date_range = Orders( + [ + Order.from_api_response(order_raw_data) + for order_raw_data in orders_in_date_range + ] + ) + logger.info(f"Received {len(orders_in_date_range)} orders.") + + relevant_sku = "TEE-05-BBO-BLACK" + logger.info(f"Filtering by SKU: {relevant_sku}") + relevant_orders = orders_in_date_range.filter_orders_by_sku(sku=relevant_sku) + logger.info(f"Kept {len(relevant_orders)} orders.") + + logger.info("Checking if all orders have the sats_received entry filled in.") + orders_without_sats_received = relevant_orders.filter_orders_without_sats_received() + if orders_without_sats_received: + logger.warning( + f"There are {len(orders_without_sats_received)} orders without a properly filled sats_received entry." + ) + logger.warning(f"See details below.") + logger.warning(orders_without_sats_received) + raise ValueError( + "Not all orders have sats_received. Can't compute sats owed without that." + ) + logger.info("Success, all orders have sats_received filled in.") + + logger.info("Removing settled orders.") + unsettled_orders = relevant_orders.filter_unsettled_orders() + logger.info(f"Kept {len(unsettled_orders)} unsettled orders.") + # Fetch orders: + # - Between specific dates + # - That contain the hardcoded products + # - That have been paid, hence status is either processing or completed + # - That have not been settled yet (is_settled_with_um = 0) + logger.info("Order filtering finished.") + + logger.info(f"Relevant orders: {[order['id'] for order in unsettled_orders]}.") + + report = [] + for order in unsettled_orders: + report.append( + { + "order_id": order["id"], + "sku": relevant_sku, + "units_sold": order.units_of_sku(relevant_sku), + "eur_income": order.sales_of_sku(relevant_sku), + "sats_income": order.sats_received_for_sku(relevant_sku), + "sats_owed_to_um": order.sats_received_for_sku(relevant_sku) + * um_first_agreement_percentage, + } + ) + logger.info("Report generated.") + logger.info(report) + + keys = report[0].keys() + + with open("report.csv", "w", newline="") as output_file: + dict_writer = csv.DictWriter(output_file, keys) + dict_writer.writeheader() + dict_writer.writerows(report) + + +def generate_sku_report(start_date, end_date, sku): + logger.info(f"Fetching orders between {start_date} and {end_date}.") + + orders_in_date_range = WC_API.get( + endpoint="orders", + params={ + "after": start_date.isoformat(), + "before": end_date.isoformat(), + "per_page": 100, + "status": "processing,completed", + }, + ).json() + orders_in_date_range = Orders( + [ + Order.from_api_response(order_raw_data) + for order_raw_data in orders_in_date_range + ] + ) + logger.info(f"Received {len(orders_in_date_range)} orders.") + + logger.info(f"Filtering by SKU: {sku}") + relevant_orders = orders_in_date_range.filter_orders_by_sku(sku=sku) + logger.info(f"Kept {len(relevant_orders)} orders.") + + logger.info("Removing settled orders.") + unsettled_orders = relevant_orders.filter_unsettled_orders() + logger.info(f"Kept {len(unsettled_orders)} unsettled orders.") + logger.info("Order filtering finished.") + + logger.info(f"Relevant orders: {[order['id'] for order in unsettled_orders]}.") + + report = [] + for order in unsettled_orders: + report.append( + { + "order_id": order["id"], + "sku": sku, + "units_sold": order.units_of_sku(sku), + "eur_income": order.sales_of_sku(sku), + } + ) + logger.info("Report generated.") + logger.info(report) + + keys = report[0].keys() + + with open("report.csv", "w", newline="") as output_file: dict_writer = csv.DictWriter(output_file, keys) dict_writer.writeheader() dict_writer.writerows(report) From 1f0f66a64621372b7144be64f1b6be40f43e1107 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 3 Aug 2023 23:29:22 +0200 Subject: [PATCH 19/27] Clean up a bit --- README.md | 5 +- camisatoshi_wordpress_reports/controllers.py | 113 ++++--------------- 2 files changed, 25 insertions(+), 93 deletions(-) diff --git a/README.md b/README.md index 46d5094..154dd5a 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,4 @@ This repository hosts a Python CLI app that can be used to generate reports by f # Open issues -- Clean up controller -- Better output -- Update readme with instructions -- Sit down and write process in camisatoshi docs to clarify how much of this process should be done by the script and what belongs to the operator. \ No newline at end of file +- Pagination is not being managed. The moment we have more than 100 orders, we are gonna run into issues. \ No newline at end of file diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index 38bac4b..2dbeba1 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -7,7 +7,9 @@ from dotenv import dotenv_values from woocommerce import API from camisatoshi_wordpress_reports.order import Order, Orders -from camisatoshi_wordpress_reports.constants import um_first_agreement_percentage +from camisatoshi_wordpress_reports.constants import ( + um_first_agreement_percentage, +) API_CONFIG = dotenv_values( dotenv_path=Path.home() / Path(".camisatoshi-wordpress-reports/.env") @@ -24,12 +26,16 @@ logger = logging.getLogger() def check_health(): - logger.info(f"Connecting to the configured WooCommerce at {API_CONFIG['URL']}") + logger.info( + f"Connecting to the configured WooCommerce at {API_CONFIG['URL']}" + ) try: api_reported_version = WC_API.get("").json()["namespace"] except: - raise ConnectionError("There was an issue connecting to the WooCommerce API.") + raise ConnectionError( + "There was an issue connecting to the WooCommerce API." + ) logger.info(f"Informed version of the API: {API_CONFIG['VERSION']}") logger.info(f"Version reported by the API itself: {api_reported_version}") @@ -61,87 +67,17 @@ def generate_um_report( relevant_sku = "TEE-05-BBO-BLACK" logger.info(f"Filtering by SKU: {relevant_sku}") - relevant_orders = orders_in_date_range.filter_orders_by_sku(sku=relevant_sku) - logger.info(f"Kept {len(relevant_orders)} orders.") - - logger.info("Checking if all orders have the sats_received entry filled in.") - orders_without_sats_received = relevant_orders.filter_orders_without_sats_received() - if orders_without_sats_received: - logger.warning( - f"There are {len(orders_without_sats_received)} orders without a properly filled sats_received entry." - ) - logger.warning(f"See details below.") - logger.warning(orders_without_sats_received) - raise ValueError( - "Not all orders have sats_received. Can't compute sats owed without that." - ) - logger.info("Success, all orders have sats_received filled in.") - - logger.info("Removing settled orders.") - unsettled_orders = relevant_orders.filter_unsettled_orders() - logger.info(f"Kept {len(unsettled_orders)} unsettled orders.") - # Fetch orders: - # - Between specific dates - # - That contain the hardcoded products - # - That have been paid, hence status is either processing or completed - # - That have not been settled yet (is_settled_with_um = 0) - logger.info("Order filtering finished.") - - logger.info(f"Relevant orders: {[order['id'] for order in unsettled_orders]}.") - - report = [] - for order in unsettled_orders: - report.append( - { - "order_id": order["id"], - "sku": relevant_sku, - "units_sold": order.units_of_sku(relevant_sku), - "eur_income": order.sales_of_sku(relevant_sku), - "sats_income": order.sats_received_for_sku(relevant_sku), - "sats_owed_to_um": order.sats_received_for_sku(relevant_sku) - * um_first_agreement_percentage, - } - ) - logger.info("Report generated.") - logger.info(report) - - keys = report[0].keys() - - with open("report.csv", "w", newline="") as output_file: - dict_writer = csv.DictWriter(output_file, keys) - dict_writer.writeheader() - dict_writer.writerows(report) - - -def generate_um_report( - start_date: datetime.datetime, end_date: datetime.datetime -) -> None: - logger.info(f"Fetching orders between {start_date} and {end_date}.") - - orders_in_date_range = WC_API.get( - endpoint="orders", - params={ - "after": start_date.isoformat(), - "before": end_date.isoformat(), - "per_page": 100, - "status": "processing,completed", - }, - ).json() - orders_in_date_range = Orders( - [ - Order.from_api_response(order_raw_data) - for order_raw_data in orders_in_date_range - ] + relevant_orders = orders_in_date_range.filter_orders_by_sku( + sku=relevant_sku ) - logger.info(f"Received {len(orders_in_date_range)} orders.") - - relevant_sku = "TEE-05-BBO-BLACK" - logger.info(f"Filtering by SKU: {relevant_sku}") - relevant_orders = orders_in_date_range.filter_orders_by_sku(sku=relevant_sku) logger.info(f"Kept {len(relevant_orders)} orders.") - logger.info("Checking if all orders have the sats_received entry filled in.") - orders_without_sats_received = relevant_orders.filter_orders_without_sats_received() + logger.info( + "Checking if all orders have the sats_received entry filled in." + ) + orders_without_sats_received = ( + relevant_orders.filter_orders_without_sats_received() + ) if orders_without_sats_received: logger.warning( f"There are {len(orders_without_sats_received)} orders without a properly filled sats_received entry." @@ -156,15 +92,12 @@ def generate_um_report( logger.info("Removing settled orders.") unsettled_orders = relevant_orders.filter_unsettled_orders() logger.info(f"Kept {len(unsettled_orders)} unsettled orders.") - # Fetch orders: - # - Between specific dates - # - That contain the hardcoded products - # - That have been paid, hence status is either processing or completed - # - That have not been settled yet (is_settled_with_um = 0) + logger.info("Order filtering finished.") - logger.info(f"Relevant orders: {[order['id'] for order in unsettled_orders]}.") - + logger.info( + f"Relevant orders: {[order['id'] for order in unsettled_orders]}." + ) report = [] for order in unsettled_orders: report.append( @@ -218,7 +151,9 @@ def generate_sku_report(start_date, end_date, sku): logger.info(f"Kept {len(unsettled_orders)} unsettled orders.") logger.info("Order filtering finished.") - logger.info(f"Relevant orders: {[order['id'] for order in unsettled_orders]}.") + logger.info( + f"Relevant orders: {[order['id'] for order in unsettled_orders]}." + ) report = [] for order in unsettled_orders: From 0c50e9b86e3d8e47d2c170dcead39dab9fb5f5a5 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 4 Aug 2023 00:09:05 +0200 Subject: [PATCH 20/27] Fix string for meta_data key --- camisatoshi_wordpress_reports/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/camisatoshi_wordpress_reports/constants.py b/camisatoshi_wordpress_reports/constants.py index 92b0993..67181ed 100644 --- a/camisatoshi_wordpress_reports/constants.py +++ b/camisatoshi_wordpress_reports/constants.py @@ -13,7 +13,7 @@ order_keys.line_item_keys.quantity = "quantity" order_keys.line_item_keys.total = "total" custom_meta_data_keys = SimpleNamespace() -custom_meta_data_keys.is_settled_with_um = "is_settled_with_um" +custom_meta_data_keys.is_settled_with_um = "is_settled_um" custom_meta_data_keys.sats_received = "sats_received" From 88b7b290002e18d65dcc49034689cdb2a470c607 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 4 Aug 2023 00:09:53 +0200 Subject: [PATCH 21/27] Fix string for meta_data key --- camisatoshi_wordpress_reports/constants.py | 2 +- camisatoshi_wordpress_reports/order.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/camisatoshi_wordpress_reports/constants.py b/camisatoshi_wordpress_reports/constants.py index 67181ed..fcd32ad 100644 --- a/camisatoshi_wordpress_reports/constants.py +++ b/camisatoshi_wordpress_reports/constants.py @@ -13,7 +13,7 @@ order_keys.line_item_keys.quantity = "quantity" order_keys.line_item_keys.total = "total" custom_meta_data_keys = SimpleNamespace() -custom_meta_data_keys.is_settled_with_um = "is_settled_um" +custom_meta_data_keys.is_settled_um = "is_settled_um" custom_meta_data_keys.sats_received = "sats_received" diff --git a/camisatoshi_wordpress_reports/order.py b/camisatoshi_wordpress_reports/order.py index 8610e6f..12c555a 100644 --- a/camisatoshi_wordpress_reports/order.py +++ b/camisatoshi_wordpress_reports/order.py @@ -59,7 +59,7 @@ class Order: def is_settled_with_um(self): is_settled = self.meta_data_entries.get( - custom_meta_data_keys.is_settled_with_um, None + custom_meta_data_keys.is_settled_um, None ) return bool(is_settled) @@ -110,7 +110,7 @@ class Orders: unsettled_orders = [] for order in self: - if not order.is_settled_with_um(): + if not order.is_settled_um(): unsettled_orders.append(order) return Orders(unsettled_orders) From 54fa31f9888973d967790632f7d2dda3f7cd0c49 Mon Sep 17 00:00:00 2001 From: counterweight Date: Sun, 3 Sep 2023 05:05:22 +0200 Subject: [PATCH 22/27] Fixed bad method call --- camisatoshi_wordpress_reports/order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/camisatoshi_wordpress_reports/order.py b/camisatoshi_wordpress_reports/order.py index 12c555a..a12665e 100644 --- a/camisatoshi_wordpress_reports/order.py +++ b/camisatoshi_wordpress_reports/order.py @@ -110,7 +110,7 @@ class Orders: unsettled_orders = [] for order in self: - if not order.is_settled_um(): + if not order.is_settled_with_um(): unsettled_orders.append(order) return Orders(unsettled_orders) From 99a63b9ba9d439c63ef59674eecef8b1c6f19b13 Mon Sep 17 00:00:00 2001 From: counterweight Date: Sun, 3 Sep 2023 05:15:26 +0200 Subject: [PATCH 23/27] Included some stuff in the readme --- README.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 154dd5a..caaa186 100644 --- a/README.md +++ b/README.md @@ -10,5 +10,31 @@ This repository hosts a Python CLI app that can be used to generate reports by f 3. Copy the provided `.env-example` file in that directory and fill it with the required params from Woocomerce. -# Open issues +## How to run + +### Check that the API is reachable + +```shell +camisatoshi-wordpress-reports check-health +``` + +### Make a report for the UM agreement between two dates + +```shell +camisatoshi-wordpress-reports generate-um-report --start-date "2023-08-01T00:00:00" --end-date "2023-09-01T00:00:00"c +``` + +This will generate a file named `report.csv` in the current working directory. + + +### Make a simple report for the sales of some SKU + +```shell +camisatoshi-wordpress-reports generate-sku-report --start-date "2023-08-01T00:00:00" --end-date "2023-09-01T00:00:00" --sku TEE-05-BBO-BLACK +``` + +This will generate a file named `report.csv` in the current working directory. + + +## Open issues - Pagination is not being managed. The moment we have more than 100 orders, we are gonna run into issues. \ No newline at end of file From 7fff27893b3acda984182986207d69c5e0b0da3e Mon Sep 17 00:00:00 2001 From: counterweight Date: Sun, 3 Sep 2023 05:16:36 +0200 Subject: [PATCH 24/27] Remove unsettled filtered in SKU report --- camisatoshi_wordpress_reports/controllers.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index 2dbeba1..57325d3 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -146,9 +146,6 @@ def generate_sku_report(start_date, end_date, sku): relevant_orders = orders_in_date_range.filter_orders_by_sku(sku=sku) logger.info(f"Kept {len(relevant_orders)} orders.") - logger.info("Removing settled orders.") - unsettled_orders = relevant_orders.filter_unsettled_orders() - logger.info(f"Kept {len(unsettled_orders)} unsettled orders.") logger.info("Order filtering finished.") logger.info( From b00a95cee5ea6a38aa6471f9ce6cd0ea737f116d Mon Sep 17 00:00:00 2001 From: counterweight Date: Sun, 3 Sep 2023 05:16:59 +0200 Subject: [PATCH 25/27] Rename --- camisatoshi_wordpress_reports/controllers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index 57325d3..c46023e 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -149,11 +149,11 @@ def generate_sku_report(start_date, end_date, sku): logger.info("Order filtering finished.") logger.info( - f"Relevant orders: {[order['id'] for order in unsettled_orders]}." + f"Relevant orders: {[order['id'] for order in relevant_orders]}." ) report = [] - for order in unsettled_orders: + for order in relevant_orders: report.append( { "order_id": order["id"], From b966559ff095fa98e278f3633f88eaaee101f041 Mon Sep 17 00:00:00 2001 From: counterweight Date: Sun, 3 Sep 2023 05:20:31 +0200 Subject: [PATCH 26/27] Moved hardcode to constants --- camisatoshi_wordpress_reports/constants.py | 7 ++++++- camisatoshi_wordpress_reports/controllers.py | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/camisatoshi_wordpress_reports/constants.py b/camisatoshi_wordpress_reports/constants.py index fcd32ad..2bb1a9c 100644 --- a/camisatoshi_wordpress_reports/constants.py +++ b/camisatoshi_wordpress_reports/constants.py @@ -1,5 +1,9 @@ from types import SimpleNamespace +### Config + +DEFAULT_DOTENV_FILEPATH = ".camisatoshi-wordpress-reports/.env" + ### Order keys order_keys = SimpleNamespace() @@ -20,4 +24,5 @@ custom_meta_data_keys.sats_received = "sats_received" ### Other um_first_agreement_percentage = 0.5 -bbo_royalty_fee = 0.2 \ No newline at end of file +bbo_royalty_fee = 0.2 + diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index c46023e..300794f 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -8,11 +8,11 @@ from woocommerce import API from camisatoshi_wordpress_reports.order import Order, Orders from camisatoshi_wordpress_reports.constants import ( - um_first_agreement_percentage, + um_first_agreement_percentage, DEFAULT_DOTENV_FILEPATH, ) API_CONFIG = dotenv_values( - dotenv_path=Path.home() / Path(".camisatoshi-wordpress-reports/.env") + dotenv_path=Path.home() / Path(DEFAULT_DOTENV_FILEPATH) ) WC_API = API( url=API_CONFIG["URL"], From a3af630c1f362377b029a194736356c259c587be Mon Sep 17 00:00:00 2001 From: counterweight Date: Tue, 5 Sep 2023 10:15:01 +0200 Subject: [PATCH 27/27] Properly compute amount owed to UM --- camisatoshi_wordpress_reports/controllers.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index 300794f..51f2f13 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -8,7 +8,9 @@ from woocommerce import API from camisatoshi_wordpress_reports.order import Order, Orders from camisatoshi_wordpress_reports.constants import ( - um_first_agreement_percentage, DEFAULT_DOTENV_FILEPATH, + um_first_agreement_percentage, + DEFAULT_DOTENV_FILEPATH, + bbo_royalty_fee, ) API_CONFIG = dotenv_values( @@ -107,7 +109,10 @@ def generate_um_report( "units_sold": order.units_of_sku(relevant_sku), "eur_income": order.sales_of_sku(relevant_sku), "sats_income": order.sats_received_for_sku(relevant_sku), - "sats_owed_to_um": order.sats_received_for_sku(relevant_sku) + "sats_owed_to_um": ( + order.sats_received_for_sku(relevant_sku) + * (1 - bbo_royalty_fee) + ) * um_first_agreement_percentage, } )