diff --git a/CHANGELOG.md b/CHANGELOG.md index c6392e99..9bcbc503 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ ml). ## master ## testing +- [added] #303 Add export Lots. +- [added] #303 Add export relating lots with devices. +- [added] #303 To do possible add and remove one device in one lot transfer. ## [2.2.0 rc1] - 2022-06-07 - [added] #212 Server side render parser Workbench Snapshots. diff --git a/ereuse_devicehub/inventory/models.py b/ereuse_devicehub/inventory/models.py index 69414c10..45d25157 100644 --- a/ereuse_devicehub/inventory/models.py +++ b/ereuse_devicehub/inventory/models.py @@ -1,6 +1,7 @@ from uuid import uuid4 from citext import CIText +from flask import g from sqlalchemy import Column, Integer from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import backref, relationship @@ -43,6 +44,15 @@ class Transfer(Thing): return False + def type_transfer(self): + if self.user_from == g.user: + return 'Outgoing' + + if self.user_to == g.user: + return 'Incoming' + + return 'Temporary' + class DeliveryNote(Thing): id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) diff --git a/ereuse_devicehub/inventory/views.py b/ereuse_devicehub/inventory/views.py index b7a8298b..83b78343 100644 --- a/ereuse_devicehub/inventory/views.py +++ b/ereuse_devicehub/inventory/views.py @@ -33,6 +33,7 @@ from ereuse_devicehub.parser.models import SnapshotsLog from ereuse_devicehub.resources.action.models import Trade from ereuse_devicehub.resources.device.models import Computer, DataStorage, Device from ereuse_devicehub.resources.documents.device_row import ActionRow, DeviceRow +from ereuse_devicehub.resources.enums import SnapshotSoftware from ereuse_devicehub.resources.hash_reports import insert_hash from ereuse_devicehub.resources.lot.models import Lot from ereuse_devicehub.resources.tag.model import Tag @@ -476,6 +477,8 @@ class ExportsView(View): 'metrics': self.metrics, 'devices': self.devices_list, 'certificates': self.erasure, + 'lots': self.lots_export, + 'devices_lots': self.devices_lots_export, } if export_id not in export_ids: @@ -577,6 +580,111 @@ class ExportsView(View): } return flask.render_template('inventory/erasure.html', **params) + def lots_export(self): + data = StringIO() + cw = csv.writer(data, delimiter=';', lineterminator="\n", quotechar='"') + + cw.writerow( + [ + 'Lot Id', + 'Lot Name', + 'Lot Type', + 'Transfer Status', + 'Transfer Code', + 'Transfer Date', + 'Transfer Creation Date', + 'Transfer Update Date', + 'Transfer Description', + 'Devices Number', + 'Devices Snapshots', + 'Devices Placeholders', + 'Delivery Note Number', + 'Delivery Note Date', + 'Delivery Note Units', + 'Delivery Note Weight', + 'Receiver Note Number', + 'Receiver Note Date', + 'Receiver Note Units', + 'Receiver Note Weight', + ] + ) + + for lot in Lot.query.filter_by(owner=g.user): + delivery_note = lot.transfer and lot.transfer.delivery_note or '' + receiver_note = lot.transfer and lot.transfer.receiver_note or '' + wb_devs = 0 + placeholders = 0 + + for dev in lot.devices: + snapshots = [e for e in dev.actions if e.type == 'Snapshot'] + if not snapshots or snapshots[-1].software not in [ + SnapshotSoftware.Workbench + ]: + placeholders += 1 + elif snapshots[-1].software in [SnapshotSoftware.Workbench]: + wb_devs += 1 + + row = [ + lot.id, + lot.name, + lot.type_transfer(), + lot.transfer and (lot.transfer.closed and 'Closed' or 'Open') or '', + lot.transfer and lot.transfer.code or '', + lot.transfer and lot.transfer.date or '', + lot.transfer and lot.transfer.created or '', + lot.transfer and lot.transfer.updated or '', + lot.transfer and lot.transfer.description or '', + len(lot.devices), + wb_devs, + placeholders, + delivery_note and delivery_note.number or '', + delivery_note and delivery_note.date or '', + delivery_note and delivery_note.units or '', + delivery_note and delivery_note.weight or '', + receiver_note and receiver_note.number or '', + receiver_note and receiver_note.date or '', + receiver_note and receiver_note.units or '', + receiver_note and receiver_note.weight or '', + ] + cw.writerow(row) + + return self.response_csv(data, "lots_export.csv") + + def devices_lots_export(self): + data = StringIO() + cw = csv.writer(data, delimiter=';', lineterminator="\n", quotechar='"') + head = [ + 'DHID', + 'Lot Id', + 'Lot Name', + 'Lot Type', + 'Transfer Status', + 'Transfer Code', + 'Transfer Date', + 'Transfer Creation Date', + 'Transfer Update Date', + ] + cw.writerow(head) + + for dev in self.find_devices(): + for lot in dev.lots: + row = [ + dev.devicehub_id, + lot.id, + lot.name, + lot.type_transfer(), + lot.transfer and (lot.transfer.closed and 'Closed' or 'Open') or '', + lot.transfer and lot.transfer.code or '', + lot.transfer and lot.transfer.date or '', + lot.transfer and lot.transfer.created or '', + lot.transfer and lot.transfer.updated or '', + ] + cw.writerow(row) + + return self.response_csv( + data, "Devices_Incoming_and_Outgoing_Lots_Spreadsheet.csv" + ) + class SnapshotListView(GenericMixin): template_name = 'inventory/snapshots_list.html' diff --git a/ereuse_devicehub/resources/lot/models.py b/ereuse_devicehub/resources/lot/models.py index ef6577a8..c2309224 100644 --- a/ereuse_devicehub/resources/lot/models.py +++ b/ereuse_devicehub/resources/lot/models.py @@ -151,6 +151,16 @@ class Lot(Thing): """Gets the lots that are not under any other lot.""" return cls.query.join(cls.paths).filter(db.func.nlevel(Path.path) == 1) + def type_transfer(self): + # Used in reports lots_export.csv + if not self.transfer: + return 'Temporary' + if self.transfer.user_from == g.user: + return 'Outgoing' + if self.transfer.user_to == g.user: + return 'Incoming' + return '' + def add_children(self, *children): """Add children lots to this lot. diff --git a/ereuse_devicehub/resources/lot/views.py b/ereuse_devicehub/resources/lot/views.py index 117d3209..0f0752ab 100644 --- a/ereuse_devicehub/resources/lot/views.py +++ b/ereuse_devicehub/resources/lot/views.py @@ -111,7 +111,7 @@ class LotView(View): query = query.filter(Lot.name.ilike(args['search'] + '%')) lots = query.paginate(per_page=6 if args['search'] else query.count()) return things_response( - self.schema.dump(lots.items, many=True, nested=2), + self.get_lots_dump(lots), lots.page, lots.per_page, lots.total, @@ -120,6 +120,17 @@ class LotView(View): ) return jsonify(ret) + def get_lots_dump(self, lots): + lots_dump = self.schema.dump(lots.items, many=True, nested=2) + for lot in lots.items: + if not lot.transfer: + continue + for _lot in lots_dump: + if _lot['id'] == str(lot.id): + _lot['transfer'] = lot.type_transfer() + break + return lots_dump + def visibility_filter(self, query): query = ( query.outerjoin(Trade) @@ -141,7 +152,9 @@ class LotView(View): # temporary if lot_type == "temporary": - return query.filter(Lot.trade == None).filter(Lot.transfer == None) + return query.filter(Lot.trade == None).filter( + or_(Lot.transfer == None, Transfer.date == None) + ) if lot_type == "incoming": return query.filter( diff --git a/ereuse_devicehub/static/js/main_inventory.build.js b/ereuse_devicehub/static/js/main_inventory.build.js index 16bb720a..1e1b21fd 100644 --- a/ereuse_devicehub/static/js/main_inventory.build.js +++ b/ereuse_devicehub/static/js/main_inventory.build.js @@ -681,17 +681,32 @@ async function processSelectedDevices() { return lot; }); - let lotsList = []; - lotsList.push(lots.filter(lot => lot.state == "true").sort((a, b) => a.name.localeCompare(b.name))); - lotsList.push(lots.filter(lot => lot.state == "indetermined").sort((a, b) => a.name.localeCompare(b.name))); - lotsList.push(lots.filter(lot => lot.state == "false").sort((a, b) => a.name.localeCompare(b.name))); - lotsList = lotsList.flat(); // flat array listHTML.html(""); - lotsList.forEach(lot => templateLot(lot, selectedDevices, listHTML, actions)); + const lot_temporary = lots.filter(lot => !lot.transfer); + appendMenu(lot_temporary, listHTML, templateLot, selectedDevices, actions, "Temporary"); + + const lot_incoming = lots.filter(lot => lot.transfer && lot.transfer == "Incoming"); + appendMenu(lot_incoming, listHTML, templateLot, selectedDevices, actions, "Incoming"); + + const lot_outgoing = lots.filter(lot => lot.transfer && lot.transfer == "Outgoing"); + appendMenu(lot_outgoing, listHTML, templateLot, selectedDevices, actions, "Outgoing"); + lotsSearcher.enable(); + } catch (error) { console.log(error); listHTML.html("