Source code for stoqlib.gui.editors.tilleditor

# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4

##
## Copyright (C) 2005-2007 Async Open Source <http://www.async.com.br>
## All rights reserved
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU Lesser General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU Lesser General Public License for more details.
##
## You should have received a copy of the GNU Lesser General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., or visit: http://www.gnu.org/.
##
## Author(s): Stoq Team <stoq-devel@async.com.br>
##
""" Editors implementation for open/close operation on till operation"""

from datetime import timedelta

from kiwi.currency import currency
from kiwi.datatypes import ValidationError
from kiwi.python import Settable
from kiwi.ui.objectlist import Column, ColoredColumn, SummaryLabel

from stoqdrivers.exceptions import DriverError

from stoqlib.api import api
from stoqlib.database.expr import TransactionTimestamp
from stoqlib.domain.account import AccountTransaction
from stoqlib.domain.events import (TillOpenEvent, TillCloseEvent,
                                   TillAddTillEntryEvent,
                                   TillAddCashEvent, TillRemoveCashEvent)
from stoqlib.domain.person import Employee
from stoqlib.domain.till import Till
from stoqlib.exceptions import DeviceError, TillError
from stoqlib.gui.editors.baseeditor import BaseEditor
from stoqlib.gui.slaves.tillslave import RemoveCashSlave, BaseCashSlave
from stoqlib.lib.dateutils import localnow
from stoqlib.lib.message import warning
from stoqlib.lib.parameters import sysparam
from stoqlib.lib.translation import stoqlib_gettext

_ = stoqlib_gettext


def _create_transaction(store, till_entry):
    if till_entry.value > 0:
        operation_type = AccountTransaction.TYPE_IN
        source_account = sysparam.get_object_id('IMBALANCE_ACCOUNT')
        dest_account = sysparam.get_object_id('TILLS_ACCOUNT')
    else:
        operation_type = AccountTransaction.TYPE_OUT
        source_account = sysparam.get_object_id('TILLS_ACCOUNT')
        dest_account = sysparam.get_object_id('IMBALANCE_ACCOUNT')

    AccountTransaction(description=till_entry.description,
                       source_account_id=source_account,
                       account_id=dest_account,
                       value=abs(till_entry.value),
                       code=unicode(till_entry.identifier),
                       date=TransactionTimestamp(),
                       store=store,
                       payment=till_entry.payment,
                       operation_type=operation_type)


class _TillOpeningModel(object):
    def __init__(self, till, value):
        self.till = till
        self.value = value

    def get_balance(self):
        return currency(self.till.get_balance() + self.value)


class _TillClosingModel(object):
    def __init__(self, till, value):
        self.till = till
        self.value = value

    def get_opening_date(self):
        # self.till is None only in the special case that the user added the ECF
        # to Stoq with a pending reduce Z, so we need to close the till on the
        # ECF, but not on Stoq.
        # Return a date in the past
        if not self.till:
            return localnow() - timedelta(1)
        return self.till.opening_date

    def get_cash_amount(self):
        if not self.till:
            return currency(0)
        return currency(self.till.get_cash_amount() - self.value)

    def get_balance(self):
        if not self.till:
            return currency(0)
        return currency(self.till.get_balance() - self.value)


