Skip to content

Commit 41b324f

Browse files
MPT-18440 AWS billing transfer: support PLS additional charge
1 parent ef880bb commit 41b324f

File tree

21 files changed

+540
-43
lines changed

21 files changed

+540
-43
lines changed

helm/swo-extension-aws/charts/api/templates/configmap.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,4 @@ data:
5353
EXT_PENDING_ORDERS_INFORMATION_REPORT_PAGE_ID: {{ .Values.global.PendingOrdersInformationReportPageId | quote }}
5454
EXT_CONFLUENCE_BASE_URL: {{ .Values.global.ConfluenceBaseUrl | quote }}
5555
EXT_CONFLUENCE_USER: {{ .Values.global.ConfluenceUser | quote }}
56+
EXT_PLS_CHARGE_PERCENTAGE: {{ .Values.global.PlSChargePercentage | quote }}

helm/swo-extension-aws/charts/worker/templates/configmap.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,4 @@ data:
5353
EXT_PENDING_ORDERS_INFORMATION_REPORT_PAGE_ID: {{ .Values.global.PendingOrdersInformationReportPageId | quote }}
5454
EXT_CONFLUENCE_BASE_URL: {{ .Values.global.ConfluenceBaseUrl | quote }}
5555
EXT_CONFLUENCE_USER: {{ .Values.global.ConfluenceUser | quote }}
56+
EXT_PLS_CHARGE_PERCENTAGE: {{ .Values.global.PlSChargePercentage | quote }}

helm/swo-extension-aws/values.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ global:
6262
ConfluenceUser: default
6363
ConfluenceToken: ZGVmYXVsdA==
6464
ConfluenceBaseUrl: default
65+
PlSChargePercentage: "5.0"
6566

6667
cronjobs:
6768
order-querying:

swo_aws_extension/config.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
from django.conf import settings
55

6+
DEFAULT_PLS_CHARGE_PERCENTAGE = 5.0
7+
68

79
class Config:
810
"""AWS extension configuration."""
@@ -196,6 +198,13 @@ def mpt_portal_base_url(self) -> str:
196198
"""Get the base URL for the MPT Portal."""
197199
return settings.MPT_PORTAL_BASE_URL
198200

201+
@property
202+
def pls_charge_percentage(self) -> float:
203+
"""Get the PLS charge percentage (defaults to 5.0)."""
204+
return float(
205+
settings.EXTENSION_CONFIG.get("PLS_CHARGE_PERCENTAGE", DEFAULT_PLS_CHARGE_PERCENTAGE),
206+
)
207+
199208
def _patch_path(self, file_path):
200209
"""Fixes relative paths to be from the project root."""
201210
path = Path(file_path)

swo_aws_extension/flows/jobs/billing_journal/generators/agreement.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
from mpt_extension_sdk.runtime.tracer import dynamic_trace_span
22

3+
from swo_aws_extension.constants import SupportTypesEnum
34
from swo_aws_extension.flows.jobs.billing_journal.generators.invoice import InvoiceGenerator
45
from swo_aws_extension.flows.jobs.billing_journal.generators.journal_line import (
56
JournalLineGenerator,
67
)
8+
from swo_aws_extension.flows.jobs.billing_journal.generators.pls_charge_manager import (
9+
PlSChargeManager,
10+
)
711
from swo_aws_extension.flows.jobs.billing_journal.generators.usage import (
812
BaseOrganizationUsageGenerator,
913
)
@@ -14,6 +18,7 @@
1418
)
1519
from swo_aws_extension.flows.jobs.billing_journal.models.usage import OrganizationUsageResult
1620
from swo_aws_extension.logger import get_logger
21+
from swo_aws_extension.parameters import get_support_type
1722
from swo_aws_extension.utils.decorators import with_log_context
1823

1924
logger = get_logger(__name__)
@@ -30,6 +35,7 @@ def __init__(
3035
invoice_generator: InvoiceGenerator,
3136
) -> None:
3237
self._authorization_currency = authorization_currency
38+
self._pls_charge_percentage = context.pls_charge_percentage
3339
self._config = context.config
3440
self._mpt_client = context.mpt_client
3541
self._billing_period = context.billing_period
@@ -77,18 +83,22 @@ def run(self, agreement: dict) -> list[JournalLine]:
7783
)
7884

7985
return self._generate_lines_for_accounts(
86+
agreement,
8087
usage_result,
8188
journal_details,
8289
invoice_result.invoice,
8390
)
8491

