Example #1
0
class AircraftBurn(Audit):
    APPROVAL_DRAFT = 'DRAFT'
    APPROVAL_SUBMITTED = 'USER'
    APPROVAL_ENDORSED = 'SRM'
    APPROVAL_APPROVED = 'SDO'
    APPROVAL_CHOICES = (
        (APPROVAL_DRAFT, 'Draft'),
        (APPROVAL_SUBMITTED, 'District Submitted'),
        (APPROVAL_ENDORSED, 'Region Endorsed'),
        (APPROVAL_APPROVED, 'State Approved'),
    )

    fmt = "%Y-%m-%d %H:%M"

    prescription = models.ForeignKey(Prescription,
                                     verbose_name="Burn ID",
                                     related_name='aircraft_burns',
                                     null=True,
                                     blank=True,
                                     on_delete=models.PROTECT)
    #prescribed_burn = models.ForeignKey(PrescribedBurn, verbose_name="Daily Burn ID", related_name='aircraft_burn', null=True, blank=True)

    date = models.DateField(auto_now_add=False)
    area = models.DecimalField(verbose_name="Area (ha)",
                               max_digits=12,
                               decimal_places=1,
                               validators=[MinValueValidator(0.0)],
                               null=True,
                               blank=True)
    est_start = models.TimeField('Estimated Start Time', null=True, blank=True)
    bombing_duration = models.DecimalField(
        verbose_name="Bombing Duration (hrs)",
        max_digits=5,
        decimal_places=1,
        validators=[MinValueValidator(0.0)],
        null=True,
        blank=True)
    min_smc = models.DecimalField(verbose_name="Min SMC",
                                  max_digits=5,
                                  decimal_places=1,
                                  validators=[MinValueValidator(0.0)],
                                  null=True,
                                  blank=True)
    max_fdi = models.DecimalField(verbose_name="Max FDI",
                                  max_digits=5,
                                  decimal_places=1,
                                  validators=[MinValueValidator(0.0)],
                                  null=True,
                                  blank=True)
    sdi_per_day = models.DecimalField(verbose_name="SDI Each Day",
                                      max_digits=5,
                                      decimal_places=1,
                                      validators=[MinValueValidator(0.0)],
                                      null=True,
                                      blank=True)
    flight_seq = models.TextField(verbose_name="Flight Sequence",
                                  null=True,
                                  blank=True)
    aircraft_rego = models.TextField(verbose_name="Aircraft Rego",
                                     null=True,
                                     blank=True)
    arrival_time = models.TimeField(verbose_name="Arrival Time Over Burn",
                                    null=True,
                                    blank=True)
    program = models.TextField(verbose_name="Program", null=True, blank=True)
    aircrew = models.TextField(verbose_name="Aircrew", null=True, blank=True)

    rolled = models.BooleanField(verbose_name="Fire Rolled from yesterday",
                                 editable=False,
                                 default=False)
    #location= models.TextField(verbose_name="Location", null=True, blank=True)

    @property
    def regional_approval(self):
        return True

    @property
    def state_duty_approval(self):
        return True

    @property
    def state_aviation_approval(self):
        return True

    def __str__(self):
        return self.prescription.burn_id

    class Meta:
        unique_together = ('prescription', 'date')
        verbose_name = 'Aircraft Burn'
        verbose_name_plural = 'Aircraft Burns'
        permissions = (
            ("can_endorse", "Can endorse burns"),
            ("can_approve", "Can approve burns"),
        )