[docs]class TillOpeningEditor(BaseEditor): """An editor to open a till. You can add cash to the till in the editor and it also shows the balance of the till, after the cash has been added. Callers of this editor are responsible for sending in a valid Till object, which the method open_till() can be called. """ title = _(u'Till Opening') model_type = _TillOpeningModel gladefile = 'TillOpening' confirm_widgets = ['value'] proxy_widgets = ('value', 'balance') help_section = 'till-open' # # BaseEditorSlave #
[docs] def create_model(self, store): till = Till(store=store, station=api.get_current_station(store)) till.open_till() return _TillOpeningModel(till=till, value=currency(0))
[docs] def setup_proxies(self): self.proxy = self.add_proxy(self.model, TillOpeningEditor.proxy_widgets)
[docs] def on_confirm(self): till = self.model.till # Using api.get_default_store instead of self.store # or it will return self.model.till last_opened = Till.get_last_opened(api.get_default_store()) if (last_opened and last_opened.opening_date.date() == till.opening_date.date()): warning(_("A till was opened earlier this day.")) self.retval = False return try: TillOpenEvent.emit(till=till) except (TillError, DeviceError) as e: warning(str(e)) self.retval = False return value = self.proxy.model.value if value: TillAddCashEvent.emit(till=till, value=value) till_entry = till.add_credit_entry(value, _(u'Initial Cash amount')) _create_transaction(self.store, till_entry)
# The callsite is responsible for interacting with # the fiscal printer # # Kiwi callbacks #
[docs] def on_value__validate(self, entry, data): if data < currency(0): self.proxy.update('balance', currency(0)) return ValidationError( _("You cannot add a negative amount when opening the till."))
[docs] def after_value__content_changed(self, entry): self.proxy.update('balance')
[docs]class TillClosingEditor(BaseEditor): size = (500, 440) title = _(u'Closing Opened Till') model_type = _TillClosingModel gladefile = 'TillClosing' confirm_widgets = ['value'] proxy_widgets = ('value', 'balance', 'opening_date', 'observations') help_section = 'till-close' def __init__(self, store, model=None, previous_day=False, close_db=True, close_ecf=True): """ Create a new TillClosingEditor object. :param previous_day: If the till wasn't closed previously """ self._previous_day = previous_day self.till = Till.get_last(store) if close_db: assert self.till self._close_db = close_db self._close_ecf = close_ecf BaseEditor.__init__(self, store, model) self._setup_widgets() def _setup_widgets(self): # We cant remove cash if closing till from a previous day self.value.set_sensitive(not self._previous_day) if self._previous_day: value = 0 else: value = self.model.get_balance() self.value.update(value) self.day_history.set_columns(self._get_columns()) self.day_history.connect('row-activated', lambda olist, row: self.confirm()) self.day_history.add_list(self._get_day_history()) summary_day_history = SummaryLabel( klist=self.day_history, column='value', label='<b>%s</b>' % api.escape(_(u'Total balance:'))) summary_day_history.show() self.day_history_box.pack_start(summary_day_history, False) def _get_day_history(self): if not self.till: assert self._close_ecf and not self._close_db return day_history = {} day_history[_(u'Initial Amount')] = self.till.initial_cash_amount for entry in self.till.get_entries(): payment = entry.payment if payment is not None: desc = payment.method.get_description() else: if entry.value > 0: desc = _(u'Cash In') else: desc = _(u'Cash Out') if desc in day_history.keys(): day_history[desc] += entry.value else: day_history[desc] = entry.value for description, value in day_history.items(): yield Settable(description=description, value=value) def _get_columns(self): return [Column('description', title=_('Description'), data_type=str, width=300, sorted=True), ColoredColumn('value', title=_('Amount'), data_type=currency, color='red', data_func=lambda x: x < 0)] # # BaseEditorSlave #
[docs] def create_model(self, trans): return _TillClosingModel(till=self.till, value=currency(0))
[docs] def setup_proxies(self): if self.till and not self.till.get_balance(): self.value.set_sensitive(False) self.proxy = self.add_proxy(self.model, TillClosingEditor.proxy_widgets)
[docs] def validate_confirm(self): till = self.model.till removed = abs(self.model.value) if removed and removed > till.get_balance(): warning(_("The amount that you want to remove is " "greater than the current balance.")) return False return True
[docs] def on_confirm(self): till = self.model.till removed = abs(self.model.value) if removed: # We need to do this inside a new transaction, because if the # till closing fails further on, this still needs to be recorded # in the database store = api.new_store() t_till = store.fetch(till) TillRemoveCashEvent.emit(till=t_till, value=removed) reason = _('Amount removed from Till by %s') % ( api.get_current_user(self.store).get_description(), ) till_entry = t_till.add_debit_entry(removed, reason) # Financial transaction _create_transaction(store, till_entry) # DB transaction store.confirm(True) store.close() if self._close_ecf: try: retval = TillCloseEvent.emit(till=till, previous_day=self._previous_day) except (TillError, DeviceError) as e: warning(str(e)) return None # If the event was captured and its return value is False, then we # should not close the till. if retval is False: return False if self._close_db: try: till.close_till(observations=self.model.observations) except ValueError as err: warning(str(err)) return # The callsite is responsible for interacting with # the fiscal printer return self.model
# # Kiwi handlers #
[docs] def after_value__validate(self, widget, value): if not hasattr(self, 'proxy'): return if value < currency(0): self.proxy.update('balance', currency(0)) return ValidationError(_("Value cannot be less than zero")) if value > self.till.get_balance(): self.proxy.update('balance', currency(0)) return ValidationError(_("You can not specify an amount " "removed greater than the " "till balance."))
[docs] def after_value__content_changed(self, entry): self.proxy.update('balance')
[docs]class TillVerifyEditor(TillClosingEditor): title = _('Till verification') help_section = 'till-verify' def __init__(self, store, model=None, previous_day=False, close_db=False, close_ecf=False): assert not close_db and not close_ecf super(TillVerifyEditor, self).__init__(store, model=model, previous_day=previous_day, close_db=close_db, close_ecf=close_ecf) self.set_message( _("Use this to adjust the till for the next user.\n" "Note that this will not really close the till or ecf."))
[docs]class CashAdvanceEditor(BaseEditor): """An editor which extends BaseCashSlave to include. It extends BaseCashSlave to include an employee combobox """ model_name = _(u'Cash Advance') model_type = Settable gladefile = 'CashAdvanceEditor' def _get_employee(self): return self.employee_combo.get_selected_data() def _get_employee_name(self): return self.employee_combo.get_selected_label() def _setup_widgets(self): employees = self.store.find(Employee) self.employee_combo.prefill(api.for_person_combo(employees)) self.employee_combo.set_active(0) # # BaseEditorSlave #
[docs] def create_model(self, store): till = Till.get_current(self.store) return Settable(employee=None, payment=None, # FIXME: should send in consts.now() open_date=None, till=till, balance=till.get_balance(), value=currency(0))
[docs] def setup_slaves(self): self.cash_slave = RemoveCashSlave(self.store, self.model) self.cash_slave.value.connect('content-changed', self._on_cash_slave__value_changed) self.attach_slave("base_cash_holder", self.cash_slave) self._setup_widgets()
[docs] def on_confirm(self): till = self.model.till value = abs(self.model.value) assert till try: TillRemoveCashEvent.emit(till=till, value=value) except (TillError, DeviceError, DriverError) as e: warning(str(e)) self.retval = False return till_entry = till.add_debit_entry( value, (_(u'Cash advance paid to employee: %s') % ( self._get_employee_name(), ))) TillAddTillEntryEvent.emit(till_entry, self.store) _create_transaction(self.store, till_entry)
# # Callbacks # def _on_cash_slave__value_changed(self, entry): self.cash_slave.model.value = -abs(self.cash_slave.model.value)
[docs]class BaseCashEditor(BaseEditor): model_type = Settable gladefile = 'BaseCashEditor' def __init__(self, store): BaseEditor.__init__(self, store) self.set_confirm_widget(self.reason) self.set_confirm_widget(self.cash_slave.value) # # BaseEditorSlave #
[docs] def create_model(self, store): till = Till.get_current(store) return Settable(value=currency(0), reason=u'', till=till, balance=till.get_balance())
[docs] def setup_proxies(self): self.proxy = self.add_proxy(self.model, [u'reason'])
[docs] def setup_slaves(self): self.cash_slave = self.cash_slave_class(self.store, self.model) self.attach_slave("base_cash_holder", self.cash_slave)
[docs] def on_confirm(self): value = abs(self.model.value) till = self.model.till assert till try: self.event.emit(till=till, value=value) except (TillError, DeviceError, DriverError) as e: warning(str(e)) self.retval = False return till_entry = self.create_entry(till, value, self.model.reason) TillAddTillEntryEvent.emit(till_entry, self.store) _create_transaction(self.store, till_entry)
[docs]class CashOutEditor(BaseCashEditor): """An editor to Remove cash from the Till """ model_name = _(u'Cash Out') title = _(u'Reverse Payment') cash_slave_class = RemoveCashSlave event = TillRemoveCashEvent help_section = 'till-remove-money'
[docs] def create_entry(self, till, value, reason): return till.add_debit_entry(value, (_(u'Cash out: %s') % (reason, )))
[docs]class CashInEditor(BaseCashEditor): """An editor to Add cash to the Till """ model_name = _(u'Cash In') cash_slave_class = BaseCashSlave event = TillAddCashEvent help_section = 'till-add-money'
[docs] def create_entry(self, till, value, reason): return till.add_credit_entry(value, (_(u'Cash in: %s') % (reason, )))