8592
def _generate_lines_for_accounts(
8693
self,
94+
agreement: dict,
8795
usage_result: OrganizationUsageResult,
8896
journal_details: JournalDetails,
8997
organization_invoice,
9098
) -> list[JournalLine]:
91-
line_generator = JournalLineGenerator()
99+
is_pls = get_support_type(agreement) == SupportTypesEnum.AWS_RESOLD_SUPPORT
100+
line_generator = JournalLineGenerator(is_pls=is_pls)
101+
92102
all_lines: list[JournalLine] = []
93103
for account_id, account_usage in usage_result.usage_by_account.items():
94104
all_lines.extend(
@@ -99,4 +109,16 @@ def _generate_lines_for_accounts(
99109
organization_invoice,
100110
)
101111
)
112+
113+
# If PLS is active, calculate and add the PLS charge line.
114+
if is_pls:
115+
all_lines.extend(
116+
PlSChargeManager().process(
117+
self._pls_charge_percentage,
118+
usage_result,
119+
journal_details,
120+
organization_invoice,
121+
)
122+
)
123+
102124
return all_lines
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from decimal import Decimal
2+
3+
from swo_aws_extension.constants import DEC_ZERO
4+
from swo_aws_extension.flows.jobs.billing_journal.models.invoice import InvoiceEntity
5+
6+
7+
def resolve_service_amount(
8+
amount: Decimal,
9+
invoice_entity: InvoiceEntity | None,
10+
) -> Decimal:
11+
"""Calculate the service amount converted to the local payment currency."""
12+
if not invoice_entity:
13+
return amount
14+
15+
if invoice_entity.payment_currency_code == invoice_entity.base_currency_code:
16+
return amount
17+
if invoice_entity.exchange_rate <= DEC_ZERO:
18+
return amount
19+
20+
return round(amount * invoice_entity.exchange_rate, 6)

swo_aws_extension/flows/jobs/billing_journal/generators/journal_line.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
from swo_aws_extension.constants import AWSRecordTypeEnum
22
from swo_aws_extension.flows.jobs.billing_journal.generators.line_processors.base import (
3-
LineProcessor,
3+
JournalLineProcessor,
44
)
55
from swo_aws_extension.flows.jobs.billing_journal.generators.line_processors.credit import (
6-
CreditLineProcessor,
6+
CreditJournalLineProcessor,
7+
)
8+
from swo_aws_extension.flows.jobs.billing_journal.generators.line_processors.marketplace import (
9+
MarketplaceJournalLineProcessor,
710
)
811
from swo_aws_extension.flows.jobs.billing_journal.models.context import LineProcessorContext
912
from swo_aws_extension.flows.jobs.billing_journal.models.invoice import OrganizationInvoice
@@ -17,25 +20,31 @@
1720
logger = get_logger(__name__)
1821

1922

20-
def _build_processor_registry() -> dict[str, LineProcessor]:
21-
default = LineProcessor()
22-
credit = CreditLineProcessor()
23+
def _build_processor_registry(*, is_pls: bool = False) -> dict[str, JournalLineProcessor]:
24+
default = JournalLineProcessor()
25+
credit = CreditJournalLineProcessor()
26+
marketplace = MarketplaceJournalLineProcessor()
2327

