Esempio n. 1
0
class PlotSearchSerializerBase(
        EnumSupportSerializerMixin,
        FieldPermissionsSerializerMixin,
        serializers.ModelSerializer,
):
    id = serializers.ReadOnlyField()
    type = PlotSearchTypeSerializer(source="subtype.plot_search_type",
                                    read_only=True)
    subtype = PlotSearchSubtypeSerializer()
    stage = PlotSearchStageSerializer()

    form = InstanceDictPrimaryKeyRelatedField(
        instance_class=Form,
        queryset=Form.objects.all(),
        related_serializer=FormSerializer,
        required=False,
        allow_null=True,
    )

    decisions = InstanceDictPrimaryKeyRelatedField(
        instance_class=Decision,
        queryset=Decision.objects.all(),
        related_serializer=DecisionSerializer,
        required=False,
        allow_null=True,
        many=True,
    )

    class Meta:
        model = PlotSearch
        fields = "__all__"
Esempio n. 2
0
class InvoiceRowCreateUpdateSerializer(FieldPermissionsSerializerMixin, serializers.ModelSerializer):
    id = serializers.IntegerField(required=False)
    tenant = InstanceDictPrimaryKeyRelatedField(instance_class=Tenant,
                                                queryset=Tenant.objects.all(),
                                                related_serializer=TenantSerializer, required=False, allow_null=True)
    receivable_type = InstanceDictPrimaryKeyRelatedField(instance_class=ReceivableType,
                                                         queryset=ReceivableType.objects.all(),
                                                         related_serializer=ReceivableTypeSerializer)

    class Meta:
        model = InvoiceRow
        fields = ('id', 'tenant', 'receivable_type', 'billing_period_start_date', 'billing_period_end_date',
                  'description', 'amount')
Esempio n. 3
0
class InvoiceUpdateSerializer(UpdateNestedMixin, EnumSupportSerializerMixin,
                              FieldPermissionsSerializerMixin,
                              serializers.ModelSerializer):
    id = serializers.ReadOnlyField()
    recipient = InstanceDictPrimaryKeyRelatedField(
        instance_class=Contact,
        queryset=Contact.objects.all(),
        related_serializer=ContactSerializer)
    rows = InvoiceRowCreateUpdateSerializer(many=True)
    payments = InvoicePaymentCreateUpdateSerializer(many=True,
                                                    required=False,
                                                    allow_null=True)

    def validate(self, attrs):
        if self.instance.sent_to_sap_at:
            raise ValidationError(
                _("Can't edit invoices that have been sent to SAP"))

        return super().validate(attrs)

    def update(self, instance, validated_data):
        instance = super().update(instance, validated_data)
        instance.update_amounts()
        if instance.credited_invoice:
            instance.credited_invoice.update_amounts()

        return instance

    class Meta:
        model = Invoice
        exclude = ('deleted', )
        read_only_fields = ('generated', 'sent_to_sap_at', 'sap_id', 'state',
                            'adjusted_due_date', 'credit_invoices')
Esempio n. 4
0
class CreateChargeInvoiceRowSerializer(serializers.Serializer):
    amount = serializers.DecimalField(max_digits=10, decimal_places=2, required=False)
    receivable_type = InstanceDictPrimaryKeyRelatedField(
        instance_class=ReceivableType,
        queryset=ReceivableType.objects.all(),
        related_serializer=ReceivableTypeSerializer,
    )
Esempio n. 5
0
class SendEmailSerializer(EnumSupportSerializerMixin, serializers.Serializer):
    type = EnumField(enum=EmailLogType, required=True)
    recipients = PrimaryKeyRelatedField(many=True, queryset=User.objects.all())
    text = serializers.CharField()
    lease = InstanceDictPrimaryKeyRelatedField(
        instance_class=Lease,
        queryset=Lease.objects.all(),
        related_serializer=LeaseSuccinctSerializer,
        required=False)
