Ejemplo n.º 1
0
def merge_wallets():
    """ Merge wallet_2 into wallet_1, removing the balance from wallet_2 and adding it to wallet_1
        args[id_wallet_1, id_wallet_2]
    """
    w1 = db(db.wallet.id == request.args(0)).select(for_update=True).first()
    w2 = db(db.wallet.id == request.args(1)).select(for_update=True).first()

    if not (w1 and w2):
        session.info = T('Wallet not found')
        redirect(URL('index', args=w1.id))
    if w1.id == w2.id:
        session.info = T('The specified wallets are the same')
        redirect(URL('index', args=w1.id))

    _w, amount = wallet_utils.transaction(
        w2.balance, wallet_utils.CONCEPT_WALLET_MERGE,
        ref=w2.id, wallet=w1
    )
    wallet_utils.transaction(
        -amount, wallet_utils.CONCEPT_WALLET_MERGE,
        ref=w1.id, wallet=w2
    )

    session.info = T(
        'Transaction done, added $ %s to the specified wallet.'
    ) % (DQ(amount, True))

    redirect(URL('index', args=w1.id))
Ejemplo n.º 2
0
def cancel():
    """ args: [id_sale] """

    sale = db.sale(request.args(0))
    valid_sale(sale)
    # cannot cancel a defered sale, without credit note and all that stuff
    if sale.is_deferred or sale.id_bag.is_paid:
        raise HTTP(405)

    # return wallet payments
    wallet_opt = get_wallet_payment_opt()
    payments = db((db.payment.id_payment_opt == wallet_opt.id) & (db.payment.id_sale == sale.id)).select()
    for payment in payments:
        wallet_utils.transaction(
            payment.amount, wallet_utils.CONCEPT_UNDO_PAYMENT, ref=payment.id, wallet_code=payment.wallet_code
        )

    # delete the bag and all its bag items
    db(db.bag_item.id_bag == sale.id_bag.id).delete()
    db(db.bag.id == sale.id_bag.id).delete()
    db(db.payment.id_sale == sale.id).delete()
    db(db.sale_order.id_bag == sale.id_bag.id).delete()
    sale.delete_record()

    session.info = T("Sale canceled")
    redirect(URL("default", "index"))
Ejemplo n.º 3
0
def update_to_wallet_transactions():
    """ Add wallet transactions previously unavailable, this function will
        only consider the credit notes and payments, rewards points will not be
        added.
    """

    db = current.db

    db(
        (db.wallet_transaction.concept == wallet_utils.CONCEPT_CREDIT_NOTE) |
        (db.wallet_transaction.concept == wallet_utils.CONCEPT_PAYMENT)
    ).delete()

    for credit_note in db(db.credit_note.id > 0).iterselect():
        try:
            wallet_utils.transaction(
                credit_note.total, wallet_utils.CONCEPT_CREDIT_NOTE,
                ref=credit_note.id, wallet_code=credit_note.code,
                is_system_op=True, created_by=credit_note.created_by.id,
                now=credit_note.created_on
            )
        except:
            print "ERROR with credit note: ", credit_note.id


    payments = db(
        (db.payment.id_sale == db.sale.id) &
        (db.sale.is_done == True) &
        (db.payment.id_payment_opt == common_utils.get_wallet_payment_opt())
    ).iterselect(db.payment.ALL)
    for payment in payments:
        try:
            wallet_utils.transaction(
                -payment.amount, wallet_utils.CONCEPT_PAYMENT,
                ref=payment.id, wallet_code=payment.wallet_code,
                is_system_op=True, created_by=payment.created_by.id,
                now=payment.created_on
            )
        except Exception as e:
            print e
            print "ERROR with payment: ", payment.id

    for wallet in db(db.wallet.id > 0).iterselect():
        s = db.wallet_transaction.amount.sum()
        balance = db(
            db.wallet_transaction.id_wallet == wallet.id
        ).select(s).first()[s] or 0
        wallet.balance = balance
        wallet.update_record()
        client = db(
            (db.auth_user.id_wallet == wallet.id) &
            (db.auth_user.is_client == True)
        ).select().first()
        if not client:
            client = Storage(first_name="???", last_name='???')
        print "{first_name} {last_name}, {wallet_code}, {new_balance}".format(
            first_name=client.first_name, last_name=client.last_name,
            wallet_code=wallet.wallet_code, new_balance=wallet.balance
        )
