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))
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"))
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 )
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"))
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))
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
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
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 )