24-
return {
28+
registry = {
2529
AWSRecordTypeEnum.USAGE: default,
2630
AWSRecordTypeEnum.SUPPORT: default,
2731
AWSRecordTypeEnum.RECURRING: default,
2832
AWSRecordTypeEnum.SAVING_PLAN_RECURRING_FEE: default,
2933
AWSRecordTypeEnum.CREDIT: credit,
30-
"MARKETPLACE": default,
34+
"MARKETPLACE": marketplace,
3135
}
3236

37+
if is_pls:
38+
registry.pop(AWSRecordTypeEnum.SUPPORT, None)
39+
40+
return registry
41+
3342

3443
class JournalLineGenerator:
3544
"""Generates journal lines from account usage data using line processors."""
3645

37-
def __init__(self) -> None:
38-
self._processors = _build_processor_registry()
46+
def __init__(self, *, is_pls: bool = False) -> None:
47+
self._processors = _build_processor_registry(is_pls=is_pls)
3948

4049
def generate(
4150
self,

swo_aws_extension/flows/jobs/billing_journal/generators/line_processors/base.py

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from decimal import Decimal
2-
31
from swo_aws_extension.constants import DEC_ZERO
2+
from swo_aws_extension.flows.jobs.billing_journal.generators.currency import (
3+
resolve_service_amount,
4+
)
45
from swo_aws_extension.flows.jobs.billing_journal.models.context import LineProcessorContext
5-
from swo_aws_extension.flows.jobs.billing_journal.models.invoice import OrganizationInvoice
66
from swo_aws_extension.flows.jobs.billing_journal.models.journal_line import (
77
InvoiceDetails,
88
JournalLine,
@@ -12,7 +12,7 @@
1212
ITEM_SKU = "AWS Usage"
1313

1414

15-
class LineProcessor:
15+
class JournalLineProcessor:
1616
"""Base class for all line processors."""
1717

1818
def __init__(
@@ -47,7 +47,8 @@ def _build_line(
4747
context: LineProcessorContext,
4848
) -> JournalLine:
4949
service_name = f"{self._prefix_name}{metric.service_name}{self._suffix_name}"
50-
service_amount = self._resolve_service_amount(metric, context.organization_invoice)
50+
invoice_entity = context.organization_invoice.entities.get(metric.invoice_entity or "")
51+
service_amount = resolve_service_amount(metric.amount, invoice_entity)
5152
invoice_details = InvoiceDetails(
5253
item_sku=ITEM_SKU,
5354
service_name=service_name,
@@ -57,20 +58,3 @@ def _build_line(
5758
invoice_id=metric.invoice_id or "invoice_id",
5859
)
5960
return JournalLine.build(ITEM_SKU, context.journal_details, invoice_details)
60-
61-
def _resolve_service_amount(
62-
self,
63-
metric: ServiceMetric,
64-
organization_invoice: OrganizationInvoice,
65-
) -> Decimal:
66-
invoice_entity_name = metric.invoice_entity or ""
67-
invoice_entity = organization_invoice.entities.get(invoice_entity_name)
68-
if not invoice_entity:
69-
return metric.amount
70-
71-
if invoice_entity.payment_currency_code == invoice_entity.base_currency_code:
72-
return metric.amount
73-
if invoice_entity.exchange_rate <= DEC_ZERO:
74-
return metric.amount
75-
76-
return round(metric.amount * invoice_entity.exchange_rate, 6)

swo_aws_extension/flows/jobs/billing_journal/generators/line_processors/credit.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from swo_aws_extension.constants import DEC_ZERO, AWSRecordTypeEnum
44
from swo_aws_extension.flows.jobs.billing_journal.generators.line_processors.base import (
5-
LineProcessor,
5+
JournalLineProcessor,
66
)
77
from swo_aws_extension.flows.jobs.billing_journal.models.context import LineProcessorContext
88
from swo_aws_extension.flows.jobs.billing_journal.models.journal_line import JournalLine
@@ -16,7 +16,7 @@
1616
)
1717

1818

19-
class CreditLineProcessor(LineProcessor):
19+
class CreditJournalLineProcessor(JournalLineProcessor):
2020
"""Generates journal lines for Credit metrics.
2121
2222
Always generates a credit line with the CREDIT prefix. When the principal invoice amount is
@@ -25,7 +25,7 @@ class CreditLineProcessor(LineProcessor):
2525

2626
def __init__(self) -> None:
2727
super().__init__(prefix_name=CREDIT_PREFIX)
28-
self._spp_processor = LineProcessor(
28+
self._spp_processor = JournalLineProcessor(
2929
prefix_name=SPP_PREFIX,
3030
suffix_name=SPP_SUFFIX,
3131
)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from typing import override
2+
3+
from swo_aws_extension.flows.jobs.billing_journal.generators.line_processors.base import (
4+
JournalLineProcessor,
5+
)
6+
from swo_aws_extension.flows.jobs.billing_journal.models.context import LineProcessorContext
7+
from swo_aws_extension.flows.jobs.billing_journal.models.journal_line import JournalLine
8+
from swo_aws_extension.flows.jobs.billing_journal.models.usage import ServiceMetric
9+
10+
TAX_SERVICE_NAME = "Tax"
11+
12+
13+
class MarketplaceJournalLineProcessor(JournalLineProcessor):
14+
"""Generates journal lines for Marketplace metrics, excluding Tax entries."""
15+
16+
@override
17+
def process(
18+
self,
19+
metric: ServiceMetric,
20+
context: LineProcessorContext,
21+
) -> list[JournalLine]:
22+
"""Process a marketplace metric, skipping Tax services.
23+
24+
Args:
25+
metric: The service metric to process.
26+
context: Shared context for the current account.
27+
28+
Returns:
29+
List of journal lines (empty if metric is Tax or should be skipped).
30+
"""
31+
if metric.service_name == TAX_SERVICE_NAME:
32+
return []
33+
return super().process(metric, context)

0 commit comments

Comments
 (0)