diff --git a/api/__init__.py b/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/api.py b/api/api.py new file mode 100644 index 0000000..28a513a --- /dev/null +++ b/api/api.py @@ -0,0 +1,251 @@ +from aiohttp import ClientSession + +from static.endpoints import * +from static.headers import appjson +from static.exceptions import * +from static.parcels import * + + +class Inpost: + def __init__(self, phone_number: str): + self.phone_number: str = phone_number + self.sms_code: str | None = None + self.auth_token: str | None = None + self.refr_token: str | None = None + self.sess: ClientSession = ClientSession() + self.parcel: Parcel | None = None + + def __repr__(self): + return f'Username: {self.phone_number}\nToken: {self.auth_token}' + + async def send_sms_code(self) -> Optional[bool]: + async with await self.sess.post(url=send_sms_code, + json={ + 'phoneNumber': f'{self.phone_number}' + }) as phone: + if phone.status == 200: + return True + else: + raise PhoneNumberError(reason=phone) + + async def confirm_sms_code(self, sms_code: str) -> Optional[bool]: + async with await self.sess.post(url=confirm_sms_code, + headers=appjson, + json={ + "phoneNumber": self.phone_number, + "smsCode": sms_code, + "phoneOS": "Android" + }) as confirmation: + if confirmation.status == 200: + resp = await confirmation.json() + self.sms_code = sms_code + self.refr_token = resp['refreshToken'] + self.auth_token = resp['authToken'] + return True + else: + raise SmsCodeConfirmationError(reason=confirmation) + + async def refresh_token(self) -> Optional[bool]: + if not self.auth_token: + raise NotAuthenticatedError(reason='Authentication token missing') + + if not self.refr_token: + raise NotAuthenticatedError(reason='Refresh token missing') + + async with await self.sess.post(url=refresh_token, + headers=appjson, + json={ + "refreshToken": self.refr_token, + "phoneOS": "Android" + }) as confirmation: + if confirmation.status == 200: + resp = await confirmation.json() + if resp['reauthenticationRequired']: + raise ReAuthenticationError(reason='You need to log in again!') + self.auth_token = resp['authToken'] + return True + + else: + raise RefreshTokenException(reason=confirmation) + + async def logout(self) -> Optional[bool]: + if not self.auth_token: + raise NotAuthenticatedError(reason='Not logged in') + + async with await self.sess.post(url=logout, + headers={'Authorization': self.auth_token}) as resp: + if resp.status == 200: + self.phone_number = None + self.refr_token = None + self.auth_token = None + self.sms_code = None + return True + else: + raise UnidentifiedAPIError(reason=resp) + + async def disconnect(self) -> bool: + if await self.logout(): + await self.sess.close() + return True + + return False + + async def get_parcel(self, shipment_number: Union[int, str], parse=False) -> Union[dict, Parcel]: + if not self.auth_token: + raise NotAuthenticatedError(reason='Not logged in') + + async with await self.sess.get(url=f"{parcel}{shipment_number}", + headers={'Authorization': self.auth_token}, + ) as resp: + if resp.status == 200: + return await resp.json() if not parse else Parcel(await resp.json()) + + else: + raise UnidentifiedAPIError(reason=resp) + + async def get_parcels(self, + parcel_type: ParcelType = ParcelType.TRACKED, + status: Optional[Union[ParcelStatus, List[ParcelStatus]]] = None, + pickup_point: Optional[Union[str, List[str]]] = None, + shipment_type: Optional[Union[ParcelShipmentType, List[ParcelShipmentType]]] = None, + parcel_size: Optional[Union[ParcelLockerSize, ParcelCarrierSize]] = None, + parse: bool = False) -> Union[List[dict], List[Parcel]]: + if not self.auth_token: + raise NotAuthenticatedError(reason='Not logged in') + + if not isinstance(parcel_type, ParcelType): + raise ParcelTypeError(reason=f'Unknown parcel type: {parcel_type}') + + match parcel_type: + case ParcelType.TRACKED: + url = parcels + case ParcelType.SENT: + url = sent + case ParcelType.RETURNS: + url = returns + case _: + raise ParcelTypeError(reason=f'Unknown parcel type: {parcel_type}') + + async with await self.sess.get(url=url, + headers={'Authorization': self.auth_token}, + ) as resp: + if resp.status == 200: + _parcels = (await resp.json())['parcels'] + + if status is not None: + if isinstance(status, ParcelStatus): + status = [status] + + _parcels = (_parcel for _parcel in _parcels if ParcelStatus[_parcel['status']] in status) + + if pickup_point is not None: + if isinstance(pickup_point, str): + pickup_point = [pickup_point] + + _parcels = (_parcel for _parcel in _parcels if _parcel['pickUpPoint']['name'] in pickup_point) + + if shipment_type is not None: + if isinstance(shipment_type, ParcelShipmentType): + shipment_type = [shipment_type] + + _parcels = (_parcel for _parcel in _parcels if + ParcelShipmentType[_parcel['shipmentType']] in shipment_type) + + if parcel_size is not None: + if isinstance(parcel_size, ParcelCarrierSize): + parcel_size = [parcel_size] + + _parcels = (_parcel for _parcel in _parcels if + ParcelCarrierSize[_parcel['parcelSize']] in parcel_size) + + if isinstance(parcel_size, ParcelLockerSize): + parcel_size = [parcel_size] + + _parcels = (_parcel for _parcel in _parcels if + ParcelLockerSize[_parcel['parcelSize']] in parcel_size) + + return _parcels if not parse else [Parcel(parcel_data=data) for data in _parcels] + + else: + raise UnidentifiedAPIError(reason=resp) + + async def collect_compartment_properties(self, shipment_number: str | None = None, parcel_obj: Parcel | None = None, + location: dict | None = None) -> bool: + + if shipment_number is not None and parcel_obj is None: + parcel_obj = await self.get_parcel(shipment_number=shipment_number, parse=True) + + async with await self.sess.post(url=collect, + headers={'Authorization': self.auth_token}, + json={ + 'parcel': parcel_obj.compartment_open_data, + 'geoPoint': location if location is not None else parcel_obj.mocked_location + }) as collect_resp: + if collect_resp.status == 200: + parcel_obj.compartment_properties = await collect_resp.json() + self.parcel = parcel_obj + return True + + else: + raise UnidentifiedAPIError(reason=collect_resp) + + async def open_compartment(self): + async with await self.sess.post(url=compartment_open, + headers={'Authorization': self.auth_token}, + json={ + 'sessionUuid': self.parcel.compartment_properties.session_uuid + }) as compartment_open_resp: + if compartment_open_resp.status == 200: + self.parcel.compartment_properties.location = await compartment_open_resp.json() + return True + + else: + raise UnidentifiedAPIError(reason=compartment_open_resp) + + async def check_compartment_status(self, + expected_status: CompartmentExpectedStatus = CompartmentExpectedStatus.OPENED): + async with await self.sess.post(url=compartment_status, + headers={'Authorization': self.auth_token}, + json={ + 'sessionUuid': self.parcel.compartment_properties.session_uuid, + 'expectedStatus': expected_status.name + }) as compartment_status_resp: + if compartment_status_resp.status == 200: + return CompartmentExpectedStatus[(await compartment_status_resp.json())['status']] == expected_status + else: + raise UnidentifiedAPIError(reason=compartment_status_resp) + + async def terminate_collect_session(self): + async with await self.sess.post(url=terminate_collect_session, + headers={'Authorization': self.auth_token}, + json={ + 'sessionUuid': self.parcel.compartment_properties.session_uuid + }) as terminate_resp: + if terminate_resp.status == 200: + return True + else: + raise UnidentifiedAPIError(reason=terminate_resp) + + async def collect(self, shipment_number: str | None = None, parcel_obj: Parcel | None = None, + location: dict | None = None) -> bool: + if shipment_number is not None and parcel_obj is None: + parcel_obj = await self.get_parcel(shipment_number=shipment_number, parse=True) + + if await self.collect_compartment_properties(parcel_obj=parcel_obj, location=location): + if await self.open_compartment(): + if await self.check_compartment_status(): + return True + + return False + + async def close_compartment(self) -> bool: + if await self.check_compartment_status(expected_status=CompartmentExpectedStatus.CLOSED): + if await self.terminate_collect_session(): + return True + + return False + + async def get_prices(self) -> dict: + async with await self.sess.get(url=parcel_prices, + headers={'Authorization': self.auth_token}) as resp: + return await resp.json() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3e7d0aa --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +aiohttp~=3.8.1 +setuptools~=57.0.0 +arrow~=1.2.3 +PyYAML~=6.0 +Telethon~=1.26.0 + +qrcode~=7.3.1 \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..a2bad49 --- /dev/null +++ b/setup.py @@ -0,0 +1,12 @@ +from setuptools import find_packages, setup + +setup( + name='inpost-python', + packages=find_packages(), + version='0.0.1', + description='InPost API written in python', + author='loboda4450, mrkazik99', + author_email='loboda4450@gmail.com, mrkazik99@gmail.com', + + license='LGPL 2.1', +) diff --git a/static/__init__.py b/static/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/static/endpoints.py b/static/endpoints.py new file mode 100644 index 0000000..da566fe --- /dev/null +++ b/static/endpoints.py @@ -0,0 +1,20 @@ +login: str = 'https://api-inmobile-pl.easypack24.net/v1/authenticate' +send_sms_code: str = 'https://api-inmobile-pl.easypack24.net/v1/sendSMSCode/' # get +confirm_sms_code: str = 'https://api-inmobile-pl.easypack24.net/v1/confirmSMSCode' # post + +# \/ Secured by JWT \/ + +refresh_token: str = 'https://api-inmobile-pl.easypack24.net/v1/authenticate' # post +parcels: str = 'https://api-inmobile-pl.easypack24.net/v3/parcels/tracked' # get +parcel: str = 'https://api-inmobile-pl.easypack24.net/v3/parcels/tracked/' # get +collect: str = 'https://api-inmobile-pl.easypack24.net/v1/collect/validate' # post +compartment_open: str = 'https://api-inmobile-pl.easypack24.net/v1/collect/compartment/open' # post +compartment_status: str = 'https://api-inmobile-pl.easypack24.net/v1/collect/compartment/status' # post +terminate_collect_session: str = 'https://api-inmobile-pl.easypack24.net/v1/collect/terminate' # post +friends: str = 'https://api-inmobile-pl.easypack24.net/v1/friends/' # get +shared: str = 'https://api-inmobile-pl.easypack24.net/v1/parcels/shared' # post +sent: str = 'https://api-inmobile-pl.easypack24.net/v3/parcels/sent/' # get +returns: str = 'https://api-inmobile-pl.easypack24.net/v1/returns/parcels/' # get +parcel_prices: str = 'https://api-inmobile-pl.easypack24.net/v1/prices/parcels' # get +tickets: str = 'https://api-inmobile-pl.easypack24.net/v1/returns/tickets' # get +logout: str = 'https://api-inmobile-pl.easypack24.net/v1/logout' # post diff --git a/static/exceptions.py b/static/exceptions.py new file mode 100644 index 0000000..2c7dbad --- /dev/null +++ b/static/exceptions.py @@ -0,0 +1,114 @@ +# ----------------- Parcels ----------------- # +from typing import Optional, Any + + +class UnidentifiedParcelError(Exception): + def __init__(self, reason): + super().__init__(reason) + self.msg: str = Optional[str] + self.reason: Any = Optional[Any] + + @property + def stack(self): + return self.reason + + +class ParcelTypeError(Exception): + def __init__(self, reason): + super().__init__(reason) + self.msg: str = Optional[str] + self.reason: Any = Optional[Any] + + @property + def stack(self): + return self.reason + + +# ----------------- API ----------------- # +class NotAuthenticatedError(Exception): + def __init__(self, reason): + super().__init__(reason) + self.msg: str = Optional[str] + self.reason: Any = Optional[Any] + + @property + def stack(self): + return self.reason + + +class ReAuthenticationError(Exception): + def __init__(self, reason): + super().__init__(reason) + self.msg: str = Optional[str] + self.reason: Any = Optional[Any] + + @property + def stack(self): + return self.reason + + +class PhoneNumberError(Exception): + def __init__(self, reason): + super().__init__(reason) + self.msg: str = Optional[str] + self.reason: Any = Optional[Any] + + @property + def stack(self): + return self.reason + + +class SmsCodeConfirmationError(Exception): + def __init__(self, reason): + super().__init__(reason) + self.msg: str = Optional[str] + self.reason: Any = Optional[Any] + + @property + def stack(self): + return self.reason + + +class RefreshTokenException(Exception): + def __init__(self, reason): + super().__init__(reason) + self.msg: str = Optional[str] + self.reason: Any = Optional[Any] + + @property + def stack(self): + return self.reason + + +class UnidentifiedAPIError(Exception): + def __init__(self, reason): + super().__init__(reason) + self.msg: str = Optional[str] + self.reason: Any = Optional[Any] + + @property + def stack(self): + return self.reason + + +# ----------------- Other ----------------- # +class UserLocationError(Exception): + def __init__(self, reason): + super().__init__(reason) + self.msg: str = Optional[str] + self.reason: Any = Optional[Any] + + @property + def stack(self): + return self.reason + + +class UnidentifiedError(Exception): + def __init__(self, reason): + super().__init__(reason) + self.msg: str = Optional[str] + self.reason: Any = Optional[Any] + + @property + def stack(self): + return self.reason diff --git a/static/headers.py b/static/headers.py new file mode 100644 index 0000000..9e06775 --- /dev/null +++ b/static/headers.py @@ -0,0 +1 @@ +appjson = {"Content-Type": "application/json"} diff --git a/static/parcels.py b/static/parcels.py new file mode 100644 index 0000000..bfe1bfd --- /dev/null +++ b/static/parcels.py @@ -0,0 +1,235 @@ +import random +from io import BytesIO +from typing import List, Optional, Tuple, Union + +import qrcode +from arrow import get, arrow + +from static.statuses import * + + +class Parcel: + def __init__(self, parcel_data: dict): + self.shipment_number: str = parcel_data['shipmentNumber'] + self.shipment_type: ParcelShipmentType = ParcelShipmentType[parcel_data['shipmentType']] + self._open_code: Optional[str] = parcel_data['openCode'] if 'openCode' in parcel_data else None + self._qr_code: Optional[QRCode] = QRCode(parcel_data['qrCode']) if 'qrCode' in parcel_data else None + self.stored_date: Optional[arrow] = get(parcel_data['storedDate']) if 'storedDate' in parcel_data else None + self.pickup_date: Optional[arrow] = get(parcel_data['pickUpDate']) if 'pickUpDate' in parcel_data else None + self.parcel_size: Union[ParcelLockerSize, ParcelCarrierSize] = ParcelLockerSize[parcel_data['parcelSize']] \ + if self.shipment_type == ParcelShipmentType.parcel else ParcelCarrierSize[parcel_data['parcelSize']] + self.receiver: Receiver = Receiver(receiver_data=parcel_data['receiver']) + self.sender: Sender = Sender(sender_data=parcel_data['sender']) + self.pickup_point: PickupPoint = PickupPoint(pickuppoint_data=parcel_data['pickUpPoint']) + self.multi_compartment: Optional[MultiCompartment] = MultiCompartment(parcel_data['multiCompartment']) \ + if 'multiCompartment' in parcel_data else None + self.is_end_off_week_collection: bool = parcel_data['endOfWeekCollection'] + self.operations: Operations = Operations(operations_data=parcel_data['operations']) + self.status: ParcelStatus = ParcelStatus[parcel_data['status']] + self.event_log: List[EventLog] = [EventLog(eventlog_data=event) for event in parcel_data['eventLog']] + self.avizo_transaction_status: str = parcel_data['avizoTransactionStatus'] + self.shared_to: List[SharedTo] = [SharedTo(sharedto_data=person) for person in parcel_data['sharedTo']] + self.ownership_status: ParcelOwnership = ParcelOwnership[parcel_data['ownershipStatus']] + self._compartment_properties: CompartmentProperties | None = None + + def __str__(self): + return f"Shipment number: {self.shipment_number}\n" \ + f"Status: {self.status}\n" \ + f"Pickup point: {self.pickup_point}\n" \ + f"Sender: {str(self.sender)}" + + @property + def open_code(self): + return self._open_code + + @property + def generate_qr_image(self): + return self._qr_code.qr_image + + @property + def compartment_properties(self): + return self._compartment_properties + + @compartment_properties.setter + def compartment_properties(self, compartmentproperties_data: dict): + self._compartment_properties = CompartmentProperties(compartmentproperties_data=compartmentproperties_data) + + @property + def compartment_location(self): + return self._compartment_properties.location + + @compartment_location.setter + def compartment_location(self, location_data): + self._compartment_properties.location = location_data + + @property + def compartment_status(self): + return self._compartment_properties.status + + @compartment_status.setter + def compartment_status(self, status): + self._compartment_properties.status = status + + @property + def compartment_open_data(self): + return { + 'shipmentNumber': self.shipment_number, + 'openCode': self._open_code, + 'receiverPhoneNumber': self.receiver.phone_number + } + + @property + def mocked_location(self): + return { + 'latitude': round(self.pickup_point.latitude + random.uniform(-0.00005, 0.00005), 6), + 'longitude': round(self.pickup_point.longitude + random.uniform(-0.00005, 0.00005), 6), + 'accuracy': round(random.uniform(1, 4), 1) + } + + +class Receiver: + def __init__(self, receiver_data: dict): + self.email: str = receiver_data['email'] + self.phone_number: str = receiver_data['phoneNumber'] + self.name: str = receiver_data['name'] + + +class Sender: + def __init__(self, sender_data: dict): + self.sender_name: str = sender_data['name'] + + def __str__(self): + return self.sender_name + + +class PickupPoint: + def __init__(self, pickuppoint_data): + self.name: str = pickuppoint_data['name'] + self.latitude: float = pickuppoint_data['location']['latitude'] + self.longitude: float = pickuppoint_data['location']['longitude'] + self.description: str = pickuppoint_data['locationDescription'] + self.opening_hours: str = pickuppoint_data['openingHours'] + self.post_code: str = pickuppoint_data['addressDetails']['postCode'] + self.city: str = pickuppoint_data['addressDetails']['city'] + self.province: str = pickuppoint_data['addressDetails']['province'] + self.street: str = pickuppoint_data['addressDetails']['street'] + self.building_number: str = pickuppoint_data['addressDetails']['buildingNumber'] + self.virtual: int = pickuppoint_data['virtual'] + self.point_type: str = pickuppoint_data['pointType'] + self.type: List[ParcelDeliveryType] = [ParcelDeliveryType[data] for data in pickuppoint_data['type']] + self.location_round_the_clock: bool = pickuppoint_data['location247'] + self.doubled: bool = pickuppoint_data['doubled'] + self.image_url: str = pickuppoint_data['imageUrl'] + self.easy_access_zone: bool = pickuppoint_data['easyAccessZone'] + self.air_sensor: bool = pickuppoint_data['airSensor'] + + def __str__(self): + return self.name + + @property + def location(self) -> Tuple[float, float]: + return self.latitude, self.longitude + + +class MultiCompartment: + def __init__(self, multicompartment_data): + self.uuid = multicompartment_data['uuid'] + self.shipment_numbers: Optional[List['str']] = multicompartment_data['shipmentNumbers'] \ + if 'shipmentNumbers' in multicompartment_data else None + self.presentation: bool = multicompartment_data['presentation'] + self.collected: bool = multicompartment_data['collected'] + + +class Operations: + def __init__(self, operations_data): + self.manual_archive: bool = operations_data['manualArchive'] + self.auto_archivable_since: Optional[arrow] = get( + operations_data['autoArchivableSince']) if 'autoArchivableSince' in operations_data else None + self.delete: bool = operations_data['delete'] + self.collect: bool = operations_data['collect'] + self.expand_avizo: bool = operations_data['expandAvizo'] + self.highlight: bool = operations_data['highlight'] + self.refresh_until: arrow = get(operations_data['refreshUntil']) + self.request_easy_access_zone: str = operations_data['requestEasyAccessZone'] + self.is_voicebot: bool = operations_data['voicebot'] + self.can_share_to_observe: bool = operations_data['canShareToObserve'] + self.can_share_open_code: bool = operations_data['canShareOpenCode'] + self.can_share_parcel: bool = operations_data['canShareParcel'] + + +class EventLog: + def __init__(self, eventlog_data: dict): + self.type: str = eventlog_data['type'] + self.name: ParcelStatus = ParcelStatus[eventlog_data['name']] + self.date: arrow = get(eventlog_data['date']) + + +class SharedTo: + def __init__(self, sharedto_data): + self.uuid: str = sharedto_data['uuid'] + self.name: str = sharedto_data['name'] + self.phone_number = sharedto_data['phoneNumber'] + + +class QRCode: + def __init__(self, qrcode_data: str): + self._qr_code = qrcode_data + + @property + def qr_image(self) -> BytesIO: + qr = qrcode.QRCode( + version=3, + error_correction=qrcode.constants.ERROR_CORRECT_H, + box_size=20, + border=4, + mask_pattern=5 + ) + + qr.add_data(self._qr_code) + qr.make(fit=False) + img1 = qr.make_image(fill_color="black", back_color="white") + bio = BytesIO() + bio.name = 'qr.png' + img1.save(bio, 'PNG') + bio.seek(0) + return bio + + +class CompartmentLocation: + def __init__(self, compartmentlocation_data: dict): + self.name: str = compartmentlocation_data['compartment']['name'] + self.side: str = compartmentlocation_data['compartment']['location']['side'] + self.column: str = compartmentlocation_data['compartment']['location']['column'] + self.row: str = compartmentlocation_data['compartment']['location']['row'] + self.open_compartment_waiting_time: int = compartmentlocation_data['openCompartmentWaitingTime'] + self.action_time: int = compartmentlocation_data['actionTime'] + self.confirm_action_time: int = compartmentlocation_data['confirmActionTime'] + + +class CompartmentProperties: + def __init__(self, compartmentproperties_data: dict): + self._session_uuid: str = compartmentproperties_data['sessionUuid'] + self._session_expiration_time: int = compartmentproperties_data['sessionExpirationTime'] + self._location: CompartmentLocation | None = None + self._status: CompartmentActualStatus | None = None + + @property + def session_uuid(self): + return self._session_uuid + + @property + def location(self): + return self._location + + @location.setter + def location(self, location_data: dict): + self._location = CompartmentLocation(location_data) + + @property + def status(self): + return self._status + + @status.setter + def status(self, status_data: str | CompartmentActualStatus): + self._status = status_data if isinstance(status_data, CompartmentActualStatus) \ + else CompartmentActualStatus[status_data] diff --git a/static/statuses.py b/static/statuses.py new file mode 100644 index 0000000..510d553 --- /dev/null +++ b/static/statuses.py @@ -0,0 +1,104 @@ +from enum import Enum + + +class ParcelBase(Enum): + def __gt__(self, other): + ... + + def __ge__(self, other): + ... + + def __le__(self, other): + ... + + def __lt__(self, other): + ... + + def __eq__(self, other): + if isinstance(other, ParcelBase): + return self.name == other.name + + return False + + +class ParcelCarrierSize(ParcelBase): + A = '8x38x64' + B = '19x38x64' + C = '41x38x64' + D = '50x50x80' + + +class ParcelLockerSize(ParcelBase): + A = '8x38x64' + B = '19x38x64' + C = '41x38x64' + + +class ParcelDeliveryType(ParcelBase): + parcel_locker = 'Paczkomat' + carrier = 'Kurier' + parcel_point = 'PaczkoPunkt' + + +class ParcelShipmentType(ParcelBase): + parcel = 'Paczkomat' + carrier = 'Kurier' + parcel_point = 'PaczkoPunkt' + + +class ParcelAdditionalInsurance(ParcelBase): + UNINSURANCED = 1 + ONE = 2 # UPTO 5000 + TWO = 3 # UPTO 10000 + THREE = 4 # UPTO 20000 + + +class ParcelType(ParcelBase): + TRACKED = 'Przychodzące' + SENT = 'Wysłane' + RETURNS = 'Zwroty' + + +class ParcelStatus(ParcelBase): + CONFIRMED = 'Potwierdzona' + COLLECTED_FROM_SENDER = 'Odebrana od nadawcy' + DISPATCHED_BY_SENDER_TO_POK = 'Nadana w PaczkoPunkcie' + DISPATCHED_BY_SENDER = 'Nadana w paczkomacie' + TAKEN_BY_COURIER = 'Odebrana przez Kuriera' + TAKEN_BY_COURIER_FROM_POK = 'Odebrana z PaczkoPunktu nadawczego' + ADOPTED_AT_SOURCE_BRANCH = 'Przyjęta w oddziale' + ADOPTED_AT_SORTING_CENTER = 'Przyjęta w sortowni' + SENT_FROM_SOURCE_BRANCH = 'Wysłana z oddziału' + OUT_FOR_DELIVERY = 'Wydana do doręczenia' + READY_TO_PICKUP = 'Gotowa do odbioru' + DELIVERED = 'Doręczona' + + +class ParcelOwnership(ParcelBase): + FRIEND = 'Zaprzyjaźniona' + OWN = 'Własna' + + +# both are the same, only for being clear +class CompartmentExpectedStatus(ParcelBase): + OPENED = 'Otwarta' + CLOSED = 'Zamknięta' + + +class CompartmentActualStatus(ParcelBase): + OPENED = 'Otwarta' + CLOSED = 'Zamknięta' + + +class ParcelServiceName(ParcelBase): + ALLEGRO_PARCEL = 1 + ALLEGRO_PARCEL_SMART = 2 + ALLEGRO_LETTER = 3 + ALLEGRO_COURIER = 4 + STANDARD = 5 + STANDARD_PARCEL_SMART = 6 + PASS_THRU = 7 + CUSTOMER_SERVICE_POINT = 8 + REVERSE = 9 + STANDARD_COURIER = 10 + REVERSE_RETURN = 11