Skip to content

Commit

Permalink
Merge d51d6b2 into be5b768
Browse files Browse the repository at this point in the history
  • Loading branch information
osantana committed Apr 25, 2017
2 parents be5b768 + d51d6b2 commit c89b6a6
Show file tree
Hide file tree
Showing 16 changed files with 1,088 additions and 289 deletions.
71 changes: 67 additions & 4 deletions correios/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,24 @@
# limitations under the License.


import os
from datetime import datetime
from typing import Union, Sequence, List, Dict
from decimal import Decimal
from typing import Union, Sequence, List, Dict, Optional

import os

from correios import xml_utils, DATADIR
from correios.exceptions import PostingListSerializerError, TrackingCodesLimitExceededError
from correios.models.data import EXTRA_SERVICE_MP, EXTRA_SERVICE_AR
from correios.utils import to_decimal
from .models.address import ZipAddress, ZipCode
from .models.posting import (NotFoundTrackingEvent, TrackingCode, PostingList, ShippingLabel,
TrackingEvent, EventStatus)
from .models.user import User, FederalTaxNumber, StateTaxNumber, Contract, PostingCard, Service
TrackingEvent, EventStatus, Package, Freight)
from .models.user import User, FederalTaxNumber, StateTaxNumber, Contract, PostingCard, Service, ExtraService
from .soap import SoapClient

KG = 1000 # g


class ModelBuilder:
def build_service(self, service_data):
Expand Down Expand Up @@ -149,6 +155,23 @@ def load_tracking_events(self, tracking_codes: Dict[str, TrackingCode], response

return result

def build_freights_list(self, response):
result = []
for service in response.Servicos.cServico:
value = to_decimal(service.Valor)
delivery_time = int(service.PrazoEntrega)
declared_value = to_decimal(service.ValorValorDeclarado)
freight = Freight(
service=service.Codigo,
total=value,
delivery_time=delivery_time,
declared_value=declared_value,
saturday=service.EntregaSabado.upper() == "S",
home=service.EntregaDomiciliar.upper() == "S",
)
result.append(freight)
return result


class PostingListSerializer:
def _get_posting_list_element(self, posting_list):
Expand Down Expand Up @@ -278,6 +301,7 @@ class Correios:
'test': ("https://apphom.correios.com.br/SigepMasterJPA/AtendeClienteService/AtendeCliente?wsdl", False),
}
websro_url = "https://webservice.correios.com.br/service/rastro/Rastro.wsdl"
freight_url = "http://ws.correios.com.br/calculador/CalcPrecoPrazo.asmx?WSDL"

def __init__(self, username, password, timeout=8, environment="production"):
self.username = username
Expand All @@ -294,6 +318,9 @@ def __init__(self, username, password, timeout=8, environment="production"):
self.websro_client = SoapClient(self.websro_url, timeout=self.timeout)
self.websro = self.websro_client.service

self.freight_client = SoapClient(self.freight_url, timeout=self.timeout)
self.freight = self.freight_client.service

self.model_builder = ModelBuilder()

def _auth_call(self, method_name, *args, **kwargs):
Expand Down Expand Up @@ -380,3 +407,39 @@ def get_tracking_code_events(self, tracking_list):
response = self.websro.buscaEventosLista(self.username, self.password, "L", "T", "101",
tuple(tracking_codes.keys()))
return self.model_builder.load_tracking_events(tracking_codes, response)

def calculate_freights(self,
posting_card: PostingCard,
services: List[Union[Service, int]],
from_zip: Union[ZipCode, int, str], to_zip: Union[ZipCode, int, str],
package: Package,
value: Union[Decimal, float] = 0.00,
extra_services: Optional[Sequence[Union[ExtraService, int]]] = None):

administrative_code = posting_card.administrative_code
services = [Service.get(s) for s in services]
from_zip = ZipCode.create(from_zip)
to_zip = ZipCode.create(to_zip)

if extra_services is None:
extra_services = []
else:
extra_services = [ExtraService.get(es) for es in extra_services]

response = self.freight.CalcPrecoPrazo(
administrative_code,
self.password,
",".join(str(s) for s in services),
str(from_zip),
str(to_zip),
package.weight / KG,
package.package_type,
package.length,
package.height,
package.width,
package.diameter,
"S" if EXTRA_SERVICE_MP in extra_services else "N",
value,
"S" if EXTRA_SERVICE_AR in extra_services else "N",
)
return self.model_builder.build_freights_list(response)
7 changes: 7 additions & 0 deletions correios/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,10 @@ class MaximumDeclaredValueError(InvalidDeclaredValueError):

