Skip to content

Commit 1d6ec75

Browse files
author
Robert Segal
committed
Added billing custom ledgers endpoints
1 parent 835e706 commit 1d6ec75

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,4 +1,8 @@
11
from mpt_api_client.http import AsyncHTTPClient, HTTPClient
2+
from mpt_api_client.resources.billing.custom_ledgers import (
3+
AsyncCustomLedgersService,
4+
CustomLedgersService,
5+
)
26
from mpt_api_client.resources.billing.journals import AsyncJournalsService, JournalsService
37
from mpt_api_client.resources.billing.ledgers import AsyncLedgersService, LedgersService
48

@@ -19,6 +23,11 @@ def ledgers(self) -> LedgersService:
1923
"""Ledgers service."""
2024
return LedgersService(http_client=self.http_client)
2125

26+
@property
27+
def custom_ledgers(self) -> CustomLedgersService:
28+
"""Custom ledgers service."""
29+
return CustomLedgersService(http_client=self.http_client)
30+
2231

2332
class AsyncBilling:
2433
"""Billing MPT API Module."""
@@ -35,3 +44,8 @@ def journals(self) -> AsyncJournalsService:
3544
def ledgers(self) -> AsyncLedgersService:
3645
"""Ledgers service."""
3746
return AsyncLedgersService(http_client=self.http_client)
47+
48+
@property
49+
def custom_ledgers(self) -> AsyncCustomLedgersService:
50+
"""Custom ledgers service."""
51+
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
@@ -174,3 +174,55 @@ async def queue(self, resource_id: str, resource_data: ResourceData | None = Non
174174
return await self._resource_action( # type: ignore[attr-defined, no-any-return]
175175
resource_id, "POST", "queue", json=resource_data
176176
)
177+
178+
179+
class AcceptableMixin[Model]:
180+
"""Acceptable mixin adds the ability to accept resources."""
181+
182+
def accept(self, resource_id: str, resource_data: ResourceData | None = None) -> Model:
183+
"""Accept resource.
184+
185+
Args:
186+
resource_id: Resource ID
187+
resource_data: Resource data will be updated
188+
"""
189+
return self._resource_action( # type: ignore[attr-defined, no-any-return]
190+
resource_id, "POST", "accept", json=resource_data
191+
)
192+
193+
def queue(self, resource_id: str, resource_data: ResourceData | None = None) -> Model:
194+
"""Queue resource.
195+
196+
Args:
197+
resource_id: Resource ID
198+
resource_data: Resource data will be updated
199+
"""
200+
return self._resource_action( # type: ignore[attr-defined, no-any-return]
201+
resource_id, "POST", "queue", json=resource_data
202+
)
203+
204+
205+
class AsyncAcceptableMixin[Model]:
206+
"""Acceptable mixin adds the ability to accept resources."""
207+
208+
async def accept(self, resource_id: str, resource_data: ResourceData | None = None) -> Model:
209+
"""Accept resource.
210+
211+
Args:
212+
resource_id: Resource ID
213+
resource_data: Resource data will be updated
214+
"""
215+
return await self._resource_action( # type: ignore[attr-defined, no-any-return]
216+
resource_id, "POST", "accept", json=resource_data
217+
)
218+
219+
async def queue(self, resource_id: str, resource_data: ResourceData | None = None) -> Model:
220+
"""Queue resource.
221+
222+
Args:
223+
resource_id: Resource ID
224+
resource_data: Resource data will be updated
225+
"""
226+
return await self._resource_action( # type: ignore[attr-defined, no-any-return]
227+
resource_id, "POST", "queue", json=resource_data
228+
)

tests/resources/billing/test_billing.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import pytest
22

33
from mpt_api_client.resources.billing.billing import AsyncBilling, Billing
4+
from mpt_api_client.resources.billing.custom_ledgers import (
5+
AsyncCustomLedgersService,
6+
CustomLedgersService,
7+
)
48
from mpt_api_client.resources.billing.journals import AsyncJournalsService, JournalsService
59
from mpt_api_client.resources.billing.ledgers import AsyncLedgersService, LedgersService
610

@@ -20,6 +24,7 @@ def async_billing(async_http_client):
2024
[
2125
("journals", JournalsService),
2226
("ledgers", LedgersService),
27+
("custom_ledgers", CustomLedgersService),
2328
],
2429
)
2530
def test_billing_properties(billing, property_name, expected_service_class):
@@ -35,6 +40,7 @@ def test_billing_properties(billing, property_name, expected_service_class):
3540
[
3641
("journals", AsyncJournalsService),
3742
("ledgers", AsyncLedgersService),
43+
("custom_ledgers", AsyncCustomLedgersService),
3844
],
3945
)
4046
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
@@ -19,15 +19,15 @@ def async_journals_service(async_http_client):
1919