Example #2
0
class PrescribedBurn(Audit):
    BURN_ACTIVE = 1
    BURN_INACTIVE = 2
    BURN_MONITORED = 3
    BURN_CHOICES = (
        (BURN_ACTIVE, 'Yes'),
        (BURN_INACTIVE, 'No'),
        (BURN_MONITORED, 'Monitored'),
    )

    IGNITION_STATUS_REQUIRED = 1
    IGNITION_STATUS_COMPLETED = 2
    IGNITION_STATUS_CHOICES = (
        (IGNITION_STATUS_REQUIRED, 'Further ignitions required'),
        (IGNITION_STATUS_COMPLETED, 'Ignition now complete'),
    )

    APPROVAL_DRAFT = 'DRAFT'
    APPROVAL_SUBMITTED = 'USER'
    APPROVAL_ENDORSED = 'SRM'
    APPROVAL_APPROVED = 'SDO'
    APPROVAL_CHOICES = (
        (APPROVAL_DRAFT, 'Draft'),
        (APPROVAL_SUBMITTED, 'District Submitted'),
        (APPROVAL_ENDORSED, 'Region Endorsed'),
        (APPROVAL_APPROVED, 'State Approved'),
    )

    FORM_268A = 1
    FORM_268B = 2
    FORM_NAME_CHOICES = (
        (FORM_268A, 'Form 268a'),
        (FORM_268B, 'Form 268b'),
    )
    '''
    BUSHFIRE_DISTRICT_ALIASES can be used to override the District's original code from the model. Usage e.g.:
    BUSHFIRE_DISTRICT_ALIASES = {
        'PHS' : 'PH',
        'SWC' : 'SC',
    }
    '''
    BUSHFIRE_DISTRICT_ALIASES = {}

    fmt = "%Y-%m-%d %H:%M"

    prescription = models.ForeignKey(Prescription,
                                     verbose_name="Burn ID",
                                     related_name='prescribed_burn',
                                     null=True,
                                     blank=True,
                                     on_delete=models.PROTECT)
    #    prescription = ChainedForeignKey(
    #        Prescription, chained_field="region", chained_model_field="region",
    #        show_all=False, auto_choose=True, blank=True, null=True)

    # Required for Fire records
    fire_id = models.CharField(verbose_name="Fire Number",
                               max_length=15,
                               null=True,
                               blank=True)
    fire_name = models.TextField(verbose_name="Name", null=True, blank=True)
    region = models.PositiveSmallIntegerField(choices=[
        (r.id, r.name) for r in Region.objects.all()
    ],
                                              null=True,
                                              blank=True)
    district = ChainedForeignKey(District,
                                 chained_field="region",
                                 chained_model_field="region",
                                 show_all=False,
                                 auto_choose=True,
                                 blank=True,
                                 null=True,
                                 on_delete=models.PROTECT)
    fire_tenures = models.ManyToManyField(FireTenure,
                                          verbose_name="Tenures",
                                          blank=True)
    date = models.DateField(auto_now_add=False)
    form_name = models.PositiveSmallIntegerField(
        verbose_name="Form Name (268a / 268b)",
        choices=FORM_NAME_CHOICES,
        editable=True)
    status = models.PositiveSmallIntegerField(verbose_name="Active",
                                              choices=BURN_CHOICES,
                                              null=True,
                                              blank=True)
    ignition_status = models.PositiveSmallIntegerField(
        verbose_name="Ignition Status",
        choices=IGNITION_STATUS_CHOICES,
        null=True,
        blank=True)
    external_assist = models.ManyToManyField(
        ExternalAssist, verbose_name="Assistance received from", blank=True)

    planned_area = models.DecimalField(
        verbose_name="Today's treatment area (ha)",
        max_digits=12,
        decimal_places=1,
        validators=[MinValueValidator(0.0)],
        null=True,
        blank=True)
    area = models.DecimalField(verbose_name="Yesterday's treatment area (ha)",
                               max_digits=12,
                               decimal_places=1,
                               validators=[MinValueValidator(0.0)],
                               null=True,
                               blank=True)

    planned_distance = models.DecimalField(
        verbose_name="Today's treatment distance (km)",
        max_digits=12,
        decimal_places=1,
        validators=[MinValueValidator(0.0)],
        null=True,
        blank=True)
    distance = models.DecimalField(
        verbose_name="Yesterday's treatment distance (km)",
        max_digits=12,
        decimal_places=1,
        validators=[MinValueValidator(0.0)],
        null=True,
        blank=True)

    tenures = models.TextField(verbose_name="Tenure")
    location = models.TextField(verbose_name="Location", null=True, blank=True)
    est_start = models.TimeField('Estimated Start Time', null=True, blank=True)
    conditions = models.TextField(verbose_name='SDO Special Conditions',
                                  null=True,
                                  blank=True)
    rolled = models.BooleanField(verbose_name="Fire Rolled from yesterday",
                                 editable=False,
                                 default=False)
    latitude = models.FloatField(default=0.0)
    longitude = models.FloatField(default=0.0)

    #aircraft_burn = models.BooleanField(verbose_name="Aircraft Burn", default=False)

    def clean(self):
        if not self.form_name:
            if self.prescription and self.area == None and self.distance == None:
                self.form_name = 1
            else:
                self.form_name = 2

        if self.prescription and not (self.region and self.district):
            self.region = self.prescription.region.id
            self.district = self.prescription.district

    def clean_fire_id(self):
        # set the Lat/Long to Zero, since Bushfire is not assigning these required fields
        self.latitude = 0.0
        self.longitude = 0.0