Ejemplo n.º 4
0
def undo():
    """ Undo a sale, only available for 5 minutes after the sale creation
        args: [sale_id]
    """

    sale = (
        db((db.sale.id == request.args(0)) & (db.sale.created_by == auth.user.id) & (db.sale.is_done == True))
        .select()
        .first()
    )

    # undo timeout
    err = ""
    if not sale:
        err = T("Sale not found")
    if request.now > sale.modified_on + timedelta(minutes=5):
        err = T("Its too late to undo it")
    if err:
        session.info = err
        redirect(URL("default", "index"))

    # return wallet payments
    for payment in db(
        (db.payment.id_payment_opt == get_wallet_payment_opt()) & (db.payment.id_sale == sale.id)
    ).select():
        wallet_utils.transaction(
            payment.amount, wallet_utils.CONCEPT_UNDO_PAYMENT, ref=payment.id, wallet_code=payment.wallet_code
        )

    if sale.id_client:
        wallet_utils.transaction(
            sale.reward_points,
            wallet_utils.CONCEPT_UNDO_SALE_REWARD,
            ref=sale.id,
            wallet_id=sale.id_client.id_wallet.id,
        )

    undo_stock_removal(bag=sale.id_bag)

    session.info = T("Sale undone")
    redirect(URL("default", "index"))
Ejemplo n.º 5
0
def add_money():
    """ Add or remove money to a specified wallet """

    qty = 0
    try:
        qty = D(request.vars.amount)
    except:
        session.info = T('Invalid amount')
        redirect(URL('index', args=wallet.id))

    wallet, amount = wallet_utils.transaction(
        qty, wallet_utils.CONCEPT_ADMIN, wallet_id=request.args(0)
    )

    msg = '$ %s added to wallet.'
    if qty < 0:
        msg = '$ %s removed from wallet.'
    qty = DQ(abs(qty), True)

    session.info = T(msg) % qty
    redirect(URL('index', args=wallet.id))