Esempio n. 6
0
    def __init__(self, instance=None, data=empty, **kwargs):
        super().__init__(instance=instance, data=data, **kwargs)

        # Lease field must be added dynamically to prevent circular imports
        from leasing.serializers.lease import LeaseSuccinctSerializer
        from leasing.models.lease import Lease

        self.fields['lease'] = InstanceDictPrimaryKeyRelatedField(instance_class=Lease, queryset=Lease.objects.all(),
                                                                  related_serializer=LeaseSuccinctSerializer)
Esempio n. 7
0
class InvoiceCreateSerializer(UpdateNestedMixin, EnumSupportSerializerMixin,
                              FieldPermissionsSerializerMixin,
                              serializers.ModelSerializer):
    id = serializers.ReadOnlyField()
    recipient = InstanceDictPrimaryKeyRelatedField(
        instance_class=Contact,
        queryset=Contact.objects.all(),
        related_serializer=ContactSerializer)
    rows = InvoiceRowCreateUpdateSerializer(many=True)
    payments = InvoicePaymentCreateUpdateSerializer(many=True,
                                                    required=False,
                                                    allow_null=True)
    # Make total_amount, billed_amount, and type not requided in serializer and set them in create() if needed
    total_amount = serializers.DecimalField(max_digits=10,
                                            decimal_places=2,
                                            required=False)
    billed_amount = serializers.DecimalField(max_digits=10,
                                             decimal_places=2,
                                             required=False)
    type = EnumField(enum=InvoiceType, required=False)

    def create(self, validated_data):
        validated_data['state'] = InvoiceState.OPEN

        if not validated_data.get('total_amount'):
            total_amount = Decimal(0)
            for row in validated_data.get('rows', []):
                total_amount += row.get('amount', Decimal(0))

            validated_data['total_amount'] = total_amount

        if not validated_data.get('billed_amount'):
            billed_amount = Decimal(0)
            for row in validated_data.get('rows', []):
                billed_amount += row.get('amount', Decimal(0))

            validated_data['billed_amount'] = billed_amount

        if not validated_data.get('type'):
            validated_data['type'] = InvoiceType.CHARGE

        return super().create(validated_data)

    class Meta:
        model = Invoice
        exclude = ('deleted', )
        read_only_fields = ('number', 'generated', 'sent_to_sap_at', 'sap_id',
                            'state', 'adjusted_due_date', 'credit_invoices')
Esempio n. 8
0
class InvoiceUpdateSerializer(
    UpdateNestedMixin,
    EnumSupportSerializerMixin,
    FieldPermissionsSerializerMixin,
    serializers.ModelSerializer,
):
    id = serializers.ReadOnlyField()
    recipient = InstanceDictPrimaryKeyRelatedField(
        instance_class=Contact,
        queryset=Contact.objects.all(),
        related_serializer=ContactSerializer,
    )
    rows = InvoiceRowCreateUpdateSerializer(many=True)
    payments = InvoicePaymentCreateUpdateSerializer(
        many=True, required=False, allow_null=True
    )

    def update(self, instance, validated_data):
        instance = super().update(instance, validated_data)
        instance.update_amounts()
        if instance.credited_invoice:
            instance.credited_invoice.update_amounts()

        return instance

    class Meta:
        model = Invoice
        exclude = ("deleted",)
        read_only_fields = (
            "generated",
            "sent_to_sap_at",
            "sap_id",
            "state",
            "adjusted_due_date",
            "credit_invoices",
            "interest_invoices",
        )
