Example #1
0
    def _create_item_transfers(self, sum_item_amounts, error_items=[]):
        """
        Create ItemTransfers to alter inventory amounts
        """
        transfers = []
        for item, amount in sum_item_amounts.items():
            try:
                amount_symbol = '{:~}'.format(amount['amount']).split(' ')[1]
                measure = AmountMeasure.objects.get(symbol=amount_symbol)
                amount['amount'] = amount['amount'].magnitude
            except:
                measure = AmountMeasure.objects.get(symbol='item')

            # Look up to see if there is a matching ItemTransfer already and then
            # use this instead
            try:
                transfer = ItemTransfer.objects.get(
                    item=item,
                    barcode=amount.get('barcode', None),
                    coordinates=amount.get('coordinates', None))
                # At this point need to subtract amount from available in existing
                # transfer! Need to mark in some way not completed though so can put
                # back if the trasnfer is cancelled
                transfer.amount_to_take = amount['amount']
            except (ObjectDoesNotExist, MultipleObjectsReturned):
                transfer = ItemTransfer(item=item,
                                        barcode=amount.get('barcode', None),
                                        coordinates=amount.get(
                                            'coordinates', None),
                                        amount_taken=amount['amount'],
                                        amount_measure=measure)
            if item in error_items:
                transfer.is_valid = False
            transfers.append(transfer)
        return transfers
Example #2
0
    def _create_item_transfers(self, sum_item_amounts, error_items=[]):
        """
        Create ItemTransfers to alter inventory amounts
        """
        transfers = []
        for item, amount in sum_item_amounts.items():
            try:
                amount_symbol = '{:~}'.format(amount['amount']).split(' ')[1]
                measure = AmountMeasure.objects.get(symbol=amount_symbol)
                amount['amount'] = amount['amount'].magnitude
            except:
                measure = AmountMeasure.objects.get(symbol='item')

            # Look up to see if there is a matching ItemTransfer already and then
            # use this instead
            try:
                transfer = ItemTransfer.objects.get(item=item,
                                                    barcode=amount.get('barcode', None),
                                                    coordinates=amount.get('coordinates', None))
                # At this point need to subtract amount from available in existing
                # transfer! Need to mark in some way not completed though so can put
                # back if the trasnfer is cancelled
                transfer.amount_to_take = amount['amount']
            except (ObjectDoesNotExist, MultipleObjectsReturned):
                transfer = ItemTransfer(
                    item=item,
                    barcode=amount.get('barcode', None),
                    coordinates=amount.get('coordinates', None),
                    amount_taken=amount['amount'],
                    amount_measure=measure)
            if item in error_items:
                transfer.is_valid = False
            transfers.append(transfer)
        return transfers
Example #3
0
    def _update_products(self, task_id, products, activeworkflow, request, retry=False):
        """
        Utility function to update products at end of task.
        """
        task_list = activeworkflow.workflow.order.split(',')

        for p in products:
            data_entries = DataEntry.objects.filter(run_identifier=p.run_identifier)
            created_from_items = []
            for entry in data_entries:
                created_from_items.append(entry.item)
                if retry:
                    entry.state = 'failed'
                else:
                    entry.state = 'succeeded'
                entry.save()

            # Make outputs
            # This requires an ItemTransfer to also be created
            for output in data_entries[0].data['output_fields']:
                item_type = ItemType.objects.get(name=output['lookup_type'])
                measure = AmountMeasure.objects.get(symbol=output['measure'])
                location = Location.objects.get(name='Lab')
                output_item = Item(
                    name='{} {}'.format(p.product_identifier(), output['lookup_type']),
                    identifier='{}OPT{}'.format(p.product_identifier(), datetime.datetime.now()),
                    item_type=item_type,
                    in_inventory=True,
                    amount_available=float(output['amount']),
                    amount_measure=measure,
                    location=location,
                    added_by=request.user,
                )
                output_item.save()

                output_item.created_from.add(*created_from_items)

                # We want to add it to history but say it is
                # and addition rather than a subtraction
                tsf = ItemTransfer(
                    item=output_item,
                    amount_taken=float(output['amount']),
                    amount_measure=measure,
                    run_identifier=p.run_identifier,
                    is_addition=True,
                )
                tsf.save()

            p.run_identifier = ''
            p.task_in_progress = False

            p.save()
            if not retry:
                if p.current_task + 1 < len(task_list):
                    p.current_task += 1
                    p.save()
                else:
                    p.delete()
