1 | from common.db.basic import Logged
|
---|
2 | from django.db import models
|
---|
3 | from django.utils.translation import gettext_lazy as _
|
---|
4 | from django_fsm import FSMField, transition
|
---|
5 |
|
---|
6 | from shipment.models.trip import Trip
|
---|
7 |
|
---|
8 |
|
---|
9 | class ShipmentStateChoices(models.TextChoices):
|
---|
10 | NEW = "NW", _("new") # before the trip is created
|
---|
11 | WAITING = "WA", _("waiting for courier") # Trip is created but no courier has been assigned to it yet
|
---|
12 | PICKUP = "PI", _(
|
---|
13 | "on the source way") # Trip is assigned to a courier and the courier is on his way to pickup packages from the source
|
---|
14 | DROP_OFF = "DO", _(
|
---|
15 | "delivering to destination") # Trip is picked up by the courier, and he is on his way to deliver the packages
|
---|
16 | DELIVERED = "DV", _("delivered")
|
---|
17 | CANCELLED = "CA", _("cancelled")
|
---|
18 |
|
---|
19 |
|
---|
20 | class ShipmentQuerySet(models.QuerySet):
|
---|
21 | def actives(self, *args, **kwargs):
|
---|
22 | return super(ShipmentQuerySet, self).filter(*args, **kwargs).filter(
|
---|
23 | state__in=[
|
---|
24 | ShipmentStateChoices.NEW, ShipmentStateChoices.WAITING,
|
---|
25 | ShipmentStateChoices.PICKUP, ShipmentStateChoices.DROP_OFF
|
---|
26 | ])
|
---|
27 |
|
---|
28 | def closes(self, *args, **kwargs):
|
---|
29 | return super(ShipmentQuerySet, self).filter(*args, **kwargs).filter(
|
---|
30 | state__in=[
|
---|
31 | ShipmentStateChoices.DELIVERED, ShipmentStateChoices.CANCELLED
|
---|
32 | ])
|
---|
33 |
|
---|
34 | class ShipmentManager(models.Manager):
|
---|
35 | def get_queryset(self):
|
---|
36 | return ShipmentQuerySet(self.model, using=self._db)
|
---|
37 |
|
---|
38 | def actives(self, *args, **kwargs):
|
---|
39 | return self.get_queryset().actives(*args, **kwargs)
|
---|
40 |
|
---|
41 | def closes(self, *args, **kwargs):
|
---|
42 | return self.get_queryset().closes(*args, **kwargs)
|
---|
43 |
|
---|
44 |
|
---|
45 | class Shipment(Logged):
|
---|
46 | order_id = models.PositiveIntegerField(verbose_name=_('order id'))
|
---|
47 | trip = models.ForeignKey(Trip, on_delete=models.PROTECT, verbose_name=_('trip'), null=True,
|
---|
48 | related_name='shipments')
|
---|
49 | state = FSMField(default=ShipmentStateChoices.NEW, choices=ShipmentStateChoices.choices,
|
---|
50 | verbose_name=_('State'), protected=True)
|
---|
51 | tracking_number = models.CharField(verbose_name=_('tracking number'), max_length=400, null=True, blank=True)
|
---|
52 | tracking_url = models.CharField(verbose_name=_('tracking URL'), null=True, blank=True, max_length=500)
|
---|
53 | total_weight = models.FloatField(verbose_name=_('total weight'), null=True, blank=True)
|
---|
54 | destination = models.JSONField(verbose_name=_('destination address'))
|
---|
55 | dropped_off_at = models.DateTimeField(verbose_name=_('drop off time'), null=True, blank=True, help_text=_(
|
---|
56 | 'The exact time this course we delivered to the customer. It will be null if the course is not delivered yet'))
|
---|
57 |
|
---|
58 | shipment_start_time = models.DateTimeField(verbose_name=_('shipment start time'), null=True, blank=True)
|
---|
59 | shipment_end_time = models.DateTimeField(verbose_name=_('shipment end time'), null=True, blank=True)
|
---|
60 |
|
---|
61 | objects = ShipmentManager()
|
---|
62 |
|
---|
63 | class Meta:
|
---|
64 | verbose_name = _('Shipment')
|
---|
65 | verbose_name_plural = _("Shipments")
|
---|
66 | unique_together = ('order_id', 'trip')
|
---|
67 | index_together = [
|
---|
68 | ['order_id', 'trip']
|
---|
69 | ]
|
---|
70 |
|
---|
71 | @property
|
---|
72 | def is_cancellable(self):
|
---|
73 | return self.state in [ShipmentStateChoices.NEW, ShipmentStateChoices.WAITING, ShipmentStateChoices.PICKUP]
|
---|
74 |
|
---|
75 | @property
|
---|
76 | def is_editable(self):
|
---|
77 | return self.state in [ShipmentStateChoices.NEW, ShipmentStateChoices.WAITING]
|
---|
78 |
|
---|
79 | @transition(field='state', source=[
|
---|
80 | ShipmentStateChoices.NEW, ShipmentStateChoices.WAITING, ShipmentStateChoices.PICKUP,
|
---|
81 | ShipmentStateChoices.DROP_OFF], target=ShipmentStateChoices.WAITING,
|
---|
82 | custom={'button_name': _('set as waiting')})
|
---|
83 | def set_as_waiting(self):
|
---|
84 | pass
|
---|
85 |
|
---|
86 | @transition(field='state', source=[
|
---|
87 | ShipmentStateChoices.NEW, ShipmentStateChoices.WAITING, ShipmentStateChoices.PICKUP,
|
---|
88 | ShipmentStateChoices.DROP_OFF
|
---|
89 | ], target=ShipmentStateChoices.PICKUP,
|
---|
90 | custom={'button_name': _('set as pickup')})
|
---|
91 | def set_as_pickup(self):
|
---|
92 | pass
|
---|
93 |
|
---|
94 | @transition(field='state', source=[
|
---|
95 | ShipmentStateChoices.NEW, ShipmentStateChoices.WAITING, ShipmentStateChoices.PICKUP,
|
---|
96 | ShipmentStateChoices.DROP_OFF], target=ShipmentStateChoices.DROP_OFF,
|
---|
97 | custom={'button_name': _('set as drop off')})
|
---|
98 | def set_as_drop_off(self):
|
---|
99 | pass
|
---|
100 |
|
---|
101 | @transition(field='state', source=[
|
---|
102 | ShipmentStateChoices.NEW, ShipmentStateChoices.WAITING,
|
---|
103 | ShipmentStateChoices.PICKUP, ShipmentStateChoices.DROP_OFF
|
---|
104 | ], target=ShipmentStateChoices.CANCELLED,
|
---|
105 | custom={'button_name': _('set as cancelled')})
|
---|
106 | def set_as_cancelled(self):
|
---|
107 | if self.is_cancellable:
|
---|
108 | handler = self.trip.handler
|
---|
109 | if self.trip.shipments.all().count() == 1:
|
---|
110 | response = handler.cancel_trip(self.trip)
|
---|
111 | else:
|
---|
112 | response = handler.remove_course(course=self)
|
---|
113 | return response
|
---|
114 | else:
|
---|
115 | raise Exception(_("Can not cancel the shipment. The courier is on the way!"))
|
---|
116 |
|
---|
117 | @transition(field='state', source=[
|
---|
118 | ShipmentStateChoices.NEW, ShipmentStateChoices.WAITING,
|
---|
119 | ShipmentStateChoices.PICKUP, ShipmentStateChoices.DROP_OFF, ShipmentStateChoices.DELIVERED],
|
---|
120 | target=ShipmentStateChoices.DELIVERED,
|
---|
121 | custom={'button_name': _('set as delivered')})
|
---|
122 | def set_as_delivered(self):
|
---|
123 | pass
|
---|
124 |
|
---|
125 | # This is for admin panel
|
---|
126 | @transition(field='state', source=[ShipmentStateChoices.NEW, ShipmentStateChoices.WAITING,
|
---|
127 | ShipmentStateChoices.PICKUP, ShipmentStateChoices.DROP_OFF],
|
---|
128 | target=ShipmentStateChoices.DELIVERED,
|
---|
129 | custom={'button_name': _('delivered')})
|
---|
130 | def delivered(self):
|
---|
131 | pass
|
---|
132 |
|
---|
133 | # def update_instance(self):
|
---|
134 | # raise NotImplementedError
|
---|
135 | #
|
---|
136 |
|
---|
137 | @classmethod
|
---|
138 | def check_active_shipment_exists(cls, order_id):
|
---|
139 | return cls.objects.filter(order_id=order_id).exclude(
|
---|
140 | state__in=[ShipmentStateChoices.CANCELLED, ShipmentStateChoices.DELIVERED]
|
---|
141 | ).exists()
|
---|
142 |
|
---|
143 | def update_driver_info(self, driver_info):
|
---|
144 | new_trip = Trip.objects.create(
|
---|
145 | source=self.trip.source,
|
---|
146 | method=self.trip.method,
|
---|
147 | driver_info=driver_info,
|
---|
148 | driver=driver_info['national_code']
|
---|
149 | )
|
---|
150 | old_trip = self.trip
|
---|
151 | self.trip = new_trip
|
---|
152 | self.save()
|
---|
153 | if not old_trip.shipments.exists():
|
---|
154 | old_trip.delete()
|
---|
155 |
|
---|
156 |
|
---|
157 | class ShipmentItem(Logged):
|
---|
158 | shipment = models.ForeignKey(Shipment, on_delete=models.CASCADE, verbose_name=_('Shipment'))
|
---|
159 | state = FSMField(default=ShipmentStateChoices.NEW, choices=ShipmentStateChoices.choices,
|
---|
160 | verbose_name=_('State'))
|
---|
161 | order_item_id = models.PositiveIntegerField(verbose_name=_('order Item id'))
|
---|
162 | title = models.CharField(verbose_name=_('title'), max_length=200)
|
---|
163 | quantity = models.FloatField(verbose_name=_("Quantity"))
|
---|
164 | warehouse_id = models.PositiveIntegerField(verbose_name=_("warehouse id"), null=True)
|
---|
165 |
|
---|
166 | class Meta:
|
---|
167 | verbose_name = _('Shipment')
|
---|
168 | verbose_name_plural = _("Shipment")
|
---|