def send_req():
    """
        Function to send items for a Request.
        - i.e. copy data from a req into a send
        arg: req_id
        vars: site_id

        @ToDo: Rewrite as S3Method
                - means that permissions are better-controlled
    """

    req_id = request.args[0]
    site_id = request.vars.get("site_id", None)
    table = s3db.req_req
    r_req = db(table.id == req_id).select(table.req_ref,
                                          table.requester_id,
                                          table.site_id,
                                          limitby=(0, 1)).first()

    # User must have permissions over facility which is sending
    (prefix, resourcename, id) = s3db.get_instance(db.org_site, site_id)
    if not site_id or not auth.s3_has_permission("update",
                                                 "%s_%s" % (prefix,
                                                            resourcename),
                                                 record_id=id):
        session.error = T("You do not have permission to send this shipment.")
        redirect(URL(c="req", f="req",
                     args = [req_id]))

    ritable = s3db.req_req_item
    iitable = s3db.inv_inv_item
    sendtable = s3db.inv_send
    tracktable = s3db.inv_track_item
    siptable = s3db.supply_item_pack

    # Get the items for this request that have not been fulfilled or in transit
    sip_id_field = siptable.id
    sip_quantity_field = siptable.quantity
    query = (ritable.req_id == req_id) & \
            (ritable.quantity_transit < ritable.quantity) & \
            (ritable.deleted == False) & \
            (ritable.item_pack_id == sip_id_field)
    req_items = db(query).select(ritable.id,
                                 ritable.quantity,
                                 ritable.quantity_transit,
                                 ritable.quantity_fulfil,
                                 ritable.item_id,
                                 sip_quantity_field
                                 )

    if not req_items:
        # Can't use site_name as gluon/languages.py def translate has a str() which can give a Unicode error
        #site_name = s3db.org_site_represent(site_id, show_link=False)
        session.warning = \
            T("This request has no items outstanding!")

        # Redirect to view the list of items in the Request
        redirect(URL(c="req", f="req",
                     args=[req_id, "req_item"])
                 )

    # Create a new send record
    code = s3db.supply_get_shipping_code("WB",
                                         site_id,
                                         s3db.inv_send.send_ref
                                         )
    send_id = sendtable.insert(send_ref = code,
                               req_ref = r_req.req_ref,
                               sender_id = auth.s3_logged_in_person(),
                               site_id = site_id,
                               date = request.utcnow,
                               recipient_id = r_req.requester_id,
                               to_site_id = r_req.site_id,
                               status = s3db.inv_ship_status["IN_PROCESS"],
                               )

    # Loop through each request item and find matches in the site inventory
    # - don't match items which are expired or in bad condition
    IN_PROCESS = s3db.inv_tracking_status["IN_PROCESS"]
    insert = tracktable.insert
    inv_remove = s3db.inv_remove
    ii_item_id_field = iitable.item_id
    ii_quantity_field = iitable.quantity
    ii_expiry_field = iitable.expiry_date
    ii_purchase_field = iitable.purchase_date
    iifields = [iitable.id,
                ii_item_id_field,
                ii_quantity_field,
                iitable.item_pack_id,
                iitable.pack_value,
                iitable.currency,
                ii_expiry_field,
                ii_purchase_field,
                iitable.bin,
                iitable.owner_org_id,
                iitable.supply_org_id,
                sip_quantity_field,
                iitable.item_source_no,
                ]
    bquery = (ii_quantity_field > 0) & \
             (iitable.site_id == site_id) & \
             (iitable.deleted == False) & \
             (iitable.item_pack_id == sip_id_field) & \
             ((iitable.expiry_date >= request.now) | ((iitable.expiry_date == None))) & \
             (iitable.status == 0)
    orderby = ii_expiry_field | ii_purchase_field

    no_match = True

    for ritem in req_items:
        rim = ritem.req_req_item
        rim_id = rim.id
        query = bquery & \
                (ii_item_id_field == rim.item_id)
        inv_items = db(query).select(*iifields,
                                     orderby=orderby)

        if len(inv_items) == 0:
            continue
        no_match = False
        one_match = len(inv_items) == 1
        # Get the Quantity Needed
        quantity_shipped = max(rim.quantity_transit, rim.quantity_fulfil)
        quantity_needed = (rim.quantity - quantity_shipped) * ritem.supply_item_pack.quantity
        # Insert the track item records
        # If there is more than one item match then we select the stock with the oldest expiry date first
        # then the oldest purchase date first
        # then a complete batch, if-possible
        iids = []
        append = iids.append
        for item in inv_items:
            if not quantity_needed:
                break
            iitem = item.inv_inv_item
            if one_match:
                # Remove this total from the warehouse stock
                send_item_quantity = inv_remove(iitem, quantity_needed)
                quantity_needed -= send_item_quantity
                append(iitem.id)
            else:
                quantity_available = iitem.quantity * item.supply_item_pack.quantity
                if iitem.expiry_date:
                    # We take first from the oldest expiry date
                    send_item_quantity = min(quantity_needed, quantity_available)
                    # Remove this total from the warehouse stock
                    send_item_quantity = inv_remove(iitem, send_item_quantity)
                    quantity_needed -= send_item_quantity
                    append(iitem.id)
                elif iitem.purchase_date:
                    # We take first from the oldest purchase date for non-expiring stock
                    send_item_quantity = min(quantity_needed, quantity_available)
                    # Remove this total from the warehouse stock
                    send_item_quantity = inv_remove(iitem, send_item_quantity)
                    quantity_needed -= send_item_quantity
                    append(iitem.id)
                elif quantity_available <= quantity_needed:
                    # Assign a complete batch together if possible
                    # Remove this total from the warehouse stock
                    send_item_quantity = inv_remove(iitem, quantity_needed)
                    quantity_needed = 0
                    append(iitem.id)
                else:
                    # Try again on the second loop, if-necessary
                    continue

            insert(send_id = send_id,
                   send_inv_item_id = iitem.id,
                   item_id = iitem.item_id,
                   req_item_id = rim_id,
                   item_pack_id = iitem.item_pack_id,
                   quantity = send_item_quantity,
                   recv_quantity = send_item_quantity,
                   status = IN_PROCESS,
                   pack_value = iitem.pack_value,
                   currency = iitem.currency,
                   bin = iitem.bin,
                   expiry_date = iitem.expiry_date,
                   owner_org_id = iitem.owner_org_id,
                   supply_org_id = iitem.supply_org_id,
                   item_source_no = iitem.item_source_no,
                   #comments = comment,
                   )
        # 2nd pass
        for item in inv_items:
            if not quantity_needed:
                break
            iitem = item.inv_inv_item
            if iitem.id in iids:
                continue
            # We have no way to know which stock we should take 1st so show all with quantity 0 & let the user decide
            send_item_quantity = 0
            insert(send_id = send_id,
                   send_inv_item_id = iitem.id,
                   item_id = iitem.item_id,
                   req_item_id = rim_id,
                   item_pack_id = iitem.item_pack_id,
                   quantity = send_item_quantity,
                   status = IN_PROCESS,
                   pack_value = iitem.pack_value,
                   currency = iitem.currency,
                   bin = iitem.bin,
                   expiry_date = iitem.expiry_date,
                   owner_org_id = iitem.owner_org_id,
                   supply_org_id = iitem.supply_org_id,
                   item_source_no = iitem.item_source_no,
                   #comments = comment,
                   )

    if no_match:
        # Can't use %(site_name)s as gluon/languages.py def translate has a str() which can give a Unicode error
        #site_name = s3db.org_site_represent(site_id, show_link=False)
        session.warning = \
            T("This site has no items exactly matching this request. There may still be other items in stock which can fulfill this request!")

    # Redirect to view the list of items in the Send
    redirect(URL(c="inv", f="send",
                 args=[send_id, "track_item"])
             )

# =============================================================================
dataset/ETHPy150Open sahana/eden/controllers/req.py/send_req/swapped operands of `<=`