Example #4
0
    def start_task(self, request, pk=None):
        """
        Start a task creating data entry and removing from inventory.

        Takes in task data, serializes for use in the data entry
        and takes the relevant amounts from the inventory.

        Can also act in preview mode and just return the info
        required for the task.

        ### query_params

        - _is_preview_: Don't actually start the task, just return
                        the task information.

        ### query data

        - _task_ (**required**): A JSON representation of the task to be serialized
        - _products_ (**required**): A list of product ID's that are a part of the task
        - _input_files_: A list of file contents to be used as input for the task
        """
        task_data = json.loads(self.request.data.get('task', None))
        task_serializer = TaskValuesSerializer(data=task_data)

        product_data = json.loads(self.request.data.get('products', []))
        file_data = self.request.data.getlist('input_files', [])

        is_preview = request.query_params.get('is_preview', False)

        active_workflow = self.get_object()
        uuid = uuid4()
        ureg = UnitRegistry()

        # This takes each input file and merges all rows with
        # matching identifiers into a single dict.
        # This will overwrite duplicated entries with the last
        # file processed, which is why values should only be
        # defined in a single file.
        input_file_data = {}
        for f in file_data:
            try:
                ft = FileTemplate.objects.get(name=f.name)
            except:
                pass
            else:
                parsed_file = ft.read(TextIOWrapper(f.file, encoding=f.charset))
                if parsed_file:
                    for key, row in parsed_file.items():
                        if key not in input_file_data:
                            input_file_data[key] = {}
                        input_file_data[key].update(row)
                else:
                    return Response(
                        {'message':
                         'Input file "{}" has incorrect headers/format'.format(f.name)},
                        status=400)

        products = Product.objects.filter(id__in=[p['product'] for p in product_data])

        if task_serializer.is_valid(raise_exception=True) and len(products) > 0:
            # A dict of items and amounts required from inputs
            required_amounts = {}
            input_items = []
            fields_from_file = []
            data_items = {}

            # Look for items for use as inputs to the task and add the amounts that
            # are required to the list
            for prd in products:
                items = Item.objects.filter(
                    products__id=prd.id, item_type__name=task_data['product_input'])
                if items.count() == 0:
                    return Response(
                        {'message':
                         '{}: {} does not contain any items to use as input'.format(
                             prd.product_identifier, prd.name)}, status=400)
                input_items.extend(items)

                # Add items from Product's to the list
                for itm in items:
                    identifier = frozenset([prd.product_identifier, itm.identifier])
                    data_items[identifier] = {
                        'data': task_serializer.data,
                        'product': prd,
                        'item': itm
                    }
                    self.update_item_amounts(itm.identifier,
                                             task_serializer.data['product_input_amount'],
                                             task_serializer.data['product_input_measure'],
                                             required_amounts, ureg)

            # Look through any input files for matching identifiers and add their amounts
            # to the list
            for key, row in input_file_data.items():
                for header, value in row.items():
                    # If the header has identifier/amount combo then
                    # is aplicable to an item in the product
                    # if not, it is applicable TO the product
                    exists = False
                    matched_headers = []
                    label = header.rstrip(' identifier').rstrip(' amount')
                    # Look for _amount + _identifier combos and math them up
                    if header.endswith('identifier') and header not in matched_headers:
                        identifier = value
                        amount_field_name = '{} amount'.format(label)
                        if amount_field_name in row.keys():
                            amount = row[amount_field_name]
                            matched_headers.extend([header, amount_field_name])
                            exists = True
                    elif header.endswith('amount') and header not in matched_headers:
                        identifier_field_name = '{} identifier'.format(label)
                        if identifier_field_name in row.keys():
                            identifier = row[identifier_field_name]
                            amount = value
                            matched_headers.extend([header, identifier_field_name])
                            exists = True
                        elif identifier_field_name not in row.keys() and header in row.keys():
                            # This value is directly for an amount on the item
                            # rather than another input
                            identifier = key[1]
                            amount = value
                            label = header.replace(' ', '_')
                            measure_label = label.replace('amount', 'measure')
                            matched_headers.append(header)
                            exists = True
                    if exists:

                        # Look for the matching pairs
                        try:
                            data_entry_field = next((fld for fld in data_items[key]['data'][
                                                    'input_fields'] if fld['label'] == label))
                            data_entry_field['amount'] = amount
                            measure = data_entry_field['measure']
                        except StopIteration:
                            data_items[key]['data'][label] = amount
                            measure = data_items[key]['data'][measure_label]

                        # Try to get the item from the inventory and add to list
                        try:
                            ti = Item.objects.get(identifier=identifier)
                        except ObjectDoesNotExist:
                            return Response(
                                {'message':
                                 '{} does not exist in the inventory'.format(identifier)},
                                status=400)
                        if ti not in input_items:
                            input_items.append(ti)
                        self.update_item_amounts(ti.identifier, amount,
                                                 measure, required_amounts, ureg)

                        # Record this field has data from a file and does not need to
                        # be processed again
                        fields_from_file.append(label)

            # Now just read through the fields left and fill in any more details
            for itm in task_serializer.data['input_fields']:
                # If we already have set the value in a file we don't want to overwrite it
                if itm['label'] not in fields_from_file and itm['from_input_file'] is False:
                    try:
                        ti = Item.objects.get(identifier=itm['inventory_identifier'])
                    except ObjectDoesNotExist:
                        return Response({'message': '{} does not exist in the inventory'.format(
                            itm['inventory_identifier'])}, status=400)
                    if ti not in input_items:
                        input_items.append(ti)
                    self.update_item_amounts(ti.identifier, itm['amount'], itm[
                                             'measure'], required_amounts, ureg)
                # But if it's suposed to have been from a file and is still here, return an error
                elif itm['label'] not in fields_from_file and itm['from_input_file'] is True:
                    return Response({'message':
                                     'The value for field "{}" is not present in file'
                                     .format(itm['label'])}, status=400)

            # input checks
            preview_data = []
            valid_amounts = True
            amount_error_messages = []
            for item in input_items:
                # Check enough of each item is avilable.
                # First, translate to a known amount (if possible) or just be presented as
                # a raw number
                try:
                    available = item.amount_available * ureg(item.amount_measure.symbol)
                except UndefinedUnitError:
                    available = item.amount_available
                required = required_amounts[item.identifier]
                # The actual check
                if available < required:
                    missing = (available - required_amounts[item.identifier]) * -1
                    valid_amounts = False
                    amount_error_messages.append(
                        'Inventory item {} ({}) is short of amount by {}'.format(
                            item.identifier, item.name, missing))

                # If it's a preview then just serialize the amount, don't actually
                # do anything with it
                if is_preview:
                    amount = required_amounts[item.identifier]
                    amount_symbol = '{:~}'.format(amount).split(' ')[1]
                    item_transfer = ItemTransfer(
                        item=item,
                        amount_taken=amount.magnitude,
                        amount_measure=AmountMeasure.objects.get(symbol=amount_symbol)
                    )
                    sit = ItemTransferPreviewSerializer(item_transfer)
                    preview_data.append(sit.data)

            if not is_preview:
                # If valid, continue to create item transfers
                if valid_amounts:
                    for item in input_items:
                        # Create item transfers
                        amount = required_amounts[item.identifier]
                        amount_symbol = '{:~}'.format(amount).split(' ')[1]
                        item_transfer = ItemTransfer(
                            item=item,
                            run_identifier=uuid,
                            amount_taken=amount.magnitude,
                            amount_measure=AmountMeasure.objects.get(symbol=amount_symbol)
                        )
                        item_transfer.save()

                        # Alter amounts in DB to corrospond to the amount taken
                        new_amount = available - required_amounts[item.identifier]
                        item.amount_available = new_amount.magnitude
                        item.save()
                else:
                    return Response({'message': '\n'.join(amount_error_messages)}, status=400)

                # make data entries to record the values of the task
                task = TaskTemplate.objects.get(pk=task_data['id'])
                for key, data in data_items.items():
                    entry = DataEntry(
                        run_identifier=uuid,
                        product=data['product'],
                        item=data['item'],
                        created_by=self.request.user,
                        state='active',
                        data=data['data'],
                        workflow=active_workflow.workflow,
                        task=task
                    )
                    entry.save()

                    # update products that task started
                    active = WorkflowProduct.objects.get(product=data['product'])
                    active.task_in_progress = True
                    active.run_identifier = uuid
                    active.save()

                return Response({'message': 'Task started'})
            return Response(preview_data)
        return Response({'message': 'You must provide task data and products'}, status=400)