Parcourir la source

Allow services to use other services

Jocelyn Delande il y a 9 ans
Parent
commit
2c0a7d86d0

+ 60 - 0
costs/migrations/0009_auto_20160131_1208.py

@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import datetime
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('costs', '0008_auto_20160108_2331'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='ServiceUse',
+            fields=[
+                ('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')),
+                ('share', models.FloatField()),
+            ],
+            options={
+                'abstract': False,
+            },
+        ),
+        migrations.AddField(
+            model_name='service',
+            name='capacity_unit',
+            field=models.CharField(blank=True, choices=[('a', 'A'), ('mbps', 'Mbps'), ('u', 'U'), ('ipv4', 'IPv4'), ('eth', 'ports'), ('services', 'services')], max_length=10),
+        ),
+        migrations.AddField(
+            model_name='service',
+            name='total_capacity',
+            field=models.FloatField(default=1),
+        ),
+        migrations.AlterField(
+            model_name='cost',
+            name='capacity_unit',
+            field=models.CharField(blank=True, choices=[('a', 'A'), ('mbps', 'Mbps'), ('u', 'U'), ('ipv4', 'IPv4'), ('eth', 'ports'), ('services', 'services')], max_length=10),
+        ),
+        migrations.AlterField(
+            model_name='document',
+            name='date',
+            field=models.DateField(default=datetime.datetime.now),
+        ),
+        migrations.AlterField(
+            model_name='good',
+            name='capacity_unit',
+            field=models.CharField(blank=True, choices=[('a', 'A'), ('mbps', 'Mbps'), ('u', 'U'), ('ipv4', 'IPv4'), ('eth', 'ports'), ('services', 'services')], max_length=10),
+        ),
+        migrations.AddField(
+            model_name='serviceuse',
+            name='resource',
+            field=models.ForeignKey(to='costs.Service', related_name='dependent_services'),
+        ),
+        migrations.AddField(
+            model_name='serviceuse',
+            name='service',
+            field=models.ForeignKey(to='costs.Service'),
+        ),
+    ]

+ 19 - 0
costs/migrations/0010_service_reusable.py

@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('costs', '0009_auto_20160131_1208'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='service',
+            name='reusable',
+            field=models.BooleanField(help_text="Peut-être utilisé par d'autres services", default=False, verbose_name='Ré-utilisable'),
+        ),
+    ]

+ 31 - 2
costs/models.py

@@ -46,6 +46,7 @@ class AbstractResource(AbstractItem):
     UNIT_U = 'u'
     UNIT_IPV4 = 'ipv4'
     UNIT_ETHERNET_PORT = 'eth'
+    UNIT_SERVICE = 'services'
 
     capacity_unit = models.CharField(
         max_length=10,
@@ -55,6 +56,7 @@ class AbstractResource(AbstractItem):
             (UNIT_U, 'U'),
             (UNIT_IPV4, 'IPv4'),
             (UNIT_ETHERNET_PORT, 'ports'),
+            (UNIT_SERVICE, 'services'),
         ),
         blank=True,
     )
@@ -214,7 +216,7 @@ class GoodUse(AbstractUse):
             return monthly_share/subscriptions_count
 
 
