Ticket #34589: shipment.py

File shipment.py, 7.1 KB (added by ftamy9, 16 months ago)
Line 
1from common.db.basic import Logged
2from django.db import models
3from django.utils.translation import gettext_lazy as _
4from django_fsm import FSMField, transition
5
6from shipment.models.trip import Trip
7
8
9class 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
20class 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
34class 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
45class 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
157class 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")
Back to Top