Skip to content

Commit 409e2b5

Browse files
committed
add InstalmentLinkOpen
1 parent 07e1df1 commit 409e2b5

File tree

13 files changed

+286
-18
lines changed

13 files changed

+286
-18
lines changed

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.0.4
1+
0.0.5

procuret/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
from procuret.session import Session, Lifecycle, Perspective
2-
from procuret.instalment_link import InstalmentLink, CommunicationOption
2+
from procuret.ancillary.communication_option import CommunicationOption
3+
from procuret.instalment_link import InstalmentLink, InstalmentLinkOpen
4+
from procuret.instalment_link import InstalmentLinkOrderBy
5+
from procuret.data.order import Order
36
from procuret.ancillary.entity_headline import EntityHeadline
47
from procuret import errors

procuret/data/codable.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from typing import Callable
99
from enum import Enum
1010
from decimal import Decimal
11+
from procuret.data.decodable import Decodable
1112

1213
T = TypeVar('T', bound='Codable')
1314
K = TypeVar('K', bound='CodableType')
@@ -93,7 +94,7 @@ def decode(self, data: Any) -> Any:
9394
raise
9495

9596

96-
class Codable:
97+
class Codable(Decodable):
9798

9899
coding_map: Dict[str, CodingDefinition] = NotImplemented
99100

procuret/data/decodable.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""
2+
Procuret Python
3+
Decodable Module
4+
author: hugh@blinkybeach.com
5+
"""
6+
from json import loads
7+
from typing import Any, Optional, TypeVar, Type, List
8+
9+
T = TypeVar('T', bound='Decodable')
10+
11+
12+
class Decodable:
13+
"""Abstract protocol defining an interface for decodable classes"""
14+
15+
@classmethod
16+
def decode(self, data: Any) -> T:
17+
"""Return a JSON-serialisable form of the object"""
18+
raise NotImplementedError
19+
20+
@classmethod
21+
def optionally_decode(cls: Type[T], data: Optional[Any]) -> Optional[T]:
22+
"""Optionally return a decoded object from serialised data"""
23+
if data is None:
24+
return None
25+
return cls.decode(data)
26+
27+
@classmethod
28+
def deserialise(cls: Type[T], serial: str) -> T:
29+
"""Return a JSON string representation of the object"""
30+
return cls.decode(loads(serial))
31+
32+
@classmethod
33+
def optionally_deserialise(
34+
cls: Type[T],
35+
serial: Optional[str]
36+
) -> Optional[T]:
37+
if serial is None:
38+
return None
39+
return cls.deserialise(serial)
40+
41+
@classmethod
42+
def decode_many(cls: Type[T], data: Any) -> List[T]:
43+
"""Return list of decoded instances of an object"""
44+
return [cls.decode(d) for d in data]
45+
46+
@classmethod
47+
def optionally_decode_many(
48+
cls: Type[T],
49+
data: Optional[Any],
50+
default_to_empty_list: bool = False
51+
) -> Optional[List[T]]:
52+
"""Optionally return a list of decoded objects"""
53+
if data is None and default_to_empty_list is True:
54+
return list()
55+
if data is None:
56+
return None
57+
return cls.decode_many(data)

procuret/data/order.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""
2+
Procuret Python
3+
Order Enumeration Module
4+
author: hugh@blinkybeach.com
5+
"""
6+
from enum import Enum
7+
8+
9+
class Order(Enum):
10+
11+
ASCENDING = 'ascending'
12+
DESCENDING = 'descending'

procuret/http/api_request.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,15 @@ def make(
6565
session=session
6666
)
6767

68+
encoded_data: Optional[bytes] = None
6869
if data is not None:
69-
data = json.dumps(data).encode('utf-8')
70+
encoded_data = json.dumps(data).encode('utf-8')
7071
headers['Content-Type'] = 'application/json'
7172

7273
request = Request(
7374
url=url,
7475
method=method.value,
75-
data=data,
76+
data=encoded_data,
7677
headers=headers
7778
)
7879

