Skip to content

Commit 082f128

Browse files
author
Robert Segal
committed
Added billing custom ledgers endpoints
1 parent e43c4bb commit 082f128

File tree

7 files changed

+291
-2
lines changed

7 files changed

+291
-2
lines changed

mpt_api_client/resources/billing/billing.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
from mpt_api_client.http import AsyncHTTPClient, HTTPClient
22
from mpt_api_client.resources.billing.invoices import AsyncInvoicesService, InvoicesService
3+
from mpt_api_client.resources.billing.custom_ledgers import (
4+
AsyncCustomLedgersService,
5+
CustomLedgersService,
6+
)
37
from mpt_api_client.resources.billing.journals import AsyncJournalsService, JournalsService
48
from mpt_api_client.resources.billing.ledgers import AsyncLedgersService, LedgersService
59
from mpt_api_client.resources.billing.statements import AsyncStatementsService, StatementsService
@@ -31,6 +35,11 @@ def invoices(self) -> InvoicesService:
3135
"""Invoices service."""
3236
return InvoicesService(http_client=self.http_client)
3337

38+
@property
39+
def custom_ledgers(self) -> CustomLedgersService:
40+
"""Custom ledgers service."""
41+
return CustomLedgersService(http_client=self.http_client)
42+
3443

3544
class AsyncBilling:
3645
"""Billing MPT API Module."""
@@ -57,3 +66,8 @@ def statements(self) -> AsyncStatementsService:
5766
def invoices(self) -> AsyncInvoicesService:
5867
"""Invoices service."""
5968
return AsyncInvoicesService(http_client=self.http_client)
69+
70+
@property
71+
def custom_ledgers(self) -> AsyncCustomLedgersService:
72+
"""Custom ledgers service."""
73+
return AsyncCustomLedgersService(http_client=self.http_client)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from mpt_api_client.http import AsyncService, Service
2+
from mpt_api_client.http.mixins import (
3+
AsyncCreateMixin,
4+
AsyncDeleteMixin,
5+
AsyncUpdateMixin,
6+
CreateMixin,
7+
DeleteMixin,
8+
UpdateMixin,
9+
)
10+
from mpt_api_client.models import Model
11+
from mpt_api_client.resources.billing.mixins import AcceptableMixin, AsyncAcceptableMixin
12+
13+
14+
class CustomLedger(Model):
15+
"""Custom Ledger resource."""
16+
17+
18+
class CustomLedgersServiceConfig:
19+
"""Custom Ledgers service configuration."""
20+
21+
_endpoint = "/public/v1/billing/custom-ledgers"
22+
_model_class = CustomLedger
23+
_collection_key = "data"
24+
25+
26+
class CustomLedgersService(
27+
CreateMixin[CustomLedger],
28+
DeleteMixin,
29+
UpdateMixin[CustomLedger],
30+
AcceptableMixin[CustomLedger],
31+
Service[CustomLedger],
32+
CustomLedgersServiceConfig,
33+
):
34+
"""Custom Ledgers service."""
35+
36+
37+
class AsyncCustomLedgersService(
38+
AsyncCreateMixin[CustomLedger],
39+
AsyncDeleteMixin,
40+
AsyncUpdateMixin[CustomLedger],
41+
AsyncAcceptableMixin[CustomLedger],
42+
AsyncService[CustomLedger],
43+
CustomLedgersServiceConfig,
44+
):
45+
"""Async Custom Ledgers service."""