#    def clean_date(self):
#        today = date.today()
#        tomorrow = today + timedelta(days=1)
#        if not self.pk and (self.date < today or self.date > tomorrow):
#            raise ValidationError("You must enter burn plans for today or tommorow's date only.")

    def clean_planned_distance(self):
        if self.planned_area == None and self.planned_distance == None:
            raise ValidationError(
                "Must input at least one of Area or Distance")

    def clean_distance(self):
        if self.area == None and self.distance == None:
            raise ValidationError(
                "Must input at least one of Area or Distance")

    @property
    def is_acknowledged(self):
        if all(x in [i.acknow_type for i in self.acknowledgements.all()]
               for x in ['SDO_A', 'SDO_B']):
            return True
        else:
            return False

    @property
    def user_a_record(self):
        ack = self.acknowledgements.filter(acknow_type='USER_A')
        return ack[0].record if ack else None

    @property
    def srm_a_record(self):
        ack = self.acknowledgements.filter(acknow_type='SRM_A')
        return ack[0].record if ack else None

    @property
    def sdo_a_record(self):
        ack = self.acknowledgements.filter(acknow_type='SDO_A')
        return ack[0].record if ack else None

    @property
    def user_b_record(self):
        ack = self.acknowledgements.filter(acknow_type='USER_B')
        return ack[0].record if ack else None

    @property
    def srm_b_record(self):
        ack = self.acknowledgements.filter(acknow_type='SRM_B')
        return ack[0].record if ack else None

    @property
    def sdo_b_record(self):
        ack = self.acknowledgements.filter(acknow_type='SDO_B')
        return ack[0].record if ack else None

    @property
    def formA_isDraft(self):
        return not any(
            x in [i.acknow_type for i in self.acknowledgements.all()]
            for x in ['USER_A', 'SRM_A', 'SDO_A'])

    @property
    def formB_isDraft(self):
        return not any(
            x in [i.acknow_type for i in self.acknowledgements.all()]
            for x in ['USER_B', 'SRM_B', 'SDO_B'])

    @property
    def formA_user_acknowledged(self):
        return True if self.user_a_record else False

    @property
    def formA_srm_acknowledged(self):
        return True if self.srm_a_record else False

    @property
    def formA_sdo_acknowledged(self):
        return True if self.sdo_a_record else False

    @property
    def formB_user_acknowledged(self):
        return True if self.user_b_record else False

    @property
    def formB_srm_acknowledged(self):
        return True if self.srm_b_record else False

    @property
    def formB_sdo_acknowledged(self):
        return True if self.sdo_b_record else False

    @property
    def fire_type(self):
        return "Burn" if self.prescription else "Fire"

    @property
    def fire_idd(self):
        if self.prescription:
            return self.prescription.burn_id
        else:
            return self.fire_id

    @property
    def further_ignitions_req(self):
        if self.ignition_status == self.IGNITION_STATUS_REQUIRED:
            return True
        elif self.ignition_status == self.IGNITION_STATUS_COMPLETED:
            return False
        return None

    @property
    def active(self):
        # if self.status==self.BURN_ACTIVE:
        if self.status == self.BURN_ACTIVE or self.status == self.BURN_MONITORED:
            return True
        elif self.status == self.BURN_INACTIVE:
            return False
        return None

    @property
    def has_conditions(self):
        if self.conditions:
            return True
        return False

    @property
    def planned_area_str(self):
        _str = ''
        if self.planned_area:
            _str += str(self.planned_area) + " ha {} ".format(
                '-' if self.planned_distance else '')

        if self.planned_distance:
            _str += str(self.planned_distance) + " km"

        return _str

    @property
    def area_str(self):
        _str = ''
        if self.area >= 0:
            _str += str(
                self.area) + " ha {} ".format('-' if self.distance else '')

        if self.distance >= 0:
            _str += str(self.distance) + " km"

        return _str

    @property
    def tenures_str(self):
        if self.prescription:
            return self.tenures  #', '.join([t.name for t in self.tenures.all()])
        else:
            return ', '.join([i.name for i in self.fire_tenures.all()])

    @property
    def had_external_assist(self):
        if self.external_assist.all().count() > 0:
            return True
        return False

    @property
    def external_assist_str(self):
        return ', '.join([i.name for i in self.external_assist.all()])

    @property
    def name(self):
        if self.prescription:
            return self.prescription.name
        else:
            return self.fire_name

    @property
    def get_region(self):
        return self.prescription.region if self.prescription else self.region

    @property
    def get_district(self):
        return self.prescription.district if self.prescription else self.district

    @property
    def can_endorse(self):
        return (self.status == self.APPROVAL_SUBMITTED)

    @property
    def can_approve(self):
        return (self.status == self.APPROVAL_ENDORSED)

    @property
    def last_ignition(self):
        if self.prescription:
            area_achievements = self.prescription.areaachievement_set.all()
            if area_achievements:
                return max([i.ignition for i in area_achievements])
        return None

    def short_str(self):
        return self.prescription.burn_id if self.prescription else self.fire_id

    def copy_ongoing_records(self, dt):
        """
        Copy today's 'active' records to tomorrow

        268b - (Automatically) copy all records from yesterday that were Active when 268a Region Endorsement occurs,
        except for Active and Area Burnt Yesterday

        dt = from date, copied to dt+1
        """

        tomorrow = dt + timedelta(days=1)  # relative to dt
        #objects = [obj for obj in PrescribedBurn.objects.filter(date=dt, status=PrescribedBurn.BURN_ACTIVE)]
        objects = [self]
        now = timezone.now()
        admin = User.objects.get(username='******')
        count = 0
        for obj in objects:
            if obj.fire_id and PrescribedBurn.objects.filter(
                    fire_id=obj.fire_id,
                    date=tomorrow,
                    form_name=PrescribedBurn.FORM_268B):
                # don't copy if already exists - since record is unique on Prescription (not fire_id)
                logger.info(
                    'Ongoing Record Already Exists (Fire) - not copied (268b today to 268b tomorrow). Record {}, today {}, tomorrow {}'
                    .format(obj.fire_idd, dt, tomorrow))
                continue
            if obj.prescription and PrescribedBurn.objects.filter(
                    prescription__burn_id=obj.prescription.burn_id,
                    date=tomorrow,
                    form_name=PrescribedBurn.FORM_268B,
                    location=obj.location):
                # don't copy if already exists - since record is unique on Prescription (not fire_id)
                logger.info(
                    'Ongoing Record Already Exists (Burn) - not copied (268b today to 268b tomorrow). Record {}, today {}, tomorrow {}'
                    .format(obj.fire_idd, dt, tomorrow))
                continue
            try:
                obj.pk = None
                obj.date = tomorrow
                obj.area = None
                obj.status = None
                obj.approval_268a_status = PrescribedBurn.APPROVAL_DRAFT
                obj.approval_268a_status_modified = now
                obj.approval_268b_status = PrescribedBurn.APPROVAL_DRAFT
                obj.approval_268b_status_modified = now
                obj.acknowledgements.all().delete()
                obj.rolled = True
                obj.save()
                count += 1
                logger.info(
                    'Ongoing Record copied (268b today to 268b tomorrow). Record {}, today {}, tomorrow {}'
                    .format(obj.fire_idd, dt, tomorrow))
            except:
                # records already exist - pk (pres, date) will not allow overwrite, so ignore the exception
                logger.warn(
                    'Ongoing Record not copied. Record {} already exists on day {}'
                    .format(obj.fire_idd, tomorrow))

    def copy_planned_approved_records_adhoc(self, dt):
        """
        Copy today's 'planned' records (268a), that have been SDO approved. to tomorrow ongoing (268b)

        set Active and Area Burnt fields to None

        dt = from date, copied to dt+1
        """

        tomorrow = dt + timedelta(days=1)  # relative to dt
        if not self.formA_sdo_acknowledged:
            logger.info(
                'Only SDO Acknowledged record can be copied from dt {} to tomorrow {}'
                .format(dt, tomorrow))
            return

        #objects = PrescribedBurn.objects.filter(date=dt, acknowledgements__acknow_type__in=['SDO_A'], form_name=PrescribedBurn.FORM_268A)
        objects = [self]
        now = timezone.now()
        count = 0
        for obj in objects:
            if obj.fire_id and PrescribedBurn.objects.filter(
                    fire_id=obj.fire_id,
                    date=tomorrow,
                    form_name=PrescribedBurn.FORM_268B):
                # don't copy if already exists - since record is unique on Prescription (not fire_id)
                logger.info(
                    'Planned Approved Record Already Exists (Fire) - not copied (268a today to 268b tomorrow). Record {}, today {}, tomorrow {}'
                    .format(obj.fire_idd, dt, tomorrow))
                continue
            if obj.prescription and PrescribedBurn.objects.filter(
                    prescription__burn_id=obj.prescription.burn_id,
                    date=tomorrow,
                    form_name=PrescribedBurn.FORM_268B,
                    location=obj.location):
                # don't copy if already exists - since record is unique on Prescription (not fire_id)
                logger.info(
                    'Planned Approved Record Already Exists (Burn) - not copied (268a today to 268b tomorrow). Record {}, today {}, tomorrow {}'
                    .format(obj.fire_idd, dt, tomorrow))
                continue
            try:
                obj.pk = None
                obj.date = tomorrow
                obj.area = None
                obj.distance = None
                obj.status = None
                obj.approval_268a_status = PrescribedBurn.APPROVAL_DRAFT
                obj.approval_268a_status_modified = now
                obj.approval_268b_status = PrescribedBurn.APPROVAL_DRAFT
                obj.approval_268b_status_modified = now
                #obj.acknowledgements.all().delete()
                obj.form_name = PrescribedBurn.FORM_268B
                obj.rolled = True
                obj.save()
                count += 1
                logger.info(
                    'Planned Approved Record copied (268a today to 268b tomorrow). Record {}, today {}, tomorrow {}'
                    .format(obj.fire_idd, dt, tomorrow))
            except:
                # records already exist - pk (pres, date) will not allow overwrite, so ignore the exception
                logger.warn(
                    'Planned Approved Record not copied. Record {} already exists on day {}'
                    .format(obj.fire_idd, tomorrow))

    def __str__(self):
        return self.prescription.burn_id + ' (Burn)' if self.prescription else self.fire_id + ' (Fire)'

    class Meta:
        unique_together = ('prescription', 'date', 'form_name', 'location')
        verbose_name = 'Prescribed Burn or Bushfire'
        verbose_name_plural = 'Prescribed Burns and Bushfires'
        permissions = (
            ("can_endorse", "Can endorse burns"),
            ("can_approve", "Can approve burns"),
        )