-class Service(AbstractItem):
+class Service(AbstractResource):
     """ A service we sell
 
     (considered monthly)
@@ -227,13 +229,28 @@ class Service(AbstractItem):
         Good,
         through=GoodUse,
         related_name='using_services')
-    # services = models.ManyToMany('Service') #TODO
 
     subscriptions_count = models.PositiveIntegerField(default=0)
+    reusable = models.BooleanField(
+        "Ré-utilisable", default=False,
+        help_text="Peut-être utilisé par d'autres services")
+
+    @property
+    def price(self):
+        return self.get_prices()['total_costs_price']
+
+    def save(self, *args, **kwargs):
+        if self.reusable:
+            self.capacity_unit = self.UNIT_SERVICE
+            self.total_capacity = self.subscriptions_count
+        return super().save(*args, **kwargs)
 
     def get_absolute_url(self):
         return reverse('detail-service', kwargs={'pk': self.pk})
 
+    def get_use_class(self):
+        return ServiceUse
+
     def get_prices(self):
         costs_uses = CostUse.objects.filter(service=self)
         goods_uses = GoodUse.objects.filter(service=self)
@@ -258,3 +275,15 @@ class Service(AbstractItem):
             'total_goods_value_share': total_goods_value_share,
             'unit_goods_value_share': unit_goods_value_share,
         }
+
+
+def validate_reusable_service(v):
+    if not Service.objects.get(pk=v).reusable:
+        raise ValidationError('{} is not a reusable service'.format(v))
+
+
+class ServiceUse(AbstractUse):
+    resource = models.ForeignKey(
+        Service, related_name='dependent_services',
+        limit_choices_to={'reusable': True},
+        validators=[validate_reusable_service])

+ 74 - 1
costs/tests/test_models.py

@@ -4,7 +4,7 @@ from django.test import TestCase
 from django.core.exceptions import ValidationError
 
 from ..models import (
-    Cost, CostUse, Document, Good, GoodUse, Service)
+    Cost, CostUse, Document, Good, GoodUse, Service, ServiceUse)
 
 
 class ServiceTests(TestCase):
@@ -97,6 +97,12 @@ class AbstractUseTests(TestCase):
             total_capacity=4,
         )
 
+        self.carrier_connection = Cost.objects.create(
+            name='carrier connection',
+            price=100,
+            document=self.doc,
+        )
+
     def test_can_add_service_share(self):
         use = CostUse(
             service=self.hosting_service,
@@ -227,3 +233,70 @@ class AbstractUseTests(TestCase):
 
         self.assertEqual(self.electricity_cost.used(), 0.5)
         self.assertEqual(self.electricity_cost.unused(), 3.5)
+
+    def test_service_using_service(self):
+        """
+        Wifi+VPN is a service, but using VPN access
+        So there is a service using another service
+        """
+
+        vpn_service = Service.objects.create(
+            name="VPN",
+            document=self.doc,
+            subscriptions_count=20,  # includes wifi+vpn subscribers
+            reusable=True,
+        )
+        # both should be auto-set
+        self.assertEqual(vpn_service.capacity_unit, 'services')
+        self.assertEqual(vpn_service.total_capacity, 20)
+
+        wifi_service = Service.objects.create(
+            name="Wifi, via VPN",
+            document=self.doc,
+            subscriptions_count=2,
+            reusable=True,
+        )
+        self.assertEqual(vpn_service.capacity_unit, 'services')
+
+        # To simplify, VPN is only using electricity
+        CostUse.objects.create(
+            service=vpn_service,
+            resource=self.electricity_cost,
+            share=0.5,  # Amp
+        )
+
+        # Wifi is using VPN + a carrier connection
+        wifi_vpn_use = ServiceUse.objects.create(
+            service=wifi_service,
+            resource=vpn_service,
+            share=2,
+        )
+        CostUse.objects.create(
+            service=wifi_service,
+            resource=self.carrier_connection,
+            share=1,  # 100%
+        )
+
+        self.assertEqual(wifi_vpn_use.share, 0.5*4*10/20*2)
+        self.assertEqual(wifi_vpn_use.unit_share(), 0.5*4*10/20)
+
+        # VPN this is the only service using electricity
+        self.assertEqual(wifi_vpn_use.unit_real_share(), 10)
+
+    def test_service_using_service_edgecases(self):
+        serva = Service.objects.create(
+            name='A', document=self.doc,
+            subscriptions_count=4,
+            reusable=False,
+        )
+
+        # A default service is not reusable
+
+        with self.assertRaises(ValidationError):
+            su = ServiceUse(
+                service=self.mailbox_service,
+                resource=serva,
+                share=1,
+            )
+            su.full_clean()
+            su.save()