[POS-commit] r1975 - in stoq/trunk/stoq: gui/pos gui/till lib

Henrique Romano henrique at async.com.br
Fri Dec 23 14:49:26 BRST 2005


Author: henrique
Date: Fri Dec 23 14:49:26 2005
New Revision: 1975

Modified:
   stoq/trunk/stoq/gui/pos/pos.py
   stoq/trunk/stoq/gui/till/till.py
   stoq/trunk/stoq/lib/drivers.py
Log:
Fix for bug #2364: Implements support for concomitant impression of
fiscal coupons.

Initial implementation.

r=evandro


Modified: stoq/trunk/stoq/gui/pos/pos.py
==============================================================================
--- stoq/trunk/stoq/gui/pos/pos.py	(original)
+++ stoq/trunk/stoq/gui/pos/pos.py	Fri Dec 23 14:49:26 2005
@@ -43,6 +43,7 @@
 from stoq.lib.runtime import new_transaction
 from stoq.lib.validators import (format_quantity, get_price_format_str)
 from stoq.lib.parameters import sysparam
+from stoq.lib.drivers import FiscalCoupon
 from stoq.domain.sellable import AbstractSellable, FancySellable
 from stoq.domain.service import ServiceSellableItem
 from stoq.domain.product import ProductSellableItem, FancyProduct
@@ -82,9 +83,13 @@
                 'header_label',
                 'pos_vbox',
                 'search_box',
-                'SalesMenu') + client_widgets + product_widgets +
-                               sellable_widgets)
-    
+                'SalesMenu',
+                'CancelOrder',
+                'ResetOrder')
+               + client_widgets
+               + product_widgets
+               + sellable_widgets)
+
     def __init__(self, app):
         AppWindow.__init__(self, app)
         self.conn = new_transaction()
@@ -94,6 +99,7 @@
             self.app.shutdown()
         self.max_results = get_max_search_results()
         self.client_table = Person.getAdapterClass(IClient)
+        self.coupon = None
         self._setup_widgets()
         self._setup_proxies()
         self._clear_order()
@@ -107,6 +113,8 @@
         self.order_list.clear()
         self.sale = None
         self.client_proxy.new_model(self.sale, relax_type=True)
+        self.CancelOrder.set_sensitive(False)
+        self.ResetOrder.set_sensitive(True)
 
     def _delete_sellable_item(self, item):
         self.order_list.remove(item)
@@ -146,7 +154,6 @@
         self._setup_entry_completion()
         # Waiting for bug 2319
         self.client_details_button.set_sensitive(False)
-                
 
     def _update_totals(self, *args):
         self.summary_label.update_total()
@@ -190,6 +197,8 @@
         self.order_list.append(sellable_item)
         self.order_list.select(sellable_item)
         self.product.set_text('')
+        if not sysparam(self.conn).CONFIRM_SALES_ON_TILL:
+            self.coupon.add_item(sellable_item)
 
     def _get_sellable(self):
         if self.product_proxy.model:
@@ -233,8 +242,24 @@
             self.header_box.hide()
             self.pos_vbox.set_sensitive(True)
             self.product.grab_focus()
+            self.ResetOrder.set_sensitive(False)
+            self.CancelOrder.set_sensitive(True)
         else:
             rollback_and_begin(self.conn)
+        if sysparam(self.conn).CONFIRM_SALES_ON_TILL:
+            return
+        if not self.coupon:
+            self.coupon = FiscalCoupon(self.conn, self.sale)
+        if self.sale.client:
+            self.coupon.identify_customer(self.sale.client.get_adapted())
+        while not self.coupon.open():
+            if warning(
+                _("It is not possible open a fiscal coupon"),
+                _("It is not possible start a new sale since a "
+                  "fiscal does not can be opened."),
+                buttons=((_("Confirm later"), gtk.RESPONSE_CANCEL),
+                         (_("Try Again"), gtk.RESPONSE_OK))) != gtk.RESPONSE_OK:
+                self.app.shutdown()
 
     def _update_widgets(self):
         has_sellables = len(self.order_list[:]) >= 1
@@ -341,12 +366,17 @@
 
     def on_remove_item_button__clicked(self, *args):
         item = self.order_list.get_selected()
+        if (not sysparam(self.conn).CONFIRM_SALES_ON_TILL
+            and not self.coupon.remove_item(item)):
+            return
         self._delete_sellable_item(item)
         self.select_first_item()
         self._update_widgets()
 
     def _on_cancel_order_action_clicked(self, *args):
-        pass
+        self._clear_order()
+        if not sysparam(self.conn).CONFIRM_SALES_ON_TILL:
+            self.coupon.cancel()
 
     def _on_resetorder_action__clicked(self, *args):
         self._new_order()
@@ -382,6 +412,11 @@
                              param.SET_PAYMENT_METHODS_ON_TILL)
         if self.run_dialog(SaleWizard, self.conn, self.sale,
                            skip_payment_step=skip_payment_step):
+            if not skip_payment_step and not param.CONFIRM_SALES_ON_TILL:
+                if (not self.coupon.totalize()
+                    or not self.coupon.setup_payments()
+                    or not self.coupon.close()):
+                    return
             self.conn.commit()
             self._clear_order()
 