Esempio n. 9
0
class CreateChargeSerializer(serializers.Serializer):
    lease = InstanceDictPrimaryKeyRelatedField(
        instance_class=Lease,
        queryset=Lease.objects.all(),
        related_serializer=LeaseSuccinctSerializer)
    due_date = serializers.DateField()
    billing_period_start_date = serializers.DateField(required=False)
    billing_period_end_date = serializers.DateField(required=False)
    rows = serializers.ListSerializer(child=CreateChargeInvoiceRowSerializer(),
                                      required=True)
    notes = serializers.CharField(required=False)

    def to_representation(self, instance):
        if isinstance(instance, InvoiceSet):
            return InvoiceSetSerializer().to_representation(instance=instance)
        elif isinstance(instance, Invoice):
            return InvoiceSerializer().to_representation(instance=instance)

    def validate(self, data):
        if (data.get('billing_period_start_date')
                and not data.get('billing_period_end_date')) or (
                    not data.get('billing_period_start_date')
                    and data.get('billing_period_end_date')):
            raise serializers.ValidationError(
                _("Both Billing period start and end are "
                  "required if one of them is provided"))

        if data.get('billing_period_start_date', 0) > data.get(
                'billing_period_end_date', 0):
            raise serializers.ValidationError(
                _("Billing period end must be the same or after the start"))

        return data

    def create(self, validated_data):
        today = timezone.now().date()
        lease = validated_data.get('lease')
        billing_period_start_date = validated_data.get(
            'billing_period_start_date', today)
        billing_period_end_date = validated_data.get('billing_period_end_date',
                                                     today)
        billing_period = (billing_period_start_date, billing_period_end_date)

        total_amount = sum(
            [row.get('amount') for row in validated_data.get('rows', [])])

        # TODO: Handle possible exception
        shares = lease.get_tenant_shares_for_period(billing_period_start_date,
                                                    billing_period_end_date)

        invoice = None
        invoiceset = None

        if len(shares.items()) > 1:
            invoiceset = InvoiceSet.objects.create(
                lease=lease,
                billing_period_start_date=billing_period_start_date,
                billing_period_end_date=billing_period_end_date)

        # TODO: check for periods without 1/1 shares
        for contact, share in shares.items():
            invoice_row_data = []
            billable_amount = Decimal(0)

            for tenant, overlaps in share.items():
                for row in validated_data.get('rows', []):
                    overlap_amount = Decimal(0)
                    for overlap in overlaps:
                        overlap_amount += fix_amount_for_overlap(
                            row.get('amount', Decimal(0)), overlap,
                            subtract_ranges_from_ranges([billing_period],
                                                        [overlap]))

                        share_amount = Decimal(
                            overlap_amount *
                            Decimal(tenant.share_numerator /
                                    tenant.share_denominator)).quantize(
                                        Decimal('.01'), rounding=ROUND_HALF_UP)

                        billable_amount += share_amount

                        invoice_row_data.append({
                            'tenant':
                            tenant,
                            'receivable_type':
                            row.get('receivable_type'),
                            'billing_period_start_date':
                            overlap[0],
                            'billing_period_end_date':
                            overlap[1],
                            'amount':
                            share_amount,
                        })

            invoice = Invoice.objects.create(
                type=InvoiceType.CHARGE,
                lease=lease,
                recipient=contact,
                due_date=validated_data.get('due_date'),
                invoicing_date=today,
                state=InvoiceState.OPEN,
                billing_period_start_date=billing_period_start_date,
                billing_period_end_date=billing_period_end_date,
                total_amount=total_amount,
                billed_amount=billable_amount,
                outstanding_amount=billable_amount,
                invoiceset=invoiceset,
                notes=validated_data.get('notes', ''),
            )

            for invoice_row_datum in invoice_row_data:
                invoice_row_datum['invoice'] = invoice
                InvoiceRow.objects.create(**invoice_row_datum)

        if invoiceset:
            return invoiceset
        else:
            return invoice
