diff --git a/ereuse_devicehub/inventory/forms.py b/ereuse_devicehub/inventory/forms.py index 4f68c899..1226dcbf 100644 --- a/ereuse_devicehub/inventory/forms.py +++ b/ereuse_devicehub/inventory/forms.py @@ -1,4 +1,5 @@ import copy +import datetime import json from json.decoder import JSONDecodeError @@ -667,6 +668,10 @@ class AllocateForm(ActionFormMix): self.start_time.errors = ['Not a valid date value.!'] return False + if start_time > datetime.datetime.now().date(): + self.start_time.errors = ['Not a valid date value.!'] + return False + if start_time and end_time and end_time < start_time: error = ['The action cannot finish before it starts.'] self.end_time.errors = error @@ -679,23 +684,100 @@ class AllocateForm(ActionFormMix): def check_devices(self): if self.type.data == 'Allocate': - txt = "You need deallocate before allocate this device again" - for device in self._devices: - if device.allocated: - self.devices.errors = [txt] - return False - - device.allocated = True - + return self.check_allocate() if self.type.data == 'Deallocate': - txt = "Sorry some of this devices are actually deallocate" - for device in self._devices: - if not device.allocated: + return self.check_deallocate() + return True + + def check_allocate(self): + txt = "You need deallocate before allocate this device again" + for device in self._devices: + # | Allo - Deallo | Allo - Deallo | + + allocates = [ + ac for ac in device.actions if ac.type in ['Allocate', 'Deallocate'] + ] + allocates.sort(key=lambda x: x.start_time) + allocates.reverse() + last_deallocate = None + last_allocate = None + for ac in allocates: + if ( + ac.type == 'Deallocate' + and ac.start_time.date() < self.start_time.data + ): + # allow to do the action + break + + # check if this action is between an old allocate - deallocate + if ac.type == 'Deallocate': + last_deallocate = ac + continue + + if ( + ac.type == 'Allocate' + and ac.start_time.date() > self.start_time.data + ): + last_deallocate = None + last_allocate = None + continue + + if ac.type == 'Allocate': + last_allocate = ac + + if last_allocate or not last_deallocate: self.devices.errors = [txt] return False - device.allocated = False + device.allocated = True + return True + def check_deallocate(self): + txt = "Sorry some of this devices are actually deallocate" + for device in self._devices: + allocates = [ + ac for ac in device.actions if ac.type in ['Allocate', 'Deallocate'] + ] + allocates.sort(key=lambda x: x.start_time) + allocates.reverse() + last_deallocate = None + last_allocate = None + + for ac in allocates: + # check if this action is between an old allocate - deallocate + # | Allo - Deallo | Allo - Deallo | + # | Allo | + if ( + ac.type == 'Allocate' + and ac.start_time.date() > self.start_time.data + ): + last_allocate = None + last_deallocate = None + continue + + if ac.type == 'Allocate' and not last_deallocate: + last_allocate = ac + break + + if ( + ac.type == 'Deallocate' + and ac.start_time.date() > self.start_time.data + ): + last_deallocate = ac + continue + + if ac.type == 'Deallocate': + last_allocate = None + + if last_deallocate or not last_allocate: + self.devices.errors = [txt] + return False + + if not last_deallocate and not last_allocate: + self.devices.errors = [txt] + return False + + device.allocated = False return True diff --git a/ereuse_devicehub/resources/device/models.py b/ereuse_devicehub/resources/device/models.py index 746ce2b8..37339afc 100644 --- a/ereuse_devicehub/resources/device/models.py +++ b/ereuse_devicehub/resources/device/models.py @@ -1,6 +1,5 @@ import copy import pathlib -import time from contextlib import suppress from fractions import Fraction from itertools import chain @@ -358,7 +357,6 @@ class Device(Thing): from ereuse_devicehub.resources.device import states with suppress(LookupError, ValueError): - # import pdb; pdb.set_trace() return self.last_action_of(*states.Physical.actions()) @property @@ -407,7 +405,7 @@ class Device(Thing): def tradings(self): return {str(x.id): self.trading(x.lot) for x in self.actions if x.t == 'Trade'} - def trading(self, lot, simple=None): + def trading(self, lot, simple=None): # noqa: C901 """The trading state, or None if no Trade action has ever been performed to this device. This extract the posibilities for to do. This method is performed for show in the web. diff --git a/tests/test_render_2_0.py b/tests/test_render_2_0.py index 67911765..be5241a3 100644 --- a/tests/test_render_2_0.py +++ b/tests/test_render_2_0.py @@ -1,4 +1,5 @@ import csv +import datetime import json from io import BytesIO from pathlib import Path @@ -686,6 +687,34 @@ def test_action_allocate_error_dates(user3: UserClientFlask): assert dev.actions[-1].type != 'Allocate' +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_action_allocate_error_future_dates(user3: UserClientFlask): + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + dev = snap.device + uri = '/inventory/device/' + user3.get(uri) + start_time = (datetime.datetime.now() + datetime.timedelta(1)).strftime('%Y-%m-%d') + end_time = (datetime.datetime.now() + datetime.timedelta(10)).strftime('%Y-%m-%d') + + data = { + 'csrf_token': generate_csrf(), + 'type': "Allocate", + 'severity': "Info", + 'devices': "{}".format(dev.id), + 'start_time': start_time, + 'end_time': end_time, + 'end_users': 2, + } + + uri = '/inventory/action/allocate/add/' + body, status = user3.post(uri, data=data) + assert status == '200 OK' + assert 'Action Allocate error' in body + assert 'Not a valid date value.!' in body + assert dev.actions[-1].type != 'Allocate' + + @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) def test_action_deallocate(user3: UserClientFlask): @@ -707,7 +736,7 @@ def test_action_deallocate(user3: UserClientFlask): uri = '/inventory/action/allocate/add/' user3.post(uri, data=data) - assert dev.actions[-1].type == 'Allocate' + assert dev.allocated_status.type == 'Allocate' data = { 'csrf_token': generate_csrf(), @@ -720,11 +749,200 @@ def test_action_deallocate(user3: UserClientFlask): } body, status = user3.post(uri, data=data) assert status == '200 OK' - assert dev.actions[-1].type == 'Deallocate' + assert dev.allocated_status.type == 'Deallocate' assert 'Action "Deallocate" created successfully!' in body assert dev.devicehub_id in body +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_action_deallocate_error(user3: UserClientFlask): + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + dev = snap.device + uri = '/inventory/device/' + user3.get(uri) + + data = { + 'csrf_token': generate_csrf(), + 'type': "Allocate", + 'severity': "Info", + 'devices': "{}".format(dev.id), + 'start_time': '2000-05-01', + 'end_time': '2000-06-01', + 'end_users': 2, + } + + uri = '/inventory/action/allocate/add/' + + user3.post(uri, data=data) + assert dev.allocated_status.type == 'Allocate' + + data = { + 'csrf_token': generate_csrf(), + 'type': "Deallocate", + 'severity': "Info", + 'devices': "{}".format(dev.id), + 'start_time': '2000-01-01', + 'end_time': '2000-02-01', + 'end_users': 2, + } + body, status = user3.post(uri, data=data) + assert status == '200 OK' + assert dev.allocated_status.type != 'Deallocate' + assert 'Action Deallocate error!' in body + assert 'Sorry some of this devices are actually deallocate' in body + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_action_allocate_deallocate_error(user3: UserClientFlask): + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + dev = snap.device + uri = '/inventory/device/' + user3.get(uri) + + data = { + 'csrf_token': generate_csrf(), + 'type': "Allocate", + 'severity': "Info", + 'devices': "{}".format(dev.id), + 'start_time': '2000-01-01', + 'end_time': '2000-01-01', + 'end_users': 2, + } + + uri = '/inventory/action/allocate/add/' + + user3.post(uri, data=data) + assert dev.allocated_status.type == 'Allocate' + assert len(dev.actions) == 13 + + data = { + 'csrf_token': generate_csrf(), + 'type': "Deallocate", + 'severity': "Info", + 'devices': "{}".format(dev.id), + 'start_time': '2000-02-01', + 'end_time': '2000-02-01', + 'end_users': 2, + } + body, status = user3.post(uri, data=data) + assert status == '200 OK' + assert dev.allocated_status.type == 'Deallocate' + assert len(dev.actions) == 14 + + # is not possible to do an allocate between an allocate and an deallocate + data = { + 'csrf_token': generate_csrf(), + 'type': "Allocate", + 'severity': "Info", + 'devices': "{}".format(dev.id), + 'start_time': '2000-01-15', + 'end_time': '2000-01-15', + 'end_users': 2, + } + + user3.post(uri, data=data) + assert dev.allocated_status.type == 'Deallocate' + # assert 'Action Deallocate error!' in body + # assert 'Sorry some of this devices are actually deallocate' in body + # + data = { + 'csrf_token': generate_csrf(), + 'type': "Deallocate", + 'severity': "Info", + 'devices': "{}".format(dev.id), + 'start_time': '2000-01-15', + 'end_time': '2000-01-15', + 'end_users': 2, + } + + user3.post(uri, data=data) + assert len(dev.actions) == 14 + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_action_allocate_deallocate_error2(user3: UserClientFlask): + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + dev = snap.device + uri = '/inventory/device/' + user3.get(uri) + + data = { + 'csrf_token': generate_csrf(), + 'type': "Allocate", + 'severity': "Info", + 'devices': "{}".format(dev.id), + 'start_time': '2000-01-10', + 'end_users': 2, + } + + uri = '/inventory/action/allocate/add/' + + user3.post(uri, data=data) + assert len(dev.actions) == 13 + + data = { + 'csrf_token': generate_csrf(), + 'type': "Deallocate", + 'severity': "Info", + 'devices': "{}".format(dev.id), + 'start_time': '2000-01-20', + 'end_users': 2, + } + body, status = user3.post(uri, data=data) + assert status == '200 OK' + assert len(dev.actions) == 14 + + data = { + 'csrf_token': generate_csrf(), + 'type': "Allocate", + 'severity': "Info", + 'devices': "{}".format(dev.id), + 'start_time': '2000-02-10', + 'end_users': 2, + } + + uri = '/inventory/action/allocate/add/' + + user3.post(uri, data=data) + assert len(dev.actions) == 15 + + data = { + 'csrf_token': generate_csrf(), + 'type': "Deallocate", + 'severity': "Info", + 'devices': "{}".format(dev.id), + 'start_time': '2000-02-20', + 'end_users': 2, + } + user3.post(uri, data=data) + assert len(dev.actions) == 16 + + data = { + 'csrf_token': generate_csrf(), + 'type': "Allocate", + 'severity': "Info", + 'devices': "{}".format(dev.id), + 'start_time': '2000-01-25', + 'end_users': 2, + } + user3.post(uri, data=data) + assert len(dev.actions) == 17 + + data = { + 'csrf_token': generate_csrf(), + 'type': "Deallocate", + 'severity': "Info", + 'devices': "{}".format(dev.id), + 'start_time': '2000-01-27', + 'end_users': 2, + } + user3.post(uri, data=data) + assert len(dev.actions) == 18 + + @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) def test_action_toprepare(user3: UserClientFlask):