Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions e2e_config.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"accounts.seller.id": "SEL-7310-3075",
"accounts.user.id": "USR-9673-3314",
"accounts.user_group.id": "UGR-6822-0561",
"billing.journal.id": "BJO-6562-0928",
"catalog.authorization.id": "AUT-9288-6146",
"catalog.listing.id": "LST-5489-0806",
"catalog.price_list.id": "PRC-7255-3950-0245",
Expand All @@ -24,9 +25,9 @@
"catalog.product.terms.id": "TCS-7255-3950-0001",
"catalog.product.terms.variant.id": "TCV-7255-3950-0001-0001",
"catalog.unit.id": "UNT-1229",
"commerce.agreement.attachment.id": "ATT-9850-2169-6098-0001",
"commerce.agreement.id": "AGR-9850-2169-6098",
"commerce.agreement.subscription.line.id": "ALI-9850-2169-6098-0001",
"commerce.agreement.attachment.id": "ATT-0078-7880-7436-0001",
"commerce.agreement.id": "AGR-0078-7880-7436",
"commerce.agreement.subscription.line.id": "ALI-0078-7880-7436-0001",
"commerce.assets.agreement.id": "AGR-2473-3299-1721",
"commerce.assets.agreement.line.id": "ALI-9320-4904-7940-0001",
"commerce.assets.id": "AST-0625-6526-6154",
Expand All @@ -37,7 +38,7 @@
"commerce.assets.product.template.id": "",
"commerce.authorization.id": "AUT-0031-2873",
"commerce.client.id": "ACC-1086-6867",
"commerce.order.id": "ORD-6969-3541-5426",
"commerce.order.id": "ORD-0557-5037-6263",
"commerce.product.id": "PRD-1767-7355",
"commerce.product.item.id": "ITM-1767-7355-0001",
"commerce.product.listing.id": "LST-5489-0806",
Expand Down
38 changes: 0 additions & 38 deletions mpt_api_client/resources/billing/journal_upload.py

This file was deleted.

79 changes: 63 additions & 16 deletions mpt_api_client/resources/billing/journals.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from urllib.parse import urljoin

from mpt_api_client.http import AsyncService, Service
from mpt_api_client.http.mixins import (
AsyncCollectionMixin,
AsyncManagedResourceMixin,
CollectionMixin,
ManagedResourceMixin,
)
from mpt_api_client.http.types import FileTypes
from mpt_api_client.models import Model
from mpt_api_client.resources.billing.journal_attachments import (
AsyncJournalAttachmentsService,
Expand All @@ -18,10 +21,6 @@
AsyncJournalSellersService,
JournalSellersService,
)
from mpt_api_client.resources.billing.journal_upload import (
AsyncJournalUploadService,
JournalUploadService,
)
from mpt_api_client.resources.billing.mixins import AsyncRegeneratableMixin, RegeneratableMixin


Expand All @@ -35,6 +34,8 @@ class JournalsServiceConfig:
_endpoint = "/public/v1/billing/journals"
_model_class = Journal
_collection_key = "data"
_upload_file_key = "file"
_upload_data_key = "id"