Esempio n. 10
0
class InvoiceCreateSerializer(UpdateNestedMixin, EnumSupportSerializerMixin, FieldPermissionsSerializerMixin,
                              serializers.ModelSerializer):
    id = serializers.ReadOnlyField()
    recipient = InstanceDictPrimaryKeyRelatedField(instance_class=Contact, queryset=Contact.objects.all(),
                                                   related_serializer=ContactSerializer, required=False)
    tenant = InstanceDictPrimaryKeyRelatedField(instance_class=Tenant, queryset=Tenant.objects.all(),
                                                related_serializer=TenantSerializer, required=False)
    rows = InvoiceRowCreateUpdateSerializer(many=True)
    payments = InvoicePaymentCreateUpdateSerializer(many=True, required=False, allow_null=True)
    # Make total_amount, billed_amount, and type not required in the serializer and set them in create() if needed
    total_amount = serializers.DecimalField(max_digits=10, decimal_places=2, required=False)
    billed_amount = serializers.DecimalField(max_digits=10, decimal_places=2, required=False)
    type = EnumField(enum=InvoiceType, required=False)

    def override_permission_check_field_name(self, field_name):
        if field_name == 'tenant':
            return 'recipient'

        return field_name

    def validate(self, attrs):
        if not bool(attrs.get('recipient')) ^ bool(attrs.get('tenant')):
            raise ValidationError(_('Either recipient or tenant is required.'))

        if attrs.get('tenant') and attrs.get('tenant') not in attrs.get('lease').tenants.all():
            raise ValidationError(_('Tenant not found in lease'))

        return attrs

    def create(self, validated_data):
        validated_data['state'] = InvoiceState.OPEN

        if not validated_data.get('total_amount'):
            total_amount = Decimal(0)
            for row in validated_data.get('rows', []):
                total_amount += row.get('amount', Decimal(0))

            validated_data['total_amount'] = total_amount

        if not validated_data.get('billed_amount'):
            billed_amount = Decimal(0)
            for row in validated_data.get('rows', []):
                billed_amount += row.get('amount', Decimal(0))

            validated_data['billed_amount'] = billed_amount

        if not validated_data.get('type'):
            validated_data['type'] = InvoiceType.CHARGE

        if validated_data.get('tenant'):
            today = datetime.date.today()
            tenant = validated_data.pop('tenant')
            billing_tenantcontact = tenant.get_billing_tenantcontacts(today, today).first()
            if not billing_tenantcontact:
                raise ValidationError(_('Billing contact not found for tenant'))

            validated_data['recipient'] = billing_tenantcontact.contact
            for row in validated_data.get('rows', []):
                row['tenant'] = tenant

        invoice = super().create(validated_data)

        invoice.invoicing_date = timezone.now().date()
        invoice.outstanding_amount = validated_data['total_amount']
        invoice.save()

        return invoice

    class Meta:
        model = Invoice
        exclude = ('deleted',)
        read_only_fields = ('number', 'generated', 'sent_to_sap_at', 'sap_id', 'state', 'adjusted_due_date',
                            'credit_invoices', 'interest_invoices')
Esempio n. 11
0
class PlotSearchCreateSerializer(PlotSearchUpdateSerializer):
    subtype = InstanceDictPrimaryKeyRelatedField(
        instance_class=PlotSearchSubtype,
        queryset=PlotSearchSubtype.objects.all(),
        related_serializer=PlotSearchSubtypeSerializer,
        required=False,
    )
    stage = InstanceDictPrimaryKeyRelatedField(
        instance_class=PlotSearchStage,
        queryset=PlotSearchStage.objects.all(),
        related_serializer=PlotSearchStageSerializer,
        required=False,
    )
    preparer = InstanceDictPrimaryKeyRelatedField(
        instance_class=User,
        queryset=User.objects.all(),
        related_serializer=UserSerializer,
        required=False,
    )

    plot_search_targets = PlotSearchTargetSerializer(many=True, required=False)
    plan_unit = PlanUnitSerializer(read_only=True)

    class Meta:
        model = PlotSearch
        fields = "__all__"

    @staticmethod
    def handle_targets(targets, plot_search):
        for target in targets:
            plan_unit = PlanUnit.objects.get(id=target.get("plan_unit_id"))
            plot_search_target = PlotSearchTarget.objects.create(
                plot_search=plot_search,
                plan_unit=plan_unit,
                target_type=target.get("target_type"),
            )
            plot_search_target.save()
            if "info_links" in target:
                for link in target["info_links"]:
                    link["plot_search_target"] = plot_search_target
                    plot_search_target.info_links.add(
                        PlotSearchTargetInfoLinkSerializer().create(link))

    def create(self, validated_data):
        targets = None
        if "plot_search_targets" in validated_data:
            targets = validated_data.pop("plot_search_targets")
        decisions = None
        if "decisions" in validated_data:
            decisions = validated_data.pop("decisions")

        plot_search = PlotSearch.objects.create(**validated_data)

        if targets:
            self.handle_targets(targets, plot_search)
        if decisions:
            for decision in decisions:
                plot_search.decisions.add(decision)
            plot_search.save()

        return plot_search