2020
@pytest.mark.parametrize(
2121
"method",
22-
["get", "create", "update", "delete"],
22+
["get", "create", "update", "delete", "regenerate", "submit", "enquiry", "accept"],
2323
)
2424
def test_mixins_present(journals_service, method):
2525
assert hasattr(journals_service, method)
2626

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_async_mixins_present(async_journals_service, method):
3333
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
@@ -5,6 +5,8 @@
55
from mpt_api_client.http.async_service import AsyncService
66
from mpt_api_client.http.service import Service
77
from mpt_api_client.resources.billing.mixins import (
8+
AcceptableMixin,
9+
AsyncAcceptableMixin,
810
AsyncRecalculatableMixin,
911
AsyncRegeneratableMixin,
1012
RecalculatableMixin,
@@ -49,6 +51,24 @@ class DummyAsyncRecalculatableService(
4951
_collection_key = "data"
5052

5153

54+
class DummyAcceptableService(
55+
AcceptableMixin[DummyModel],
56+
Service[DummyModel],
57+
):
58+
_endpoint = "/public/v1/dummy/acceptable/"
59+
_model_class = DummyModel
60+
_collection_key = "data"
61+
62+
63+
class DummyAsyncAcceptableService(
64+
AsyncAcceptableMixin[DummyModel],
65+
AsyncService[DummyModel],
66+
):
67+
_endpoint = "/public/v1/dummy/acceptable/"
68+
_model_class = DummyModel
69+
_collection_key = "data"
70+
71+
5272
@pytest.fixture
5373
def regeneratable_service(http_client):
5474
return DummyRegeneratableService(http_client=http_client)
@@ -69,6 +89,16 @@ def async_recalculatable_service(async_http_client):
6989
return DummyAsyncRecalculatableService(http_client=async_http_client)
7090

7191

92+
@pytest.fixture
93+
def acceptable_service(http_client):
94+
return DummyAcceptableService(http_client=http_client)
95+
96+
97+
@pytest.fixture
98+
def async_acceptable_service(async_http_client):
99+
return DummyAsyncAcceptableService(http_client=async_http_client)
100+
101+
72102
@pytest.mark.parametrize(
73103
("action", "input_status"),
74104
[
@@ -321,3 +351,119 @@ async def test_async_recalculate_resource_actions_no_data(
321351
assert request.content == request_expected_content
322352
assert recalc_obj.to_dict() == response_expected_data
323353
assert isinstance(recalc_obj, DummyModel)
354+
355+
356+
@pytest.mark.parametrize(
357+
("action", "input_status"),
358+
[
359+
("accept", {"id": "OBJ-0000-0001", "status": "update"}),
360+
("queue", {"id": "OBJ-0000-0001", "status": "update"}),
361+
],
362+
)
363+
def test_acceptable_resource_actions(acceptable_service, action, input_status):
364+
request_expected_content = b'{"id":"OBJ-0000-0001","status":"update"}'
365+
response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"}
366+
with respx.mock:
367+
mock_route = respx.post(
368+
f"https://api.example.com/public/v1/dummy/acceptable/OBJ-0000-0001/{action}"
369+
).mock(
370+
return_value=httpx.Response(
371+
status_code=httpx.codes.OK,
372+
headers={"content-type": "application/json"},
373+
json=response_expected_data,
374+
)
375+
)
376+
accept_obj = getattr(acceptable_service, action)("OBJ-0000-0001", input_status)
377+
378+
assert mock_route.call_count == 1
379+
request = mock_route.calls[0].request
380+
381+
assert request.content == request_expected_content
382+
assert accept_obj.to_dict() == response_expected_data
383+
assert isinstance(accept_obj, DummyModel)
384+
385+
386+
@pytest.mark.parametrize(
387+
("action", "input_status"),
388+
[("accept", None), ("queue", None)],
389+
)
390+
def test_acceptable_resource_actions_no_data(acceptable_service, action, input_status):
391+
request_expected_content = b""
392+
response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"}
393+
with respx.mock:
394+
mock_route = respx.post(
395+
f"https://api.example.com/public/v1/dummy/acceptable/OBJ-0000-0001/{action}"
396+
).mock(
397+
return_value=httpx.Response(
398+
status_code=httpx.codes.OK,
399+
headers={"content-type": "application/json"},
400+
json=response_expected_data,
401+
)
402+
)
403+
accept_obj = getattr(acceptable_service, action)("OBJ-0000-0001", input_status)
404+
405+
assert mock_route.call_count == 1
406+
request = mock_route.calls[0].request
407+
408+
assert request.content == request_expected_content
409+
assert accept_obj.to_dict() == response_expected_data
410+
assert isinstance(accept_obj, DummyModel)
411+
412+
413+
@pytest.mark.parametrize(
414+
("action", "input_status"),
415+
[
416+
("accept", {"id": "OBJ-0000-0001", "status": "update"}),
417+
("queue", {"id": "OBJ-0000-0001", "status": "update"}),
418+
],
419+
)
420+
async def test_async_acceptable_resource_actions(async_acceptable_service, action, input_status):
421+
request_expected_content = b'{"id":"OBJ-0000-0001","status":"update"}'
422+
response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"}
423+
with respx.mock:
424+
mock_route = respx.post(
425+
f"https://api.example.com/public/v1/dummy/acceptable/OBJ-0000-0001/{action}"
426+
).mock(
427+
return_value=httpx.Response(
428+
status_code=httpx.codes.OK,
429+
headers={"content-type": "application/json"},
430+
json=response_expected_data,
431+
)
432+
)
433+
accept_obj = await getattr(async_acceptable_service, action)("OBJ-0000-0001", input_status)
434+
435+
assert mock_route.call_count == 1
436+
request = mock_route.calls[0].request
437+
438+
assert request.content == request_expected_content
439+
assert accept_obj.to_dict() == response_expected_data
440+
assert isinstance(accept_obj, DummyModel)
441+
442+
443+
@pytest.mark.parametrize(
444+
("action", "input_status"),
445+
[("accept", None), ("queue", None)],
446+
)
447+
async def test_async_acceptable_resource_actions_no_data(
448+
async_acceptable_service, action, input_status
449+
):
450+
request_expected_content = b""
451+
response_expected_data = {"id": "OBJ-0000-0001", "status": "new_status"}
452+
with respx.mock:
453+
mock_route = respx.post(
454+
f"https://api.example.com/public/v1/dummy/acceptable/OBJ-0000-0001/{action}"
455+
).mock(
456+
return_value=httpx.Response(
457+
status_code=httpx.codes.OK,
458+
headers={"content-type": "application/json"},
459+
json=response_expected_data,
460+
)
461+
)
462+
accept_obj = await getattr(async_acceptable_service, action)("OBJ-0000-0001", input_status)
463+
464+
assert mock_route.call_count == 1
465+
request = mock_route.calls[0].request
466+
467+
assert request.content == request_expected_content
468+
assert accept_obj.to_dict() == response_expected_data
469+
assert isinstance(accept_obj, DummyModel)

0 commit comments

Comments
 (0)