Created
March 7, 2013 22:33
-
-
Save shofetim/5112457 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from __future__ import unicode_literals | |
| from datetime import datetime, timedelta | |
| from django.conf import settings | |
| from django.db import models | |
| from django.core.mail import send_mail | |
| from django.template import Context, loader | |
| from django_cached_field import CachedDecimalField, CachedFloatField, CachedIntegerField, CachedCharField | |
| import django_snailtracker | |
| import django_snailtracker.models | |
| from beehive.models import BaseModel, SearchableBaseModel | |
| from drops.managers import DropPointTripManager | |
| from drops.constants import (EXCLUSIVITY_CHOICES, PICK_AREA_STATUS_NEW, | |
| PICK_AREA_STATUS_PARTIAL, PICK_AREA_STATUS_CLAIMED, | |
| PICK_AREA_STATUS_DONE, DROP_COLOR_NAMES, MINIMUM_DROP_AMOUNT, | |
| RGB, DROP_MANAGER_RCV_TYPE, DROP_MANAGER_RCV_MUST_SHOW, | |
| DROP_MANAGER_CONTACT_TYPE, DROP_MANAGER_CONTACT_YES, | |
| DROP_MANAGER_FEES_TYPE, DROP_MANAGER_FEES_NO_TIPS, | |
| DROP_MANAGER_NEW_MEMBER_TYPE, DROP_MANAGER_NEW_MEMBER_PLEASE_CONTACT, | |
| DROP_MANAGER_CONTACT_METHOD_TYPE, DROP_MANAGER_CONTACT_METHOD_EMAIL, | |
| DROP_MANAGER_PARKING_TYPE, DROP_MANAGER_PARKING_HOME, | |
| DROP_MANAGER_STORABLES_TYPE, DROP_MANAGER_STORABLES_DRY) | |
| from inventory.models import Stock | |
| from lib.aquameta.addresses.models import Address | |
| from lib.aquameta.helpers.models import cached_property | |
| from lib.aquameta.helpers.util import CurryByArea, phasing | |
| from orders import ORDER_STATUS_PLACED | |
| from palletizing import PALLET_STATUS_DONE | |
| from picking import PICK_STATUS_NEW, PICK_STATUS_DONE, SMALL_PICK, BULK_PICK, \ | |
| DRY_CHILLED_PICK_CLIMATE, FROZEN_PICK_CLIMATE, GREENHOUSE_PICK_CLIMATE | |
| from routes.models import Route, RouteTrip, ROUTE_TRIP_SHIPPED, ROUTE_TRIP_VERIFIED | |
| import logging | |
| cache_logger = logging.getLogger('beehive.cache') | |
| deprecation = logging.getLogger('DEPRECATED') | |
| class DropPointStorablesType(BaseModel): | |
| storable_type = models.CharField(max_length=100) | |
| def __unicode__(self): | |
| return self.storable_type | |
| class DropPointContactType(BaseModel): | |
| contact_type = models.CharField(max_length=100) | |
| def __unicode__(self): | |
| return self.contact_type | |
| class DropPointManagerPreferences(BaseModel): | |
| @classmethod | |
| def suggested_defaults(cls): | |
| # This is the suggested defaults for this model if you want to use | |
| # them somewhere. | |
| return { | |
| 'driver_callable_phone': '', | |
| 'parking_type': DROP_MANAGER_PARKING_HOME, | |
| 'other_parking': '', | |
| 'members_receive': DROP_MANAGER_RCV_MUST_SHOW, | |
| 'hold_time': '', | |
| 'contact_need': DROP_MANAGER_CONTACT_YES, | |
| 'drop_fees': DROP_MANAGER_FEES_NO_TIPS, | |
| 'accept_cod': True, | |
| 'fee_amount_and_structure': '', | |
| 'publishable': True, | |
| 'new_members': DROP_MANAGER_NEW_MEMBER_PLEASE_CONTACT, | |
| 'customer_callable_phone': '', | |
| 'other_contact': '', | |
| 'notes': '' | |
| } | |
| driver_callable_phone = models.CharField(max_length=25, blank=True, | |
| null=True, | |
| verbose_name="Phone number the driver can call to get in touch with you: ") | |
| parking_type = models.IntegerField(choices=DROP_MANAGER_PARKING_TYPE, | |
| verbose_name="What kind of drop location is your drop? ", | |
| null=True) | |
| other_parking = models.CharField(max_length=80, blank=True, | |
| null=True, | |
| verbose_name="") | |
| members_receive = models.IntegerField(choices=DROP_MANAGER_RCV_TYPE, | |
| verbose_name="How do your members receive their items?", | |
| null=True) | |
| hold_time = models.CharField(max_length=25, blank=True, | |
| null=True, | |
| verbose_name = "How long after delivery will you hold orders?") | |
| contact_need = models.IntegerField(choices=DROP_MANAGER_CONTACT_TYPE, | |
| verbose_name="Does the customer need to contact you to pick up their order?", | |
| null=True) | |
| item_store = models.ManyToManyField(DropPointStorablesType, | |
| null=True, | |
| verbose_name="What kind of items can you store?") | |
| drop_fees = models.IntegerField(choices=DROP_MANAGER_FEES_TYPE, | |
| verbose_name="Do you charge any fees?", | |
| null=True) | |
| accept_cod = models.NullBooleanField( | |
| null=True, | |
| verbose_name="Does your drop accept COD orders? ") | |
| fee_amount_and_structure = models.CharField(max_length=140, blank=True, | |
| null=True, | |
| verbose_name="How much, and what is your fee structure?") | |
| publishable = models.NullBooleanField(null=True, | |
| verbose_name="Can we publish your drop on the website?") | |
| new_members = models.IntegerField(choices=DROP_MANAGER_NEW_MEMBER_TYPE, | |
| null=True, | |
| verbose_name="How are new members added to your drop?") | |
| contact_type = models.ManyToManyField(DropPointContactType, | |
| null=True, | |
| verbose_name="How can customers contact you? ") | |
| customer_callable_phone = models.CharField(max_length=25, blank=True, | |
| null=True, | |
| verbose_name="") | |
| other_contact = models.CharField(max_length=80, blank=True, | |
| null=True, | |
| verbose_name="") | |
| notes = models.TextField(blank=True, null=True, | |
| verbose_name="If you'd like to tell us anything else, write it here. ") | |
| class DropPoint(SearchableBaseModel, Address): | |
| """ | |
| DropPoint is imported from the customers table of the website in | |
| 002-droppoint.sql some fields are incomplete | |
| """ | |
| searchable_fields = ['name', 'drop_code', 'address_1', 'address_2', | |
| 'city', 'state', 'province', 'country', | |
| 'postal_code'] | |
| routes = models.ManyToManyField("routes.Route", | |
| through="drops.RouteDropAssoc") | |
| route_trips = models.ManyToManyField("routes.RouteTrip", | |
| through="drops.DropPointTrip") | |
| primary_contact = models.ForeignKey('customers.Customer', null=True, | |
| blank=True, related_name='managed_drop_point', | |
| on_delete=models.SET_NULL) | |
| directions = models.TextField(blank=True, null=True) | |
| active = models.BooleanField(default=True) | |
| exclusivity = models.CharField(max_length=10, | |
| choices=EXCLUSIVITY_CHOICES, blank=True, null=True) | |
| _preferences = models.OneToOneField("drops.DropPointManagerPreferences", | |
| null=True) | |
| @classmethod | |
| def ups(cls): | |
| return cls.objects.get(contact_name="UPS") | |
| @classmethod | |
| def dufur_will_call(cls): | |
| return cls.objects.get(contact_name="Dufur Will-Call") | |
| @classmethod | |
| def moro_will_call(cls): | |
| return cls.objects.get(contact_name="Moro Will-Call") | |
| def __unicode__(self): | |
| return "%s: %s" % (self.drop_code, self.name) | |
| def droppointmembership_for_edit(self): | |
| return self.droppointmembership_set.all().order_by('-is_drop_manager', 'customer__user__last_name', 'customer__user__first_name') | |
| def add_member(self, customer): | |
| """Adds a customer to the droppoint as a non-default point. | |
| Returns the membership | |
| """ | |
| drop_membership = DropPointMembership.objects.filter( | |
| customer=customer, drop_point=self) | |
| if not drop_membership.exists(): | |
| drop_membership = DropPointMembership(customer=customer, drop_point=self) | |
| drop_membership.save() | |
| return drop_membership | |
| return drop_membership[0] | |
| def email_drop_manager(self, customer, order): | |
| if self.primary_contact.email and self.primary_contact.email != 'NA': | |
| template = loader.get_template('drops/website/new_member_email.html') | |
| context = Context({'customer': customer, 'order': order, 'drop_point': self}) | |
| send_mail( | |
| 'A Customer Has Joined Your Drop', | |
| template.render(context), | |
| 'info@azurestandard.com', | |
| (self.primary_contact.email,), | |
| fail_silently=False) | |
| @property | |
| def preferences(self): | |
| # We only want preferences to exist if they've been saved by the customer. | |
| #if self._preferences == None: | |
| # self._preferences = DropPointManagerPreferences.objects.create() | |
| # self.save() | |
| return self._preferences | |
| @preferences.setter | |
| def preferences(self, value): | |
| self._preferences = value | |
| @property | |
| def order_frequency_list_for_one_year(self): | |
| one_year_ago = datetime.now() - timedelta(days=180) | |
| dpts = self.droppointtrip_set.filter(route_trip__cutoff__gte=one_year_ago, route_trip__cutoff__lte=datetime.now()) | |
| return [drop_trip.orders.count() for drop_trip in dpts.all()] | |
| @property | |
| def last_shipable_drop_point_trip(self): | |
| try: | |
| return self.droppointtrip_set.filter(will_definitely_pick=True).order_by('-route_trip__departure')[0] | |
| except IndexError: | |
| return None | |
| @property | |
| def last_ten_drop_point_trips(self): | |
| return self.droppointtrip_set.filter(route_trip__departure__lte=datetime.now()).order_by('-route_trip__departure')[:10] | |
| @models.permalink | |
| def get_absolute_url(self): | |
| return ('drop_point_detail', [self.id]) | |
| @property | |
| def state_or_province(self): | |
| return self.state or self.province | |
| def one_line_address(self): | |
| return "%s %s, %s, %s, %s" % (self.address_1 or "", self.address_2 or "", self.city or "", self.state_or_province or "", self.country or "") | |
| def get_exclusivity_pretty(self): | |
| if self.exclusivity == None: | |
| return "Unknown" | |
| else: | |
| return self.get_exclusivity_display() | |
| def save(self, *args, **kwargs): | |
| for droppointtrip in self.droppointtrip_set.all(): | |
| droppointtrip.flag_name_as_stale() | |
| super(DropPoint, self).save(*args, **kwargs) | |
| class Meta: | |
| verbose_name_plural = "Drop Points" | |
| ordering = ('contact_name',) | |
| @property | |
| def drop_code(self): | |
| if self.id: | |
| return "D%s" % self.id | |
| else: | |
| return "" | |
| def route_trips_that_will_stop_here(self): | |
| if 'csw' in settings.ENV: | |
| return self.route_trips.filter(status__lt=ROUTE_TRIP_VERIFIED).order_by('departure') | |
| else: | |
| return self.route_trips.filter(cutoff__gt=datetime.now()).order_by('departure') | |
| @property | |
| def next_pending_trip(self): | |
| try: | |
| rt = self.route_trips.filter(departure__gt=datetime.now()).order_by('departure')[0] | |
| return rt.droppointtrip_set.get(drop_point=self) | |
| except: | |
| return None | |
| @property | |
| def is_ups(self): | |
| return self.name == "UPS" | |
| @property | |
| def is_moro_will_call(self): | |
| return self.name == "Moro Will-Call" | |
| @property | |
| def is_dufur_will_call(self): | |
| return self.name == "Dufur Will-Call" | |
| @property | |
| def name(self): | |
| return self.contact_name | |
| @name.setter | |
| def name(self, value): | |
| self.contact_name = value | |
| deprecation.warning('Use of "DropPoint.name =" is deprecated!') | |
| @phone.getter | |
| def phone(self): | |
| return "fooy" | |
| class DropPointMembership(BaseModel): | |
| """ | |
| DropPointMembership is imported from the customers table of the website in 003-droppointmembership.sql | |
| The import script needs to be checked to make sure ids are correct, could be improved | |
| Hard coded values need to be verified | |
| """ | |
| customer = models.ForeignKey('customers.Customer', on_delete=models.CASCADE) | |
| drop_point = models.ForeignKey(DropPoint, on_delete=models.CASCADE) | |
| is_default_drop = models.BooleanField(default=False) | |
| cutoff_email_notifications = models.BooleanField(default=True) | |
| cutoff_reminder_when_ordered = models.BooleanField(default=False) | |
| manager_notified = models.BooleanField(default=False) | |
| snailtracker_child_of = 'drop_point' | |
| # permissions - TODO - pull these!! | |
| is_approved = models.BooleanField(default=False) | |
| is_drop_manager = models.BooleanField(default=False) | |
| can_email = models.BooleanField(default=True) | |
| can_call = models.BooleanField(default=True) | |
| allow_chilled = models.BooleanField(default=True) | |
| allow_frozen = models.BooleanField(default=True) | |
| allow_cod = models.BooleanField(default=False) | |
| must_meet_truck = models.BooleanField(default=False) | |
| class Meta: | |
| ordering = ('customer',) | |
| def __unicode__(self): | |
| return self.drop_point.drop_code | |
| class SentCutoffNotification(BaseModel): | |
| drop_point_trip = models.ForeignKey('DropPointTrip') | |
| customer = models.ForeignKey('customers.Customer') | |
| class RouteDropAssoc(BaseModel): | |
| """ | |
| DropPoint is imported from the customers table of the website joined with routes_route and drops_droppoint in 006-routedropassoc.sql | |
| distance and sequence are not in the import | |
| """ | |
| route = models.ForeignKey("routes.Route", on_delete=models.CASCADE) | |
| drop_point = models.ForeignKey(DropPoint, on_delete=models.CASCADE) | |
| applies_shipping_fee = models.BooleanField(default=True) | |
| distance = models.IntegerField(default=0) | |
| sequence = models.IntegerField(null=True, blank=True) | |
| class Meta: | |
| unique_together = (("route", "drop_point",),) | |
| ordering = ("sequence","distance","drop_point") | |
| def __unicode__(self): | |
| return "RouteDropAssociation: %s -> %s" % (self.route, self.drop_point) | |
| class PickingInProgressError(Exception): | |
| pass | |
| class PickingDoneError(Exception): | |
| pass | |
| class DropPointTrip(BaseModel): | |
| drop_point = models.ForeignKey(DropPoint, on_delete=models.PROTECT) | |
| route_trip = models.ForeignKey(RouteTrip, on_delete=models.PROTECT) | |
| color_number = models.IntegerField(null=True, blank=True) | |
| will_definitely_pick = models.NullBooleanField(default=None, null=True, blank=True) | |
| distance = CachedIntegerField(null=True, blank=True) | |
| name = CachedCharField(max_length=255, null=True, blank=True) | |
| ordered_amount = CachedDecimalField(max_digits=12, decimal_places=2, null=True, blank=True) | |
| ordered_volume_cubic_ft = CachedFloatField(null=True, blank=True) | |
| ordered_weight = CachedFloatField(null=True, blank=True) | |
| picked_weight = CachedFloatField(null=True, blank=True) | |
| picked_volume = CachedFloatField(null=True, blank=True) | |
| sequence = CachedIntegerField(null=True, blank=True) | |
| snailtracker_child_of = 'drop_point' | |
| #TODO cache drop address here! | |
| objects = DropPointTripManager() | |
| class Meta: | |
| ordering = ["route_trip__departure", "cached_sequence"] | |
| unique_together = (("route_trip", "cached_sequence",),) | |
| def __unicode__(self): | |
| return "DropPointTrip: %s[%s]" % (self.route_trip, self.drop_point) | |
| def save(self, *args, **kwargs): | |
| if self.cached_sequence is None: | |
| self.cached_sequence = self.calculate_sequence() | |
| self.cached_distance = self.calculate_distance() | |
| self.cached_name = self.calculate_name() | |
| super(DropPointTrip, self).save(*args, **kwargs) | |
| def update_cached_ordered_attrs(self): | |
| cache_logger.debug('update_cached_ordered_attrs on dpt %s' % (self,)) | |
| self.cached_ordered_amount = self.calculate_ordered_amount() | |
| self.cached_ordered_weight = self.calculate_ordered_weight() | |
| self.cached_ordered_volume_cubic_ft = self.calculate_ordered_volume_cubic_ft() | |
| def update_aggregate_references(self): | |
| self.route_trip.update_cached_attrs() | |
| self.route_trip.save() | |
| if self.pick_set is not None: | |
| self.pick_set.update_cached_attrs() | |
| self.pick_set.save() | |
| def by_area(self, pick_climate=None, bulk=None): | |
| return CurryByArea(self, pick_climate, bulk) | |
| @property | |
| def route_drop_assoc(self): | |
| return self.route_trip.route.routedropassoc_set.get(drop_point=self.drop_point) | |
| @property | |
| def shipping_fee_percent(self): | |
| return 8.5 | |
| @property | |
| def frozen(self): | |
| return self.by_area(pick_climate=FROZEN_PICK_CLIMATE) | |
| @property | |
| def greenhouse(self): | |
| return self.by_area(pick_climate=GREENHOUSE_PICK_CLIMATE) | |
| @property | |
| def dry_chilled(self): | |
| return self.by_area(pick_climate=DRY_CHILLED_PICK_CLIMATE) | |
| @property | |
| def applies_shipping_fee(self): | |
| return self.route_drop_assoc.applies_shipping_fee | |
| @property | |
| def has_shipping_fees(self): | |
| return self.route_trip.route.has_shipping_fees and self.applies_shipping_fee | |
| @property | |
| def order_shipments_shipping(self): | |
| return self.ordershipment_set.filter(order__order_status__gte=ORDER_STATUS_PLACED) | |
| @property | |
| def orders(self): | |
| Order = models.get_model('orders', 'Order') | |
| return Order.objects.filter(ordershipment__in=self.order_shipments_shipping) | |
| def delay_orders_until_next_possible_route_trip(self): | |
| for os in list(self.order_shipments_shipping): | |
| os.order.push_to_next_possible_ship_date() | |
| @cached_property | |
| def pick_set(self): | |
| PickSet = models.get_model('pick_set', 'PickSet') | |
| if self.sequence is None: | |
| return None | |
| try: | |
| return self.route_trip.pick_sets.get(drop_point_sequence_start__lte=self.sequence, | |
| drop_point_sequence_end__gte=self.sequence) | |
| except PickSet.DoesNotExist: | |
| return None | |
| def order_pieces_by_area(self, pick_climate=None, bulk=None): | |
| opieces = self.orderpiece_set.all() | |
| if pick_climate: | |
| opieces = opieces.in_pick_climate(pick_climate) | |
| if bulk: | |
| opieces = opieces.in_small_or_bulk(bulk) | |
| return opieces | |
| def order_pieces_frozen(self): | |
| return self.order_pieces_by_area(pick_climate=FROZEN_PICK_CLIMATE) | |
| def order_pieces_dry(self): | |
| return self.order_pieces_by_area(pick_climate=DRY_CHILLED_PICK_CLIMATE) | |
| def order_pieces_to_be_picked_by_area(self, pick_climate=None, bulk=None): | |
| return self.order_pieces_by_area(pick_climate=pick_climate, bulk=bulk).filter(stock=None) | |
| def stock_set_by_area(self, pick_climate=None, bulk=None): | |
| return Stock.objects.filter(pk__in=self.order_pieces_by_area(pick_climate=pick_climate, bulk=bulk).values_list('stock_id', flat=True)) | |
| def palletizing_done_by_area(self, pick_climate=None, bulk=None): | |
| return( | |
| not self.needs_picking_by_area(pick_climate, bulk) | |
| and self.piles_to_palletize_by_area(pick_climate, bulk) == self.piles_palletized_by_area(pick_climate, bulk) | |
| and self.boxes_to_palletize_by_area(pick_climate, bulk) == self.boxes_palletized_by_area(pick_climate, bulk) | |
| #:MC: wtf? using .exists() on the next line causes timeouts! | |
| and self.pallets_by_area(pick_climate, bulk).filter(status__lt=PALLET_STATUS_DONE).count() == 0) | |
| def pallets_by_area(self, pick_climate=None, bulk=None): | |
| Pallet = models.get_model('palletizing', 'Pallet') | |
| stock = self.stock_set_by_area(pick_climate, bulk) | |
| pallets = Pallet.objects.filter(stock__in=stock) | |
| return pallets | |
| def piles_by_area(self, pick_climate=None, bulk=None): | |
| bulk = BULK_PICK #:MC: piles are only bulk | |
| return self.pick_claims_by_area(pick_climate=pick_climate, bulk=bulk).filter(status=PICK_STATUS_DONE) | |
| def piles_to_palletize_by_area(self, pick_climate=None, bulk=None): | |
| return self.piles_by_area(pick_climate, bulk).count() | |
| def piles_palletized_by_area(self, pick_climate=None, bulk=None): | |
| bulk = BULK_PICK #:MC: piles are only bulk | |
| return len(set(self.stock_set_by_area(pick_climate, bulk).exclude(pallet=None).values_list('pick_claim', flat=True))) | |
| def boxes_by_area(self, pick_climate=None, bulk=None): | |
| Box = models.get_model('picking', 'Box') | |
| bulk = SMALL_PICK #:MC: boxes are only small | |
| return reduce( | |
| lambda boxes, claim: boxes | claim.orderpick.box_set.all(), | |
| self.pick_claims_by_area(pick_climate=pick_climate, bulk=bulk).filter(status=PICK_STATUS_DONE), | |
| Box.objects.none()) | |
| def boxes_to_palletize_by_area(self, pick_climate=None, bulk=None): | |
| return self.boxes_by_area(pick_climate, bulk).count() | |
| def boxes_palletized_by_area(self, pick_climate=None, bulk=None): | |
| bulk = SMALL_PICK #:MC: boxes are only small | |
| return len(set(self.stock_set_by_area(pick_climate, bulk).exclude(pallet=None).values_list('box', flat=True))) | |
| def orders_by_area(self, pick_climate=None, bulk=None): | |
| Order = models.get_model('orders', 'Order') | |
| return [order.by_area(pick_climate, bulk) | |
| for order in Order.objects.filter(pk__in=self.order_shipments_shipping.values_list("order", flat=True)) | |
| if order.in_area(pick_climate, bulk)] | |
| def needs_picking_by_area(self, pick_climate=None, bulk=None): | |
| status = self.picking_status_by_area(pick_climate, bulk) | |
| return status in (PICK_AREA_STATUS_NEW, PICK_AREA_STATUS_PARTIAL) | |
| def pick_claim_for_by_area(self, user, pick_climate=None, bulk=None): | |
| bulk = BULK_PICK | |
| try: | |
| return(self.pickclaim_set.filter( | |
| small_or_bulk=bulk, pick_climate=pick_climate) | |
| .exclude(status=PICK_STATUS_DONE) | |
| .get(picker=user)) | |
| except: | |
| return None | |
| def picking_status_by_area(self, pick_climate=None, bulk=None): | |
| pick_claims = self.pick_claims_by_area(pick_climate=pick_climate, bulk=bulk).exclude(status=PICK_STATUS_NEW) | |
| if not pick_claims.exists(): | |
| return PICK_AREA_STATUS_NEW | |
| elif not pick_claims.exclude(status=PICK_STATUS_DONE).exists(): | |
| needed = (self.order_pieces_by_area( | |
| pick_climate=pick_climate, bulk=bulk) | |
| .filter(stock=None, shorted=False,)) #piece__out_of_stock=False | |
| if not needed.exists(): | |
| return PICK_AREA_STATUS_DONE | |
| else: | |
| return PICK_AREA_STATUS_PARTIAL | |
| else: # unfinished pick claims exist | |
| unclaimed = (self.order_pieces_by_area( | |
| pick_climate=pick_climate, bulk=bulk) | |
| .filter(stock=None, #piece__out_of_stock=False, | |
| shorted=False, pick_claim=None)) | |
| if unclaimed.exists(): | |
| return PICK_AREA_STATUS_PARTIAL | |
| else: | |
| return PICK_AREA_STATUS_CLAIMED | |
| # :MC: this code was too slow. | |
| # ordered = self.total_piece_count_by_area(pick_climate=pick_climate, bulk=bulk) | |
| # picking = self.order_pieces_by_area(pick_climate=pick_climate, bulk=bulk) | |
| # inprogress = picking.filter( | |
| # pick_claim__status=PICK_STATUS_CLAIMED | |
| # ).count() | |
| # picked = picking.exclude(stock=None).count() # + picking.filter(shorted=True, stock=None).count() | |
| # if picked + inprogress == 0: | |
| # return PICK_AREA_STATUS_NEW | |
| # elif( not pick_claims.exclude(status=PICK_STATUS_DONE).exists() | |
| # and picked >= ordered): | |
| # return PICK_AREA_STATUS_DONE | |
| # elif inprogress >= (ordered - picked): | |
| # return PICK_AREA_STATUS_CLAIMED | |
| # else: | |
| # return PICK_AREA_STATUS_PARTIAL | |
| def pick_claims_by_area(self, pick_climate=None, bulk=None): | |
| pclaims = self.pickclaim_set.all() | |
| if pick_climate: | |
| pclaims = pclaims.filter(pick_climate=pick_climate) | |
| if bulk: | |
| pclaims = pclaims.filter(small_or_bulk=bulk) | |
| return pclaims | |
| def tentative_pick_claim_by_area(self, pick_climate, bulk): | |
| bulk = bulk or BULK_PICK | |
| status = self.picking_status_by_area(pick_climate, bulk) | |
| if status == PICK_AREA_STATUS_CLAIMED: | |
| raise PickingInProgressError | |
| elif status == PICK_AREA_STATUS_DONE: | |
| raise PickingDoneError | |
| else: | |
| # TODO martin_c: this was a hack-in to fix a picking problem. We should figure out how this failed and fix it. | |
| if self.pickclaim_set.filter(pick_climate=pick_climate, small_or_bulk=bulk, picker__isnull=True).exists(): | |
| return self.pickclaim_set.filter(pick_climate=pick_climate, small_or_bulk=bulk, picker__isnull=True)[0] | |
| else: | |
| return self.pickclaim_set.get_or_create(pick_climate=pick_climate, small_or_bulk=bulk, picker__isnull=True)[0] | |
| def pieces_by_area(self, pick_climate=None, bulk=None): | |
| Piece = models.get_model('warehouse_pieces', 'Piece') | |
| return Piece.objects.filter(pk__in=self.order_pieces_by_area(pick_climate, bulk).values_list("piece_id", flat=True)).distinct() | |
| def total_piece_count_by_area(self, pick_climate=None, bulk=None): | |
| return (self.order_pieces_by_area(pick_climate=pick_climate, bulk=bulk) | |
| .count()) | |
| def ordered_weight_by_area(self, pick_climate=None, bulk=None): | |
| return sum([o.ordered_weight for o in self.orders_by_area(pick_climate, bulk)]) | |
| def picked_weight_by_area(self, pick_climate=None, bulk=None): | |
| return sum([st.weight | |
| for st in self.stock_set_by_area(pick_climate, bulk).select_related("piece")]) | |
| def ordered_volume_cubic_ft_by_area(self, pick_climate=None, bulk=None): | |
| return sum([o.ordered_volume_cubic_ft for o in self.orders_by_area(pick_climate, bulk)]) | |
| def picked_volume_cubic_ft_by_area(self, pick_climate=None, bulk=None): | |
| return sum([st.piece.volume_cubic_ft | |
| for st in self.stock_set_by_area(pick_climate, bulk).select_related("piece")]) | |
| def shippables_count_by_area(self, pick_climate=None, bulk=None): | |
| count = 0 | |
| for claim in self.pick_claims_by_area(pick_climate, bulk).exclude(orderpick=None): | |
| count += claim.orderpick.box_set.count() | |
| count += self.stock_set_by_area(pick_climate, BULK_PICK).count() | |
| return count | |
| @property | |
| def has_small_dry_and_chilled(self): | |
| return self.order_pieces_by_area(DRY_CHILLED_PICK_CLIMATE, SMALL_PICK).exists() | |
| @property | |
| def has_bulk_dry_and_chilled(self): | |
| return self.order_pieces_by_area(DRY_CHILLED_PICK_CLIMATE, BULK_PICK).exists() | |
| @property | |
| def has_small_frozen(self): | |
| return self.order_pieces_by_area(FROZEN_PICK_CLIMATE, SMALL_PICK).exists() | |
| @property | |
| def has_bulk_frozen(self): | |
| return self.order_pieces_by_area(FROZEN_PICK_CLIMATE, BULK_PICK).exists() | |
| @property | |
| def has_small_greenhouse(self): | |
| return self.order_pieces_by_area(GREENHOUSE_PICK_CLIMATE, SMALL_PICK).exists() | |
| @property | |
| def truck_number(self): | |
| return self.pick_set.truck_number | |
| @property | |
| def drop_sticker_color(self): | |
| return "drop_sticker_color_%d" % self.color_number | |
| @property | |
| def drop_sticker_color_name(self): | |
| if self.color_number is not None: | |
| return DROP_COLOR_NAMES[self.color_number] | |
| else: | |
| return "" | |
| @property | |
| def drop_sticker_color_rgb(self): | |
| return RGB[self.color_number] | |
| @property | |
| def drop_code(self): | |
| if self.drop_point_id: | |
| return "D%s" % self.drop_point_id | |
| else: | |
| return "" | |
| @property | |
| def picked_amount(self): | |
| return sum([o.order.picked_price for o in self.order_shipments_shipping]) | |
| @property | |
| def invoiced_total(self): | |
| return sum([o.order.invoiced_total for o in self.order_shipments_shipping]) | |
| def calculate_name(self): | |
| return self.drop_point.name | |
| def calculate_sequence(self): | |
| return self.route_drop_assoc.sequence | |
| def calculate_distance(self): | |
| return self.route_drop_assoc.distance | |
| def calculate_ordered_amount(self): | |
| return sum([o.ordered_price for o in self.orders]) | |
| def calculate_ordered_volume_cubic_ft(self): | |
| return sum([o.ordered_volume_cubic_ft for o in self.orders]) | |
| @property | |
| def weight_to_display(self): | |
| return self.calculate_weight_to_display() | |
| @phasing("route_trip.status") | |
| def calculate_weight_to_display(self): | |
| return self.ordered_weight | |
| @calculate_weight_to_display.phase_after(ROUTE_TRIP_SHIPPED) | |
| def calculate_weight_to_display(self): | |
| return self.picked_weight | |
| @property | |
| def volume_to_display(self): | |
| return self.calculate_volume_to_display() | |
| @phasing("route_trip.status") | |
| def calculate_volume_to_display(self): | |
| return self.ordered_volume_cubic_ft | |
| @calculate_volume_to_display.phase_after(ROUTE_TRIP_SHIPPED) | |
| def calculate_volume_to_display(self): | |
| return self.picked_volume | |
| @property | |
| def amount_to_display(self): | |
| return self.calculate_amount_to_display() | |
| @phasing("route_trip.status") | |
| def calculate_amount_to_display(self): | |
| return self.ordered_amount | |
| @calculate_amount_to_display.phase_after(ROUTE_TRIP_SHIPPED) | |
| def calculate_amount_to_display(self): | |
| return self.picked_amount | |
| def calculate_ordered_weight(self): | |
| return sum([o.ordered_weight for o in self.orders.all()]) | |
| def calculate_picked_weight(self): | |
| return sum([o.picked_weight for o in self.orders.all()]) | |
| def calculate_picked_volume(self): | |
| return sum([o.picked_volume_cubic_ft for o in self.orders.all()]) | |
| @property | |
| def meets_drop_minimums(self): | |
| return self.ordered_amount >= MINIMUM_DROP_AMOUNT | |
| @property | |
| def will_pick(self): | |
| if self.will_definitely_pick is not None: | |
| return self.will_definitely_pick | |
| elif self.route_trip.route in (Route.ups(), Route.moro_will_call(), Route.dufur_will_call()): | |
| return True | |
| else: | |
| return self.meets_drop_minimums | |
| def set_will_definitely_pick_off(self): | |
| self.will_definitely_pick = False | |
| self.save() | |
| self.update_aggregate_references() | |
| def set_will_definitely_pick_on(self): | |
| self.will_definitely_pick = True | |
| self.save() | |
| self.update_aggregate_references() | |
| def save_will_pick(self): | |
| if self.will_definitely_pick != self.will_pick: | |
| self.will_definitely_pick = self.will_pick | |
| self.save() | |
| # self.update_aggregate_references() #:MC: this is killing the server |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment