diff --git a/ereuse_devicehub/migrations/versions/1bb2b5e0fae7_change_action_device.py b/ereuse_devicehub/migrations/versions/1bb2b5e0fae7_change_action_device.py new file mode 100644 index 00000000..d03df285 --- /dev/null +++ b/ereuse_devicehub/migrations/versions/1bb2b5e0fae7_change_action_device.py @@ -0,0 +1,69 @@ +""" +change action_device + +Revision ID: 1bb2b5e0fae7 +Revises: a0978ac6cf4a +Create Date: 2021-11-04 10:32:49.116399 + +""" +import sqlalchemy as sa +from alembic import context +from alembic import op +from sqlalchemy.dialects import postgresql + + +# revision identifiers, used by Alembic. +revision = '1bb2b5e0fae7' +down_revision = 'a0978ac6cf4a' +branch_labels = None +depends_on = None + + +def get_inv(): + INV = context.get_x_argument(as_dictionary=True).get('inventory') + if not INV: + raise ValueError("Inventory value is not specified") + return INV + + +def upgrade_data(): + con = op.get_bind() + + values = f"action_id, {get_inv()}.action.created" + table = f"{get_inv()}.action_device" + joins = f"inner join {get_inv()}.action" + on = f"on {get_inv()}.action_device.action_id = {get_inv()}.action.id" + sql = f"select {values} from {table} {joins} {on}" + + actions_devs = con.execute(sql) + for a in actions_devs: + action_id = a.action_id + created = a.created + sql = f"update {get_inv()}.action_device set created='{created}' where action_id='{action_id}';" + con.execute(sql) + + +def upgrade(): + op.add_column('action_device', + sa.Column('created', sa.TIMESTAMP(timezone=True), server_default=sa.text('CURRENT_TIMESTAMP'), + nullable=False, comment='When Devicehub created this.'), + schema=f'{get_inv()}') + + op.add_column('action_status', + sa.Column('trade_id', postgresql.UUID(as_uuid=True), nullable=True), + schema=f'{get_inv()}') + + op.create_foreign_key("fk_action_status_trade", + "action_status", "trade", + ["trade_id"], ["id"], + ondelete="SET NULL", + source_schema=f'{get_inv()}', + referent_schema=f'{get_inv()}') + + upgrade_data() + + +def downgrade(): + op.drop_constraint("fk_action_status_trade", "action_status", type_="foreignkey", schema=f'{get_inv()}') + op.drop_column('action_device', 'created', schema=f'{get_inv()}') + op.drop_column('action_status', 'trade_id', schema=f'{get_inv()}') diff --git a/ereuse_devicehub/resources/action/models.py b/ereuse_devicehub/resources/action/models.py index 5679decd..b2fb2c18 100644 --- a/ereuse_devicehub/resources/action/models.py +++ b/ereuse_devicehub/resources/action/models.py @@ -304,6 +304,23 @@ class ActionDevice(db.Model): device_id = Column(BigInteger, ForeignKey(Device.id), primary_key=True) action_id = Column(UUID(as_uuid=True), ForeignKey(ActionWithMultipleDevices.id), primary_key=True) + device = relationship(Device, + backref=backref('actions_device', + lazy=True), + primaryjoin=Device.id == device_id) + action = relationship(Action, + backref=backref('actions_device', + lazy=True), + primaryjoin=Action.id == action_id) + created = db.Column(db.TIMESTAMP(timezone=True), + nullable=False, + index=True, + server_default=db.text('CURRENT_TIMESTAMP')) + created.comment = """When Devicehub created this.""" + + def __init__(self, **kwargs) -> None: + self.created = kwargs.get('created', datetime.now(timezone.utc)) + super().__init__(**kwargs) class ActionWithMultipleTradeDocuments(ActionWithMultipleDevices): @@ -1360,6 +1377,16 @@ class ActionStatus(JoinedTableMixin, ActionWithMultipleTradeDocuments): default=lambda: g.user.id) rol_user = db.relationship(User, primaryjoin=rol_user_id == User.id) rol_user_comment = """The user that .""" + trade_id = db.Column(UUID(as_uuid=True), + db.ForeignKey('trade.id'), + nullable=True) + trade = db.relationship('Trade', + backref=backref('status_changes', + uselist=True, + lazy=True, + order_by=lambda: Action.end_time, + collection_class=list), + primaryjoin='ActionStatus.trade_id == Trade.id') class Recycling(ActionStatus): diff --git a/ereuse_devicehub/resources/action/schemas.py b/ereuse_devicehub/resources/action/schemas.py index bd81590a..0202a3a3 100644 --- a/ereuse_devicehub/resources/action/schemas.py +++ b/ereuse_devicehub/resources/action/schemas.py @@ -439,18 +439,24 @@ class ActionStatus(Action): @pre_load def put_devices(self, data: dict): - if not 'devices' in data.keys(): + if 'devices' not in data.keys(): data['devices'] = [] - # @post_load - # def put_rol_user(self, data: dict): - # TODO we need rebuild this functin - # for dev in data['devices']: - # if dev.trading in [None, 'Revoke']: - # return data - # trade = [ac for ac in dev.actions if ac.t == 'Trade'][-1] - # if trade.user_to != g.user: - # data['rol_user'] = trade.user_to + @post_load + def put_rol_user(self, data: dict): + for dev in data['devices']: + if dev.trading in [None, 'Revoke']: + return data + + trades = [ac for ac in dev.actions if ac.t == 'Trade'] + if not trades: + return data + + trade = trades[-1] + + if trade.user_to != g.user: + data['rol_user'] = trade.user_to + data['trade'] = trade class Recycling(ActionStatus): diff --git a/ereuse_devicehub/resources/device/metrics.py b/ereuse_devicehub/resources/device/metrics.py index 898fc5d8..3365e6cc 100644 --- a/ereuse_devicehub/resources/device/metrics.py +++ b/ereuse_devicehub/resources/device/metrics.py @@ -5,16 +5,17 @@ class MetricsMix: """we want get the data metrics of one device""" def __init__(self, *args, **kwargs): - self.actions.sort(key=lambda x: x.created) + # self.actions.sort(key=lambda x: x.created) self.rows = [] self.lifetime = 0 self.last_trade = None self.action_create_by = 'Receiver' - self.status_receiver = 'Use' + self.status_receiver = '' self.status_supplier = '' self.act = None self.end_users = 0 self.final_user_code = '' + self.trades = {} def get_template_row(self): """ @@ -63,27 +64,34 @@ class Metrics(MetricsMix): """ Mark the status of one device. If exist one trade before this action, then modify the trade action - else, create one row new. + else, create one new row. """ - self.status_receiver = self.act.type - self.status_supplier = '' - if self.act.author != self.act.rol_user: - # It is neccesary exist one trade action before - self.last_trade['status_supplier'] = self.act.type - self.last_trade['status_supplier_created'] = self.act.created + if self.act.trade not in self.trades: + # If not exist one trade, the status is of the Receive + self.action_create_by = 'Receiver' + self.status_receiver = self.act.type + self.status_supplier = '' + row = self.get_template_row() + row['status_supplier_created'] = '' + row['status_receiver_created'] = self.act.created + self.rows.append(row) return - self.action_create_by = 'Receiver' - if self.last_trade: - # if exist one trade action before - self.last_trade['status_receiver'] = self.act.type - self.last_trade['status_receiver_created'] = self.act.created + trade = self.trades[self.act.trade] + + if trade['trade_supplier'] == self.act.author.email: + trade['status_supplier'] = self.act.type + trade['status_supplier_created'] = self.act.created return - # If not exist any trade action for this device - row = self.get_template_row() - row['status_receiver_created'] = self.act.created - self.rows.append(row) + if trade['trade_receiver'] == self.act.author.email: + trade['status_receiver'] = self.act.type + trade['status_receiver_created'] = self.act.created + return + + # import pdb; pdb.set_trace() + # necesitamos poder poner un cambio de estado de un trade mas antiguo que last_trade + # lo mismo con confirm def get_snapshot(self): """ @@ -97,6 +105,7 @@ class Metrics(MetricsMix): """ If the action is one Allocate, need modify the row base. """ + self.action_create_by = 'Receiver' self.end_users = self.act.end_users self.final_user_code = self.act.final_user_code row = self.get_template_row() @@ -112,6 +121,7 @@ class Metrics(MetricsMix): """ If the action is one Live, need modify the row base. """ + self.action_create_by = 'Receiver' row = self.get_template_row() row['type'] = 'Live' row['finalUserCode'] = self.final_user_code @@ -127,6 +137,7 @@ class Metrics(MetricsMix): """ If the action is one Dellocate, need modify the row base. """ + self.action_create_by = 'Receiver' row = self.get_template_row() row['type'] = 'Deallocate' row['start'] = self.act.start_time @@ -147,15 +158,18 @@ class Metrics(MetricsMix): """ if self.act.author == self.act.user_from: self.action_create_by = 'Supplier' + self.status_receiver = '' + row = self.get_template_row() self.last_trade = row row['type'] = 'Trade' row['action_type'] = 'Trade' row['trade_supplier'] = self.act.user_from.email row['trade_receiver'] = self.act.user_to.email - row['self.status_receiver'] = self.status_receiver - row['self.status_supplier'] = self.status_supplier + row['status_receiver'] = self.status_receiver + row['status_supplier'] = '' row['trade_confirmed'] = self.get_confirms() + self.trades[self.act] = row self.rows.append(row) def get_metrics(self): @@ -219,9 +233,9 @@ class TradeMetrics(MetricsMix): row['status_receiver'] = '' row['status_supplier'] = '' row['trade_weight'] = self.document.weight - if self.last_trade.author == self.last_trade.user_from: + if self.document.owner == self.last_trade.user_from: row['action_create_by'] = 'Supplier' - elif self.last_trade.author == self.last_trade.user_to: + elif self.document.owner == self.last_trade.user_to: row['action_create_by'] = 'Receiver' self.rows.append(row) @@ -233,8 +247,20 @@ class TradeMetrics(MetricsMix): if the action is one trade action, is possible than have a list of confirmations. Get the doble confirm for to know if this trade is confirmed or not. """ - if hasattr(self.last_trade, 'acceptances_document'): - accept = self.last_trade.acceptances_document[-1] - if accept.t == 'Confirm' and accept.user == self.last_trade.user_to: + trade = None + confirmations = [] + confirms = [] + for ac in self.document.actions: + if ac.t == 'Trade': + trade = ac + elif ac.t == 'ConfirmDocument': + confirms.append(ac.author) + confirmations.append(ac) + elif ac.t in ['RevokeDocument', 'ConfirmDocumentRevoke']: + confirmations.append(ac) + + if confirmations and confirmations[-1].t == 'ConfirmDocument': + if trade.user_from in confirms and trade.user_to in confirms: return True + return False diff --git a/ereuse_devicehub/resources/device/models.py b/ereuse_devicehub/resources/device/models.py index 95040dd0..b0d09848 100644 --- a/ereuse_devicehub/resources/device/models.py +++ b/ereuse_devicehub/resources/device/models.py @@ -171,7 +171,16 @@ class Device(Thing): Actions are returned by descending ``created`` time. """ - return sorted(chain(self.actions_multiple, self.actions_one), key=lambda x: x.created) + actions_multiple = copy.copy(self.actions_multiple) + actions_one = copy.copy(self.actions_one) + + for ac in actions_multiple: + ac.real_created = ac.actions_device[0].created + + for ac in actions_one: + ac.real_created = ac.created + + return sorted(chain(actions_multiple, actions_one), key=lambda x: x.real_created) @property def problems(self): @@ -693,7 +702,13 @@ class Computer(Device): @property def actions(self) -> list: - return sorted(chain(super().actions, self.actions_parent)) + actions = copy.copy(super().actions) + actions_parent = copy.copy(self.actions_parent) + for ac in actions_parent: + ac.real_created = ac.created + + return sorted(chain(actions, actions_parent), key=lambda x: x.real_created) + # return sorted(chain(super().actions, self.actions_parent)) @property def ram_size(self) -> int: diff --git a/ereuse_devicehub/resources/documents/device_row.py b/ereuse_devicehub/resources/documents/device_row.py index de550d91..26eecd21 100644 --- a/ereuse_devicehub/resources/documents/device_row.py +++ b/ereuse_devicehub/resources/documents/device_row.py @@ -435,7 +435,7 @@ class ActionRow(OrderedDict): self['Action-User-LastOwner-Receiver'] = allocate['trade_receiver'] self['Action-Create-By'] = allocate['action_create_by'] self['Trade-Confirmed'] = allocate['trade_confirmed'] - self['Status-Supplier'] = allocate['status_supplier'] + self['Status-Created-By-Supplier-About-Reciber'] = allocate['status_supplier'] self['Status-Receiver'] = allocate['status_receiver'] self['Status Supplier – Created Date'] = allocate['status_supplier_created'] self['Status Receiver – Created Date'] = allocate['status_receiver_created'] diff --git a/tests/test_metrics.py b/tests/test_metrics.py index efdb26e4..6ad7a516 100644 --- a/tests/test_metrics.py +++ b/tests/test_metrics.py @@ -134,7 +134,7 @@ def test_metrics_action_status(user: UserClient, user2: UserClient): item='actions/', accept='text/csv', query=[('filter', {'type': ['Computer']})]) - head = 'DHID;Hid;Document-Name;Action-Type;Action-User-LastOwner-Supplier;Action-User-LastOwner-Receiver;Action-Create-By;Trade-Confirmed;Status-Supplier;Status-Receiver;Status Supplier – Created Date;Status Receiver – Created Date;Trade-Weight;Action-Create;Allocate-Start;Allocate-User-Code;Allocate-NumUsers;UsageTimeAllocate;Type;LiveCreate;UsageTimeHdd\n' + head = 'DHID;Hid;Document-Name;Action-Type;Action-User-LastOwner-Supplier;Action-User-LastOwner-Receiver;Action-Create-By;Trade-Confirmed;Status-Created-By-Supplier-About-Reciber;Status-Receiver;Status Supplier – Created Date;Status Receiver – Created Date;Trade-Weight;Action-Create;Allocate-Start;Allocate-User-Code;Allocate-NumUsers;UsageTimeAllocate;Type;LiveCreate;UsageTimeHdd\n' body = 'O48N2;desktop-lenovo-9644w8n-0169622-00:1a:6b:5e:7f:10;;Status;;foo@foo.com;Receiver;;;Use;;' assert head in csv_str assert body in csv_str @@ -156,6 +156,10 @@ def test_complet_metrics_with_trade(user: UserClient, user2: UserClient): res=Lot, item='{}/devices'.format(lot['id']), query=devices) + + action = {'type': ma.Refurbish.t, 'devices': [snap1['device']['id']]} + user.post(action, res=ma.Action) + request_post = { 'type': 'Trade', 'devices': [snap1['device']['id'], snap2['device']['id']], @@ -169,34 +173,44 @@ def test_complet_metrics_with_trade(user: UserClient, user2: UserClient): user.post(res=ma.Action, data=request_post) - action = {'type': ma.Refurbish.t, 'devices': [snap1['device']['id']]} + action = {'type': ma.Use.t, 'devices': [snap1['device']['id']]} action_use, _ = user.post(action, res=ma.Action) csv_str, _ = user.get(res=documents.DocumentDef.t, item='actions/', accept='text/csv', query=[('filter', {'type': ['Computer']})]) - body1_lenovo = 'O48N2;desktop-lenovo-9644w8n-0169622-00:1a:6b:5e:7f:10;;Trade;foo@foo.com;foo2@foo.com;Supplier;False;Refurbish;Use;' + body1_lenovo = 'O48N2;desktop-lenovo-9644w8n-0169622-00:1a:6b:5e:7f:10;;Trade;foo@foo.com;' + body1_lenovo += 'foo2@foo.com;Supplier;False;Use;;' body2_lenovo = ';;0;0;Trade;0;0\n' - body1_acer = 'J2MA2;laptop-acer-aohappy-lusea0d010038879a01601-00:26:c7:8e:cb:8c;;Trade;foo@foo.com;foo2@foo.com;Supplier;False;;Use;;;0;' + body1_acer = 'J2MA2;laptop-acer-aohappy-lusea0d010038879a01601-00:26:c7:8e:cb:8c;;Trade;' + body1_acer += 'foo@foo.com;foo2@foo.com;Supplier;False;;;;;0;' body2_acer = ';;0;0;Trade;0;4692.0\n' + # import pdb; pdb.set_trace() assert body1_lenovo in csv_str assert body2_lenovo in csv_str assert body1_acer in csv_str assert body2_acer in csv_str # User2 mark this device as Refurbish - action = {'type': ma.Refurbish.t, 'devices': [snap1['device']['id']]} + action = {'type': ma.Use.t, 'devices': [snap1['device']['id']]} action_use2, _ = user2.post(action, res=ma.Action) csv_str, _ = user.get(res=documents.DocumentDef.t, item='actions/', accept='text/csv', query=[('filter', {'type': ['Computer']})]) - body2_lenovo = ';Refurbish;0;0;Trade;0;0\n' - body2_acer = ';Refurbish;0;0;Trade;0;4692.0\n' + body1_lenovo = 'O48N2;desktop-lenovo-9644w8n-0169622-00:1a:6b:5e:7f:10;;Trade;foo@foo.com;' + body1_lenovo += 'foo2@foo.com;Supplier;False;Use;Use;' + body2_lenovo = ';;0;0;Trade;0;0\n' + body2_acer = ';;0;0;Trade;0;4692.0\n' + + assert body1_lenovo in csv_str + assert body2_lenovo in csv_str + assert body2_acer in csv_str + @pytest.mark.mvp