Ejemplo n.º 6
0
def refund(sale, now, user, return_items=None, wallet_code=None):
    """
        Given a sale, performs a partial return if return items are specified or
        a full return otherwise, creating a credit note in the process,

        when is delivered is true, it means that items were removed from stock,
        so we have to reintegrate them, in other case just return the money
    """

    if return_items is None:
        return_items = []

    db = current.db
    session = current.session


    full_refund = False

    if not return_items:
        full_refund = True
        return_items = db(
            db.bag_item.id_bag == sale.id_bag.id
        ).iterselect(db.bag_item.id, db.bag_item.quantity)

    def return_items_iter():
        for return_item in return_items:
            if not full_refund:
                bag_item = return_item[0]
                qty = return_item[1]
            else:
                bag_item = db.bag_item(return_item.id)
                qty = return_item.quantity
            yield bag_item, qty


    id_new_credit_note = db.credit_note.insert(
        id_sale=sale.id, id_store=session.store,
        is_usable=True, # deprecated
        created_on=now, modified_on=now, created_by=user.id
    )

    subtotal = 0
    total = 0
    reward_points = 0

    if not sale.is_deferred:
        for bag_item, qty in return_items_iter():
            if bag_item.id_bag != sale.id_bag.id:
                continue

            # avoid returning more items than the ones sold
            qty = max(min(bag_item.quantity, qty), 0)
            if not qty:
                continue

            # add the credit note item
            credit_note_item = db.credit_note_item.insert(
                id_bag_item=bag_item.id, quantity=qty,
                id_credit_note=id_new_credit_note
            )
            dp = (1 - sale.discount_percentage / 100)
            sale_price = bag_item.sale_price * dp
            subtotal += sale_price * qty
            total += (sale_price + bag_item.sale_taxes * dp) * qty
            reward_points += (bag_item.reward_points or 0) * qty

            item_utils.reintegrate_bag_item(
                bag_item, qty, True, 'id_credit_note', id_new_credit_note
            )

    # since defered sales do not remove stocks we just have to return the money
    else:

        payments_sum = (db.payment.amount - db.payment.change_amount).sum()
        payments_total = db(
            (db.payment.id_sale == sale.id)
        ).select(payments_sum).first()[payments_sum] or 0

        subtotal = total = payments_total
        db(db.sale_order.id_sale == sale.id).delete()

        create_sale_event(sale, SALE_REFUNDED, now)
        sale.update_record()


    create_new_wallet = False
    wallet = None

    if sale.id_client:
        client = db.auth_user(sale.id_client)
        wallet = client.id_wallet
    # add funds to the specified wallet
    elif wallet_code:
        wallet = db(
            db.wallet.wallet_code == wallet_code
        ).select().first()
        # if the specified wallet was not found, create a new one
        create_new_wallet = not wallet
    else:
        create_new_wallet = True

    if create_new_wallet:
        wallet = db.wallet(common_utils.new_wallet())

    if sale.id_client:
        total -= reward_points

    # update credit note data
    credit_note = db.credit_note(id_new_credit_note)
    credit_note.subtotal = subtotal
    credit_note.total = total
    credit_note.code = wallet.wallet_code
    credit_note.id_wallet = wallet.id
    credit_note.update_record()

    # add wallet funds
    wallet_utils.transaction(
        total, wallet_utils.CONCEPT_CREDIT_NOTE, ref=credit_note.id,
        wallet_id=wallet.id
    )


    return credit_note
Ejemplo n.º 7
0
def modify_payment(sale, payment, payment_data, delete=False):
    """
        payment_data: {
            'amount',
            'wallet_code',
            'account'
        }

    """

    db = current.db
    T = current.T


    new_amount = payment_data.get('amount')

    # delete payment
    if delete:
        new_amount = 0

    # since calling this function, could cause modification to other payments, we have to store the modified payments, so the client can render the changes
    extra_updated_payments = []
    total, change, payments = get_payments_data(sale.id)
    new_total = total - change - (payment.amount - payment.change_amount) + new_amount
    if new_total > (sale.total - sale.discount):
        # when the payment does not allow change, cut the amount to the exact remaining
        if not payment.id_payment_opt.allow_change:
            new_amount -= new_total - (sale.total - sale.discount)
    else:
        remaining = (sale.total - sale.discount) - new_total
        # when the payment modification makes the new total lower than the sale total, we have to find all the payments with change > 0 (if any) and recalculate their amount and their change
        for other_payment in payments:
            if not other_payment.id_payment_opt.allow_change or other_payment.id == payment.id:
                continue
            if remaining == 0:
                break
            if other_payment.change_amount > 0:
                new_remaining = min(remaining, other_payment.change_amount)
                new_change = other_payment.change_amount - new_remaining
                remaining -= new_remaining
                other_payment.change_amount = new_change
                other_payment.update_record()
                extra_updated_payments.append(other_payment)

    change = max(0, new_total - (sale.total - sale.discount))
    account = payment_data.get('account')
    wallet_code = payment_data.get('wallet_code')
    # fix payment info
    if not payment.id_payment_opt.allow_change:
        change = 0
    if not payment.id_payment_opt.requires_account:
         account = None
    if not common_utils.is_wallet(payment.id_payment_opt):
         wallet_code = None
    # in this case the payment is wallet
    else:
        if payment.wallet_code:
            # return wallet funds, if the wallet payment is removed or the wallet code is changed
            if delete or wallet_code != payment.wallet_code:
                wallet_utils.transaction(
                    payment.amount, wallet_utils.UNDO_PAYMENT, ref=payment.id,
                    wallet_code=payment.wallet_code
                )

        # only accept the first wallet code specified
        if wallet_code != payment.wallet_code:
            try:
                # new_amount = max(0, min(wallet.balance, (sale.total - sale.discount) - new_total))

                rem_q = (sale.total - sale.discount) - new_total
                if rem_q >= 0:
                    _wallet, _amount = wallet_utils.transaction(
                        -rem_q, wallet_utils.CONCEPT_PAYMENT, ref=payment.id,
                        wallet_code=wallet_code
                    )
                    new_amount = abs(_amount)
            except:
                wallet_code = None
                new_amount = 0

    payment.amount = new_amount
    payment.change_amount = change
    payment.account = account
    payment.wallet_code = wallet_code
    payment.update_record()
    if not delete:
        extra_updated_payments.append(payment)
    elif payment.is_updatable:
        payment.delete_record()

    # get the total payments data
    payments_data = get_payments_data(sale.id)
    # amount - change_amount
    payments_total = payments_data[0] - payments_data[1]

    data = dict(
        payments_total=payments_total,
        updated_payments=extra_updated_payments
    )

    return data
