Files
vontor-cz/backend/thirdparty/zasilkovna/models.py
Brunobrno 564418501c Refactor email system and add contact form backend
Refactored email sending to use a single HTML template with a base layout, removed plain text email templates, and updated all related backend logic. Introduced a new ContactMe model, serializer, Celery task, and API endpoints for handling contact form submissions, including email notifications. Renamed ShopConfiguration to SiteConfiguration throughout the backend for consistency. Updated frontend to remove unused components, add a new Services section, and adjust navigation and contact form integration.
2025-12-12 01:52:41 +01:00

160 lines
6.2 KiB
Python

"""Models for integration with Packeta (Zásilkovna) API.
These models wrap calls to the SOAP API via the immutable client defined in
`client.py` (PacketaAPI). DO NOT modify the client; we only consume it here.
Workflow:
- Create a `PacketaPacket` instance locally with required recipient data.
- On first save (when `packet_id` is empty) call the remote API to create
the packet and persist the returned identifier + metadata.
- Use helper methods to refresh remote info, fetch label PDF, cancel packet.
- Group packets into a `PacketaShipment` and create shipment remotely.
Edge cases handled:
- API faults raise exceptions (to be surfaced by serializer validation).
- Missing remote fields are stored in `metadata` JSON.
- Label PDF stored as binary field; can be re-fetched if empty.
"""
import json
from django.db import models
from django.utils import timezone
from django.core.validators import RegexValidator
from django.core.files.base import ContentFile
from django.apps import apps
from rest_framework.exceptions import ValidationError
from .client import PacketaAPI
from configuration.models import SiteConfiguration
packeta_client = PacketaAPI() # single reusable instance
class ZasilkovnaPacket(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
class STATE(models.TextChoices):
WAITING_FOR_ORDER = "WAITING_FOR_ORDERING_SHIPMENT", "cz#Čeká na objednání zásilkovny"
PENDING = "PENDING", "cz#Podáno"
SENDED = "SENDED", "cz#Odesláno"
ARRIVED = "ARRIVED", "cz#Doručeno"
CANCELED = "CANCELED", "cz#Zrušeno"
RETURNING = "RETURNING", "cz#Posláno zpátky"
RETURNED = "RETURNED", "cz#Vráceno"
state = models.CharField(max_length=20, choices=STATE.choices, default=STATE.PENDING)
# ------- API -------
# https://client.packeta.com/cs/senders (admin rozhraní)
addressId = models.IntegerField(null=True, blank=True, help_text="ID adresy/pointu, ve Widgetu zásilkovny který si vybere uživatel.")
packet_id = models.IntegerField(null=True, blank=True, help_text="Číslo zásilky v Packetě (vraceno od API od Packety)")
barcode = models.CharField(null=True, blank=True, max_length=64, help_text="Čárový kód zásilky od Packety")
weight = models.IntegerField(
default=0,
help_text="Hmotnost zásilky v gramech"
)
# 🚚 návratové směrovací kódy (pro vrácení zásilky)
return_routing = models.JSONField(
default=list,
blank=True,
null=True,
help_text="Seznam 2 routing stringů pro vrácení zásilky"
)
class PDF_SIZE(models.TextChoices):
A6_ON_A6 = ("A6 on A6", "105x148 mm (A6) label on a page of the same size")
A7_ON_A7 = ("A7 on A7", "105x74 mm (A7) label on a page of the same size")
A6_ON_A4 = ("A6 on A4", "105x148 mm (A6) label on a page of size 210x297 mm (A4)")
A7_ON_A4 = ("A7 on A4", "105x74 mm (A7) label on a page of size 210x297 mm (A4)")
A8_ON_A8 = ("A8 on A8", "50x74 mm (A8) label on a page of the same size")
size_of_pdf = models.CharField(max_length=20, choices=PDF_SIZE.choices, default=PDF_SIZE.A6_ON_A6)
def save(self, *args, **kwargs):
return super().save(args, **kwargs)
def order_shippment(self):
if self.addressId is None:
raise ValidationError("AddressId must be set to order shipping.")
Carrier = apps.get_model('commerce', 'Carrier')
Order = apps.get_model('commerce', 'Order')
carrier = Carrier.objects.get(zasilkovna=self)
order = Order.objects.get(carrier=carrier)
cash_on_delivery = order.payment.payment_method == order.payment.PAYMENT.CASH_ON_DELIVERY
if not self.packet_id:
response = packeta_client.create_packet(
addressId=self.addressId, # ID z widgetu
weight=self.weight,
number=order.id,
name=order.first_name,
surname=order.last_name,
company=order.company,
email=order.email,
cod=order.total_price if cash_on_delivery else 0, # dobírka
value=order.total_price,
currency=SiteConfiguration.get_solo().currency, #CZK
eSite= SiteConfiguration.get_solo().name,
)
self.packet_id = response['packet_id']
self.barcode = response['barcode']
else:
raise ValidationError("Přeprava už byla objednana!!!.")
return self.save()
def cancel_packet(self):
"""Cancel this packet via the Packeta API."""
packeta_client.cancel_packet(self.packet_id)
self.state = self.STATE.CANCELED
self.save() # persist state change
def get_tracking_url(self):
"""Vrátí veřejnou URL pro sledování zásilky (Zásilkovna.cz)."""
base_url = "https://www.zasilkovna.cz/vyhledavani?query="
return f"{base_url}{self.barcode}"
def returning_packet(self):
"""Mark this packet as returning via the Packeta API."""
response = packeta_client.get_return_routing(self.packet_id)
self.return_routing = json.loads(response)
self.state = self.STATE.RETURNING
self.save() # persist state change
class ZasilkovnaShipment(models.Model):
created_at = models.DateTimeField(auto_now_add=True, editable=False)
shipment_id = models.CharField(max_length=255, unique=True, help_text="ID zásilky v Packetě", editable=False)
barcode = models.CharField(
max_length=64,
help_text="Čárový kód zásilky v Packetě (format: )",
validators=[
RegexValidator(r'D-***-XM-<id>', message="Neplatný formát čárového kódu.")
]
)
packets = models.ManyToManyField(ZasilkovnaPacket, related_name="shipments", help_text="Seznam zásilek v této zásilce (packet_id)")
def save(self, *args, **kwargs):
if not self.shipment_id:
response = packeta_client.create_shipment(
packet_ids=[packet.packet_id for packet in self.packets.all()]
)
self.shipment_id = response['shipment_id']
self.barcode = response['barcode']
return super().save(args, **kwargs)