From f3776966bc2266cf5145daa072a2db920bb80065 Mon Sep 17 00:00:00 2001 From: counterweight Date: Tue, 5 Sep 2023 11:47:38 +0200 Subject: [PATCH 01/13] A lot of work --- camisatoshi_wordpress_reports/controllers.py | 47 ++++++ camisatoshi_wordpress_reports/order.py | 9 ++ .../report_building.py | 148 ++++++++++++++++++ 3 files changed, 204 insertions(+) create mode 100644 camisatoshi_wordpress_reports/report_building.py diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index 51f2f13..7a27aa8 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -1,3 +1,4 @@ +from functools import partial from pathlib import Path import datetime import logging @@ -12,6 +13,7 @@ from camisatoshi_wordpress_reports.constants import ( DEFAULT_DOTENV_FILEPATH, bbo_royalty_fee, ) +from camisatoshi_wordpress_reports.report_building import ReportChainBuilder, WoocomerceOrderScope, keep_orders_containing_sku API_CONFIG = dotenv_values( dotenv_path=Path.home() / Path(DEFAULT_DOTENV_FILEPATH) @@ -176,3 +178,48 @@ def generate_sku_report(start_date, end_date, sku): dict_writer = csv.DictWriter(output_file, keys) dict_writer.writeheader() dict_writer.writerows(report) + + +def wip_generate_sku_report(start_date, end_date, sku): + logger.info(f"Fetching orders between {start_date} and {end_date}.") + + report_chain_builder = ReportChainBuilder() + + report_chain_builder.add_order_fetching_step( + wc_order_scope=WoocomerceOrderScope( + after=start_date.isoformat(), + before=end_date.isoformat(), + status="processing,completed" + ) + ) + + report_chain_builder.add_order_filtering_step( + partial( + keep_orders_containing_sku, + sku=sku + ) + ) + + report_chain = report_chain_builder.get_report_chain() + + relevant_orders = report_chain.run_chain(WC_API) + + report = [] + for order in relevant_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) diff --git a/camisatoshi_wordpress_reports/order.py b/camisatoshi_wordpress_reports/order.py index a12665e..fd693e9 100644 --- a/camisatoshi_wordpress_reports/order.py +++ b/camisatoshi_wordpress_reports/order.py @@ -114,3 +114,12 @@ class Orders: unsettled_orders.append(order) return Orders(unsettled_orders) + + def filter_orders_by_metadata_value(self, key, value): + orders_with_metadata_value = [] + + for order in self: + if order.contains_meta_data_entry(key) and order.meta_data_entries[key] == value: + orders_with_metadata_value.append(order) + + return Orders(orders_with_metadata_value) diff --git a/camisatoshi_wordpress_reports/report_building.py b/camisatoshi_wordpress_reports/report_building.py new file mode 100644 index 0000000..568b5a4 --- /dev/null +++ b/camisatoshi_wordpress_reports/report_building.py @@ -0,0 +1,148 @@ +from functools import partial + +from woocommerce import API + +from camisatoshi_wordpress_reports.order import Orders, Order + + +class MetadataFilter: + def __init__(self, key, value, operator): + self.key = key + self.value = value + self.operator = operator + + def order_satisfies_filter(self, order: Order) -> bool: + return self.operator(order.meta_data_entries[self.key], self.value) + + +class WoocomerceOrderScope: + + serializable_fields = ("after", "before", "status") + + def __init__(self, after=None, before=None, status=None): + self.after = after + self.before = before + self.status = status + + def serialize_for_api_request(self) -> dict: + params = {} + + for field in self.serializable_fields: + if hasattr(self, field): + params[field] = self.__getattribute__(field) + + params["per_page"] = 100 + + return params + + +def fetch_orders_from_wc( + wc_api_client: API, wc_order_scope: WoocomerceOrderScope +) -> Orders: + + params = wc_order_scope.serialize_for_api_request() + + orders_in_scope = wc_api_client.get( + endpoint="orders", params=params + ).json() + orders_in_scope = Orders( + [ + Order.from_api_response(order_raw_data) + for order_raw_data in orders_in_scope + ] + ) + + return orders_in_scope + + +def keep_orders_containing_sku(orders: Orders, sku: str) -> Orders: + + return orders.filter_orders_by_sku(sku) + + +def keep_orders_containing_metadata_value( + orders: Orders, metadata_key: str, metadata_value +) -> Orders: + + return orders.filter_orders_by_metadata_value( + key=metadata_key, value=metadata_value + ) + + +def validate_orders_satisfy_metadata_filter( + orders: Orders, metadata_filter: MetadataFilter +) -> bool: + + for order in orders: + if not metadata_filter.order_satisfies_filter(order): + return False + + return True + + +class ReportChain: + def __init__(self): + self.order_fetching_step = None + self.order_filtering_steps = None + self.order_validation_steps = None + self.transformation_steps = None + self.storage_steps = None + + self._orders = None + + def run_chain(self, wc_api_client: API): + self._run_order_fetching_step(wc_api_client) + self._run_order_filtering_steps() + self._run_order_validation_steps() + + def _run_order_fetching_step(self, wc_api_client: API) -> Orders: + self._orders = self.order_fetching_step(wc_api_client) + + def _run_order_filtering_steps(self) -> Orders: + temp_orders = self._orders + + for order_filtering_step in self.order_filtering_steps: + temp_orders = order_filtering_step(temp_orders) + + return temp_orders + + def _run_order_validation_steps(self): + for order_validation_step in self.order_validation_steps: + if not order_validation_step(self._orders): + raise ValueError("Error during order validation step.") + + +class ReportChainBuilder: + def __init__(self): + self._wip_report_chain = ReportChain() + + def add_order_fetching_step( + self, wc_order_scope: WoocomerceOrderScope + ) -> "ReportChainBuilder": + """ + Define a scope of orders to get from Woocomerce. + """ + + self._order_scope = wc_order_scope + + self._wip_report_chain.order_fetching_step = partial( + fetch_orders_from_wc, wc_order_scope=wc_order_scope + ) + + return self + + def add_order_filtering_step(self, step) -> "ReportChainBuilder": + + self._wip_report_chain.order_filtering_steps.append(step) + + return self + + def add_order_validation_step(self, step) -> "ReportChainBuilder": + + self._wip_report_chain.order_validation_steps.append(step) + + return self + + def get_report_chain(self) -> ReportChain: + + return self._wip_report_chain From 83743ca5268aed103ca93792151a3f6caafbbe5b Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 6 Sep 2023 14:20:28 +0200 Subject: [PATCH 02/13] Managed to get the sku doing the same thing as the previous code. --- camisatoshi_wordpress_reports/cli.py | 5 +++- camisatoshi_wordpress_reports/controllers.py | 14 +++++------ camisatoshi_wordpress_reports/logging.py | 4 +++ camisatoshi_wordpress_reports/order.py | 8 ++++++ .../report_building.py | 25 +++++++++++++++---- 5 files changed, 43 insertions(+), 13 deletions(-) create mode 100644 camisatoshi_wordpress_reports/logging.py diff --git a/camisatoshi_wordpress_reports/cli.py b/camisatoshi_wordpress_reports/cli.py index dd6ea12..f159fda 100644 --- a/camisatoshi_wordpress_reports/cli.py +++ b/camisatoshi_wordpress_reports/cli.py @@ -5,7 +5,10 @@ import typer from typing_extensions import Annotated import camisatoshi_wordpress_reports.controllers as controllers +import camisatoshi_wordpress_reports.logging as our_logging + +our_logging.set_config_level() app = typer.Typer() @@ -20,7 +23,7 @@ def generate_sku_report( 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) + controllers.wip_generate_sku_report(start_date, end_date, sku) @app.command() def generate_um_report( diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index 7a27aa8..f775e79 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -13,7 +13,11 @@ from camisatoshi_wordpress_reports.constants import ( DEFAULT_DOTENV_FILEPATH, bbo_royalty_fee, ) -from camisatoshi_wordpress_reports.report_building import ReportChainBuilder, WoocomerceOrderScope, keep_orders_containing_sku +from camisatoshi_wordpress_reports.report_building import ( + ReportChainBuilder, + WoocomerceOrderScope, + keep_orders_containing_sku, +) API_CONFIG = dotenv_values( dotenv_path=Path.home() / Path(DEFAULT_DOTENV_FILEPATH) @@ -25,7 +29,6 @@ WC_API = API( version=API_CONFIG["VERSION"], ) -logging.basicConfig(level=logging.INFO) logger = logging.getLogger() @@ -189,15 +192,12 @@ def wip_generate_sku_report(start_date, end_date, sku): wc_order_scope=WoocomerceOrderScope( after=start_date.isoformat(), before=end_date.isoformat(), - status="processing,completed" + status="processing,completed", ) ) report_chain_builder.add_order_filtering_step( - partial( - keep_orders_containing_sku, - sku=sku - ) + partial(keep_orders_containing_sku, sku=sku) ) report_chain = report_chain_builder.get_report_chain() diff --git a/camisatoshi_wordpress_reports/logging.py b/camisatoshi_wordpress_reports/logging.py new file mode 100644 index 0000000..5d4835b --- /dev/null +++ b/camisatoshi_wordpress_reports/logging.py @@ -0,0 +1,4 @@ +import logging + +def set_config_level(): + logging.basicConfig(level=logging.DEBUG) \ No newline at end of file diff --git a/camisatoshi_wordpress_reports/order.py b/camisatoshi_wordpress_reports/order.py index fd693e9..d2182af 100644 --- a/camisatoshi_wordpress_reports/order.py +++ b/camisatoshi_wordpress_reports/order.py @@ -1,9 +1,12 @@ +import logging 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 +logger = logging.getLogger() + class Order: def __init__(self, raw_data: Dict): self.raw_data = raw_data @@ -47,9 +50,12 @@ class Order: return sats_received_for_sku def contains_sku(self, sku: str) -> bool: + logger.debug(f"Checking if order {self['id']} contains sku {sku}.") for item in self[order_keys.line_items]: if item[order_keys.line_item_keys.sku] == sku: + logger.debug(f"It does.") return True + logger.debug("It doesn't.") return False def contains_meta_data_entry(self, meta_data_entry_key: str) -> bool: @@ -94,6 +100,8 @@ class Orders: if order.contains_sku(sku): filtered_orders.append(order) + logger.debug(f"Finished filtering. I have {len(filtered_orders)} orders.") + return Orders(filtered_orders) def filter_orders_without_sats_received(self) -> "Orders": diff --git a/camisatoshi_wordpress_reports/report_building.py b/camisatoshi_wordpress_reports/report_building.py index 568b5a4..37971c8 100644 --- a/camisatoshi_wordpress_reports/report_building.py +++ b/camisatoshi_wordpress_reports/report_building.py @@ -1,3 +1,4 @@ +import logging from functools import partial from woocommerce import API @@ -5,6 +6,9 @@ from woocommerce import API from camisatoshi_wordpress_reports.order import Orders, Order +logger = logging.getLogger() + + class MetadataFilter: def __init__(self, key, value, operator): self.key = key @@ -83,28 +87,39 @@ def validate_orders_satisfy_metadata_filter( class ReportChain: def __init__(self): self.order_fetching_step = None - self.order_filtering_steps = None - self.order_validation_steps = None - self.transformation_steps = None - self.storage_steps = None + self.order_filtering_steps = [] + self.order_validation_steps = [] self._orders = None def run_chain(self, wc_api_client: API): + logger.info("Running report chain.") + self._run_order_fetching_step(wc_api_client) self._run_order_filtering_steps() self._run_order_validation_steps() + logger.info(f"Report chain ran. Returning {len(self._orders)} orders.") + + return self._orders + def _run_order_fetching_step(self, wc_api_client: API) -> Orders: + logger.debug("Running order fetching step.") self._orders = self.order_fetching_step(wc_api_client) + logger.debug(f"Received {self._orders} orders.") def _run_order_filtering_steps(self) -> Orders: + logger.debug( + f"Running {len(self.order_filtering_steps)} order filtering steps." + ) temp_orders = self._orders for order_filtering_step in self.order_filtering_steps: + logger.debug(f"{len(temp_orders)} before filtering.") temp_orders = order_filtering_step(temp_orders) + logger.debug(f"{len(temp_orders)} after filtering.") - return temp_orders + self._orders = temp_orders def _run_order_validation_steps(self): for order_validation_step in self.order_validation_steps: From 2728ad2aaecb58b80626dc600a9f3a11c242b24e Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 6 Sep 2023 14:21:10 +0200 Subject: [PATCH 03/13] Managed to get the sku doing the same thing as the previous code. --- camisatoshi_wordpress_reports/cli.py | 2 +- camisatoshi_wordpress_reports/controllers.py | 52 -------------------- 2 files changed, 1 insertion(+), 53 deletions(-) diff --git a/camisatoshi_wordpress_reports/cli.py b/camisatoshi_wordpress_reports/cli.py index f159fda..2187be9 100644 --- a/camisatoshi_wordpress_reports/cli.py +++ b/camisatoshi_wordpress_reports/cli.py @@ -23,7 +23,7 @@ def generate_sku_report( end_date: Annotated[datetime.datetime, typer.Option(prompt=True)], sku: Annotated[str, typer.Option(prompt=True)] ): - controllers.wip_generate_sku_report(start_date, end_date, sku) + controllers.generate_sku_report(start_date, end_date, sku) @app.command() def generate_um_report( diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index f775e79..1b5bc30 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -131,61 +131,9 @@ def generate_um_report( 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("Order filtering finished.") - - logger.info( - f"Relevant orders: {[order['id'] for order in relevant_orders]}." - ) - - report = [] - for order in relevant_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) - - -def wip_generate_sku_report(start_date, end_date, sku): - logger.info(f"Fetching orders between {start_date} and {end_date}.") - report_chain_builder = ReportChainBuilder() report_chain_builder.add_order_fetching_step( From 9917bb71e897113253c720e7bc21767e99683c59 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 6 Sep 2023 14:23:41 +0200 Subject: [PATCH 04/13] Minor logging stuff. --- camisatoshi_wordpress_reports/order.py | 25 +++++++++++-------- .../report_building.py | 2 +- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/camisatoshi_wordpress_reports/order.py b/camisatoshi_wordpress_reports/order.py index d2182af..edce067 100644 --- a/camisatoshi_wordpress_reports/order.py +++ b/camisatoshi_wordpress_reports/order.py @@ -1,12 +1,13 @@ import logging from typing import Dict, Collection -from camisatoshi_wordpress_reports.constants import order_keys, custom_meta_data_keys +from camisatoshi_wordpress_reports.constants import ( + order_keys, + custom_meta_data_keys, +) from camisatoshi_wordpress_reports.utils import safe_zero_division -logger = logging.getLogger() - class Order: def __init__(self, raw_data: Dict): self.raw_data = raw_data @@ -39,7 +40,9 @@ class Order: 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) + 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] ) @@ -50,12 +53,9 @@ class Order: return sats_received_for_sku def contains_sku(self, sku: str) -> bool: - logger.debug(f"Checking if order {self['id']} contains sku {sku}.") for item in self[order_keys.line_items]: if item[order_keys.line_item_keys.sku] == sku: - logger.debug(f"It does.") return True - logger.debug("It doesn't.") return False def contains_meta_data_entry(self, meta_data_entry_key: str) -> bool: @@ -100,15 +100,15 @@ class Orders: if order.contains_sku(sku): filtered_orders.append(order) - logger.debug(f"Finished filtering. I have {len(filtered_orders)} orders.") - return Orders(filtered_orders) def filter_orders_without_sats_received(self) -> "Orders": orders_without_sats_received = [] for order in self: - if not order.contains_meta_data_entry(custom_meta_data_keys.sats_received): + if not order.contains_meta_data_entry( + custom_meta_data_keys.sats_received + ): orders_without_sats_received.append(order) continue @@ -127,7 +127,10 @@ class Orders: orders_with_metadata_value = [] for order in self: - if order.contains_meta_data_entry(key) and order.meta_data_entries[key] == value: + if ( + order.contains_meta_data_entry(key) + and order.meta_data_entries[key] == value + ): orders_with_metadata_value.append(order) return Orders(orders_with_metadata_value) diff --git a/camisatoshi_wordpress_reports/report_building.py b/camisatoshi_wordpress_reports/report_building.py index 37971c8..293a355 100644 --- a/camisatoshi_wordpress_reports/report_building.py +++ b/camisatoshi_wordpress_reports/report_building.py @@ -106,7 +106,7 @@ class ReportChain: def _run_order_fetching_step(self, wc_api_client: API) -> Orders: logger.debug("Running order fetching step.") self._orders = self.order_fetching_step(wc_api_client) - logger.debug(f"Received {self._orders} orders.") + logger.debug(f"Received {len(self._orders)} orders.") def _run_order_filtering_steps(self) -> Orders: logger.debug( From 5840a85dedcab8cf3f9ac081dd9532f9c60115b3 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 6 Sep 2023 14:26:25 +0200 Subject: [PATCH 05/13] Some renaming --- camisatoshi_wordpress_reports/controllers.py | 4 ++-- .../report_building.py | 22 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index 1b5bc30..2073ec9 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -14,7 +14,7 @@ from camisatoshi_wordpress_reports.constants import ( bbo_royalty_fee, ) from camisatoshi_wordpress_reports.report_building import ( - ReportChainBuilder, + OrderObtentionChainBuilder, WoocomerceOrderScope, keep_orders_containing_sku, ) @@ -134,7 +134,7 @@ def generate_um_report( def generate_sku_report(start_date, end_date, sku): logger.info(f"Fetching orders between {start_date} and {end_date}.") - report_chain_builder = ReportChainBuilder() + report_chain_builder = OrderObtentionChainBuilder() report_chain_builder.add_order_fetching_step( wc_order_scope=WoocomerceOrderScope( diff --git a/camisatoshi_wordpress_reports/report_building.py b/camisatoshi_wordpress_reports/report_building.py index 293a355..72f406a 100644 --- a/camisatoshi_wordpress_reports/report_building.py +++ b/camisatoshi_wordpress_reports/report_building.py @@ -84,7 +84,7 @@ def validate_orders_satisfy_metadata_filter( return True -class ReportChain: +class OrderObtentionChain: def __init__(self): self.order_fetching_step = None self.order_filtering_steps = [] @@ -127,37 +127,37 @@ class ReportChain: raise ValueError("Error during order validation step.") -class ReportChainBuilder: +class OrderObtentionChainBuilder: def __init__(self): - self._wip_report_chain = ReportChain() + self._wip_order_obtention_chain = OrderObtentionChain() def add_order_fetching_step( self, wc_order_scope: WoocomerceOrderScope - ) -> "ReportChainBuilder": + ) -> "OrderObtentionChainBuilder": """ Define a scope of orders to get from Woocomerce. """ self._order_scope = wc_order_scope - self._wip_report_chain.order_fetching_step = partial( + self._wip_order_obtention_chain.order_fetching_step = partial( fetch_orders_from_wc, wc_order_scope=wc_order_scope ) return self - def add_order_filtering_step(self, step) -> "ReportChainBuilder": + def add_order_filtering_step(self, step) -> "OrderObtentionChainBuilder": - self._wip_report_chain.order_filtering_steps.append(step) + self._wip_order_obtention_chain.order_filtering_steps.append(step) return self - def add_order_validation_step(self, step) -> "ReportChainBuilder": + def add_order_validation_step(self, step) -> "OrderObtentionChainBuilder": - self._wip_report_chain.order_validation_steps.append(step) + self._wip_order_obtention_chain.order_validation_steps.append(step) return self - def get_report_chain(self) -> ReportChain: + def get_report_chain(self) -> OrderObtentionChain: - return self._wip_report_chain + return self._wip_order_obtention_chain From 907c2bc1335d914c27aa487f02ee952972ee7125 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 6 Sep 2023 14:28:47 +0200 Subject: [PATCH 06/13] Formatting --- camisatoshi_wordpress_reports/cli.py | 3 ++- camisatoshi_wordpress_reports/constants.py | 1 - camisatoshi_wordpress_reports/controllers.py | 1 + camisatoshi_wordpress_reports/logging.py | 3 ++- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/camisatoshi_wordpress_reports/cli.py b/camisatoshi_wordpress_reports/cli.py index 2187be9..e5ebcb5 100644 --- a/camisatoshi_wordpress_reports/cli.py +++ b/camisatoshi_wordpress_reports/cli.py @@ -21,10 +21,11 @@ def check_health(): 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)] + 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/constants.py b/camisatoshi_wordpress_reports/constants.py index 2bb1a9c..fce09a6 100644 --- a/camisatoshi_wordpress_reports/constants.py +++ b/camisatoshi_wordpress_reports/constants.py @@ -25,4 +25,3 @@ custom_meta_data_keys.sats_received = "sats_received" um_first_agreement_percentage = 0.5 bbo_royalty_fee = 0.2 - diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index 2073ec9..09656dc 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -131,6 +131,7 @@ def generate_um_report( 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}.") diff --git a/camisatoshi_wordpress_reports/logging.py b/camisatoshi_wordpress_reports/logging.py index 5d4835b..17b689d 100644 --- a/camisatoshi_wordpress_reports/logging.py +++ b/camisatoshi_wordpress_reports/logging.py @@ -1,4 +1,5 @@ import logging + def set_config_level(): - logging.basicConfig(level=logging.DEBUG) \ No newline at end of file + logging.basicConfig(level=logging.DEBUG) From f127328227128613d0c962c4281cad33cbbccd32 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 6 Sep 2023 14:33:03 +0200 Subject: [PATCH 07/13] Some improvement on constants naming --- camisatoshi_wordpress_reports/constants.py | 27 +++++++++--------- camisatoshi_wordpress_reports/controllers.py | 13 +++++---- camisatoshi_wordpress_reports/order.py | 30 ++++++++++---------- 3 files changed, 36 insertions(+), 34 deletions(-) diff --git a/camisatoshi_wordpress_reports/constants.py b/camisatoshi_wordpress_reports/constants.py index fce09a6..652c7f3 100644 --- a/camisatoshi_wordpress_reports/constants.py +++ b/camisatoshi_wordpress_reports/constants.py @@ -6,22 +6,23 @@ DEFAULT_DOTENV_FILEPATH = ".camisatoshi-wordpress-reports/.env" ### Order keys -order_keys = SimpleNamespace() -order_keys.meta_data = "meta_data" -order_keys.total = "total" -order_keys.line_items = "line_items" +ORDER_KEYS = SimpleNamespace() +ORDER_KEYS.id = "id" +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" +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_um = "is_settled_um" -custom_meta_data_keys.sats_received = "sats_received" +CUSTOM_META_DATA_KEYS = SimpleNamespace() +CUSTOM_META_DATA_KEYS.is_settled_um = "is_settled_um" +CUSTOM_META_DATA_KEYS.sats_received = "sats_received" ### Other -um_first_agreement_percentage = 0.5 -bbo_royalty_fee = 0.2 +UM_FIRST_AGREEMENT_PERCENTAGE = 0.5 +BBO_ROYALTY_FEE_PERCENTAGE = 0.2 diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index 09656dc..cb8f906 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -9,9 +9,10 @@ from woocommerce import API from camisatoshi_wordpress_reports.order import Order, Orders from camisatoshi_wordpress_reports.constants import ( - um_first_agreement_percentage, + ORDER_KEYS, + UM_FIRST_AGREEMENT_PERCENTAGE, DEFAULT_DOTENV_FILEPATH, - bbo_royalty_fee, + BBO_ROYALTY_FEE_PERCENTAGE, ) from camisatoshi_wordpress_reports.report_building import ( OrderObtentionChainBuilder, @@ -109,16 +110,16 @@ def generate_um_report( for order in unsettled_orders: report.append( { - "order_id": order["id"], + "order_id": order[ORDER_KEYS.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) - * (1 - bbo_royalty_fee) + * (1 - BBO_ROYALTY_FEE_PERCENTAGE) ) - * um_first_agreement_percentage, + * UM_FIRST_AGREEMENT_PERCENTAGE, } ) logger.info("Report generated.") @@ -157,7 +158,7 @@ def generate_sku_report(start_date, end_date, sku): for order in relevant_orders: report.append( { - "order_id": order["id"], + "order_id": order[ORDER_KEYS.id], "sku": sku, "units_sold": order.units_of_sku(sku), "eur_income": order.sales_of_sku(sku), diff --git a/camisatoshi_wordpress_reports/order.py b/camisatoshi_wordpress_reports/order.py index edce067..353787d 100644 --- a/camisatoshi_wordpress_reports/order.py +++ b/camisatoshi_wordpress_reports/order.py @@ -2,8 +2,8 @@ import logging from typing import Dict, Collection from camisatoshi_wordpress_reports.constants import ( - order_keys, - custom_meta_data_keys, + ORDER_KEYS, + CUSTOM_META_DATA_KEYS, ) from camisatoshi_wordpress_reports.utils import safe_zero_division @@ -19,32 +19,32 @@ class Order: def meta_data_entries(self): return { meta_data_entry["key"]: meta_data_entry["value"] - for meta_data_entry in self.raw_data[order_keys.meta_data] + 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] + 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]) + 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]) + 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] + self.meta_data_entries[CUSTOM_META_DATA_KEYS.sats_received] ) sats_received_for_sku = ( monetary_weight_of_sku_in_order * total_order_sats_received @@ -53,8 +53,8 @@ class Order: 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: + for item in self[ORDER_KEYS.line_items]: + if item[ORDER_KEYS.line_item_keys.sku] == sku: return True return False @@ -65,7 +65,7 @@ class Order: def is_settled_with_um(self): is_settled = self.meta_data_entries.get( - custom_meta_data_keys.is_settled_um, None + CUSTOM_META_DATA_KEYS.is_settled_um, None ) return bool(is_settled) @@ -107,7 +107,7 @@ class Orders: for order in self: if not order.contains_meta_data_entry( - custom_meta_data_keys.sats_received + CUSTOM_META_DATA_KEYS.sats_received ): orders_without_sats_received.append(order) continue From a1dcb826ee81736a54666ce1337b9a3fb3f292ef Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 4 Nov 2023 11:39:07 +0100 Subject: [PATCH 08/13] Thingies --- README.md | 2 +- camisatoshi_wordpress_reports/order.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index caaa186..8c8bf07 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ 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 +camisatoshi-wordpress-reports generate-um-report --start-date "2023-08-01T00:00:00" --end-date "2023-09-01T00:00:00" ``` This will generate a file named `report.csv` in the current working directory. diff --git a/camisatoshi_wordpress_reports/order.py b/camisatoshi_wordpress_reports/order.py index 353787d..faa2a1e 100644 --- a/camisatoshi_wordpress_reports/order.py +++ b/camisatoshi_wordpress_reports/order.py @@ -93,6 +93,9 @@ class Orders: return next_order raise StopIteration + def __repr__(self): + return str([f"Order {order['id']}" for order in self]) + def filter_orders_by_sku(self, sku: str) -> "Orders": filtered_orders = [] From 3b24f9635224f5dc35252e629846fcf15b686c10 Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 2 Dec 2023 17:42:50 +0100 Subject: [PATCH 09/13] Implement multi SKU filtering --- camisatoshi_wordpress_reports/controllers.py | 20 ++++++++++++++------ camisatoshi_wordpress_reports/order.py | 10 ++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index cb8f906..97eb25d 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -77,6 +77,14 @@ def generate_um_report( logger.info(f"Filtering by SKU: {relevant_sku}") relevant_orders = orders_in_date_range.filter_orders_by_sku( sku=relevant_sku + relevant_skus = [ + "TEE-05-BBO-BLACK", + "SUD-01-BBO-BLACK", + "TEE-09-SIMPLY-BITCOIN" + ] + logger.info(f"Filtering by SKUs: {relevant_skus}") + relevant_orders = orders_in_date_range.filter_orders_by_skus( + skus=relevant_skus ) logger.info(f"Kept {len(relevant_orders)} orders.") @@ -111,12 +119,12 @@ def generate_um_report( report.append( { "order_id": order[ORDER_KEYS.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), + "sku": relevant_skus, + "units_sold": order.units_of_skus(relevant_skus), + "eur_income": order.sales_of_sku(relevant_skus), + "sats_income": order.sats_received_for_sku(relevant_skus), "sats_owed_to_um": ( - order.sats_received_for_sku(relevant_sku) + order.sats_received_for_sku(relevant_skus) * (1 - BBO_ROYALTY_FEE_PERCENTAGE) ) * UM_FIRST_AGREEMENT_PERCENTAGE, @@ -160,7 +168,7 @@ def generate_sku_report(start_date, end_date, sku): { "order_id": order[ORDER_KEYS.id], "sku": sku, - "units_sold": order.units_of_sku(sku), + "units_sold": order.units_of_skus(sku), "eur_income": order.sales_of_sku(sku), } ) diff --git a/camisatoshi_wordpress_reports/order.py b/camisatoshi_wordpress_reports/order.py index faa2a1e..449e039 100644 --- a/camisatoshi_wordpress_reports/order.py +++ b/camisatoshi_wordpress_reports/order.py @@ -105,6 +105,16 @@ class Orders: return Orders(filtered_orders) + def filter_orders_by_skus(self, skus: Collection[str]) -> "Orders": + filtered_orders = [] + + for order in self: + 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 = [] From f5b33cce7460fefdf3cc84398c9f83bb410c6d3a Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 2 Dec 2023 18:03:36 +0100 Subject: [PATCH 10/13] Got the reporting fixed for multi skus. --- camisatoshi_wordpress_reports/constants.py | 1 + camisatoshi_wordpress_reports/controllers.py | 50 ++++++++++++-------- camisatoshi_wordpress_reports/order.py | 5 +- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/camisatoshi_wordpress_reports/constants.py b/camisatoshi_wordpress_reports/constants.py index 652c7f3..436c48c 100644 --- a/camisatoshi_wordpress_reports/constants.py +++ b/camisatoshi_wordpress_reports/constants.py @@ -26,3 +26,4 @@ CUSTOM_META_DATA_KEYS.sats_received = "sats_received" UM_FIRST_AGREEMENT_PERCENTAGE = 0.5 BBO_ROYALTY_FEE_PERCENTAGE = 0.2 +BBO_SKUS = ["TEE-05-BBO-BLACK", "SUD-01-BBO-BLACK"] diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index 97eb25d..c77d9f7 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -13,6 +13,7 @@ from camisatoshi_wordpress_reports.constants import ( UM_FIRST_AGREEMENT_PERCENTAGE, DEFAULT_DOTENV_FILEPATH, BBO_ROYALTY_FEE_PERCENTAGE, + BBO_SKUS, ) from camisatoshi_wordpress_reports.report_building import ( OrderObtentionChainBuilder, @@ -73,14 +74,10 @@ def generate_um_report( ) 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 relevant_skus = [ "TEE-05-BBO-BLACK", "SUD-01-BBO-BLACK", - "TEE-09-SIMPLY-BITCOIN" + "TEE-09-SIMPLY-BITCOIN", ] logger.info(f"Filtering by SKUs: {relevant_skus}") relevant_orders = orders_in_date_range.filter_orders_by_skus( @@ -115,21 +112,32 @@ def generate_um_report( f"Relevant orders: {[order['id'] for order in unsettled_orders]}." ) report = [] - for order in unsettled_orders: - report.append( - { - "order_id": order[ORDER_KEYS.id], - "sku": relevant_skus, - "units_sold": order.units_of_skus(relevant_skus), - "eur_income": order.sales_of_sku(relevant_skus), - "sats_income": order.sats_received_for_sku(relevant_skus), - "sats_owed_to_um": ( - order.sats_received_for_sku(relevant_skus) - * (1 - BBO_ROYALTY_FEE_PERCENTAGE) - ) - * UM_FIRST_AGREEMENT_PERCENTAGE, - } - ) + for relevant_sku in relevant_skus: + logger.debug(f"Reporting SKU {relevant_sku}") + for order in unsettled_orders: + if not order.contains_sku(relevant_sku): + continue + logger.debug(f"Reporting for order {order[ORDER_KEYS.id]}") + report.append( + { + "order_id": order[ORDER_KEYS.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) + * ( + 1 + - ( + BBO_ROYALTY_FEE_PERCENTAGE + * (relevant_sku in BBO_SKUS) + ) + ) + ) + * UM_FIRST_AGREEMENT_PERCENTAGE, + } + ) logger.info("Report generated.") logger.info(report) @@ -169,7 +177,7 @@ def generate_sku_report(start_date, end_date, sku): "order_id": order[ORDER_KEYS.id], "sku": sku, "units_sold": order.units_of_skus(sku), - "eur_income": order.sales_of_sku(sku), + "eur_income": order.sales_of_skus(sku), } ) logger.info("Report generated.") diff --git a/camisatoshi_wordpress_reports/order.py b/camisatoshi_wordpress_reports/order.py index 449e039..3e8988f 100644 --- a/camisatoshi_wordpress_reports/order.py +++ b/camisatoshi_wordpress_reports/order.py @@ -109,9 +109,12 @@ class Orders: filtered_orders = [] for order in self: + order_contains_at_least_one_of_the_skus = None # Guilty until proven innocent for sku in skus: if order.contains_sku(sku): - filtered_orders.append(order) + order_contains_at_least_one_of_the_skus = True + if order_contains_at_least_one_of_the_skus: + filtered_orders.append(order) return Orders(filtered_orders) From 385db5422eba27b85d1a454aa12f7dfde443cb25 Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 2 Dec 2023 18:07:21 +0100 Subject: [PATCH 11/13] Refactor a bit to avoid monster formula --- camisatoshi_wordpress_reports/controllers.py | 27 +++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index c77d9f7..27516c3 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -118,24 +118,27 @@ def generate_um_report( if not order.contains_sku(relevant_sku): continue logger.debug(f"Reporting for order {order[ORDER_KEYS.id]}") + + # A few helper variables to make the last variable more understandable + sats_received_for_sku = order.sats_received_for_sku(relevant_sku) + bbo_fee_if_applicable = (BBO_ROYALTY_FEE_PERCENTAGE * (relevant_sku in BBO_SKUS)) + discount_factor_for_bbo_skus = 1 - bbo_fee_if_applicable + + # We owe UM his percentages of the sats received after discounting + # royalties paid to BBO for the BBO products. + sats_owed_to_um = ( + (sats_received_for_sku * discount_factor_for_bbo_skus) + * UM_FIRST_AGREEMENT_PERCENTAGE, + ) + report.append( { "order_id": order[ORDER_KEYS.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) - * ( - 1 - - ( - BBO_ROYALTY_FEE_PERCENTAGE - * (relevant_sku in BBO_SKUS) - ) - ) - ) - * UM_FIRST_AGREEMENT_PERCENTAGE, + "sats_income": sats_received_for_sku, + "sats_owed_to_um": sats_owed_to_um, } ) logger.info("Report generated.") From eecd7cc55213f82444daa6e032d8b1236e4dccee Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 2 Dec 2023 18:08:06 +0100 Subject: [PATCH 12/13] Remove typo that turns float into tuple --- camisatoshi_wordpress_reports/controllers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/camisatoshi_wordpress_reports/controllers.py b/camisatoshi_wordpress_reports/controllers.py index 27516c3..7b8d346 100644 --- a/camisatoshi_wordpress_reports/controllers.py +++ b/camisatoshi_wordpress_reports/controllers.py @@ -128,7 +128,7 @@ def generate_um_report( # royalties paid to BBO for the BBO products. sats_owed_to_um = ( (sats_received_for_sku * discount_factor_for_bbo_skus) - * UM_FIRST_AGREEMENT_PERCENTAGE, + * UM_FIRST_AGREEMENT_PERCENTAGE ) report.append( From 26620995d0a9a7c08644098b6dae68c29d357c9e Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 2 Dec 2023 19:01:37 +0100 Subject: [PATCH 13/13] Little readme comment --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8c8bf07..b729812 100644 --- a/README.md +++ b/README.md @@ -37,4 +37,5 @@ 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 +- Pagination is not being managed. The moment we have more than 100 orders, we are gonna run into issues. +- Some reports will break if the SKU has absolutely no orders. But well, if that happens, there is simply no report to build, so the only serious improvement would be dropping an informative error message. \ No newline at end of file