Ejemplo n.º 8
0
def complete(sale):
    """ """

    db = current.db
    T = current.T

    payments = db(db.payment.id_sale == sale.id).select()
    verify_payments(payments, sale)


    affinity_list = []

    # verify items stock
    bag_items = db(db.bag_item.id_bag == sale.id_bag.id).iterselect()
    requires_serials = False  #TODO implement serial numbers
    for bag_item in bag_items:
        # since created bags does not remove stock, there could be more bag_items than stock items, so we need to check if theres enough stock to satisfy this sale, and if there is not, then we need to notify the seller or user
        stock_qty = item_utils.item_stock_qty(
            bag_item.id_item, sale.id_store.id, bag_item.id_bag.id
        )
        # this has to be done since using item_stock_qty with a bag specified will
        # consider bagged items as missing, thus we have to add them back,
        # item_stock_qty must be called with a bag because that way it will
        # count bundled items with the specified item
        stock_qty += bag_item.quantity
        # Cannot deliver a sale with out of stock items
        if stock_qty < bag_item.quantity:
            raise CP_OutOfStockError(
                T("You can't create a counter sale with out of stock items")
            )
        requires_serials |= bag_item.id_item.has_serial_number or False

        affinity_list.append(bag_item.id_item.id)


    # kinda crappy
    first = affinity_list[0]
    for i, id in enumerate(affinity_list):
        if i == len(affinity_list) - 1:
            continue
        for j, other_id in enumerate(affinity_list):
            if j <= i:
                continue
            first, second = (id, other_id) if id < other_id else (other_id, id)

            affinity = db(
                (db.item_affinity.id_item1 == first) &
                (db.item_affinity.id_item2 == second)
            ).select().first()
            if not affinity:
                db.item_affinity.insert(
                    id_item1=first, id_item2=second, affinity=1
                )
            else:
                affinity.affinity += 1
                affinity.update_record()


    # for every payment with a payment option with credit days, set payment to not settled
    for payment in payments:
        if payment.id_payment_opt.credit_days > 0:
            payment.epd = date(request.now.year, request.now.month, request.now.day) + timedelta(days=payment.id_payment_opt.credit_days)
            payment.is_settled = False
            payment.update_record()

    store = db(db.store.id == sale.id_store.id).select(for_update=True).first()
    sale.consecutive = store.consecutive
    sale.is_done = True
    create_sale_event(sale, SALE_PAID)
    sale.update_record()
    store.consecutive += 1
    store.update_record()

    # if defered sale, remove sale order
    db(db.sale_order.id_sale == sale.id).delete()

    # add reward points to the client's wallet, we assume that the user has a wallet
    if sale.id_client and sale.id_client.id_wallet:
        wallet_utils.transaction(
            sale.reward_points, wallet_utils.CONCEPT_SALE_REWARD, ref=sale.id,
            wallet_id=sale.id_client.id_wallet.id
        )