Esempio n. 12
0
class PlotSearchUpdateSerializer(
        UpdateNestedMixin,
        EnumSupportSerializerMixin,
        FieldPermissionsSerializerMixin,
        serializers.ModelSerializer,
):
    id = serializers.ReadOnlyField()
    name = serializers.CharField(required=True)
    type = InstanceDictPrimaryKeyRelatedField(
        instance_class=PlotSearchType,
        queryset=PlotSearchType.objects.all(),
        required=False,
        allow_null=True,
    )
    subtype = InstanceDictPrimaryKeyRelatedField(
        instance_class=PlotSearchSubtype,
        queryset=PlotSearchSubtype.objects.all(),
        related_serializer=PlotSearchSubtypeSerializer,
        required=True,
    )
    stage = InstanceDictPrimaryKeyRelatedField(
        instance_class=PlotSearchStage,
        queryset=PlotSearchStage.objects.all(),
        related_serializer=PlotSearchStageSerializer,
        required=True,
    )
    preparer = InstanceDictPrimaryKeyRelatedField(
        instance_class=User,
        queryset=User.objects.all(),
        related_serializer=UserSerializer,
        required=True,
    )
    plot_search_targets = PlotSearchTargetCreateUpdateSerializer(
        many=True, required=False)

    class Meta:
        model = PlotSearch
        fields = "__all__"

    def to_representation(self, instance):
        return PlotSearchRetrieveSerializer().to_representation(instance)

    @staticmethod
    def dict_to_instance(dictionary, model):
        if isinstance(dictionary, model):
            return dictionary
        instance, created = model.objects.get_or_create(id=dictionary["id"])
        if created:
            for k, v in dictionary:
                setattr(instance, k, v)
            instance.save()
        return instance

    @staticmethod
    def handle_targets(targets, instance):
        exist_target_ids = []
        for target in targets:
            target_id = target.get("id")
            target["plot_search"] = instance
            if target_id:
                plot_search_target = PlotSearchTarget.objects.get(
                    id=target_id, plot_search=instance)
                PlotSearchTargetCreateUpdateSerializer().update(
                    plot_search_target, target)
            else:
                plot_search_target = PlotSearchTargetCreateUpdateSerializer(
                ).create(target)
            exist_target_ids.append(plot_search_target.id)

        PlotSearchTarget.objects.filter(plot_search=instance).exclude(
            id__in=exist_target_ids).delete()

    def update(self, instance, validated_data):

        targets = validated_data.pop("plot_search_targets", None)
        subtype = validated_data.pop("subtype", None)
        stage = validated_data.pop("stage", None)
        preparer = validated_data.pop("preparer", None)

        if subtype:
            validated_data["subtype"] = self.dict_to_instance(
                subtype, PlotSearchSubtype)

        if stage:
            validated_data["stage"] = self.dict_to_instance(
                stage, PlotSearchStage)

        if preparer:
            validated_data["preparer"] = self.dict_to_instance(preparer, User)

        instance = super(PlotSearchUpdateSerializer,
                         self).update(instance, validated_data)

        if targets is not None:
            self.handle_targets(targets, instance)

        return instance
