diff --git a/.gitignore b/.gitignore index d951f68c..f2319112 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ share/python-wheels/ .installed.cfg *.egg MANIFEST +.venv # PyInstaller # Usually these files are written by a python script from a template diff --git a/lago_python_client/credit_notes/clients.py b/lago_python_client/credit_notes/clients.py index 13ea4526..4a195111 100644 --- a/lago_python_client/credit_notes/clients.py +++ b/lago_python_client/credit_notes/clients.py @@ -2,7 +2,8 @@ from ..base_client import BaseClient from ..mixins import CreateCommandMixin, FindAllCommandMixin, FindCommandMixin, UpdateCommandMixin -from ..models.credit_note import CreditNoteResponse +from ..models.credit_note import CreditNoteResponse, CreditNoteEstimatedResponse, CreditNoteEstimate +from ..services.json import to_json from ..services.request import make_headers, make_url, send_post_request, send_put_request from ..services.response import get_response_data, prepare_object_response, Response @@ -15,6 +16,7 @@ class CreditNoteClient( BaseClient, ): API_RESOURCE: ClassVar[str] = 'credit_notes' + ESTIMATE_API_RESOURCE: ClassVar[str] = 'estimated_credit_note' RESPONSE_MODEL: ClassVar[Type[CreditNoteResponse]] = CreditNoteResponse ROOT_NAME: ClassVar[str] = 'credit_note' @@ -49,3 +51,20 @@ def void(self, resource_id: str) -> CreditNoteResponse: response_model=self.RESPONSE_MODEL, data=get_response_data(response=api_response, key=self.ROOT_NAME), ) + + def estimate(self, input_object: CreditNoteEstimate) -> CreditNoteEstimatedResponse: + api_response: Response = send_post_request( + url=make_url( + origin=self.base_url, + path_parts=(self.API_RESOURCE, 'estimate'), + ), + content=to_json({ + self.ROOT_NAME: input_object.dict(), + }), + headers=make_headers(api_key=self.api_key), + ) + + return prepare_object_response( + response_model=CreditNoteEstimatedResponse, + data=get_response_data(response=api_response, key=self.ESTIMATE_API_RESOURCE), + ) diff --git a/lago_python_client/models/__init__.py b/lago_python_client/models/__init__.py index 9f331a25..21753244 100644 --- a/lago_python_client/models/__init__.py +++ b/lago_python_client/models/__init__.py @@ -3,7 +3,7 @@ from .charge import Charge, Charges, ChargesResponse from .coupon import Coupon, LimitationConfiguration from .credit import CreditResponse, CreditsResponse -from .credit_note import Item, Items, CreditNote, CreditNoteUpdate +from .credit_note import Item, Items, CreditNote, CreditNoteUpdate, CreditNoteEstimate from .plan import Plan from .add_on import AddOn from .organization import Organization, OrganizationBillingConfiguration diff --git a/lago_python_client/models/credit_note.py b/lago_python_client/models/credit_note.py index e2722b2f..b8accf5c 100644 --- a/lago_python_client/models/credit_note.py +++ b/lago_python_client/models/credit_note.py @@ -8,10 +8,8 @@ class ItemResponse(BaseResponseModel): lago_id: Optional[str] - credit_amount_cents: Optional[int] - credit_amount_currency: Optional[str] - refund_amount_cents: Optional[int] - refund_amount_currency: Optional[str] + amount_cents: Optional[int] + amount_currency: Optional[str] fee: Optional[FeeResponse] @@ -63,8 +61,7 @@ class CreditNoteResponse(BaseResponseModel): class Item(BaseModel): - credit_amount_cents: Optional[int] - refund_amount_cents: Optional[int] + amount_cents: Optional[int] fee_id: Optional[str] @@ -79,3 +76,46 @@ class CreditNote(BaseModel): class CreditNoteUpdate(BaseModel): refund_status: Optional[str] + + +class EstimatedItemResponse(BaseResponseModel): + amount_cents: Optional[int] + lago_fee_id: Optional[str] + + +class EstimatedItemsResponse(BaseResponseModel): + __root__: List[EstimatedItemResponse] + + +class CreditNoteEstimatedAppliedTax(BaseResponseModel): + lago_tax_id: Optional[str] + tax_name: Optional[str] + tax_code: Optional[str] + tax_rate: Optional[float] + tax_description: Optional[str] + amount_cents: Optional[int] + amount_currency: Optional[str] + base_amount_cents: Optional[int] + + +class CreditNoteEstimatedAppliedTaxes(BaseResponseModel): + __root__: List[CreditNoteEstimatedAppliedTax] + + +class CreditNoteEstimatedResponse(BaseResponseModel): + lago_invoice_id: str + invoice_number: str + currency: str + max_creditable_amount_cents: int + max_refundable_amount_cents: int + taxes_amount_cents: str + taxes_rate: float + sub_total_excluding_taxes_amount_cents: int + coupons_adjustment_amount_cents: int + items: EstimatedItemsResponse + applied_taxes: CreditNoteEstimatedAppliedTaxes + + +class CreditNoteEstimate(BaseModel): + invoice_id: str + items: Items diff --git a/tests/fixtures/credit_note_estimated.json b/tests/fixtures/credit_note_estimated.json new file mode 100644 index 00000000..9018722c --- /dev/null +++ b/tests/fixtures/credit_note_estimated.json @@ -0,0 +1,20 @@ +{ + "estimated_credit_note": { + "lago_invoice_id": "1a901a90-1a90-1a90-1a90-1a901a901a90", + "invoice_number": "LAG15", + "currency": "EUR", + "max_creditable_amount_cents": 100, + "max_refundable_amount_cents": 100, + "taxes_amount_cents": 40, + "taxes_rate": "20.0", + "sub_total_excluding_taxes_amount_cents": 200, + "coupons_adjustment_amount_cents": 0, + "items": [ + { + "lago_fee_id": "1a901a90-1a90-1a90-1a90-1a901a901a90", + "amount_cents": 100 + } + ], + "applied_taxes": [] + } +} diff --git a/tests/test_credit_note_client.py b/tests/test_credit_note_client.py index 2e3b39ba..29b1cc6d 100644 --- a/tests/test_credit_note_client.py +++ b/tests/test_credit_note_client.py @@ -6,20 +6,18 @@ from lago_python_client.client import Client from lago_python_client.exceptions import LagoApiError from lago_python_client.models.fee import FeeResponse -from lago_python_client.models.credit_note import Item, Items, CreditNote, CreditNoteUpdate +from lago_python_client.models.credit_note import Item, Items, CreditNote, CreditNoteUpdate, CreditNoteEstimate def credit_note_object(): item1 = Item( fee_id="fee_id_1", - credit_amount_cents=10, - refund_amount_cents=10, + amount_cents=10 ) item2 = Item( fee="fee_id_2", - credit_amount_cents=5, - refund_amount_cents=5, + amount_cents=5 ) return CreditNote( @@ -33,6 +31,23 @@ def credit_note_update_object(): return CreditNoteUpdate(refund_status='pending') +def estimate_credit_note(): + item1 = Item( + fee_id="fee_id_1", + amount_cents=10, + ) + + item2 = Item( + fee="fee_id_2", + amount_cents=5, + ) + + return CreditNoteEstimate( + invoice_id='1a901a90-1a90-1a90-1a90-1a901a901a90', + items= Items(__root__=[item1, item2]) + ) + + def mock_response(): current_dir = os.path.dirname(os.path.abspath(__file__)) data_path = os.path.join(current_dir, 'fixtures/credit_note.json') @@ -49,6 +64,14 @@ def mock_collection_response(): return credit_notes_response.read() +def mock_estimated_response(): + current_dir = os.path.dirname(os.path.abspath(__file__)) + data_path = os.path.join(current_dir, 'fixtures/credit_note_estimated.json') + + with open(data_path, 'rb') as credit_note_estimated_response: + return credit_note_estimated_response.read() + + def test_valid_find_credit_note_request(httpx_mock: HTTPXMock): client = Client(api_key='886fe239-927d-4072-ab72-6dd345e8dd0d') identifier = '5eb02857-a71e-4ea2-bcf9-57d3a41bc6ba' @@ -131,3 +154,12 @@ def test_valid_void_credit_note_request(httpx_mock: HTTPXMock): assert response.lago_id == "5eb02857-a71e-4ea2-bcf9-57d3a41bc6ba" assert response.refund_status == 'pending' + + +def test_valid_estimate_credit_note_request(httpx_mock: HTTPXMock): + client = Client(api_key='886fe239-927d-4072-ab72-6dd345e8dd0d') + + httpx_mock.add_response(method='POST', url='https://api.getlago.com/api/v1/credit_notes/estimate', content=mock_estimated_response()) + response = client.credit_notes.estimate(estimate_credit_note()) + + assert response.lago_invoice_id == '1a901a90-1a90-1a90-1a90-1a901a901a90'