Modified: stoq/trunk/stoq/gui/till/till.py
==============================================================================
--- stoq/trunk/stoq/gui/till/till.py	(original)
+++ stoq/trunk/stoq/gui/till/till.py	Fri Dec 23 14:49:26 2005
@@ -224,7 +224,7 @@
             return
         sale.confirm_sale()
 
-        if not emit_coupon(self.conn, sale):
+        if not emit_coupon(sale, self.conn):
             return
         self.conn.commit()
         self.searchbar.search_items()

Modified: stoq/trunk/stoq/lib/drivers.py
==============================================================================
--- stoq/trunk/stoq/lib/drivers.py	(original)
+++ stoq/trunk/stoq/lib/drivers.py	Fri Dec 23 14:49:26 2005
@@ -33,6 +33,7 @@
 import socket
 
 import gtk
+from zope.interface import implements
 from sqlobject.sqlbuilder import OR
 from stoqlib.exceptions import DatabaseInconsistency
 from stoqlib.exceptions import _warn
@@ -45,13 +46,11 @@
 
 from stoq.domain.drivers import PrinterSettings
 from stoq.domain.interfaces import (IIndividual, IPaymentGroup,
-                                    IMoneyPM, ICheckPM)
+                                    IMoneyPM, ICheckPM, IContainer)
 
 _ = gettext.gettext
 _printer = None
 