mpt_api_client/resources/billing/mixins.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,3 +340,55 @@ async def recalculate(
340340
return await self._resource_action( # type: ignore[attr-defined, no-any-return]
341341
resource_id, "POST", "recalculate", json=resource_data
342342
)
343+
344+
345+
class AcceptableMixin[Model]:
346+
"""Acceptable mixin adds the ability to accept resources."""
347+
348+
def accept(self, resource_id: str, resource_data: ResourceData | None = None) -> Model:
349+
"""Accept resource.
350+
351+
Args:
352+
resource_id: Resource ID
353+
resource_data: Resource data will be updated
354+
"""
355+
return self._resource_action( # type: ignore[attr-defined, no-any-return]
356+
resource_id, "POST", "accept", json=resource_data
357+
)
358+
359+
def queue(self, resource_id: str, resource_data: ResourceData | None = None) -> Model:
360+
"""Queue resource.
361+
362+
Args:
363+
resource_id: Resource ID
364+
resource_data: Resource data will be updated
365+
"""
366+
return self._resource_action( # type: ignore[attr-defined, no-any-return]
367+
resource_id, "POST", "queue", json=resource_data
368+
)
369+
370+
371+
class AsyncAcceptableMixin[Model]:
372+
"""Acceptable mixin adds the ability to accept resources."""
373+
374+
async def accept(self, resource_id: str, resource_data: ResourceData | None = None) -> Model:
375+
"""Accept resource.
376+
377+
Args:
378+
resource_id: Resource ID
379+
resource_data: Resource data will be updated
380+
"""
381+
return await self._resource_action( # type: ignore[attr-defined, no-any-return]
382+
resource_id, "POST", "accept", json=resource_data
383+
)
384+
385+
async def queue(self, resource_id: str, resource_data: ResourceData | None = None) -> Model:
386+
"""Queue resource.
387+
388+
Args:
389+
resource_id: Resource ID
390+
resource_data: Resource data will be updated
391+
"""
392+
return await self._resource_action( # type: ignore[attr-defined, no-any-return]
393+
resource_id, "POST", "queue", json=resource_data
394+
)

tests/resources/billing/test_billing.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
from mpt_api_client.resources.billing.billing import AsyncBilling, Billing
44
from mpt_api_client.resources.billing.invoices import AsyncInvoicesService, InvoicesService
5+
from mpt_api_client.resources.billing.custom_ledgers import (
6+
AsyncCustomLedgersService,
7+
CustomLedgersService,
8+
)
59
from mpt_api_client.resources.billing.journals import AsyncJournalsService, JournalsService
610
from mpt_api_client.resources.billing.ledgers import AsyncLedgersService, LedgersService
711
from mpt_api_client.resources.billing.statements import AsyncStatementsService, StatementsService
@@ -24,6 +28,7 @@ def async_billing(async_http_client):
2428
("ledgers", LedgersService),
2529
("statements", StatementsService),
2630
("invoices", InvoicesService),
31+
("custom_ledgers", CustomLedgersService),
2732
],
2833
)
2934
def test_billing_properties(billing, property_name, expected_service_class):
@@ -41,6 +46,7 @@ def test_billing_properties(billing, property_name, expected_service_class):
4146
("ledgers", AsyncLedgersService),
4247
("statements", AsyncStatementsService),
4348
("invoices", AsyncInvoicesService),
49+
("custom_ledgers", AsyncCustomLedgersService),
4450
],
4551
)
4652
def test_async_billing_properties(async_billing, property_name, expected_service_class):
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import pytest
2+
3+
from mpt_api_client.resources.billing.custom_ledgers import (
4+
AsyncCustomLedgersService,
5+
CustomLedgersService,
6+
)
7+
8+
9+
@pytest.fixture
10+
def custom_ledgers_service(http_client):
11+
return CustomLedgersService(http_client=http_client)
12+
13+
14+
@pytest.fixture
15+
def async_custom_ledgers_service(http_client):
16+
return AsyncCustomLedgersService(http_client=http_client)
17+
18+
19+
@pytest.mark.parametrize("method", ["get", "create", "update", "delete", "accept", "queue"])
20+
def test_mixins_present(custom_ledgers_service, method):
21+
assert hasattr(custom_ledgers_service, method)
22+
23+
24+
@pytest.mark.parametrize("method", ["get", "create", "update", "delete", "accept", "queue"])
25+
def test_async_mixins_present(async_custom_ledgers_service, method):
26+
assert hasattr(async_custom_ledgers_service, method)

tests/resources/billing/test_journals.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@ def async_journals_service(async_http_client):
2727

2828
@pytest.mark.parametrize(
2929
"method",
30-
["get", "create", "update", "delete"],
30+
["get", "create", "update", "delete", "regenerate", "submit", "enquiry", "accept"],
3131
)
3232
def test_mixins_present(journals_service, method):
3333
assert hasattr(journals_service, method)
3434

3535

3636
@pytest.mark.parametrize(
3737
"method",
38-
["get", "create", "update", "delete"],
38+
["get", "create", "update", "delete", "regenerate", "submit", "enquiry", "accept"],
3939
)
4040
def test_async_mixins_present(async_journals_service, method):
4141
assert hasattr(async_journals_service, method)