class MinimumDeclaredValueError(InvalidDeclaredValueError):
pass


class FreightCalculationError(ClientError):
def __init__(self, message: str, code: int, *args) -> None:
super().__init__(*args)
self.code = code
self.message = message
40 changes: 36 additions & 4 deletions correios/models/posting.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import os
import math
from datetime import datetime
from datetime import datetime, timedelta
from decimal import Decimal
from typing import Optional, Sequence, Tuple, Union, List, Dict # noqa: F401

Expand Down Expand Up @@ -43,6 +43,7 @@
MAX_CYLINDER_SIZE = 28
INSURANCE_VALUE_THRESHOLD = Decimal("50.00") # R$
INSURANCE_PERCENTUAL_COST = Decimal("0.007") # 0.7%
MONEY_QUANTIZATION = Decimal("0.00")


class EventStatus:
Expand Down Expand Up @@ -336,6 +337,10 @@ def volumetric_weight(self) -> int:
def posting_weight(self) -> int:
return Package.calculate_posting_weight(self.weight, self.volumetric_weight)

@property
def freight_package_type(self) -> int:
return [3, 1, 2][self.package_type - 1] # see documentation for freight calculation

@classmethod
def calculate_volumetric_weight(cls, width, height, length) -> int:
return int(math.ceil((width * height * length) / IATA_COEFICIENT))
Expand All @@ -355,7 +360,7 @@ def calculate_insurance(cls,
per_unit_value = Decimal(per_unit_value)
if Service.get(service) == Service.get(SERVICE_PAC) and per_unit_value > INSURANCE_VALUE_THRESHOLD:
value = (per_unit_value - INSURANCE_VALUE_THRESHOLD) * INSURANCE_PERCENTUAL_COST
return Decimal(value * quantity).quantize(Decimal('0.00'))
return Decimal(value * quantity).quantize(MONEY_QUANTIZATION)

@classmethod
def validate(cls,
Expand Down Expand Up @@ -457,7 +462,7 @@ def __init__(self,
service: Union[Service, int],
tracking_code: Union[TrackingCode, str],
package: Package,
extra_services: Optional[Sequence[Union[ExtraService, int]]] = None,
extra_services: Optional[List[Union[ExtraService, int]]] = None,
logo: Optional[Union[str, Image.Image]] = None,
order: Optional[str] = "",
invoice_number: Optional[str] = "",
Expand Down Expand Up @@ -506,7 +511,7 @@ def __init__(self,
def __repr__(self):
return "<ShippingLabel tracking={!r}>".format(str(self.tracking_code))

def add_extra_services(self, extra_services: Sequence[Union["ExtraService", int]]):
def add_extra_services(self, extra_services: List[Union["ExtraService", int]]):
for extra_service in extra_services:
self.add_extra_service(extra_service)

Expand Down Expand Up @@ -639,3 +644,30 @@ def close_with_id(self, number: int):
@property
def closed(self):
return self.number is not None


class Freight:
def __init__(self,
service: Union[Service, int],
total: Union[Decimal, float, int, str],
delivery_time: Union[int, timedelta],
declared_value: Union[Decimal, float, int, str] = 0.00,
saturday: bool = False,
home: bool = False) -> None:

self.service = Service.get(service)

if not isinstance(total, Decimal):
total = Decimal(total).quantize(MONEY_QUANTIZATION)
self.total = total

if not isinstance(delivery_time, timedelta):
delivery_time = timedelta(days=delivery_time)
self.delivery_time = delivery_time

if not isinstance(declared_value, Decimal):
declared_value = Decimal(declared_value).quantize(MONEY_QUANTIZATION)
self.declared_value = declared_value

self.saturday = saturday
self.home = home
31 changes: 10 additions & 21 deletions correios/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,23 @@
# limitations under the License.


import os
from datetime import datetime
from datetime import datetime # noqa: F401
from decimal import Decimal
from typing import Union, Optional, Sequence
from typing import Union, Optional, Sequence, List # noqa: F401

import os
from PIL import Image

from correios import DATADIR
from correios.exceptions import (InvalidFederalTaxNumberError, InvalidExtraServiceError,
InvalidRegionalDirectionError, InvalidUserContractError,
MaximumDeclaredValueError, MinimumDeclaredValueError)
from correios.utils import to_integer, to_datetime
from .data import EXTRA_SERVICES, REGIONAL_DIRECTIONS, SERVICES, EXTRA_SERVICE_VD

EXTRA_SERVICE_CODE_SIZE = 2


def to_integer(number: Union[int, str]) -> int:
try:
return int(number.strip()) # type: ignore
except AttributeError:
return int(number)


def to_datetime(date: Union[datetime, str], fmt="%Y-%m-%d %H:%M:%S%z") -> datetime:
if isinstance(date, str):
last_colon_pos = date.rindex(":")
date = date[:last_colon_pos] + date[last_colon_pos + 1:]
return datetime.strptime(date, fmt)
return date


def _to_federal_tax_number(federal_tax_number) -> "FederalTaxNumber":
if isinstance(federal_tax_number, FederalTaxNumber):
return federal_tax_number
Expand Down Expand Up @@ -155,8 +141,9 @@ def __init__(self,
self.max_declared_value = max_declared_value

if default_extra_services is None:
default_extra_services = []
self.default_extra_services = [ExtraService.get(es) for es in default_extra_services]
self.default_extra_services = [] # type: List
else:
self.default_extra_services = [ExtraService.get(es) for es in default_extra_services]

def __str__(self):
return str(self.code)
Expand Down Expand Up @@ -215,10 +202,12 @@ def __repr__(self):
return "<ExtraService number={!r}, code={!r}>".format(self.number, self.code)

def __eq__(self, other):
if isinstance(other, int):
return self.number == other
return self.number == other.number

def is_declared_value(self):
return self.number == EXTRA_SERVICE_VD
return self == EXTRA_SERVICE_VD

@classmethod
def get(cls, number: Union['ExtraService', int]) -> 'ExtraService':
Expand Down
2 changes: 1 addition & 1 deletion correios/soap.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,5 @@ def send(self, request):
class SoapClient(Client):
def __init__(self, url, cert=None, verify=True, timeout=8, *args, **kwargs):
transport = RequestsTransport(cert=cert, verify=verify, timeout=timeout)
headers = {"Content-Type": "text/xml;charset=UTF-8", "SOAPAction": ""}
headers = {"Content-Type": "text/xml;charset=UTF-8"}
super().__init__(url, transport=transport, headers=headers, **kwargs)
32 changes: 31 additions & 1 deletion correios/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import re
from decimal import Decimal
from datetime import datetime
from itertools import chain
from typing import Container, Iterable, Sized
from typing import Container, Iterable, Sized, Union


def capitalize_phrase(phrase: str) -> str:
Expand Down Expand Up @@ -51,3 +54,30 @@ def __contains__(self, elem):

def __len__(self):
return sum(len(r) for r in self.ranges)


def to_integer(number: Union[int, str]) -> int:
try:
return int(str(number).strip())
except AttributeError:
return int(number)


def to_datetime(date: Union[datetime, str], fmt="%Y-%m-%d %H:%M:%S%z") -> datetime:
if isinstance(date, str):
last_colon_pos = date.rindex(":")
date = date[:last_colon_pos] + date[last_colon_pos + 1:]
return datetime.strptime(date, fmt)
return date


def to_decimal(value: Union[str, float], precision=2):
value = rreplace(str(value), ",", ".", 1)
if "." in value:
real, imag = value.rsplit(".", 1)
else:
real, imag = value, "0"
real = re.sub("[,._]", "", real)

quantize = Decimal("0." + "0" * precision)
return Decimal("{}.{}".format(real, imag)).quantize(quantize)
Binary file not shown.
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ class Meta:
package_type = Package.TYPE_BOX
width = LazyFunction(lambda: random.randint(11, 30))
height = LazyFunction(lambda: random.randint(2, 30))
length = LazyFunction(lambda: random.randint(16, 30))
length = LazyFunction(lambda: random.randint(18, 30))
weight = LazyFunction(lambda: random.randint(1, 100) * 100)
service = LazyFunction(lambda: random.choice(_services))
sequence = Sequence(lambda n: (n, n + 1))
Expand Down

0 comments on commit c89b6a6

Please sign in to comment.