Esempio n. 13
0
class InvoiceCreateSerializer(
    UpdateNestedMixin,
    EnumSupportSerializerMixin,
    FieldPermissionsSerializerMixin,
    serializers.ModelSerializer,
):
    id = serializers.ReadOnlyField()
    recipient = InstanceDictPrimaryKeyRelatedField(
        instance_class=Contact,
        queryset=Contact.objects.all(),
        related_serializer=ContactSerializer,
        required=False,
    )
    tenant = InstanceDictPrimaryKeyRelatedField(
        instance_class=Tenant,
        queryset=Tenant.objects.all(),
        related_serializer=TenantSerializer,
        required=False,
    )
    rows = InvoiceRowCreateUpdateSerializer(many=True)
    payments = InvoicePaymentCreateUpdateSerializer(
        many=True, required=False, allow_null=True
    )
    # Make total_amount, billed_amount, and type not required in the serializer and set them in create() if needed
    total_amount = serializers.DecimalField(
        max_digits=10, decimal_places=2, required=False
    )
    billed_amount = serializers.DecimalField(
        max_digits=10, decimal_places=2, required=False
    )
    type = EnumField(enum=InvoiceType, required=False)

    def override_permission_check_field_name(self, field_name):
        if field_name == "tenant":
            return "recipient"

        return field_name

    def validate(self, attrs):
        if not bool(attrs.get("recipient")) ^ bool(attrs.get("tenant")):
            raise ValidationError(_("Either recipient or tenant is required."))

        if (
            attrs.get("tenant")
            and attrs.get("tenant") not in attrs.get("lease").tenants.all()
        ):
            raise ValidationError(_("Tenant not found in lease"))

        return attrs

    def create(self, validated_data):
        validated_data["state"] = InvoiceState.OPEN

        if not validated_data.get("total_amount"):
            total_amount = Decimal(0)
            for row in validated_data.get("rows", []):
                total_amount += row.get("amount", Decimal(0))

            validated_data["total_amount"] = total_amount

        if not validated_data.get("billed_amount"):
            billed_amount = Decimal(0)
            for row in validated_data.get("rows", []):
                billed_amount += row.get("amount", Decimal(0))

            validated_data["billed_amount"] = billed_amount

        if not validated_data.get("type"):
            validated_data["type"] = InvoiceType.CHARGE

        if validated_data.get("tenant"):
            today = datetime.date.today()
            tenant = validated_data.pop("tenant")
            billing_tenantcontact = tenant.get_billing_tenantcontacts(
                start_date=today, end_date=None
            ).first()
            if not billing_tenantcontact:
                raise ValidationError(_("Billing contact not found for tenant"))

            validated_data["recipient"] = billing_tenantcontact.contact
            for row in validated_data.get("rows", []):
                row["tenant"] = tenant

        invoice = super().create(validated_data)

        invoice.invoicing_date = timezone.now().date()
        invoice.outstanding_amount = validated_data["total_amount"]
        invoice.update_amounts()  # 0€ invoice would stay OPEN otherwise
        invoice.save()

        return invoice

    class Meta:
        model = Invoice
        exclude = ("deleted",)
        read_only_fields = (
            "number",
            "generated",
            "sent_to_sap_at",
            "sap_id",
            "state",
            "adjusted_due_date",
            "credit_invoices",
            "interest_invoices",
        )
Esempio n. 14
0
class InvoiceRowCreateUpdateSerializer(
    FieldPermissionsSerializerMixin, serializers.ModelSerializer
):
    id = serializers.IntegerField(required=False)
    tenant = InstanceDictPrimaryKeyRelatedField(
        instance_class=Tenant,
        queryset=Tenant.objects.all(),
        related_serializer=TenantSerializer,
        required=False,
        allow_null=True,
    )
    receivable_type = InstanceDictPrimaryKeyRelatedField(
        instance_class=ReceivableType,
        queryset=ReceivableType.objects.all(),
        related_serializer=ReceivableTypeSerializer,
    )

    class Meta:
        model = InvoiceRow
        fields = (
            "id",
            "tenant",
            "receivable_type",
            "billing_period_start_date",
            "billing_period_end_date",
            "description",
            "amount",
        )

    def validate(self, data):
        """Validate that rows with an inactive receivable type cannot be created

        Saving an existing row should succeed, but creating
        new rows or changing a rows receivable type to an inactive type
        should fail."""

        valid = True

        if not data["receivable_type"].is_active:
            if self.instance:
                if self.instance.receivable_type != data["receivable_type"]:
                    # We have an existing row but the receivable type wasn't the
                    # same as before
                    valid = False
            else:
                # We have data without an instance.
                # If there is no id it's a new row.
                if "id" not in data:
                    valid = False
                else:
                    # Else try to see if the existing row has the same receivable type
                    try:
                        existing_row = InvoiceRow.objects.get(pk=data["id"])

                        if existing_row.receivable_type != data["receivable_type"]:
                            valid = False
                    except ObjectDoesNotExist:
                        valid = False

        if not valid:
            raise ValidationError(_("Cannot use an inactive receivable type"))

        return data