-MAX_DIALOG_MESSAGE_LEN = 70
-
 def get_printer_settings_by_hostname(conn, hostname):
     """ Returns the PrinterSettings object associated with the given
     hostname or None if there is not settings for it.
@@ -86,15 +85,6 @@
                 % socket.gethostname()))
     return _printer
 
-def _cancel(printer):
-    """ @returns: True if the reduce Z has been emitted, False otherwise.
-    """
-    try:
-        printer.cancel()
-    except DriverError:
-        return False
-    return True
-
 def _emit_reading(conn, cmd):
     printer = _get_fiscalprinter(conn)
     if not printer:
@@ -113,86 +103,159 @@
 def emit_reduce_Z(conn):
     return _emit_reading(conn, 'close_till')
 
-def emit_coupon(conn, sale):
+def emit_coupon(sale, conn):
     """ Emit a coupon for a Sale instance.
 
     @returns: True if the coupon has been emitted, False otherwise.
     """
-    printer = _get_fiscalprinter(conn)
-    if not printer:
-        return False
-
+    coupon = FiscalCoupon(conn, sale)
     person = sale.client.get_adapted()
-    address = person.get_main_address().get_address_string()
-    individual = IIndividual(person, connection=person.get_connection())
-    if individual is None:
-        raise DatabaseInconsistency("The client must have a Individual facet")
-    cpf = individual.cpf
+    if person:
+        coupon.identify_customer(person)
+    if not coupon.open():
+        return False
+    map(coupon.add_item, sale.get_items())
+    if not coupon.totalize():
+        return False
+    if not coupon.setup_payments():
+        return False
+    return coupon.close()
 
-    printer.identify_customer(person.name, address, cpf)
-    while True:
-        try:
-            printer.open()
-            break
-        except CouponOpenError:
-            if not _cancel(printer):
-                return False
-        except OutofPaperError:
-            if warning(
-                _("The printer has run out of paper"),
-                _("The printer %s has run out of paper.\nAdd more paper "
-                  "before continuing." % printer.get_printer_name()),
-                buttons=((_("Confirm later"), gtk.RESPONSE_CANCEL),
-                         (_("Resume"), gtk.RESPONSE_OK))) != gtk.RESPONSE_OK:
-                return False
-            return emit_coupon(conn, sale)
-        except PrinterOfflineError:
-            if warning(
-                _("The printer is offline"),
-                _("The printer %s is offline, turn it on and try again"
-                  % printer.get_printer_name()),
-                buttons=((_("Confirm later"), gtk.RESPONSE_CANCEL),
-                         (_("Resume"), gtk.RESPONSE_OK))) != gtk.RESPONSE_OK:
-                return False
-            return emit_coupon(conn, sale)
-        except DriverError, details:
-            warning(_("It's not possible to emit the coupon"), str(details))
-            return False
+#
+# Class definitions
+#
+
+class FiscalCoupon:
+    """ This class is used just to allow us cancel an item with base in a
+    AbstractSellable object.
+    """
+    implements(IContainer)
+    
+    #
+    # IContainer implementation
+    #
+
+    def __init__(self, conn, sale):
+        self.sale = sale
+        self.conn = conn
+        self.printer = _get_fiscalprinter(conn)
+        if not self.printer:
+            raise ValueError
+        self._item_ids = {}
 
-    for item in sale.get_items():
+    def add_item(self, item):
         sellable = item.sellable
+        description = sellable.base_sellable_info.description
         # FIXME: TAX_NONE is a HACK, waiting for bug #2269
         # FIXME: UNIT_EMPTY is temporary and will be remove when bug #2247
         # is fixed.
-        printer.add_item(sellable.code, item.quantity, item.price, 
-                         UNIT_EMPTY, sellable.base_sellable_info.description,
-                         TAX_NONE, 0, 0)
-
-    printer.totalize(sale.discount_value, sale.charge_value, TAX_NONE)
-
-    group = IPaymentGroup(sale, connection=conn)
-    if group.default_method == group.METHOD_GIFT_CERTIFICATE:
-        printer.add_payment(MONEY_PM, sale.get_total_sale_amount(), '')
-    else:
-        for payment in group.get_items():
-            if ICheckPM.providedBy(payment.method):
-                money_type = CHEQUE_PM
-            elif IMoneyPM.providedBy(payment.method):
-                money_type = MONEY_PM
-            # FIXME: A default value, this is wrong but can't be better right
-            # now, since stoqdrivers doesn't have support for any payment
-            # method diferent than money and cheque.  This will be improved
-            # when bug #2246 is fixed.
-            else:
-                _warn(_("The payment type %d isn't supported yet. "
-                        "The default, MONEY_PM, will be used.") 
-                        % payment.method)
-                money_type = MONEY_PM
-            printer.add_payment(money_type, payment.value, '')
+        item_id = self.printer.add_item(sellable.code, item.quantity,
+                                        item.price, UNIT_EMPTY,
+                                        description, TAX_NONE, 0, 0)
+        self._item_ids[item] = item_id
+
+    def get_items(self):
+        return self._item_ids.values()
+
+    def remove_item(self, sellable):
+        item_id = self._item_ids[sellable]
+        try:
+            self.printer.cancel_item(item_id)
+        except DriverError:
+            return False
+        del self._item_ids[sellable]
+        return True
+
+    #
+    # Fiscal coupon related functions
+    #
+
+    def identify_customer(self, person):
+        address = person.get_main_address().get_address_string()
+        individual = IIndividual(person, connection=person.get_connection())
+        if individual is None:
+            raise DatabaseInconsistency("The client must have a "
+                                        "Individual facet")
+        cpf = individual.cpf
+        self.printer.identify_customer(person.name, address, cpf)
+
+    def open(self):
+        while True:
+            try:
+                self.printer.open()
+                break
+            except CouponOpenError:
+                if not self.cancel():
+                    return False
+            except OutofPaperError:
+                if warning(
+                    _("The printer has run out of paper"),
+                    _("The printer %s has run out of paper.\nAdd more paper "
+                      "before continuing." % printer.get_printer_name()),
+                    buttons=((_("Confirm later"), gtk.RESPONSE_CANCEL),
+                             (_("Resume"), gtk.RESPONSE_OK))) != gtk.RESPONSE_OK:
+                    return False
+                return self.open()
+            except PrinterOfflineError:
+                if warning(
+                    _("The printer is offline"),
+                    _("The printer %s is offline, turn it on and try again"
+                      % printer.get_printer_name()),
+                    buttons=((_("Confirm later"), gtk.RESPONSE_CANCEL),
+                             (_("Resume"), gtk.RESPONSE_OK))) != gtk.RESPONSE_OK:
+                    return False
+                return self.open()
+            except DriverError, details:
+                warning(_("It's not possible to emit the coupon"), str(details))
+                return False
+        return True
+
+    def totalize(self):
+        self.printer.totalize(self.sale.discount_value,
+                              self.sale.charge_value, TAX_NONE)
+        return True
+
+    def cancel(self):
+        try:
+            self.printer.cancel()
+        except DriverError:
+            return False
+        return True
+
+    def setup_payments(self):
+        """ Add the payments defined in the sale to the coupon. Note that this
+        function must be called after all the payments has been created.
+        """
+        sale = self.sale
+        group = IPaymentGroup(sale, connection=self.conn)
+        if not group:
+            raise ValueError("The sale object must have a PaymentGroup facet at "
+                             "this point.")
+        if group.default_method == group.METHOD_GIFT_CERTIFICATE:
+            printer.add_payment(MONEY_PM, sale.get_total_sale_amount(), '')
+        else:
+            for payment in group.get_items():
+                if ICheckPM.providedBy(payment.method):
+                    money_type = CHEQUE_PM
+                elif IMoneyPM.providedBy(payment.method):
+                    money_type = MONEY_PM
+                    # FIXME: A default value, this is wrong but can't be better right
+                    # now, since stoqdrivers doesn't have support for any payment
+                    # method diferent than money and cheque.  This will be improved
+                    # when bug #2246 is fixed.
+                else:
+                    _warn(_("The payment type %d isn't supported yet. The default, "
+                            "MONEY_PM, will be used.") 
+                          % payment.method)
+                    money_type = MONEY_PM
+                self.printer.add_payment(money_type, payment.value, '')
+        return True
+
+    def close(self):
+        try:
+            self.printer.close()
+        except DriverError, details:
+            warning(_("It's not possible to close the coupon"), str(details))
+            return False
+        return True
 
-    try:
-        printer.close()
-    except DriverError, details:
-        warning(_("It's not possible to close the coupon"), str(details))
-        return False
-    return True


More information about the POS-commit mailing list