tests/resources/billing/test_mixins.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from mpt_api_client.http.service import Service
77
from mpt_api_client.resources.billing.mixins import (
88
AsyncIssuableMixin,
9+
AcceptableMixin,
10+
AsyncAcceptableMixin,
911
AsyncRecalculatableMixin,
1012
AsyncRegeneratableMixin,
1113
IssuableMixin,
@@ -69,6 +71,24 @@ class DummyAsyncIssuableService(
6971
_collection_key = "data"
7072

7173

74+
class DummyAcceptableService(
75+
AcceptableMixin[DummyModel],
76+
Service[DummyModel],
77+
):
78+
_endpoint = "/public/v1/dummy/acceptable/"
79+
_model_class = DummyModel
80+
_collection_key = "data"
81+
82+
83+
class DummyAsyncAcceptableService(
84+
AsyncAcceptableMixin[DummyModel],
85+
AsyncService[DummyModel],
86+
):
87+
_endpoint = "/public/v1/dummy/acceptable/"
88+
_model_class = DummyModel
89+
_collection_key = "data"
90+
91+
7292
@pytest.fixture
7393
def regeneratable_service(http_client):
7494
return DummyRegeneratableService(http_client=http_client)
@@ -99,6 +119,16 @@ def async_issuable_service(async_http_client):
99119
return DummyAsyncIssuableService(http_client=async_http_client)
100120

101121

122+
@pytest.fixture
123+
def acceptable_service(http_client):
124+
return DummyAcceptableService(http_client=http_client)
125+
126+
127+
@pytest.fixture
128+
def async_acceptable_service(async_http_client):
129+
return DummyAsyncAcceptableService(http_client=async_http_client)
130+
131+
102132
@pytest.mark.parametrize(
103133
("action", "input_status"),
104134
[
@@ -493,3 +523,119 @@ async def test_async_issuable_resource_actions_no_data(
493523
assert request.content == request_expected_content
494524
assert issuable_obj.to_dict() == response_expected_data
495525
assert isinstance(issuable_obj, DummyModel)
526+
527+
528+
@pytest.mark.parametrize(
529+
("action", "input_status"),
530+
[
531+
("accept", {"id": "OBJ-0000-0001", "status": "update"}),
532+
("queue", {"id": "OBJ-0000-0001", "status": "update"}),
533+
],
534+
)
535+
def test_acceptable_resource_actions(acceptable_service, action, input_status):
536+
request_expected_content = b'{"id":"OBJ-0000-0001","status":"update"}'
537+
response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"}
538+
with respx.mock:
539+
mock_route = respx.post(
540+
f"https://api.example.com/public/v1/dummy/acceptable/OBJ-0000-0001/{action}"
541+
).mock(
542+
return_value=httpx.Response(
543+
status_code=httpx.codes.OK,
544+
headers={"content-type": "application/json"},
545+
json=response_expected_data,
546+
)
547+
)
548+
accept_obj = getattr(acceptable_service, action)("OBJ-0000-0001", input_status)
549+
550+
assert mock_route.call_count == 1
551+
request = mock_route.calls[0].request
552+
553+
assert request.content == request_expected_content
554+
assert accept_obj.to_dict() == response_expected_data
555+
assert isinstance(accept_obj, DummyModel)
556+
557+
558+
@pytest.mark.parametrize(
559+
("action", "input_status"),
560+
[("accept", None), ("queue", None)],
561+
)
562+
def test_acceptable_resource_actions_no_data(acceptable_service, action, input_status):
563+
request_expected_content = b""
564+
response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"}
565+
with respx.mock:
566+
mock_route = respx.post(
567+
f"https://api.example.com/public/v1/dummy/acceptable/OBJ-0000-0001/{action}"
568+
).mock(
569+
return_value=httpx.Response(
570+
status_code=httpx.codes.OK,
571+
headers={"content-type": "application/json"},
572+
json=response_expected_data,
573+
)
574+
)
575+
accept_obj = getattr(acceptable_service, action)("OBJ-0000-0001", input_status)
576+
577+
assert mock_route.call_count == 1
578+
request = mock_route.calls[0].request
579+
580+
assert request.content == request_expected_content
581+
assert accept_obj.to_dict() == response_expected_data
582+
assert isinstance(accept_obj, DummyModel)
583+
584+
585+
@pytest.mark.parametrize(
586+
("action", "input_status"),
587+
[
588+
("accept", {"id": "OBJ-0000-0001", "status": "update"}),
589+
("queue", {"id": "OBJ-0000-0001", "status": "update"}),
590+
],
591+
)
592+
async def test_async_acceptable_resource_actions(async_acceptable_service, action, input_status):
593+
request_expected_content = b'{"id":"OBJ-0000-0001","status":"update"}'
594+
response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"}
595+
with respx.mock:
596+
mock_route = respx.post(
597+
f"https://api.example.com/public/v1/dummy/acceptable/OBJ-0000-0001/{action}"
598+
).mock(
599+
return_value=httpx.Response(
600+
status_code=httpx.codes.OK,
601+
headers={"content-type": "application/json"},
602+
json=response_expected_data,
603+
)
604+
)
605+
accept_obj = await getattr(async_acceptable_service, action)("OBJ-0000-0001", input_status)
606+
607+
assert mock_route.call_count == 1
608+
request = mock_route.calls[0].request
609+
610+
assert request.content == request_expected_content
611+
assert accept_obj.to_dict() == response_expected_data
612+
assert isinstance(accept_obj, DummyModel)
613+
614+
615+
@pytest.mark.parametrize(
616+
("action", "input_status"),
617+
[("accept", None), ("queue", None)],
618+
)
619+
async def test_async_acceptable_resource_actions_no_data(
620+
async_acceptable_service, action, input_status
621+
):
622+
request_expected_content = b""
623+
response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"}
624+
with respx.mock:
625+
mock_route = respx.post(
626+
f"https://api.example.com/public/v1/dummy/acceptable/OBJ-0000-0001/{action}"
627+
).mock(
628+
return_value=httpx.Response(
629+
status_code=httpx.codes.OK,
630+
headers={"content-type": "application/json"},
631+
json=response_expected_data,
632+
)
633+
)
634+
accept_obj = await getattr(async_acceptable_service, action)("OBJ-0000-0001", input_status)
635+
636+
assert mock_route.call_count == 1
637+
request = mock_route.calls[0].request
638+
639+
assert request.content == request_expected_content
640+
assert accept_obj.to_dict() == response_expected_data
641+
assert isinstance(accept_obj, DummyModel)

0 commit comments

Comments
 (0)