Skip to content

Commit 6d319e8

Browse files
committed
feat(validation): use items in reseller transfer validation
1 parent 995790a commit 6d319e8

9 files changed

Lines changed: 768 additions & 264 deletions

File tree

adobe_vipm/flows/constants.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
)
1414
from adobe_vipm.flows.errors import ValidationError
1515

16-
TRANSFER_RESELLER_PRODUCT_ID = "adobe-reseller-transfer"
17-
1816

1917
class OrderType(StrEnum):
2018
"""Order types."""
@@ -229,15 +227,6 @@ class Param(StrEnum):
229227
"the automatically included item can be processed"
230228
),
231229
)
232-
233-
ERR_ADOBE_RESSELLER_CHANGE_PRODUCT_NOT_CONFIGURED = ValidationError(
234-
"VIPM0036",
235-
(
236-
"The adobe reseller change product is not configured for this product and "
237-
"cannot be added to the order."
238-
),
239-
)
240-
241230
ERR_ADOBE_GOVERNMENT_VALIDATE_IS_LGA = ValidationError(
242231
"VIPM0038",
243232
(

adobe_vipm/flows/context.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class Context:
3232
adobe_returnable_orders: dict = field(default_factory=dict)
3333
adobe_return_orders: dict = field(default_factory=dict)
3434
membership_id: str | None = None
35+
adobe_transfer: dict | None = None
3536

3637
def __str__(self):
3738
due_date = self.due_date.strftime("%Y-%m-%d") if self.due_date else "-"

adobe_vipm/flows/sync/agreement.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -564,12 +564,11 @@ def _update_agreement_line_prices(
564564
def _get_processable_agreement_lines(self, agreement: dict) -> list[tuple[dict, str]]:
565565
agreement_lines = []
566566
for line in agreement["lines"]:
567-
if line["item"]["externalIds"]["vendor"] != "adobe-reseller-transfer":
568-
actual_sku = models.get_adobe_sku(
569-
line["item"]["externalIds"]["vendor"], get_market_segment(self.product_id)
570-
)
567+
actual_sku = models.get_adobe_sku(
568+
line["item"]["externalIds"]["vendor"], get_market_segment(self.product_id)
569+
)
571570

572-
agreement_lines.append((line, actual_sku))
571+
agreement_lines.append((line, actual_sku))
573572
return agreement_lines
574573

575574
# REFACTOR: get method must not update subscriptions in mpt or terminate a subscription

adobe_vipm/flows/validation/transfer.py

Lines changed: 114 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import datetime as dt
22
import logging
3+
from typing import Any
34

4-
from mpt_extension_sdk.mpt_http.mpt import get_agreement, get_product_items_by_skus
5+
from mpt_extension_sdk.mpt_http.mpt import get_agreement, get_product_items_by_skus, update_order
56

67
from adobe_vipm.adobe.client import get_adobe_client
78
from adobe_vipm.adobe.constants import (
@@ -26,16 +27,20 @@
2627
ERR_ADOBE_MEMBERSHIP_NOT_FOUND,
2728
ERR_ADOBE_MEMBERSHIP_PROCESSING,
2829
ERR_ADOBE_RESSELLER_CHANGE_LINES,
29-
ERR_ADOBE_RESSELLER_CHANGE_PRODUCT_NOT_CONFIGURED,
3030
ERR_ADOBE_UNEXPECTED_ERROR,
3131
ERR_NO_SUBSCRIPTIONS_WITHOUT_DEPLOYMENT,
3232
ERR_UPDATING_TRANSFER_ITEMS,
3333
Param,
3434
)
3535
from adobe_vipm.flows.context import Context
36-
from adobe_vipm.flows.errors import GovernmentLGANotValidOrderError, GovernmentNotValidOrderError
36+
from adobe_vipm.flows.errors import (
37+
GovernmentLGANotValidOrderError,
38+
GovernmentNotValidOrderError,
39+
MPTError,
40+
)
3741
from adobe_vipm.flows.helpers import (
3842
FetchResellerChangeData,
43+
UpdatePrices,
3944
ValidateGovernmentTransfer,
4045
ValidateResellerChange,
4146
)
@@ -45,6 +50,7 @@
4550
exclude_items_with_deployment_id,
4651
exclude_subscriptions_with_deployment_id,
4752
get_adobe_membership_id,
53+
get_market_segment,
4854
get_order_line_by_sku,
4955
get_ordering_parameter,
5056
get_transfer_item_sku_by_subscription,
@@ -53,8 +59,11 @@
5359
is_transferring_item_expired,
5460
set_order_error,
5561
set_ordering_parameter_error,
62+
split_downsizes_upsizes_new,
5663
)
5764
from adobe_vipm.flows.utils.validation import validate_government_lga_data
65+
from adobe_vipm.flows.validation.shared import GetPreviewOrder
66+
from adobe_vipm.notifications import send_error
5867
from adobe_vipm.utils import get_3yc_commitment, get_partial_sku
5968

6069
logger = logging.getLogger(__name__)
@@ -343,13 +352,16 @@ def __call__(self, mpt_client, context, next_step):
343352
context.order["agreement"] = get_agreement(mpt_client, context.order["agreement"]["id"])
344353

345354
product_id = context.order["agreement"]["product"]["id"]
346-
authorization_id = context.order["authorization"]["id"]
355+
context.authorization_id = context.order["authorization"]["id"]
356+
context.order_id = context.order["id"]
347357
context.membership_id = get_adobe_membership_id(context.order)
358+
context.product_id = context.order["agreement"]["product"]["id"]
359+
context.market_segment = get_market_segment(context.product_id)
348360

349361
if is_migrate_customer(context.order):
350362
context.transfer = get_transfer_by_authorization_membership_or_customer(
351363
product_id,
352-
authorization_id,
364+
context.authorization_id,
353365
context.membership_id,
354366
)
355367
next_step(mpt_client, context)
@@ -520,6 +532,8 @@ def validate_reseller_change(mpt_client, order):
520532
FetchResellerChangeData(is_validation=True),
521533
ValidateResellerChange(is_validation=True),
522534
AddResellerChangeLinesToOrder(),
535+
GetPreviewOrder(),
536+
UpdatePrices(),
523537
)
524538
context = Context(order=order)
525539
pipeline.run(mpt_client, context)
@@ -531,36 +545,105 @@ class AddResellerChangeLinesToOrder(Step):
531545

532546
def __call__(self, mpt_client, context, next_step):
533547
"""Add lines from reseller change back to the MPT order."""
534-
reseller_change_item = get_product_items_by_skus(
535-
mpt_client, context.order["agreement"]["product"]["id"], ["adobe-reseller-transfer"]
536-
)
548+
order_lines_from_transfer = self._get_order_lines_from_transfer(context, mpt_client)
549+
550+
if order_lines_from_transfer:
551+
if not context.order["lines"]:
552+
logger.info("No existing order lines, proceeding with transfer lines")
553+
context.order = update_order(
554+
mpt_client, context.order_id, lines=order_lines_from_transfer
555+
)
556+
context.validation_succeeded = True
557+
elif not self._transfer_order_lines_match(
558+
context.order["lines"], order_lines_from_transfer
559+
):
560+
logger.warning("Order lines do not match transfer lines")
561+
context.order = set_order_error(
562+
context.order, ERR_ADOBE_RESSELLER_CHANGE_LINES.to_dict()
563+
)
564+
context.validation_succeeded = False
565+
return
566+
else:
567+
logger.info(
568+
"Order lines match transfer lines successfully (%d lines)",
569+
len(order_lines_from_transfer),
570+
)
571+
context.validation_succeeded = True
572+
elif context.order["lines"]:
573+
logger.info(
574+
"No transfer lines but order has %d existing lines, processing new lines",
575+
len(context.order["lines"]),
576+
)
577+
downsize_lines, upsize_lines, new_lines = split_downsizes_upsizes_new(context.order)
578+
context.downsize_lines = downsize_lines
579+
context.upsize_lines = upsize_lines
580+
context.new_lines = new_lines
537581

538-
if not reseller_change_item:
539-
context.order = set_order_error(
540-
context.order, ERR_ADOBE_RESSELLER_CHANGE_PRODUCT_NOT_CONFIGURED.to_dict()
582+
context.validation_succeeded = True
583+
else:
584+
logger.error("No transfer lines and no order lines, validation failed")
585+
context.order = update_order(
586+
mpt_client, context.order_id, lines=order_lines_from_transfer
541587
)
542588
context.validation_succeeded = False
543589
return
544590

545-
reseller_change_item = reseller_change_item[0]
546-
context.validation_succeeded = True
591+
logger.info(
592+
"Proceeding to next step with validation_succeeded=%s", context.validation_succeeded
593+
)
594+
next_step(mpt_client, context)
547595

548-
lines = context.order["lines"]
549-
if lines:
550-
if len(lines) == 1 and lines[0]["item"]["id"] == reseller_change_item["id"]:
551-
next_step(mpt_client, context)
552-
return
553-
context.order = set_order_error(
554-
context.order, ERR_ADOBE_RESSELLER_CHANGE_LINES.to_dict()
555-
)
556-
context.validation_succeeded = False
557-
new_line = [
558-
{
559-
"item": reseller_change_item,
560-
"quantity": 1,
561-
"oldQuantity": 0,
562-
"price": {"unitPP": 0},
563-
}
596+
def _get_order_lines_from_transfer(self, context: Context, mpt_client) -> list[Any]:
597+
no_deployment_transfer_items = [
598+
item for item in context.adobe_transfer["lineItems"] if not item.get("deploymentId", "")
564599
]
565-
context.order["lines"] = new_line
566-
next_step(mpt_client, context)
600+
if not no_deployment_transfer_items:
601+
logger.info("No transfer items without deployment ID")
602+
return []
603+
logger.info(
604+
"Processing %d transfer items without deployment ID", len(no_deployment_transfer_items)
605+
)
606+
reseller_items_map = {
607+
item["externalIds"]["vendor"]: item
608+
for item in get_product_items_by_skus(
609+
mpt_client,
610+
context.order["agreement"]["product"]["id"],
611+
[get_partial_sku(item["offerId"]) for item in no_deployment_transfer_items],
612+
)
613+
}
614+
logger.debug(
615+
"Created reseller items map with %d items: %s",
616+
len(reseller_items_map),
617+
list(reseller_items_map.keys()),
618+
)
619+
order_lines_from_transfer = []
620+
for item in no_deployment_transfer_items: # TODO:filter out expired?
621+
partial_sku = get_partial_sku(item["offerId"])
622+
mapped_item = reseller_items_map.get(partial_sku)
623+
if mapped_item is None:
624+
error_msg = f"No reseller item found for partial SKU '{partial_sku}'"
625+
logger.error(error_msg)
626+
send_error("Transfer Validation - Missing reseller item", error_msg)
627+
raise MPTError(error_msg)
628+
order_lines_from_transfer.append({
629+
"item": mapped_item,
630+
"oldQuantity": item["quantity"],
631+
"quantity": item["quantity"],
632+
})
633+
634+
logger.info("Created %d order lines from transfer items", len(order_lines_from_transfer))
635+
return order_lines_from_transfer
636+
637+
@staticmethod
638+
def _transfer_order_lines_match(order_lines: list[dict], transfer_lines: list[dict]) -> bool:
639+
"""Compare order lines based on relevant fields only."""
640+
if len(order_lines) != len(transfer_lines):
641+
return False
642+
643+
order_lines_to_comp = {
644+
(line["item"]["externalIds"]["vendor"], line["quantity"]) for line in order_lines
645+
}
646+
transfer_lines_to_comp = {
647+
(line["item"]["externalIds"]["vendor"], line["quantity"]) for line in transfer_lines
648+
}
649+
return order_lines_to_comp == transfer_lines_to_comp

0 commit comments

Comments
 (0)