ebarimt-pos-sdk 0.0.1b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ebarimt_pos_sdk/__init__.py +41 -0
- ebarimt_pos_sdk/client.py +104 -0
- ebarimt_pos_sdk/errors.py +65 -0
- ebarimt_pos_sdk/py.typed +0 -0
- ebarimt_pos_sdk/resources/__init__.py +6 -0
- ebarimt_pos_sdk/resources/bank_accounts/bank_accounts.py +54 -0
- ebarimt_pos_sdk/resources/bank_accounts/schema.py +48 -0
- ebarimt_pos_sdk/resources/info/info.py +30 -0
- ebarimt_pos_sdk/resources/info/schema.py +61 -0
- ebarimt_pos_sdk/resources/receipt/__init__.py +37 -0
- ebarimt_pos_sdk/resources/receipt/receipt.py +95 -0
- ebarimt_pos_sdk/resources/receipt/schema.py +181 -0
- ebarimt_pos_sdk/resources/resource.py +66 -0
- ebarimt_pos_sdk/resources/send_data/send_data.py +30 -0
- ebarimt_pos_sdk/resources/third_party/third_party.py +0 -0
- ebarimt_pos_sdk/settings.py +32 -0
- ebarimt_pos_sdk/transport/__init__.py +20 -0
- ebarimt_pos_sdk/transport/async_transport.py +48 -0
- ebarimt_pos_sdk/transport/http.py +51 -0
- ebarimt_pos_sdk/transport/sync_transport.py +39 -0
- ebarimt_pos_sdk/utils.py +0 -0
- ebarimt_pos_sdk-0.0.1b2.dist-info/METADATA +68 -0
- ebarimt_pos_sdk-0.0.1b2.dist-info/RECORD +24 -0
- ebarimt_pos_sdk-0.0.1b2.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from .client import PosApiClient
|
|
2
|
+
from .resources.receipt import (
|
|
3
|
+
BarCodeType,
|
|
4
|
+
CreateReceiptRequest,
|
|
5
|
+
CreateReceiptResponse,
|
|
6
|
+
DeleteReceiptRequest,
|
|
7
|
+
DeleteReceiptResponse,
|
|
8
|
+
Item,
|
|
9
|
+
Payment,
|
|
10
|
+
PaymentCardData,
|
|
11
|
+
PaymentCode,
|
|
12
|
+
PaymentStatus,
|
|
13
|
+
Receipt,
|
|
14
|
+
ReceiptCreateStatus,
|
|
15
|
+
ReceiptItemData,
|
|
16
|
+
ReceiptItemResponse,
|
|
17
|
+
ReceiptType,
|
|
18
|
+
TaxType,
|
|
19
|
+
)
|
|
20
|
+
from .settings import PosApiSettings
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"PosApiClient",
|
|
24
|
+
"PosApiSettings",
|
|
25
|
+
"BarCodeType",
|
|
26
|
+
"CreateReceiptRequest",
|
|
27
|
+
"CreateReceiptResponse",
|
|
28
|
+
"DeleteReceiptRequest",
|
|
29
|
+
"DeleteReceiptResponse",
|
|
30
|
+
"Item",
|
|
31
|
+
"Payment",
|
|
32
|
+
"PaymentCardData",
|
|
33
|
+
"PaymentCode",
|
|
34
|
+
"PaymentStatus",
|
|
35
|
+
"Receipt",
|
|
36
|
+
"ReceiptCreateStatus",
|
|
37
|
+
"ReceiptItemData",
|
|
38
|
+
"ReceiptItemResponse",
|
|
39
|
+
"ReceiptType",
|
|
40
|
+
"TaxType",
|
|
41
|
+
]
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from types import TracebackType
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from .resources import BankAccountsResource, InfoResource, ReceiptResource, SendDataResource
|
|
8
|
+
from .settings import PosApiSettings
|
|
9
|
+
from .transport import AsyncTransport, HeaderTypes, SyncTransport
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PosApiClient:
|
|
13
|
+
"""
|
|
14
|
+
Dual sync/async client.
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
client = PosApiClient(PosApiSettings(...))
|
|
18
|
+
resp = client.receipt.create({...})
|
|
19
|
+
|
|
20
|
+
async with PosApiClient(...) as client:
|
|
21
|
+
resp = await client.receipt.acreate({...})
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
settings: PosApiSettings,
|
|
27
|
+
*,
|
|
28
|
+
sync_client: httpx.Client | None = None,
|
|
29
|
+
async_client: httpx.AsyncClient | None = None,
|
|
30
|
+
headers: HeaderTypes | None = None,
|
|
31
|
+
) -> None:
|
|
32
|
+
self._settings = settings
|
|
33
|
+
self._headers = headers
|
|
34
|
+
|
|
35
|
+
self._owns_sync = sync_client is None
|
|
36
|
+
self._owns_async = async_client is None
|
|
37
|
+
|
|
38
|
+
self._base_url = self._settings.base_url
|
|
39
|
+
|
|
40
|
+
self._sync_client = sync_client or httpx.Client(
|
|
41
|
+
base_url=self._base_url,
|
|
42
|
+
timeout=settings.timeout_s,
|
|
43
|
+
verify=settings.verify_tls,
|
|
44
|
+
)
|
|
45
|
+
self._async_client = async_client or httpx.AsyncClient(
|
|
46
|
+
base_url=self._base_url,
|
|
47
|
+
timeout=settings.timeout_s,
|
|
48
|
+
verify=settings.verify_tls,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
self._sync_transport = SyncTransport(self._sync_client)
|
|
52
|
+
self._async_transport = AsyncTransport(self._async_client)
|
|
53
|
+
|
|
54
|
+
# Resources
|
|
55
|
+
self.receipt = ReceiptResource(
|
|
56
|
+
sync=self._sync_transport,
|
|
57
|
+
async_=self._async_transport,
|
|
58
|
+
headers=self._headers,
|
|
59
|
+
)
|
|
60
|
+
self.info = InfoResource(
|
|
61
|
+
sync=self._sync_transport,
|
|
62
|
+
async_=self._async_transport,
|
|
63
|
+
headers=self._headers,
|
|
64
|
+
)
|
|
65
|
+
self.send_data = SendDataResource(
|
|
66
|
+
sync=self._sync_transport,
|
|
67
|
+
async_=self._async_transport,
|
|
68
|
+
headers=self._headers,
|
|
69
|
+
)
|
|
70
|
+
self.bank_accounts = BankAccountsResource(
|
|
71
|
+
sync=self._sync_transport,
|
|
72
|
+
async_=self._async_transport,
|
|
73
|
+
headers=self._headers,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def close(self) -> None:
|
|
77
|
+
if self._owns_sync:
|
|
78
|
+
self._sync_client.close()
|
|
79
|
+
|
|
80
|
+
async def aclose(self) -> None:
|
|
81
|
+
if self._owns_async:
|
|
82
|
+
await self._async_client.aclose()
|
|
83
|
+
|
|
84
|
+
async def __aenter__(self) -> PosApiClient:
|
|
85
|
+
return self
|
|
86
|
+
|
|
87
|
+
async def __aexit__(
|
|
88
|
+
self,
|
|
89
|
+
exc_type: type[BaseException] | None,
|
|
90
|
+
exc: BaseException | None,
|
|
91
|
+
tb: TracebackType | None,
|
|
92
|
+
) -> None:
|
|
93
|
+
await self.aclose()
|
|
94
|
+
|
|
95
|
+
def __enter__(self) -> PosApiClient:
|
|
96
|
+
return self
|
|
97
|
+
|
|
98
|
+
def __exit__(
|
|
99
|
+
self,
|
|
100
|
+
exc_type: type[BaseException] | None,
|
|
101
|
+
exc: BaseException | None,
|
|
102
|
+
tb: TracebackType | None,
|
|
103
|
+
) -> None:
|
|
104
|
+
self.close()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Ebarimt Pos API sdk errors."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PosApiError(Exception):
|
|
9
|
+
"""Base error for the SDK."""
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
message: str,
|
|
14
|
+
*,
|
|
15
|
+
request: httpx.Request | None = None,
|
|
16
|
+
response: httpx.Response | None = None,
|
|
17
|
+
) -> None:
|
|
18
|
+
super().__init__(message)
|
|
19
|
+
self.request = request
|
|
20
|
+
self.response = response
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PosApiTransportError(PosApiError):
|
|
24
|
+
"""Network / timeout / DNS / TLS errors."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class PosApiDecodeError(PosApiError):
|
|
28
|
+
"""Response body was not valid JSON when JSON was expected."""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class PosApiValidationError(PosApiError):
|
|
32
|
+
"""Pydantic validation errors (request or response)."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class PosApiHttpError(PosApiError):
|
|
36
|
+
"""Non-2xx response from server."""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
message: str,
|
|
41
|
+
*,
|
|
42
|
+
request: httpx.Request,
|
|
43
|
+
response: httpx.Response,
|
|
44
|
+
) -> None:
|
|
45
|
+
super().__init__(message, request=request, response=response)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class PosApiBusinessError(PosApiError):
|
|
49
|
+
"""
|
|
50
|
+
2xx HTTP but domain-level failure indicated by payload fields
|
|
51
|
+
(e.g. {"status":"ERROR", "message":"..."}).
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
message: str,
|
|
57
|
+
*,
|
|
58
|
+
status: str | None = None,
|
|
59
|
+
code: str | int | None = None,
|
|
60
|
+
request: httpx.Request | None = None,
|
|
61
|
+
response: httpx.Response | None = None,
|
|
62
|
+
) -> None:
|
|
63
|
+
super().__init__(message, request=request, response=response)
|
|
64
|
+
self.status = status
|
|
65
|
+
self.code = code
|
ebarimt_pos_sdk/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
from .bank_accounts.bank_accounts import BankAccountsResource
|
|
2
|
+
from .info.info import InfoResource
|
|
3
|
+
from .receipt.receipt import ReceiptResource
|
|
4
|
+
from .send_data.send_data import SendDataResource
|
|
5
|
+
|
|
6
|
+
__all__ = ["InfoResource", "ReceiptResource", "SendDataResource", "BankAccountsResource"]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
|
|
3
|
+
from ..resource import BaseResource, HeaderTypes, _build_headers, _ensure_http_success
|
|
4
|
+
from .schema import BankAccount
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BankAccountsResource(BaseResource):
|
|
8
|
+
@property
|
|
9
|
+
def _path(self) -> str:
|
|
10
|
+
return "/rest/bankAccounts"
|
|
11
|
+
|
|
12
|
+
def read(
|
|
13
|
+
self,
|
|
14
|
+
tin: str,
|
|
15
|
+
*,
|
|
16
|
+
headers: HeaderTypes | None = None,
|
|
17
|
+
) -> list[BankAccount]:
|
|
18
|
+
result = self._sync.send(
|
|
19
|
+
"GET",
|
|
20
|
+
self._path,
|
|
21
|
+
params=httpx.QueryParams({"tin": tin}),
|
|
22
|
+
headers=_build_headers(self._headers, headers),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
success_response = _ensure_http_success(result.response)
|
|
26
|
+
|
|
27
|
+
output: list[BankAccount] = []
|
|
28
|
+
|
|
29
|
+
for data in success_response.json():
|
|
30
|
+
output.append(BankAccount.model_validate(data))
|
|
31
|
+
|
|
32
|
+
return output
|
|
33
|
+
|
|
34
|
+
async def aread(
|
|
35
|
+
self,
|
|
36
|
+
tin: str,
|
|
37
|
+
*,
|
|
38
|
+
headers: HeaderTypes | None = None,
|
|
39
|
+
) -> list[BankAccount]:
|
|
40
|
+
result = await self._async.send(
|
|
41
|
+
"GET",
|
|
42
|
+
self._path,
|
|
43
|
+
params=httpx.QueryParams({"tin": tin}),
|
|
44
|
+
headers=_build_headers(self._headers, headers),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
success_response = _ensure_http_success(result.response)
|
|
48
|
+
|
|
49
|
+
output: list[BankAccount] = []
|
|
50
|
+
|
|
51
|
+
for data in success_response.json():
|
|
52
|
+
output.append(BankAccount.model_validate(data))
|
|
53
|
+
|
|
54
|
+
return output
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SDKModel(BaseModel):
|
|
9
|
+
"""
|
|
10
|
+
Base model for the SDK:
|
|
11
|
+
- Allows forward/backward-compatible payload changes (extra keys allowed)
|
|
12
|
+
- Clean developer ergonomics (aliases supported)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
model_config = ConfigDict(
|
|
16
|
+
extra="allow",
|
|
17
|
+
populate_by_name=True,
|
|
18
|
+
validate_assignment=False,
|
|
19
|
+
str_strip_whitespace=True,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BankAccount(SDKModel):
|
|
24
|
+
"""
|
|
25
|
+
Bank account record returned by GET /rest/bankAccounts.
|
|
26
|
+
|
|
27
|
+
Note:
|
|
28
|
+
- OpenAPI lists `iBan` as required, but some examples omit it.
|
|
29
|
+
We keep it optional to avoid brittle SDK behavior.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
id: int = Field(..., description="System-registered bank account ID.")
|
|
33
|
+
tin: str = Field(..., description="Taxpayer identification number (TIN) of the account owner.")
|
|
34
|
+
|
|
35
|
+
bank_account_no: str = Field(..., alias="bankAccountNo", description="Bank account number.")
|
|
36
|
+
bank_account_name: str = Field(..., alias="bankAccountName", description="Bank account name.")
|
|
37
|
+
|
|
38
|
+
bank_id: int = Field(..., alias="bankId", description="Bank ID.")
|
|
39
|
+
bank_name: str = Field(..., alias="bankName", description="Bank name.")
|
|
40
|
+
|
|
41
|
+
iban: str = Field(
|
|
42
|
+
alias="iBan",
|
|
43
|
+
description="International Bank Account Number (IBAN) / international account numbering.",
|
|
44
|
+
examples=["100100015121212121111"],
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# If the API sometimes returns a nested blob like `data: {}`, we keep it explicitly.
|
|
48
|
+
data: dict[str, Any] | None = Field(default=None, description="Optional extra data payload.")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from ..resource import BaseResource, HeaderTypes, _build_headers, _ensure_http_success
|
|
2
|
+
from .schema import ReadInfoResponse
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class InfoResource(BaseResource):
|
|
6
|
+
@property
|
|
7
|
+
def _path(self) -> str:
|
|
8
|
+
return "/rest/info"
|
|
9
|
+
|
|
10
|
+
def read(self, *, headers: HeaderTypes | None = None) -> ReadInfoResponse:
|
|
11
|
+
result = self._sync.send(
|
|
12
|
+
"GET",
|
|
13
|
+
self._path,
|
|
14
|
+
headers=_build_headers(self._headers, headers),
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
_ensure_http_success(result.response)
|
|
18
|
+
|
|
19
|
+
return ReadInfoResponse.model_validate(result.response.json())
|
|
20
|
+
|
|
21
|
+
async def aread(self, *, headers: HeaderTypes | None = None) -> ReadInfoResponse:
|
|
22
|
+
result = await self._async.send(
|
|
23
|
+
"GET",
|
|
24
|
+
self._path,
|
|
25
|
+
headers=_build_headers(self._headers, headers),
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
_ensure_http_success(result.response)
|
|
29
|
+
|
|
30
|
+
return ReadInfoResponse.model_validate(result.response.json())
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AppInfo(BaseModel):
|
|
7
|
+
"""
|
|
8
|
+
PosAPI-н ерөнхий мэдээлэл.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
model_config = ConfigDict(populate_by_name=True, extra="ignore")
|
|
12
|
+
|
|
13
|
+
application_dir: str = Field(alias="applicationDir", description="Файл байршиж буй хавтас")
|
|
14
|
+
current_dir: str = Field(alias="currentDir", description="Файл байршиж буй хавтас")
|
|
15
|
+
database: str = Field(description="Баазын driver /Qt5 тохиргоогоор/")
|
|
16
|
+
database_host: str = Field(
|
|
17
|
+
alias="database-host", description="Баазын хаяг /sqlite бол файлын зам/"
|
|
18
|
+
)
|
|
19
|
+
work_dir: str = Field(alias="workDir", description="Ажиллаж буй хавтас")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class RestInfoCustomer(BaseModel):
|
|
23
|
+
"""
|
|
24
|
+
Үйлчлүүлэгч ААН.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
name: str = Field(description="ААН-нэр")
|
|
28
|
+
tin: str = Field(description="ААН-н ТТД")
|
|
29
|
+
vat_payer: bool = Field(
|
|
30
|
+
alias="vatPayer",
|
|
31
|
+
description="НӨАТ суутган төлөгч мөн эсэх\ntrue: НӨАТ суутган төлөгч мөн\nfalse: НӨАТ суутган төлөгч биш",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Merchant(BaseModel):
|
|
36
|
+
"""
|
|
37
|
+
PosAPI-д бүртгэлтэй ААН.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
name: str = Field(description="ААН-нэр")
|
|
41
|
+
tin: str = Field(description="ААН-н ТТД")
|
|
42
|
+
customers: list[RestInfoCustomer] = Field(description="Үйлчлүүлэгч ААН-н жагсаалт")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ReadInfoResponse(BaseModel):
|
|
46
|
+
"""
|
|
47
|
+
GET /rest/info (200 OK) response schema.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
operator_name: str = Field(alias="operatorName", description="Оператор байгууллагын нэр")
|
|
51
|
+
operator_tin: str = Field(alias="operatorTIN", description="Оператор байгууллагын ТТД")
|
|
52
|
+
pos_id: float = Field(alias="posId", description="PosAPI-н систем дэх бүртгэлийн Id")
|
|
53
|
+
pos_no: str = Field(alias="posNo", description="PosAPI-н систем дэх бүртгэлийн дугаар")
|
|
54
|
+
last_sent_date: str = Field(
|
|
55
|
+
alias="lastSentDate", description="Баримт илгээсэн огноо /Сүүлийн байдлаар/"
|
|
56
|
+
)
|
|
57
|
+
left_lotteries: int = Field(alias="leftLotteries", description="Нийт үлдсэн сугалаа")
|
|
58
|
+
app_info: AppInfo = Field(alias="appInfo", description="PosAPI-н ерөнхий мэдээлэл")
|
|
59
|
+
merchants: list[Merchant] = Field(description="PosAPI-д бүртгэлтэй ААН-н жагсаалт")
|
|
60
|
+
|
|
61
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from .schema import (
|
|
2
|
+
BarCodeType,
|
|
3
|
+
CreateReceiptRequest,
|
|
4
|
+
CreateReceiptResponse,
|
|
5
|
+
DeleteReceiptRequest,
|
|
6
|
+
DeleteReceiptResponse,
|
|
7
|
+
Item,
|
|
8
|
+
Payment,
|
|
9
|
+
PaymentCardData,
|
|
10
|
+
PaymentCode,
|
|
11
|
+
PaymentStatus,
|
|
12
|
+
Receipt,
|
|
13
|
+
ReceiptCreateStatus,
|
|
14
|
+
ReceiptItemData,
|
|
15
|
+
ReceiptItemResponse,
|
|
16
|
+
ReceiptType,
|
|
17
|
+
TaxType,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"CreateReceiptRequest",
|
|
22
|
+
"CreateReceiptResponse",
|
|
23
|
+
"DeleteReceiptRequest",
|
|
24
|
+
"DeleteReceiptResponse",
|
|
25
|
+
"ReceiptItemResponse",
|
|
26
|
+
"Item",
|
|
27
|
+
"Payment",
|
|
28
|
+
"PaymentCardData",
|
|
29
|
+
"PaymentCode",
|
|
30
|
+
"PaymentStatus",
|
|
31
|
+
"Receipt",
|
|
32
|
+
"ReceiptCreateStatus",
|
|
33
|
+
"ReceiptItemData",
|
|
34
|
+
"BarCodeType",
|
|
35
|
+
"ReceiptType",
|
|
36
|
+
"TaxType",
|
|
37
|
+
]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from ..resource import (
|
|
6
|
+
BaseResource,
|
|
7
|
+
HeaderTypes,
|
|
8
|
+
_build_headers,
|
|
9
|
+
_ensure_http_success,
|
|
10
|
+
_validate_payload,
|
|
11
|
+
)
|
|
12
|
+
from .schema import (
|
|
13
|
+
CreateReceiptRequest,
|
|
14
|
+
CreateReceiptResponse,
|
|
15
|
+
DeleteReceiptRequest,
|
|
16
|
+
DeleteReceiptResponse,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
_DEFAULT_HEADERS = {"Accept": "application/json"}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ReceiptResource(BaseResource):
|
|
23
|
+
@property
|
|
24
|
+
def _path(self) -> str:
|
|
25
|
+
return "/rest/receipt"
|
|
26
|
+
|
|
27
|
+
def create(
|
|
28
|
+
self,
|
|
29
|
+
payload: CreateReceiptRequest | dict[str, Any],
|
|
30
|
+
*,
|
|
31
|
+
headers: HeaderTypes | None = None,
|
|
32
|
+
) -> CreateReceiptResponse:
|
|
33
|
+
payload = _validate_payload(model=CreateReceiptRequest, payload=payload)
|
|
34
|
+
|
|
35
|
+
result = self._sync.send(
|
|
36
|
+
"POST",
|
|
37
|
+
self._path,
|
|
38
|
+
headers=_build_headers(self._headers, headers),
|
|
39
|
+
payload=payload.model_dump(mode="json", by_alias=True, exclude_none=True),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
_ensure_http_success(result.response)
|
|
43
|
+
|
|
44
|
+
return CreateReceiptResponse.model_validate(result.response.json())
|
|
45
|
+
|
|
46
|
+
async def acreate(
|
|
47
|
+
self,
|
|
48
|
+
payload: CreateReceiptRequest | dict[str, Any],
|
|
49
|
+
*,
|
|
50
|
+
headers: HeaderTypes | None = None,
|
|
51
|
+
) -> CreateReceiptResponse:
|
|
52
|
+
payload = _validate_payload(model=CreateReceiptRequest, payload=payload)
|
|
53
|
+
|
|
54
|
+
result = await self._async.send(
|
|
55
|
+
"POST",
|
|
56
|
+
self._path,
|
|
57
|
+
headers=_build_headers(self._headers, headers),
|
|
58
|
+
payload=payload.model_dump(mode="json", by_alias=True, exclude_none=True),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
_ensure_http_success(result.response)
|
|
62
|
+
|
|
63
|
+
return CreateReceiptResponse.model_validate(result.response.json())
|
|
64
|
+
|
|
65
|
+
def delete(
|
|
66
|
+
self, payload: DeleteReceiptRequest | dict[str, Any], *, headers: HeaderTypes | None = None
|
|
67
|
+
) -> DeleteReceiptResponse:
|
|
68
|
+
payload = _validate_payload(model=DeleteReceiptRequest, payload=payload)
|
|
69
|
+
|
|
70
|
+
result = self._sync.send(
|
|
71
|
+
"POST",
|
|
72
|
+
self._path,
|
|
73
|
+
headers=_build_headers(self._headers, headers),
|
|
74
|
+
payload=payload.model_dump(mode="json", by_alias=True, exclude_none=True),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
_ensure_http_success(result.response)
|
|
78
|
+
|
|
79
|
+
return DeleteReceiptResponse.model_validate(result.response.json())
|
|
80
|
+
|
|
81
|
+
async def adelete(
|
|
82
|
+
self, payload: DeleteReceiptRequest | dict[str, Any], *, headers: HeaderTypes | None = None
|
|
83
|
+
) -> DeleteReceiptResponse:
|
|
84
|
+
payload = _validate_payload(model=DeleteReceiptRequest, payload=payload)
|
|
85
|
+
|
|
86
|
+
result = await self._async.send(
|
|
87
|
+
"POST",
|
|
88
|
+
self._path,
|
|
89
|
+
headers=_build_headers(self._headers, headers),
|
|
90
|
+
payload=payload.model_dump(mode="json", by_alias=True, exclude_none=True),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
_ensure_http_success(result.response)
|
|
94
|
+
|
|
95
|
+
return DeleteReceiptResponse.model_validate(result.response.json())
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from decimal import Decimal
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, ConfigDict
|
|
9
|
+
|
|
10
|
+
# Json building block types
|
|
11
|
+
Number = Decimal | int | float
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ReceiptType(str, Enum):
|
|
15
|
+
B2C_RECEIPT = "B2C_RECEIPT"
|
|
16
|
+
B2B_RECEIPT = "B2B_RECEIPT"
|
|
17
|
+
B2C_INVOICE = "B2C_INVOICE"
|
|
18
|
+
B2B_INVOICE = "B2B_INVOICE"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TaxType(str, Enum):
|
|
22
|
+
VAT_ABLE = "VAT_ABLE"
|
|
23
|
+
VAT_FREE = "VAT_FREE"
|
|
24
|
+
VAT_ZERO = "VAT_ZERO"
|
|
25
|
+
NOT_VAT = "NOT_VAT"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BarCodeType(str, Enum):
|
|
29
|
+
UNDEFINED = "UNDEFINED"
|
|
30
|
+
GS1 = "GS1"
|
|
31
|
+
ISBN = "ISBN"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class PaymentCode(str, Enum):
|
|
35
|
+
CASH = "CASH"
|
|
36
|
+
PAYMENT_CARD = "PAYMENT_CARD"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class PaymentStatus(str, Enum):
|
|
40
|
+
PAID = "PAID"
|
|
41
|
+
PAY = "PAY"
|
|
42
|
+
REVERSED = "REVERSED"
|
|
43
|
+
ERROR = "ERROR"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ReceiptCreateStatus(str, Enum):
|
|
47
|
+
SUCCESS = "SUCCESS"
|
|
48
|
+
ERROR = "ERROR"
|
|
49
|
+
PAYMENT = "PAYMENT"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ReceiptItemData(BaseModel):
|
|
53
|
+
"""
|
|
54
|
+
items[].data
|
|
55
|
+
Note: docs show lotNo + stockQR, but examples also show nested "data": {"stockQR":[...]} sometimes.
|
|
56
|
+
This model follows the schema: lotNo, stockQR directly under data.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
model_config = ConfigDict(extra="ignore")
|
|
60
|
+
|
|
61
|
+
lotNo: str | None = None
|
|
62
|
+
stockQR: list[str] | None = None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class Item(BaseModel):
|
|
66
|
+
model_config = ConfigDict(extra="ignore")
|
|
67
|
+
|
|
68
|
+
name: str
|
|
69
|
+
barCode: str
|
|
70
|
+
measureUnit: str
|
|
71
|
+
qty: Number
|
|
72
|
+
unitPrice: Number
|
|
73
|
+
totalAmount: Number
|
|
74
|
+
|
|
75
|
+
barCodeType: BarCodeType | str | None = None
|
|
76
|
+
classificationCode: str | None = None
|
|
77
|
+
taxProductCode: str | None = None
|
|
78
|
+
totalVAT: Number | None = None
|
|
79
|
+
totalCityTax: Number | None = None
|
|
80
|
+
data: ReceiptItemData | None = None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class Receipt(BaseModel):
|
|
84
|
+
model_config = ConfigDict(extra="ignore")
|
|
85
|
+
|
|
86
|
+
totalAmount: Number
|
|
87
|
+
taxType: TaxType | str
|
|
88
|
+
merchantTin: str
|
|
89
|
+
items: list[Item]
|
|
90
|
+
|
|
91
|
+
totalVAT: Number | None = None
|
|
92
|
+
totalCityTax: Number | None = None
|
|
93
|
+
customerTin: str | None = None
|
|
94
|
+
|
|
95
|
+
bankAccountNo: str | None = None
|
|
96
|
+
iBan: str | None = None
|
|
97
|
+
invoiceId: str | None = None
|
|
98
|
+
data: dict[str, Any] | None = None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class PaymentCardData(BaseModel):
|
|
102
|
+
"""
|
|
103
|
+
payments[].data when code=PAYMENT_CARD
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
model_config = ConfigDict(extra="ignore")
|
|
107
|
+
|
|
108
|
+
terminalID: str
|
|
109
|
+
rrn: str
|
|
110
|
+
maskedCardNumber: str
|
|
111
|
+
easy: bool
|
|
112
|
+
bankCode: str | None = None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class Payment(BaseModel):
|
|
116
|
+
model_config = ConfigDict(extra="ignore")
|
|
117
|
+
|
|
118
|
+
code: PaymentCode | str
|
|
119
|
+
status: PaymentStatus | str
|
|
120
|
+
paidAmount: Number
|
|
121
|
+
|
|
122
|
+
exchangeCode: str | None = None
|
|
123
|
+
data: PaymentCardData | None = None # Only when code is PAYMENT_CARD
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class CreateReceiptRequest(BaseModel):
|
|
127
|
+
model_config = ConfigDict(extra="ignore")
|
|
128
|
+
|
|
129
|
+
branchNo: str
|
|
130
|
+
totalAmount: Number
|
|
131
|
+
merchantTin: str
|
|
132
|
+
posNo: str
|
|
133
|
+
type: ReceiptType | str
|
|
134
|
+
billIdSuffix: str
|
|
135
|
+
receipts: list[Receipt]
|
|
136
|
+
|
|
137
|
+
totalVAT: Number | None = None
|
|
138
|
+
totalCityTax: Number | None = None
|
|
139
|
+
districtCode: str | None = None
|
|
140
|
+
customerTin: str | None = None
|
|
141
|
+
consumerNo: str | None = None
|
|
142
|
+
inactiveId: str | None = None
|
|
143
|
+
invoiceId: str | None = None
|
|
144
|
+
reportMonth: str | None = None
|
|
145
|
+
data: dict[str, Any] | None = None
|
|
146
|
+
|
|
147
|
+
payments: list[Payment] | None = None
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class ReceiptItemResponse(BaseModel):
|
|
151
|
+
model_config = ConfigDict(extra="ignore")
|
|
152
|
+
|
|
153
|
+
id: str
|
|
154
|
+
bankAccountId: int
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class CreateReceiptResponse(BaseModel):
|
|
158
|
+
model_config = ConfigDict(extra="ignore")
|
|
159
|
+
|
|
160
|
+
id: str
|
|
161
|
+
posId: int
|
|
162
|
+
status: ReceiptCreateStatus | str
|
|
163
|
+
message: str
|
|
164
|
+
qrDate: str
|
|
165
|
+
lottery: str
|
|
166
|
+
date: datetime
|
|
167
|
+
easy: bool
|
|
168
|
+
receipts: list[ReceiptItemResponse]
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class DeleteReceiptRequest(BaseModel):
|
|
172
|
+
model_config = ConfigDict(extra="ignore")
|
|
173
|
+
|
|
174
|
+
id: str
|
|
175
|
+
date: datetime
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class DeleteReceiptResponse(BaseModel):
|
|
179
|
+
model_config = ConfigDict(extra="ignore")
|
|
180
|
+
|
|
181
|
+
# TODO
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# src/ebarimt_pos_sdk/resources/receipt.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from abc import abstractmethod
|
|
5
|
+
from typing import Any, TypeVar
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
from ..errors import PosApiHttpError
|
|
11
|
+
from ..transport import AsyncTransport, HeaderTypes, SyncTransport
|
|
12
|
+
|
|
13
|
+
T = TypeVar("T", bound=BaseModel)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BaseResource:
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
*,
|
|
20
|
+
sync: SyncTransport,
|
|
21
|
+
async_: AsyncTransport,
|
|
22
|
+
headers: HeaderTypes | None = None,
|
|
23
|
+
) -> None:
|
|
24
|
+
self._sync = sync
|
|
25
|
+
self._async = async_
|
|
26
|
+
self._headers = headers
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def _path(self) -> str: ...
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _ensure_http_success(response: httpx.Response) -> httpx.Response:
|
|
34
|
+
try:
|
|
35
|
+
response.raise_for_status()
|
|
36
|
+
except httpx.HTTPStatusError as exc:
|
|
37
|
+
raise PosApiHttpError(
|
|
38
|
+
f"HTTP {exc.response.status_code}",
|
|
39
|
+
request=exc.request,
|
|
40
|
+
response=exc.response,
|
|
41
|
+
) from exc
|
|
42
|
+
return response
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _validate_payload(model: type[T], payload: T | dict[str, Any]) -> T:
|
|
46
|
+
if isinstance(payload, model):
|
|
47
|
+
return payload
|
|
48
|
+
return model.model_validate(payload)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _build_headers(*headers: HeaderTypes | None) -> httpx.Headers:
|
|
52
|
+
"""Merges headers and returns one header.
|
|
53
|
+
|
|
54
|
+
Note:
|
|
55
|
+
Highest priority header should be the last.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
httpx.Headers: Merged header.
|
|
59
|
+
"""
|
|
60
|
+
out = httpx.Headers()
|
|
61
|
+
for header in headers:
|
|
62
|
+
out.update(header)
|
|
63
|
+
return out
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
__all__ = ["_ensure_http_success", "_validate_payload", "_build_headers"]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
|
|
2
|
+
from ..resource import BaseResource, HeaderTypes, _build_headers, _ensure_http_success
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SendDataResource(BaseResource):
|
|
6
|
+
@property
|
|
7
|
+
def _path(self) -> str:
|
|
8
|
+
return "/rest/sendData"
|
|
9
|
+
|
|
10
|
+
def send(self, headers: HeaderTypes | None = None) -> None:
|
|
11
|
+
result = self._sync.send(
|
|
12
|
+
"GET",
|
|
13
|
+
self._path,
|
|
14
|
+
headers=_build_headers(self._headers, headers),
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
_ensure_http_success(result.response)
|
|
18
|
+
|
|
19
|
+
return None
|
|
20
|
+
|
|
21
|
+
async def asend(self, headers: HeaderTypes | None = None) -> None:
|
|
22
|
+
result = await self._async.send(
|
|
23
|
+
"GET",
|
|
24
|
+
self._path,
|
|
25
|
+
headers=_build_headers(self._headers, headers),
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
_ensure_http_success(result.response)
|
|
29
|
+
|
|
30
|
+
return None
|
|
File without changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from .transport import HeaderTypes
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class PosApiSettings:
|
|
10
|
+
"""
|
|
11
|
+
SDK runtime settings.
|
|
12
|
+
|
|
13
|
+
Notes:
|
|
14
|
+
POS API usually runs on localhost or private network.
|
|
15
|
+
`base_url` may be http://localhost:7080 or an internal hostname behind VPN.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
base_url: str = "http://localhost:7080"
|
|
19
|
+
timeout_s: float = 10.0
|
|
20
|
+
verify_tls: bool = True
|
|
21
|
+
|
|
22
|
+
# auth is intentionally flexible until spec is clearer:
|
|
23
|
+
# e.g. {"Authorization": "Bearer ..."} or {"X-API-KEY": "..."}
|
|
24
|
+
default_headers: HeaderTypes | None = None
|
|
25
|
+
|
|
26
|
+
def normalized_base_url(self) -> str:
|
|
27
|
+
"""Normalizes base_url for clients to use.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
str: Normalized base_url.
|
|
31
|
+
"""
|
|
32
|
+
return self.base_url.strip().rstrip("/")
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This is the transport layer. It concerns with:
|
|
3
|
+
* send request
|
|
4
|
+
* handle network errors
|
|
5
|
+
* handle non-2xx HTTP errors
|
|
6
|
+
* decode JSON (or 204/empty)
|
|
7
|
+
* produce structured context (request/response + metadata)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .async_transport import AsyncTransport
|
|
11
|
+
from .http import HeaderTypes, HttpRequestResponse, QueryParamTypes
|
|
12
|
+
from .sync_transport import SyncTransport
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"AsyncTransport",
|
|
16
|
+
"SyncTransport",
|
|
17
|
+
"HttpRequestResponse",
|
|
18
|
+
"HeaderTypes",
|
|
19
|
+
"QueryParamTypes",
|
|
20
|
+
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
|
|
5
|
+
from .http import (
|
|
6
|
+
HeaderTypes,
|
|
7
|
+
HttpMethod,
|
|
8
|
+
HttpRequestResponse,
|
|
9
|
+
QueryParamTypes,
|
|
10
|
+
build_transport_error,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AsyncTransport:
|
|
15
|
+
"""Async request/response transport layer."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, client: httpx.AsyncClient) -> None:
|
|
18
|
+
self._client = client
|
|
19
|
+
|
|
20
|
+
async def send(
|
|
21
|
+
self,
|
|
22
|
+
method: HttpMethod,
|
|
23
|
+
url: httpx.URL | str,
|
|
24
|
+
*,
|
|
25
|
+
params: QueryParamTypes | None = None,
|
|
26
|
+
headers: HeaderTypes | None = None,
|
|
27
|
+
payload: dict[str, Any] | None = None,
|
|
28
|
+
**kwargs: Any,
|
|
29
|
+
) -> HttpRequestResponse:
|
|
30
|
+
# build http request context
|
|
31
|
+
request = self._client.build_request(
|
|
32
|
+
method=method,
|
|
33
|
+
url=url,
|
|
34
|
+
params=params,
|
|
35
|
+
headers=headers,
|
|
36
|
+
json=payload,
|
|
37
|
+
**kwargs,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
response = await self._client.send(request)
|
|
42
|
+
except httpx.HTTPError as exc:
|
|
43
|
+
raise build_transport_error(request, exc) from exc
|
|
44
|
+
|
|
45
|
+
return HttpRequestResponse(
|
|
46
|
+
request=request,
|
|
47
|
+
response=response,
|
|
48
|
+
)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping, Sequence
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Literal, TypeAlias
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from ..errors import (
|
|
10
|
+
PosApiTransportError,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
HttpMethod = Literal["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class HttpRequestResponse:
|
|
18
|
+
request: httpx.Request
|
|
19
|
+
response: httpx.Response
|
|
20
|
+
|
|
21
|
+
def as_tuple(self) -> tuple[httpx.Request, httpx.Response]:
|
|
22
|
+
return (self.request, self.response)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def build_transport_error(
|
|
26
|
+
request: httpx.Request,
|
|
27
|
+
exc: httpx.HTTPError,
|
|
28
|
+
) -> PosApiTransportError:
|
|
29
|
+
"""Build Pos Api Transport layer error."""
|
|
30
|
+
return PosApiTransportError(
|
|
31
|
+
f"Transport error for {request.method} {request.url}: {exc}",
|
|
32
|
+
request=request,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
PrimitiveData = str | int | float | bool | None
|
|
37
|
+
|
|
38
|
+
QueryParamTypes: TypeAlias = (
|
|
39
|
+
httpx.QueryParams
|
|
40
|
+
| Mapping[str, PrimitiveData | Sequence[PrimitiveData]]
|
|
41
|
+
| list[tuple[str, PrimitiveData]]
|
|
42
|
+
| tuple[tuple[str, PrimitiveData], ...]
|
|
43
|
+
| str
|
|
44
|
+
)
|
|
45
|
+
HeaderTypes: TypeAlias = (
|
|
46
|
+
httpx.Headers
|
|
47
|
+
| Mapping[str, str]
|
|
48
|
+
| Mapping[bytes, bytes]
|
|
49
|
+
| Sequence[tuple[str, str]]
|
|
50
|
+
| Sequence[tuple[bytes, bytes]]
|
|
51
|
+
)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
|
|
5
|
+
from .http import HttpMethod, HttpRequestResponse, QueryParamTypes, build_transport_error
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SyncTransport:
|
|
9
|
+
def __init__(self, client: httpx.Client) -> None:
|
|
10
|
+
self._client = client
|
|
11
|
+
|
|
12
|
+
def send(
|
|
13
|
+
self,
|
|
14
|
+
method: HttpMethod,
|
|
15
|
+
url: httpx.URL | str,
|
|
16
|
+
*,
|
|
17
|
+
params: QueryParamTypes | None = None,
|
|
18
|
+
headers: httpx.Headers | None = None,
|
|
19
|
+
payload: dict[str, Any] | None = None,
|
|
20
|
+
**kwargs: Any,
|
|
21
|
+
) -> HttpRequestResponse:
|
|
22
|
+
request = self._client.build_request(
|
|
23
|
+
method=method,
|
|
24
|
+
url=url,
|
|
25
|
+
params=params,
|
|
26
|
+
headers=headers,
|
|
27
|
+
json=payload,
|
|
28
|
+
**kwargs,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
response = self._client.send(request)
|
|
33
|
+
except httpx.HTTPError as exc:
|
|
34
|
+
raise build_transport_error(request, exc) from exc
|
|
35
|
+
|
|
36
|
+
return HttpRequestResponse(
|
|
37
|
+
request=request,
|
|
38
|
+
response=response,
|
|
39
|
+
)
|
ebarimt_pos_sdk/utils.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: ebarimt-pos-sdk
|
|
3
|
+
Version: 0.0.1b2
|
|
4
|
+
Summary: Python SDK for Ebarimt POS API 3.0
|
|
5
|
+
Author: SenergyTech
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Dist: httpx>=0.27.0
|
|
8
|
+
Requires-Dist: pydantic>=2.7.0
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# ebarimt-pos-sdk
|
|
13
|
+
|
|
14
|
+
[](https://codecov.io/gh/Amraa1/ebarimt-pos-sdk)
|
|
15
|
+
|
|
16
|
+
Modern async-first Python SDK for Ebarimt Pos API 3.0.
|
|
17
|
+
|
|
18
|
+
> Ebarimt Pos API 3.0 [documentation](https://developer.itc.gov.mn/docs/ebarimt-api/inbishdm2zj3x-pos-api-3-0-sistemijn-api-holbolt-zaavruud)
|
|
19
|
+
|
|
20
|
+
## Development setup
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
uv sync --dev
|
|
24
|
+
uv run pytest
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
# PosAPI тохируулах:
|
|
28
|
+
|
|
29
|
+
PosAPI нь суусны дараа анхны байдлаар тохируулах шаардлагатай. “posapi.ini”
|
|
30
|
+
файлд тухайн PosAPI-н үндсэн тохиргоо байрлах ба “P101.poi, P102.poi” файлуудад
|
|
31
|
+
ажиллагааны тохиргоо байрлах ба нууцлагдсан байна.
|
|
32
|
+
|
|
33
|
+
### Үндсэн тохиргооны тайлбар /posapi.ini файл/
|
|
34
|
+
|
|
35
|
+
| Нэр | Тайлбар |
|
|
36
|
+
| ---------------- | -------------------------------------------------------------- |
|
|
37
|
+
| authUrl | |
|
|
38
|
+
| authRealm | Тухайн PosAPI-н нэгдсэн нэвтрэлттэй холбогдох тохиргоо |
|
|
39
|
+
| authClientId | Өөрчлөх шаардлагагүй. |
|
|
40
|
+
| authClientSecret | |
|
|
41
|
+
| ebarimtUrl | Ebarimt системтэй холбогдох хаяг Өөрчлөх шаардлагагүй |
|
|
42
|
+
| db | Өгөгдлийн сангийн driver |
|
|
43
|
+
| dbHost | Өгөгдлийн сангийн хаяг Хэрэв QSQLITE бол файлын зам байна |
|
|
44
|
+
| dbPort | Өгөгдлийн сангийн port Хэрэв QSQLITE бол бөглөхгүй |
|
|
45
|
+
| dbUser | Өгөгдлийн сангийн хэрэглэгчийн нэр Хэрэв QSQLITE бол бөглөхгүй |
|
|
46
|
+
| dbPass | Өгөгдлийн сангийн нууц үг хэрэв QSQLITE бол бөглөхгүй |
|
|
47
|
+
| dbName | Өгөгдлийн сангийн баазын нэр Хэрэв QSQLITE бол бөглөхгүй |
|
|
48
|
+
| dbOptions | Өгөгдлийн сангийн нэмэлт тохиргоо Хэрэв QSQLITE бол бөглөхгүй |
|
|
49
|
+
| workDir | PosAPI-н ажиллагааны хавтас |
|
|
50
|
+
| webServiceHost | PosAPI-н ажиллах сүлжээний IP address |
|
|
51
|
+
| webServicePort | PosAPI-н ажиллах сүлжээний port |
|
|
52
|
+
|
|
53
|
+
**WorkDir** хавтсанд ажиллагааны тохиргоо байрлах ба уг тохиргооны файлуудын агуулга
|
|
54
|
+
нь тогтмол өөрчлөгдөж байх тул PosAPI ажиллуулж буй хэрэглэгч нь унших, бичих
|
|
55
|
+
эрхтэй байхыг анхаарана уу. Мөн уг хавтсыг ямар ч нөхцөлд **FREEZE хийх ёсгүй**
|
|
56
|
+
гэдгийг анхаарна уу.
|
|
57
|
+
|
|
58
|
+
### PosAPI-н дэмжиж ажиллах өгөгдлийн сангууд ба driver-ууд
|
|
59
|
+
|
|
60
|
+
| Нэр | Тайлбар |
|
|
61
|
+
| ------- | ----------------------------- |
|
|
62
|
+
| QMYSQL | MySQL эсвэл MariaDB |
|
|
63
|
+
| QPSQL | PostgreSQL |
|
|
64
|
+
| QODBC | ODBC for Microsoft SQL Server |
|
|
65
|
+
| QSQLITE | SQLite version 3 |
|
|
66
|
+
|
|
67
|
+
PosAPI нь ачааллах үедээ өгөгдлийн сангийн table-г автоматаар өөрөө үүсгэдэг тул
|
|
68
|
+
тухайн хэрэглэгч нь table үүсгэх эрх бүхий хэрэглэгч байх шаардлагатайг анхаарна уу
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
ebarimt_pos_sdk/__init__.py,sha256=sQPlSH_IE69ZnZiup0Uvks1vcz_gImCD_R4mGE9Nwc0,828
|
|
2
|
+
ebarimt_pos_sdk/client.py,sha256=aua7ruFgS1DoUzNAonpR47O_lzAh9PrTU8mlSdCff24,2930
|
|
3
|
+
ebarimt_pos_sdk/errors.py,sha256=faDkMH9yr9KvjrRgS0pyUnvO8l3vEpKCLr3sHyDf8fQ,1565
|
|
4
|
+
ebarimt_pos_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
ebarimt_pos_sdk/resources/__init__.py,sha256=YPnEmw-RhCK7zFFt59tqjjzNulR2t08SPVv4D9F-qZ4,284
|
|
6
|
+
ebarimt_pos_sdk/resources/bank_accounts/bank_accounts.py,sha256=k5PkBdyLlfXZaVqcYpWvaE0r-0QtMPM2X-HyeRNscg0,1397
|
|
7
|
+
ebarimt_pos_sdk/resources/bank_accounts/schema.py,sha256=qLjGxMRDbahglWITA_lPJz3saTdj_Eev66aYQkucmZg,1621
|
|
8
|
+
ebarimt_pos_sdk/resources/info/info.py,sha256=BESBm7W5JL_ihW-RL7sSNyVPjJWwh2jHl25jgoRNW2Q,936
|
|
9
|
+
ebarimt_pos_sdk/resources/info/schema.py,sha256=Sza96EyQ9oocyFr5eDOS7t7sTTT0HzBA3ocDLq1Fwfs,2628
|
|
10
|
+
ebarimt_pos_sdk/resources/receipt/__init__.py,sha256=y3G2eCUIs2AhI4pDVnsc9fGUpt-9-y6N9msSyGQqI7E,705
|
|
11
|
+
ebarimt_pos_sdk/resources/receipt/receipt.py,sha256=gTHPTauQYXlWf5eeOLo4IgDKCioTe1sOkNvN7xZKknQ,2922
|
|
12
|
+
ebarimt_pos_sdk/resources/receipt/schema.py,sha256=QudB87TAToa33h1iZd0GdVarOW_Dwv0-17U8Ti8jUYc,3883
|
|
13
|
+
ebarimt_pos_sdk/resources/resource.py,sha256=Ia4rZXTutLXARx3mommmmVB98FDKYuJeKFE4LVeqSNI,1593
|
|
14
|
+
ebarimt_pos_sdk/resources/send_data/send_data.py,sha256=cvLl2jeLEp0kDfSw59gbZwEecKciDkejtAh7WJgULLs,776
|
|
15
|
+
ebarimt_pos_sdk/resources/third_party/third_party.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
ebarimt_pos_sdk/settings.py,sha256=tMLbUo_Pfqr1va5NVPor5-fgs0onn1Ndkduf3WlLCdU,841
|
|
17
|
+
ebarimt_pos_sdk/transport/__init__.py,sha256=QD_WtnjPd2PQAV3ycnbItEuOyTMExIWdNj-wWgEQwjE,493
|
|
18
|
+
ebarimt_pos_sdk/transport/async_transport.py,sha256=2tURA1JgBmcM8ofkXfWLWIFcRz585v83XeWxziTkGK0,1149
|
|
19
|
+
ebarimt_pos_sdk/transport/http.py,sha256=Yj5lNKWHMdGckhPwH5OnJ8p_BA3WgL5s8SWCQYDJdug,1237
|
|
20
|
+
ebarimt_pos_sdk/transport/sync_transport.py,sha256=z5RPt3NU5UVlwgmupLNeQE6TlYgve2XXbFVr7Fuorbw,1007
|
|
21
|
+
ebarimt_pos_sdk/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
+
ebarimt_pos_sdk-0.0.1b2.dist-info/WHEEL,sha256=5DEXXimM34_d4Gx1AuF9ysMr1_maoEtGKjaILM3s4w4,80
|
|
23
|
+
ebarimt_pos_sdk-0.0.1b2.dist-info/METADATA,sha256=m-XFrBdpTZ2j31Xt74OWYvmtWTZkHvzM9DT8khSr8Ks,4164
|
|
24
|
+
ebarimt_pos_sdk-0.0.1b2.dist-info/RECORD,,
|