procuret/http/query_parameter.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ def __init__(
2121
self._key = key
2222
self._value = value
2323

24+
self._url_representation = self._represent(value)
25+
2426
return
2527

2628
key = property(lambda s: s._key)
2729

2830
def __str__(self) -> str:
29-
return self._key + '=' + str(self._value)
31+
return self._key + '=' + self._url_representation
3032

3133
@classmethod
3234
def remove_targets_with(
@@ -43,3 +45,16 @@ def remove_targets_with(
4345
continue
4446

4547
return targets
48+
49+
@staticmethod
50+
def _represent(value: Any) -> str:
51+
52+
if isinstance(value, str):
53+
return value
54+
55+
if isinstance(value, bool):
56+
if value is True:
57+
return 'true'
58+
return 'false'
59+
60+
return str(value)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from procuret.instalment_link.instalment_link import InstalmentLink
2+
from procuret.instalment_link.open import InstalmentLinkOpen
3+
from procuret.instalment_link.instalment_link import (
4+
OrderBy as InstalmentLinkOrderBy
5+
)

procuret/instalment_link.py renamed to procuret/instalment_link/instalment_link.py

Lines changed: 100 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,51 +4,70 @@
44
author: hugh@blinkybeach.com
55
"""
66
from procuret.ancillary.communication_option import CommunicationOption
7-
from typing import TypeVar, Type, Union
7+
from typing import TypeVar, Type, Union, List
88
from procuret.data.codable import Codable, CodingDefinition as CD
99
from procuret.ancillary.entity_headline import EntityHeadline
1010
from procuret.errors.type_error import ProcuretTypeError
1111
from procuret.http.api_request import ApiRequest, HTTPMethod
1212
from procuret.errors.inconsistent import InconsistentState
1313
from procuret.session import Session
1414
from decimal import Decimal
15+
from enum import Enum
16+
from procuret.data.order import Order
17+
from procuret.http.query_parameters import QueryParameter, QueryParameters
18+
from typing import Optional
19+
from procuret.instalment_link.open import InstalmentLinkOpen
1520

16-
T = TypeVar('T', bound='InstalmentLink')
21+
22+
Self = TypeVar('Self', bound='InstalmentLink')
23+
24+
25+
class OrderBy(Enum):
26+
CREATED = 'created'
1727

1828

1929
class InstalmentLink(Codable):
2030

21-
PATH = '/instalment-link'
31+
path = '/instalment-link'
32+
list_path = path + '/list'
2233

2334
_LINK_TEMPLATE = 'https://procuret.com/business/signup?supplier_id={entity\
2435
_id}&presented_invoice_id={invoice_id}&presented_invoice_amount={invoice_amoun\
2536
t}'
2637

2738
coding_map = {
39+
'public_id': CD(str),
2840
'supplier': CD(EntityHeadline),
2941
'invoice_amount': CD(Decimal),
3042
'invitee_email': CD(str),
3143
'invoice_identifier': CD(str),
44+
'opens': CD(InstalmentLinkOpen, array=True)
3245
}
3346

3447
def __init__(
3548
self,
49+
public_id: str,
3650
supplier: EntityHeadline,
3751
invitee_email: str,
3852
invoice_amount: Decimal,
39-
invoice_identifier: str
53+
invoice_identifier: str,
54+
opens: List[InstalmentLinkOpen]
4055
) -> None:
4156

4257
self._supplier = supplier
58+
self._public_id = public_id
4359
self._invitee_email = invitee_email
4460
self._invoice_amount = invoice_amount
4561
self._invoice_identifier = invoice_identifier
62+
self._opens = opens
4663

4764
return
4865

66+
public_id = property(lambda s: s._public_id)
4967
invitee_email = property(lambda s: s._invitee_email)
5068
invoice_amount = property(lambda s: s._invoice_amount)
5169
invoice_identifier = property(lambda s: s._invoice_identifier)
70+
opens = property(lambda s: s._opens)
5271

5372
url = property(lambda s: s._LINK_TEMPLATE.format(
5473
entity_id=str(s._supplier.entity_id),
@@ -58,14 +77,14 @@ def __init__(
5877

5978
@classmethod
6079
def create(
61-
cls: Type[T],
80+
cls: Type[Self],
6281
supplier: Union[int, EntityHeadline],
6382
invoice_amount: Decimal,
6483
invitee_email: str,
6584
invoice_identifier: str,
6685
communication: CommunicationOption,
6786
session: Session
68-
) -> T:
87+
) -> Self:
6988

7089
def infer_supplier_id(x: Union[int, EntityHeadline]) -> int:
7190
if isinstance(x, int):
@@ -113,7 +132,7 @@ def infer_communication(y: CommunicationOption) -> bool:
113132
}
114133

115134
result = ApiRequest.make(
116-
path=cls.PATH,
135+
path=cls.path,
117136
method=HTTPMethod.POST,
118137
data=data,
119138
session=session,
@@ -124,3 +143,77 @@ def infer_communication(y: CommunicationOption) -> bool:
124143
raise InconsistentState
125144

126145
return cls.decode(result)
146+
147+
@classmethod
148+
def retrieve(
149+
cls: Type[Self],
150+
public_id: str,
151+
session: Session
152+
) -> Optional[Self]:
153+
154+
if not isinstance(public_id, str):
155+
raise TypeError('public_id must be a string')
156+
157+
result = cls.retrieve_many(
158+
public_id=public_id,
159+
session=session
160+
)
161+
162+
if len(result) < 1:
163+
return None
164+
165+
return result[0]
166+
167+
@classmethod
168+
def retrieve_many(
169+
cls: Type[Self],
170+
session: Session,
171+
public_id: Optional[str] = None,
172+
limit: int = 20,
173+
offset: int = 0,
174+
order: Order = Order.ASCENDING,
175+
order_by: OrderBy = OrderBy.CREATED,
176+
opened: Optional[bool] = None
177+
) -> List[Self]:
178+
179+
if not isinstance(limit, int):
180+
raise TypeError('limit must be an integer')
181+
182+
if not isinstance(offset, int):
183+
raise TypeError('offset must be an integer')
184+
185+
if not isinstance(order, Order):
186+
raise TypeError('order must be of type Order')
187+
188+
if not isinstance(order_by, OrderBy):
189+
raise TypeError('order must be of type InstalmentLink.OrderBy')
190+
191+
if not isinstance(session, Session):
192+
raise TypeError('session must be of type Session')
193+
194+
parameters = [
195+
QueryParameter('limit', limit),
196+
QueryParameter('offset', offset),
197+
QueryParameter('order', order.value),
198+
QueryParameter('order_by', order_by.value),
199+
]
200+
201+
if opened is not None:
202+
if not isinstance(opened, bool):
203+
raise TypeError('If supplied, opened must be a bool')
204+
parameters.append(QueryParameter('opened', opened))
205+
206+
if public_id is not None:
207+
if not isinstance(public_id, str):
208+
raise TypeError('If supplied, public_id must be str')
209+
parameters.append(QueryParameter('public_id', public_id))
210+
211+
result = ApiRequest.make(
212+
path=cls.list_path,
213+
method=HTTPMethod.GET,
214+
data=None,
215+
session=session,
216+
query_parameters=QueryParameters(parameters)
217+
)
218+
219+
return cls.optionally_decode_many(result, default_to_empty_list=True)

procuret/instalment_link/open.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""
2+
Procuret Python
3+
Instalment Link Open Module
4+
author: hugh@blinkybeach.com
5+
"""
6+
from procuret.time.time import ProcuretTime
7+
from procuret.data.codable import Codable, CodingDefinition as CD
8+
from typing import TypeVar, Type
9+
from procuret.http.api_request import ApiRequest, HTTPMethod
10+
from procuret.session import Session
11+
from procuret.errors.type_error import ProcuretTypeError
12+
13+
Self = TypeVar('Self', bound='InstalmentLinkOpen')
14+
15+
16+
class InstalmentLinkOpen(Codable):
17+
18+
path = '/instalment-link/open'
19+
20+
coding_map = {
21+
'sequence': CD(int),
22+
'created': CD(ProcuretTime)
23+
}
24+
25+
def __init__(
26+
self,
27+
sequence: int,
28+
created: ProcuretTime
29+
) -> None:
30+
31+
self._sequence = sequence
32+
self._created = created
33+
34+
return
35+
36+
sequence = property(lambda s: s._sequence)
37+
created = property(lambda s: s._created)
38+
39+
@classmethod
40+
def create(
41+
cls: Type[Self],
42+
link_id: str,
43+
session: Session
44+
) -> None:
45+
46+
if not isinstance(link_id, str):
47+
raise ProcuretTypeError('str', link_id, 'link_id')
48+
49+
if not isinstance(session, Session):
50+
raise ProcuretTypeError('Session', session, 'session')
51+
52+
result = ApiRequest.make(
53+
path=cls.path,
54+
method=HTTPMethod.POST,
55+
data={'link_id': link_id},
56+
session=session
57+
)
58+
59+
return None

0 commit comments

Comments
 (0)