class JournalsService(
Expand All @@ -46,6 +47,35 @@ class JournalsService(
):
"""Journals service."""

def upload(self, journal_id: str, file: FileTypes | None = None) -> Journal: # noqa: WPS110
"""Upload journal file.

Args:
journal_id: Journal ID.
file: journal file.

Returns:
Journal: Created resource.
"""
files = {}

if file:
files[self._upload_file_key] = file # UNUSED type: ignore[attr-defined]
files[self._upload_data_key] = journal_id # UNUSED type: ignore

path = urljoin(f"{self.path}/", f"{journal_id}/upload")

response = self.http_client.request( # UNUSED type: ignore[attr-defined]
"post",
path, # UNUSED type: ignore[attr-defined]
files=files,
force_multipart=True,
)

return self._model_class.from_response(
response
) # UNUSED type: ignore[attr-defined, no-any-return]

def attachments(self, journal_id: str) -> JournalAttachmentsService:
"""Return journal attachments service."""
return JournalAttachmentsService(
Expand All @@ -65,12 +95,6 @@ def charges(self, journal_id: str) -> JournalChargesService:
http_client=self.http_client, endpoint_params={"journal_id": journal_id}
)

def upload(self, journal_id: str) -> JournalUploadService:
"""Return journal upload service."""
return JournalUploadService(
http_client=self.http_client, endpoint_params={"journal_id": journal_id}
)


class AsyncJournalsService(
AsyncRegeneratableMixin[Journal],
Expand All @@ -81,6 +105,35 @@ class AsyncJournalsService(
):
"""Async Journals service."""

async def upload(self, journal_id: str, file: FileTypes | None = None) -> Journal: # noqa: WPS110
"""Upload journal file.

Args:
journal_id: Journal ID.
file: journal file.

Returns:
Journal: Created resource.
"""
files = {}

if file:
files[self._upload_file_key] = file # UNUSED type: ignore[attr-defined]
files[self._upload_data_key] = journal_id # UNUSED type: ignore

path = urljoin(f"{self.path}/", f"{journal_id}/upload")

response = await self.http_client.request( # UNUSED type: ignore[attr-defined]
"post",
path, # UNUSED type: ignore[attr-defined]
files=files,
force_multipart=True,
)

return self._model_class.from_response(
response
) # UNUSED type: ignore[attr-defined, no-any-return]

def attachments(self, journal_id: str) -> AsyncJournalAttachmentsService:
"""Return journal attachments service."""
return AsyncJournalAttachmentsService(
Expand All @@ -99,9 +152,3 @@ def charges(self, journal_id: str) -> AsyncJournalChargesService:
return AsyncJournalChargesService(
http_client=self.http_client, endpoint_params={"journal_id": journal_id}
)

def upload(self, journal_id: str) -> AsyncJournalUploadService:
"""Return journal upload service."""
return AsyncJournalUploadService(
http_client=self.http_client, endpoint_params={"journal_id": journal_id}
)
1 change: 1 addition & 0 deletions tests/data/test_billing_journal.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"externalIds": {"vendor": "ext-seeded-billing-sub-vendor-id", "invoice": "INV12345", "reference": "ORD-7924-7691-0805"}, "search": {"source": {"type": "Subscription", "criteria": "id", "value": "SUB-5839-4140-9574"},"order":{"criteria":"order.id","value":"ORD-7924-7691-0805"}, "item": {"criteria": "item.id", "value": "ITM-1767-7355-0001"}}, "period": {"start": "2025-12-22", "end": "2026-12-21"}, "price": {"unitPP": 10, "PPx1": 8.33}, "quantity": 10, "segment": "COM", "description": {"value1": "desc-1", "value2": "desc-2"}}
Binary file added tests/data/test_billing_journal.xlsx
Binary file not shown.
18 changes: 18 additions & 0 deletions tests/e2e/billing/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import pathlib

import pytest


@pytest.fixture
def billing_journal_fd():
file_path = pathlib.Path("tests/data/test_billing_journal.jsonl").resolve()
fd = file_path.open("rb")
try:
yield fd
finally:
fd.close()


@pytest.fixture
def billing_journal_id(e2e_config):
return e2e_config["billing.journal.id"]
21 changes: 21 additions & 0 deletions tests/e2e/billing/journal/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import pytest


@pytest.fixture
def invalid_billing_journal_id():
return "BJO-0000-0000"


@pytest.fixture
def billing_journal_factory(authorization_id):
def factory(
name: str = "E2E Created Billing Journal",
):
return {
"authorization": {"id": authorization_id},
"dueDate": "2026-01-02T19:00:00.000Z",
"externalIds": {},
"name": name,
}

return factory
111 changes: 111 additions & 0 deletions tests/e2e/billing/journal/test_async_journal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import pytest

from mpt_api_client.exceptions import MPTAPIError
from mpt_api_client.rql.query_builder import RQLQuery

pytestmark = [pytest.mark.flaky]


@pytest.fixture
async def created_billing_journal(async_mpt_vendor, billing_journal_factory):
new_billing_journal_request_data = billing_journal_factory(
name="E2E Created Billing Journal",
)

created_billing_journal = await async_mpt_vendor.billing.journals.create(
new_billing_journal_request_data
)

yield created_billing_journal

try:
await async_mpt_vendor.billing.journals.delete(created_billing_journal.id)
except MPTAPIError as error:
print(f"TEARDOWN - Unable to delete billing journal: {error.title}") # noqa: WPS421


@pytest.fixture
async def submitted_billing_journal(async_mpt_vendor, created_billing_journal, billing_journal_fd):
await async_mpt_vendor.billing.journals.submit(created_billing_journal.id)
await async_mpt_vendor.billing.journals.upload(
journal_id=created_billing_journal.id,
file=billing_journal_fd,
)

return created_billing_journal


@pytest.fixture
async def completed_billing_journal(async_mpt_vendor, submitted_billing_journal):
await async_mpt_vendor.billing.journals.accept(submitted_billing_journal.id)
await async_mpt_vendor.billing.journals.complete(submitted_billing_journal.id)
return submitted_billing_journal


async def test_get_billing_journal_by_id(async_mpt_vendor, billing_journal_id):
result = await async_mpt_vendor.billing.journals.get(billing_journal_id)

assert result is not None


async def test_list_billing_journals(async_mpt_vendor):
limit = 10

result = await async_mpt_vendor.billing.journals.fetch_page(limit=limit)

assert len(result) > 0


async def test_get_billing_journal_by_id_not_found(async_mpt_vendor, invalid_billing_journal_id):
with pytest.raises(MPTAPIError, match=r"404 Not Found"):
await async_mpt_vendor.billing.journals.get(invalid_billing_journal_id)


async def test_filter_billing_journals(async_mpt_vendor, billing_journal_id):
select_fields = ["-value"]
filtered_billing_journals = (
async_mpt_vendor.billing.journals.filter(RQLQuery(id=billing_journal_id))
.filter(RQLQuery(name="E2E Seeded Billing Journal"))
.select(*select_fields)
)

result = [billing_journal async for billing_journal in filtered_billing_journals.iterate()]

assert len(result) == 1


def test_create_billing_journal(created_billing_journal):
result = created_billing_journal

assert result is not None


async def test_update_billing_journal(
async_mpt_vendor, created_billing_journal, billing_journal_factory
):
updated_name = "E2E Updated Billing Journal Name"
updated_billing_journal_data = billing_journal_factory(name=updated_name)

result = await async_mpt_vendor.billing.journals.update(
created_billing_journal.id,
updated_billing_journal_data,
)

assert result.name == updated_name


async def test_delete_billing_journal(async_mpt_vendor, created_billing_journal):
result = created_billing_journal

await async_mpt_vendor.billing.journals.delete(result.id)


async def test_upload_billing_journal(
async_mpt_vendor, created_billing_journal, billing_journal_fd
):
result = await async_mpt_vendor.billing.journals.upload(
journal_id=created_billing_journal.id,
file=billing_journal_fd,
)

assert result is not None
Loading