def _create_printer(self): if self._printer: return self._printer station = get_current_station(self.default_store) printer = self.default_store.find(ECFPrinter, station=station, is_active=True).one() if not printer: return None try: self._printer = CouponPrinter(printer) except SerialException as e: warning(_('Error opening serial port'), str(e)) except DriverError as e: warning(str(e)) return None
def _create_printer(self): if self._printer: return self._printer station = get_current_station(self.default_store) printer = self.default_store.find(ECFPrinter, station=station, is_active=True).one() if not printer: return None try: self._printer = CouponPrinter(printer) except SerialException as e: warning(_("Error opening serial port"), str(e)) except DriverError as e: warning(str(e)) return None
class ECFUI(object): def __init__(self): self._ui = None self.default_store = get_default_store() self._printer_verified = False # Delay printer creation until we are accessing pos or till app. Other # applications should still be accessible without a printer self._printer = None self._setup_params() self._setup_events() self._till_summarize_action = Gtk.Action( 'Summary', _('Summary'), None, None) self._till_summarize_action.connect( 'activate', self._on_TillSummary__activate) add_bindings([ ('plugin.ecf.read_memory', '<Primary>F9'), ('plugin.ecf.summarize', '<Primary>F11'), ]) # # Private # def _setup_params(self): for detail in params: sysparam.register_param(detail) def _setup_events(self): SaleStatusChangedEvent.connect(self._on_SaleStatusChanged) SaleAvoidCancelEvent.connect(self._on_SaleAvoidCancel) TillOpenEvent.connect(self._on_TillOpen) TillCloseEvent.connect(self._on_TillClose) TillAddCashEvent.connect(self._on_TillAddCash) TillAddTillEntryEvent.connect(self._on_AddTillEntry) TillRemoveCashEvent.connect(self._on_TillRemoveCash) StartApplicationEvent.connect(self._on_StartApplicationEvent) StopApplicationEvent.connect(self._on_StopApplicationEvent) CouponCreatedEvent.connect(self._on_CouponCreatedEvent) GerencialReportPrintEvent.connect(self._on_GerencialReportPrintEvent) GerencialReportCancelEvent.connect(self._on_GerencialReportCancelEvent) CheckECFStateEvent.connect(self._on_CheckECFStateEvent) HasPendingReduceZ.connect(self._on_HasPendingReduceZ) HasOpenCouponEvent.connect(self._on_HasOpenCouponEvent) EditorCreateEvent.connect(self._on_EditorCreateEvent) ECFGetPrinterUserNumberEvent.connect(self._on_ECFGetPrinterUserNumberEvent) def _create_printer(self): if self._printer: return self._printer station = get_current_station(self.default_store) printer = self.default_store.find(ECFPrinter, station=station, is_active=True).one() if not printer: return None try: self._printer = CouponPrinter(printer) except SerialException as e: warning(_('Error opening serial port'), str(e)) except DriverError as e: warning(str(e)) return None def _validate_printer(self): if self._printer is None: raise DeviceError( _("This operation requires a connected fiscal printer")) # Check fiscal printer setup. If something went wrong with the setup, # block the till if not self._printer._driver.setup_complete(): raise DeviceError(_("An error occurred during fiscal printer setup")) if not self._printer_verified: log.info('ecfui._validate_printer') if not self._printer.check_serial(): raise DeviceError( _("Fiscalprinters serial number is different!")) self._printer_verified = True def _add_ui_menus(self, appname, app, uimanager): self._current_app = app if appname == 'pos': self._create_printer() self._add_pos_menus(uimanager) elif appname == 'till': self._create_printer() self._add_till_menus(uimanager) elif appname == 'sales': # The sales app needs the printer to check if the # sale being returned is the last sale on the ECF. self._create_printer() elif appname == 'admin': self._add_admin_menus(uimanager) app.tasks.add_item( _('Fiscal Printers'), 'fiscal-printer', 'printer', self._on_ConfigurePrinter__activate) def _remove_ui_menus(self, uimanager): if not self._ui: return uimanager.remove_ui(self._ui) self._ui = None def _add_admin_menus(self, uimanager): ui_string = """<ui> <menubar name="menubar"> <placeholder action="AppMenubarPH"> <menu action="ConfigureMenu"> <placeholder name="ConfigurePH"> <menuitem action="ConfigurePrinter"/> </placeholder> </menu> </placeholder> </menubar> </ui>""" ag = Gtk.ActionGroup('ECFMenuActions') ag.add_actions([ ('ECFMenu', None, _('ECF')), ('ConfigurePrinter', None, _('Configure fiscal printer...'), None, None, self._on_ConfigurePrinter__activate), ]) uimanager.insert_action_group(ag, 0) self._ui = uimanager.add_ui_from_string(ui_string) def _add_ecf_menu(self, uimanager): ui_string = """<ui> <menubar name="menubar"> <placeholder name="ExtraMenubarPH"> <menu action="ECFMenu"> <menuitem action="CancelLastDocument"/> <menuitem action="Summary"/> <menuitem action="ReadMemory"/> </menu> </placeholder> </menubar> </ui>""" group = get_accels('plugin.ecf') ag = Gtk.ActionGroup('ECFMenuActions') ag.add_actions([ ('ECFMenu', None, _('ECF')), ('ReadMemory', None, _('Read Memory'), group.get('read_memory'), None, self._on_ReadMemory__activate), ('CancelLastDocument', None, _('Cancel Last Document'), None, None, self._on_CancelLastDocument__activate), ]) ag.add_action_with_accel(self._till_summarize_action, group.get('summarize')) can_cancel = sysparam.get_bool('ALLOW_CANCEL_LAST_COUPON') cancel_coupon_menu = ag.get_action('CancelLastDocument') cancel_coupon_menu.set_visible(can_cancel) uimanager.insert_action_group(ag, 0) self._ui = uimanager.add_ui_from_string(ui_string) def _add_pos_menus(self, uimanager): if sysparam.get_bool('POS_SEPARATE_CASHIER'): return self._add_ecf_menu(uimanager) def _add_till_menus(self, uimanager): self._add_ecf_menu(uimanager) def _check_ecf_state(self): log.info('ecfui._check_printer') try: self._validate_printer() except (DriverError, DeviceError): warning(_("It was not possible to communicate with the printer")) raise SystemExit self._has_open_coupon() def _has_open_coupon(self): self._validate_printer() if self._printer.has_open_coupon(): warning(_("The ECF has an open coupon. It will be canceled now.")) self._printer.cancel() def _open_till(self, till): log.info('ECFCouponPrinter.open_till(%r)' % (till, )) # Callsite catches DeviceError self._validate_printer() retval = True while True: try: self._printer.open_till() except CouponOpenError: self._printer.cancel() retval = False except DriverError: response = warning( _(u"It's not possible to emit a read X for the " "configured printer.\nWould you like to ignore " "this error and continue?"), buttons=((_(u"Cancel"), Gtk.ResponseType.CANCEL), (_(u"Ignore"), Gtk.ResponseType.YES), (_(u"Try Again"), Gtk.ResponseType.NONE))) if response == Gtk.ResponseType.NONE: continue elif response == Gtk.ResponseType.CANCEL: retval = False break return retval def _has_pending_reduce(self): self._validate_printer() return self._printer.has_pending_reduce() def _close_till(self, till, previous_day): log.info('ECFCouponPrinter.close_till(%r, %r)' % (till, previous_day)) # XXX: this is so ugly, but the printer stops responding # if its printing something. We should wait a little bit... # This only happens if closing the till from the current day. if not previous_day: time.sleep(4) # Callsite catches DeviceError self._validate_printer() printer = self._printer.get_printer() if (sysparam.get_bool('ENABLE_DOCUMENT_ON_INVOICE') and not (printer.user_number and printer.register_date and printer.register_cro)): response = warning( short=_(u"You need to set some details about your ECF " "if you want to save the paulista invoice file. " "Go to the admin application and fill the " "required information for the ECF."), buttons=((_(u"Cancel Close Till"), Gtk.ResponseType.CANCEL), )) return False retval = True while True: try: self._printer.close_till(previous_day=previous_day) except CouponOpenError: self._printer.cancel() retval = False except DriverError: response = warning( short=_(u"It's not possible to emit a reduce Z for the " "configured printer.\nWould you like to ignore " "this error and continue?"), buttons=((_(u"Cancel"), Gtk.ResponseType.CANCEL), (_(u"Ignore"), Gtk.ResponseType.YES), (_(u"Try Again"), Gtk.ResponseType.NONE))) if response == Gtk.ResponseType.NONE: continue elif response == Gtk.ResponseType.CANCEL: retval = False break if self._needs_cat52(printer): day = datetime.date.today() if previous_day: # XXX: Make sure this is tested day = till.opening_date dir = sysparam.get_string('CAT52_DEST_DIR') dir = os.path.expanduser(dir) if not os.path.exists(dir): os.mkdir(dir) generator = StoqlibCATGenerator(self.default_store, day, printer) generator.write(dir) self._reset_last_doc() return retval def _needs_cat52(self, printer): # If the param is not enabled, we dont need. if not sysparam.get_bool('ENABLE_DOCUMENT_ON_INVOICE'): return False # Even if the parameter is enabled, we can only generate cat52 for # the printer we support, and that dont have MFD: # If the printer has an MFD, it should not be present in the # MODEL_CODES variable model = MODEL_CODES.get((printer.brand, printer.model)) if not model: return False return True def _set_last_sale(self, sale, store): printer = store.fetch(self._printer._printer) printer.last_sale = sale printer.last_till_entry = None def _set_last_till_entry(self, till_entry, store): printer = store.fetch(self._printer._printer) printer.last_till_entry = till_entry printer.last_sale = None def _reset_last_doc(self): # Last ecf document is not a sale or a till_entry anymore. store = new_store() printer = store.fetch(self._printer._printer) printer.last_till_entry = None printer.last_sale = None store.commit() def _add_cash(self, till, value): log.info('ECFCouponPrinter.add_cash(%r, %r)' % (till, value, )) # XXX: this is so ugly, but the printer stops responding # if its printing something. We should wait a little bit... time.sleep(4) # Callsite catches DeviceError self._validate_printer() self._printer.add_cash(value) def _remove_cash(self, till, value): log.info('ECFCouponPrinter.remove_cash(%r, %r)' % (till, value, )) # Callsite catches DeviceError self._validate_printer() self._printer.remove_cash(value) def _confirm_sale(self, sale): log.info('ECFCouponPrinter.confirm_sale(%r)' % (sale, )) self._validate_printer() def _undo_returned_sale(self, sale): log.info('Undoing a returned_sale of the sale(%r)' % (sale, )) def _coupon_create(self, fiscalcoupon, sale): # External sales are an exception to the general rule and should not # generate an ecf. if sale and sale.is_external(): return # Callsite catches DeviceError self._validate_printer() coupon = self._printer.create_coupon(fiscalcoupon) assert coupon for signal, callback in [ ('open', self._on_coupon__open), ('identify-customer', self._on_coupon__identify_customer), ('customer-identified', self._on_coupon__customer_identified), ('add-item', self._on_coupon__add_item), ('remove-item', self._on_coupon__remove_item), ('add-payments', self._on_coupon__add_payments), ('totalize', self._on_coupon__totalize), ('close', self._on_coupon__close), ('cancel', self._on_coupon__cancel), ('get-coo', self._on_coupon__get_coo), ('get-supports-duplicate-receipt', self._on_coupon__get_supports_duplicate), ('print-payment-receipt', self._on_coupon__print_payment_receipt)]: fiscalcoupon.connect_object(signal, callback, coupon) return coupon def _get_last_document(self, store): station = api.get_current_station(store) return ECFPrinter.get_last_document(station=station, store=store) def _is_ecf_last_sale(self, sale): store = sale.store is_last_sale = store.find(ECFPrinter, last_sale=sale).count() > 0 return is_last_sale def _confirm_last_document_cancel(self, last_doc): if last_doc.last_sale is None and last_doc.last_till_entry is None: info(_("There is no sale nor till entry to cancel")) return if last_doc.last_sale: msg = _("Do you really want to cancel the sale number %s " "and value %.2f ?") % (last_doc.last_sale.identifier, last_doc.last_sale.total_amount) elif last_doc.last_till_entry.value > 0: msg = _("Do you really want to cancel the last cash added " "number %d and value %.2f ?") % ( last_doc.last_till_entry.identifier, last_doc.last_till_entry.value) else: msg = _("Do you really want to cancel the last cash removed " "number %d and value %.2f ?") % ( last_doc.last_till_entry.identifier, last_doc.last_till_entry.value) return yesno(msg, Gtk.ResponseType.NO, _("Cancel Last Document"), _("Not now")) def _cancel_last_till_entry(self, last_doc, store): last_till_entry = store.fetch(last_doc.last_till_entry) value = last_till_entry.value till = Till.get_current(store) try: self._printer.cancel_last_coupon() if last_till_entry.value > 0: till_entry = till.add_debit_entry( # TRANSLATORS: cash out = sangria, cash in = suprimento value, _(u"Cash out: last cash in cancelled")) self._set_last_till_entry(till_entry, store) else: till_entry = till.add_credit_entry( # TRANSLATORS: cash out = sangria, cash in = suprimento -value, _(u"Cash in: last cash out cancelled")) self._set_last_till_entry(till_entry, store) info(_("Document was cancelled")) except Exception: info(_("Cancelling failed, nothing to cancel")) return def _cancel_last_sale(self, last_doc, store): if last_doc.last_sale.status == Sale.STATUS_RETURNED: return sale = store.fetch(last_doc.last_sale) value = sale.total_amount reason = _(u"Cancelling last document on ECF") sale.cancel(reason, force=True) till = Till.get_current(store) # TRANSLATORS: cash out = sangria till.add_debit_entry(value, _(u"Cash out: last sale cancelled")) last_doc.last_sale = None info(_("Document was cancelled")) def _cancel_last_document(self): try: self._validate_printer() except DeviceError as e: warning(str(e)) return store = new_store() last_doc = self._get_last_document(store) if not self._confirm_last_document_cancel(last_doc): store.close() return if last_doc.last_till_entry: self._cancel_last_till_entry(last_doc, store) elif last_doc.last_sale: # Verify till balance before cancel the last sale. till = Till.get_current(store) if last_doc.last_sale.total_amount > till.get_balance(): warning(_("You do not have this value on till.")) store.close() return cancelled = self._printer.cancel_last_coupon() if not cancelled: info(_("Cancelling sale failed, nothing to cancel")) store.close() return else: self._cancel_last_sale(last_doc, store) store.commit() def _till_summarize(self): try: self._validate_printer() except DeviceError as e: warning(str(e)) return self._printer.summarize() self._reset_last_doc() def _fiscal_memory_dialog(self): try: self._validate_printer() except DeviceError as e: warning(str(e)) return retval = run_dialog(FiscalMemoryDialog, None, self.default_store, self._printer) if retval: self._reset_last_doc() def _get_client_document(self, sale): """Returns a Settable with two attributes: document, a string with the client cpf or cnpj and document_type, being one of (FiscalSaleHistory.TYPE_CPF, FiscalSaleHistory.TYPE_CNPJ ) """ client_role = sale.get_client_role() if isinstance(client_role, Individual): document_type = FiscalSaleHistory.TYPE_CPF document = client_role.cpf elif isinstance(client_role, Company): document_type = FiscalSaleHistory.TYPE_CNPJ document = client_role.cnpj else: return if document: return Settable(document_type=document_type, document=document) def _identify_customer(self, coupon, sale=None): if not sysparam.get_bool('ENABLE_DOCUMENT_ON_INVOICE'): return model = None initial_client_document = None if sale: model = self._get_client_document(sale) # Sale may have no client. if model: initial_client_document = model.document model = run_dialog(PaulistaInvoiceDialog, self._current_app, self.default_store, model) # The user has chosen the 'without cpf' option, but we still need to # inform a invalid CPF, otherwise the current client's cpf will be used if not model: coupon.identify_customer('-', '-', u'', None) return # The document didn't change. if model.document == initial_client_document: return coupon.identify_customer('-', '-', model.document, model.document_type) return model.document # # Events # def _on_StartApplicationEvent(self, appname, app): self._add_ui_menus(appname, app, app.window.uimanager) def _on_StopApplicationEvent(self, appname, app): self._remove_ui_menus(app.window.uimanager) def _on_SaleStatusChanged(self, sale, old_status): if sale.status == Sale.STATUS_CONFIRMED: if old_status == Sale.STATUS_RETURNED: self._undo_returned_sale(sale) else: self._confirm_sale(sale) self._set_last_sale(sale, sale.store) def _on_SaleAvoidCancel(self, sale, new_status): if not sysparam.get_bool('ALLOW_CANCEL_LAST_COUPON'): return False if self._is_ecf_last_sale(sale): info(_("That is last sale in ECF. Return using the menu " "ECF - Cancel Last Document")) return True return False def _on_TillOpen(self, till): return self._open_till(till) def _on_TillClose(self, till, previous_day): return self._close_till(till, previous_day) def _on_TillAddCash(self, till, value): self._add_cash(till, value) def _on_TillRemoveCash(self, till, value): self._remove_cash(till, value) def _on_CouponCreatedEvent(self, coupon, sale): self._coupon_create(coupon, sale) def _on_AddTillEntry(self, till_entry, store): self._set_last_till_entry(till_entry, store) def _on_HasPendingReduceZ(self): return self._has_pending_reduce() def _on_HasOpenCouponEvent(self): self._has_open_coupon() def _on_ECFGetPrinterUserNumberEvent(self): return self._get_last_document(self.default_store).user_number # # Callbacks # def _on_coupon__open(self, coupon): self._validate_printer() document = None if not coupon.identify_customer_at_end: document = self._identify_customer(coupon) coupon.open() return document def _on_coupon__identify_customer(self, coupon, person): if person.individual: individual = person.individual document_type = FiscalSaleHistory.TYPE_CPF document = individual.cpf elif person.company: company = person.company document_type = FiscalSaleHistory.TYPE_CNPJ document = company.cnpj else: raise TypeError( "identify_customer needs a individual or a company") name = person.name address = person.get_address_string() coupon.identify_customer(name, address, document, document_type) def _on_coupon__customer_identified(self, coupon): return coupon.is_customer_identified() def _on_coupon__add_item(self, coupon, item): self._validate_printer() return coupon.add_item(item) def _on_coupon__remove_item(self, coupon, item_id): coupon.remove_item(item_id) def _on_coupon__add_payments(self, coupon, sale): coupon.add_payments(sale) def _on_coupon__totalize(self, coupon, sale): coupon.totalize(sale) def _on_coupon__close(self, coupon, sale): if coupon.identify_customer_at_end: self._identify_customer(coupon, sale) return coupon.close(sale) def _on_coupon__cancel(self, coupon): if coupon.closed: # In this case, the Sale and TillEntries will be rolled back by # fiscalprinter. We only need to cancel the last coupon on the ecf if not self._printer.cancel_last_coupon(): info(_("Coupon cancellation failed...")) else: coupon.cancel() def _on_coupon__get_coo(self, coupon): return coupon.get_coo() def _on_coupon__get_supports_duplicate(self, coupon): return coupon.supports_duplicate_receipt def _on_coupon__print_payment_receipt(self, coupon, coo, payment, value, receipt): coupon.print_payment_receipt(coo, payment, value, receipt) def _on_TillSummary__activate(self, action): self._till_summarize() def _on_ReadMemory__activate(self, action): self._fiscal_memory_dialog() def _on_CancelLastDocument__activate(self, action): self._cancel_last_document() def _on_ConfigurePrinter__activate(self, action=None): run_dialog(ECFListDialog, None) def _on_GerencialReportCancelEvent(self): self._printer._driver.gerencial_report_close() def _on_GerencialReportPrintEvent(self, receipt, close_previous=False): try: self._validate_printer() except DeviceError as e: warning(str(e)) return if close_previous: # FIXME: dont call _driver directly # Try closing any previously opened report. This is TEF specific, to # workaround the tests where they turn off the printer self._printer._driver.gerencial_report_close() self._printer.print_report(receipt) def _on_CheckECFStateEvent(self): self._check_ecf_state() def _on_EditorCreateEvent(self, editor, model, store, *args): from stoqlib.gui.dialogs.saledetails import SaleDetailsDialog manager = get_plugin_manager() nfe_active = any(manager.is_active(plugin) for plugin in ['nfe', 'nfce']) if not nfe_active and isinstance(editor, SaleDetailsDialog): # Only display the coupon number if the nfe is not active. editor.attach_slave('coupon_number_holder', CouponNumberSlave(editor.store, model))
class ECFUI(object): def __init__(self): self._ui = None self.default_store = get_default_store() self._printer_verified = False # Delay printer creation until we are accessing pos or till app. Other # applications should still be accessible without a printer self._printer = None SaleStatusChangedEvent.connect(self._on_SaleStatusChanged) ECFIsLastSaleEvent.connect(self._on_ECFIsLastSale) TillOpenEvent.connect(self._on_TillOpen) TillCloseEvent.connect(self._on_TillClose) TillAddCashEvent.connect(self._on_TillAddCash) TillAddTillEntryEvent.connect(self._on_AddTillEntry) TillRemoveCashEvent.connect(self._on_TillRemoveCash) StartApplicationEvent.connect(self._on_StartApplicationEvent) StopApplicationEvent.connect(self._on_StopApplicationEvent) CouponCreatedEvent.connect(self._on_CouponCreatedEvent) GerencialReportPrintEvent.connect(self._on_GerencialReportPrintEvent) GerencialReportCancelEvent.connect(self._on_GerencialReportCancelEvent) CheckECFStateEvent.connect(self._on_CheckECFStateEvent) HasPendingReduceZ.connect(self._on_HasPendingReduceZ) HasOpenCouponEvent.connect(self._on_HasOpenCouponEvent) self._till_summarize_action = gtk.Action( 'Summary', _('Summary'), None, None) self._till_summarize_action.connect( 'activate', self._on_TillSummary__activate) add_bindings([ ('plugin.ecf.read_memory', '<Primary>F9'), ('plugin.ecf.summarize', '<Primary>F11'), ]) # # Private # def _create_printer(self): if self._printer: return self._printer station = get_current_station(self.default_store) printer = self.default_store.find(ECFPrinter, station=station, is_active=True).one() if not printer: return None try: self._printer = CouponPrinter(printer) except SerialException as e: warning(_('Error opening serial port'), str(e)) except DriverError as e: warning(str(e)) return None def _validate_printer(self): if self._printer is None: raise DeviceError( _("This operation requires a connected fiscal printer")) if not self._printer_verified: log.info('ecfui._validate_printer') if not self._printer.check_serial(): raise DeviceError( _("Fiscalprinters serial number is different!")) self._printer_verified = True def _add_ui_menus(self, appname, app, uimanager): if appname == 'pos': self._create_printer() self._add_pos_menus(uimanager) elif appname == 'till': self._create_printer() self._add_till_menus(uimanager) elif appname == 'sales': # The sales app needs the printer to check if the # sale being returned is the last sale on the ECF. self._create_printer() elif appname == 'admin': self._add_admin_menus(uimanager) app.tasks.add_item( _('Fiscal Printers'), 'fiscal-printer', 'printer', self._on_ConfigurePrinter__activate) def _remove_ui_menus(self, uimanager): if not self._ui: return uimanager.remove_ui(self._ui) self._ui = None def _add_admin_menus(self, uimanager): ui_string = """<ui> <menubar name="menubar"> <placeholder action="AppMenubarPH"> <menu action="ConfigureMenu"> <placeholder name="ConfigurePH"> <menuitem action="ConfigurePrinter"/> </placeholder> </menu> </placeholder> </menubar> </ui>""" ag = gtk.ActionGroup('ECFMenuActions') ag.add_actions([ ('ECFMenu', None, _('ECF')), ('ConfigurePrinter', None, _('Configure fiscal printer...'), None, None, self._on_ConfigurePrinter__activate), ]) uimanager.insert_action_group(ag, 0) self._ui = uimanager.add_ui_from_string(ui_string) def _add_pos_menus(self, uimanager): if sysparam(self.default_store).POS_SEPARATE_CASHIER: return ui_string = """<ui> <menubar name="menubar"> <placeholder name="ExtraMenubarPH"> <menu action="ECFMenu"> <menuitem action="CancelLastDocument"/> <menuitem action="Summary"/> <menuitem action="ReadMemory"/> </menu> </placeholder> </menubar> </ui>""" group = get_accels('plugin.ecf') ag = gtk.ActionGroup('ECFMenuActions') ag.add_actions([ ('ECFMenu', None, _('ECF')), ('ReadMemory', None, _('Read Memory'), group.get('read_memory'), None, self._on_ReadMemory__activate), ('CancelLastDocument', None, _('Cancel Last Document'), None, None, self._on_CancelLastDocument__activate), ]) ag.add_action_with_accel(self._till_summarize_action, group.get('summarize')) uimanager.insert_action_group(ag, 0) self._ui = uimanager.add_ui_from_string(ui_string) def _add_till_menus(self, uimanager): ui_string = """<ui> <menubar name="menubar"> <placeholder name="ExtraMenubarPH"> <menu action="ECFMenu"> <menuitem action="CancelLastDocument"/> <menuitem action="Summary"/> <menuitem action="ReadMemory"/> </menu> </placeholder> </menubar> </ui>""" group = get_accels('plugin.ecf') ag = gtk.ActionGroup('ECFMenuActions') ag.add_actions([ ('ECFMenu', None, _('ECF')), ('ReadMemory', None, _('Read Memory'), group.get('read_memory'), None, self._on_ReadMemory__activate), ('CancelLastDocument', None, _('Cancel Last Document'), None, None, self._on_CancelLastDocument__activate), ]) ag.add_action_with_accel(self._till_summarize_action, group.get('summarize')) uimanager.insert_action_group(ag, 0) self._ui = uimanager.add_ui_from_string(ui_string) def _check_ecf_state(self): log.info('ecfui._check_printer') try: self._validate_printer() except (DriverError, DeviceError): warning(_("It was not possible to communicate with the printer")) raise SystemExit self._has_open_coupon() def _has_open_coupon(self): self._validate_printer() if self._printer.has_open_coupon(): warning(_("The ECF has an open coupon. It will be canceled now.")) self._printer.cancel() def _open_till(self, till): log.info('ECFCouponPrinter.open_till(%r)' % (till, )) # Callsite catches DeviceError self._validate_printer() retval = True while True: try: self._printer.open_till() except CouponOpenError: self._printer.cancel() retval = False except DriverError: response = warning( _(u"It's not possible to emit a read X for the " "configured printer.\nWould you like to ignore " "this error and continue?"), buttons=((_(u"Cancel"), gtk.RESPONSE_CANCEL), (_(u"Ignore"), gtk.RESPONSE_YES), (_(u"Try Again"), gtk.RESPONSE_NONE))) if response == gtk.RESPONSE_NONE: continue elif response == gtk.RESPONSE_CANCEL: retval = False break return retval def _has_pending_reduce(self): self._validate_printer() return self._printer.has_pending_reduce() def _close_till(self, till, previous_day): log.info('ECFCouponPrinter.close_till(%r, %r)' % (till, previous_day)) # XXX: this is so ugly, but the printer stops responding # if its printing something. We should wait a little bit... # This only happens if closing the till from the current day. if not previous_day: time.sleep(4) # Callsite catches DeviceError self._validate_printer() printer = self._printer.get_printer() if (sysparam(self.default_store).ENABLE_PAULISTA_INVOICE and not (printer.user_number and printer.register_date and printer.register_cro)): response = warning( short=_(u"You need to set some details about your ECF " "if you want to save the paulista invoice file. " "Go to the admin application and fill the " "required information for the ECF."), buttons=((_(u"Cancel Close Till"), gtk.RESPONSE_CANCEL), )) return False retval = True while True: try: self._printer.close_till(previous_day=previous_day) except CouponOpenError: self._printer.cancel() retval = False except DriverError: response = warning( short=_(u"It's not possible to emit a reduce Z for the " "configured printer.\nWould you like to ignore " "this error and continue?"), buttons=((_(u"Cancel"), gtk.RESPONSE_CANCEL), (_(u"Ignore"), gtk.RESPONSE_YES), (_(u"Try Again"), gtk.RESPONSE_NONE))) if response == gtk.RESPONSE_NONE: continue elif response == gtk.RESPONSE_CANCEL: retval = False break if self._needs_cat52(printer): day = datetime.date.today() if previous_day: # XXX: Make sure this is tested day = till.opening_date dir = sysparam(self.default_store).CAT52_DEST_DIR.path dir = os.path.expanduser(dir) if not os.path.exists(dir): os.mkdir(dir) generator = StoqlibCATGenerator(self.default_store, day, printer) generator.write(dir) self._reset_last_doc() return retval def _needs_cat52(self, printer): # If the param is not enabled, we dont need. if not sysparam(self.default_store).ENABLE_PAULISTA_INVOICE: return False # Even if the parameter is enabled, we can only generate cat52 for # the printer we support, and that dont have MFD: # If the printer has an MFD, it should not be present in the # MODEL_CODES variable model = MODEL_CODES.get((printer.brand, printer.model)) if not model: return False return True def _set_last_sale(self, sale, store): printer = store.fetch(self._printer._printer) printer.last_sale = sale printer.last_till_entry = None def _set_last_till_entry(self, till_entry, store): printer = store.fetch(self._printer._printer) printer.last_till_entry = till_entry printer.last_sale = None def _reset_last_doc(self): # Last ecf document is not a sale or a till_entry anymore. store = new_store() printer = store.fetch(self._printer._printer) printer.last_till_entry = None printer.last_sale = None store.commit() def _add_cash(self, till, value): log.info('ECFCouponPrinter.add_cash(%r, %r)' % (till, value, )) # XXX: this is so ugly, but the printer stops responding # if its printing something. We should wait a little bit... time.sleep(4) # Callsite catches DeviceError self._validate_printer() self._printer.add_cash(value) def _remove_cash(self, till, value): log.info('ECFCouponPrinter.remove_cash(%r, %r)' % (till, value, )) # Callsite catches DeviceError self._validate_printer() self._printer.remove_cash(value) def _confirm_sale(self, sale): log.info('ECFCouponPrinter.confirm_sale(%r)' % (sale, )) self._validate_printer() def _coupon_create(self, fiscalcoupon): # Callsite catches DeviceError self._validate_printer() coupon = self._printer.create_coupon(fiscalcoupon) assert coupon for signal, callback in [ ('open', self._on_coupon__open), ('identify-customer', self._on_coupon__identify_customer), ('customer-identified', self._on_coupon__customer_identified), ('add-item', self._on_coupon__add_item), ('remove-item', self._on_coupon__remove_item), ('add-payments', self._on_coupon__add_payments), ('totalize', self._on_coupon__totalize), ('close', self._on_coupon__close), ('cancel', self._on_coupon__cancel), ('get-coo', self._on_coupon__get_coo), ('get-supports-duplicate-receipt', self._on_coupon__get_supports_duplicate), ('print-payment-receipt', self._on_coupon__print_payment_receipt)]: fiscalcoupon.connect_object(signal, callback, coupon) return coupon def _get_last_document(self, store): printer = self._printer.get_printer() return ECFPrinter.get_last_document(station=printer.station, store=store) def _confirm_last_document_cancel(self, last_doc): if last_doc.last_sale is None and last_doc.last_till_entry is None: info(_("There is no sale nor till entry to cancel")) return if last_doc.last_sale: msg = _("Do you really want to cancel the sale number %s " "and value %.2f ?") % (last_doc.last_sale.identifier, last_doc.last_sale.total_amount) elif last_doc.last_till_entry.value > 0: msg = _("Do you really want to cancel the last cash added " "number %d and value %.2f ?") % ( last_doc.last_till_entry.id, last_doc.last_till_entry.value) else: msg = _("Do you really want to cancel the last cash removed " "number %d and value %.2f ?") % ( last_doc.last_till_entry.id, last_doc.last_till_entry.value) return yesno(msg, gtk.RESPONSE_NO, _("Cancel Last Document"), _("Not now")) def _cancel_last_till_entry(self, last_doc, store): till_entry = store.fetch(last_doc.last_till_entry) try: self._printer.cancel_last_coupon() if till_entry.value > 0: till_entry.description = _(u"Cash out") else: till_entry.description = _(u"Cash in") till_entry.value = -till_entry.value last_doc.last_till_entry = None info(_("Document was cancelled")) except: info(_("Cancelling failed, nothing to cancel")) return def _cancel_last_sale(self, last_doc, store): if last_doc.last_sale.status == Sale.STATUS_RETURNED: return sale = store.fetch(last_doc.last_sale) returned_sale = sale.create_sale_return_adapter() returned_sale.reason = _(u"Cancelling last document on ECF") returned_sale.return_() last_doc.last_sale = None info(_("Document was cancelled")) def _cancel_last_document(self): try: self._validate_printer() except DeviceError as e: warning(str(e)) return store = new_store() last_doc = self._get_last_document(store) if not self._confirm_last_document_cancel(last_doc): store.close() return if last_doc.last_till_entry: self._cancel_last_till_entry(last_doc, store) elif last_doc.last_sale: # Verify till balance before cancel the last sale. till = Till.get_current(store) if last_doc.last_sale.total_amount > till.get_balance(): warning(_("You do not have this value on till.")) store.close() return cancelled = self._printer.cancel() if not cancelled: info(_("Cancelling sale failed, nothing to cancel")) store.close() return else: self._cancel_last_sale(last_doc, store) store.commit() def _till_summarize(self): try: self._validate_printer() except DeviceError as e: warning(str(e)) return self._printer.summarize() self._reset_last_doc() def _fiscal_memory_dialog(self): try: self._validate_printer() except DeviceError as e: warning(str(e)) return retval = run_dialog(FiscalMemoryDialog, None, self.default_store, self._printer) if retval: self._reset_last_doc() def _get_client_document(self, sale): """Returns a Settable with two attributes: document, a string with the client cpf or cnpj and document_type, being one of (FiscalSaleHistory.TYPE_CPF, FiscalSaleHistory.TYPE_CNPJ ) """ client_role = sale.get_client_role() if isinstance(client_role, Individual): document_type = FiscalSaleHistory.TYPE_CPF document = client_role.cpf elif isinstance(client_role, Company): document_type = FiscalSaleHistory.TYPE_CNPJ document = client_role.cnpj else: return if document: return Settable(document_type=document_type, document=document) def _identify_customer(self, coupon, sale=None): if not sysparam(self.default_store).ENABLE_PAULISTA_INVOICE: return model = None initial_client_document = None if sale: model = self._get_client_document(sale) # Sale may have no client. if model: initial_client_document = model.document model = run_dialog(PaulistaInvoiceDialog, self, self.default_store, model) # User cancelled the dialog or the document didn't change. if not model or model.document == initial_client_document: return coupon.identify_customer('-', '-', model.document, model.document_type) # # Events # def _on_StartApplicationEvent(self, appname, app): self._add_ui_menus(appname, app, app.window.uimanager) def _on_StopApplicationEvent(self, appname, app): self._remove_ui_menus(app.window.uimanager) def _on_SaleStatusChanged(self, sale, old_status): if sale.status == Sale.STATUS_CONFIRMED: self._confirm_sale(sale) self._set_last_sale(sale, sale.store) def _on_ECFIsLastSale(self, sale): last_doc = self._get_last_document(sale.store) return last_doc.last_sale == sale def _on_TillOpen(self, till): return self._open_till(till) def _on_TillClose(self, till, previous_day): return self._close_till(till, previous_day) def _on_TillAddCash(self, till, value): self._add_cash(till, value) def _on_TillRemoveCash(self, till, value): self._remove_cash(till, value) def _on_CouponCreatedEvent(self, coupon): self._coupon_create(coupon) def _on_AddTillEntry(self, till_entry, store): self._set_last_till_entry(till_entry, store) def _on_HasPendingReduceZ(self): return self._has_pending_reduce() def _on_HasOpenCouponEvent(self): self._has_open_coupon() # # Callbacks # def _on_coupon__open(self, coupon): self._validate_printer() if not coupon.identify_customer_at_end: self._identify_customer(coupon) coupon.open() def _on_coupon__identify_customer(self, coupon, person): if person.individual: individual = person.individual document_type = FiscalSaleHistory.TYPE_CPF document = individual.cpf elif person.company: company = person.company document_type = FiscalSaleHistory.TYPE_CNPJ document = company.cnpj else: raise TypeError( "identify_customer needs a individual or a company") name = person.name address = person.get_address_string() coupon.identify_customer(name, address, document, document_type) def _on_coupon__customer_identified(self, coupon): return coupon.is_customer_identified() def _on_coupon__add_item(self, coupon, item): self._validate_printer() return coupon.add_item(item) def _on_coupon__remove_item(self, coupon, item_id): coupon.remove_item(item_id) def _on_coupon__add_payments(self, coupon, sale): coupon.add_payments(sale) def _on_coupon__totalize(self, coupon, sale): coupon.totalize(sale) def _on_coupon__close(self, coupon, sale): if coupon.identify_customer_at_end: self._identify_customer(coupon, sale) return coupon.close(sale) def _on_coupon__cancel(self, coupon): coupon.cancel() def _on_coupon__get_coo(self, coupon): return coupon.get_coo() def _on_coupon__get_supports_duplicate(self, coupon): return coupon.supports_duplicate_receipt def _on_coupon__print_payment_receipt(self, coupon, coo, payment, value, receipt): coupon.print_payment_receipt(coo, payment, value, receipt) def _on_TillSummary__activate(self, action): self._till_summarize() def _on_ReadMemory__activate(self, action): self._fiscal_memory_dialog() def _on_CancelLastDocument__activate(self, action): self._cancel_last_document() def _on_ConfigurePrinter__activate(self, action=None): run_dialog(ECFListDialog, None) def _on_GerencialReportCancelEvent(self): self._printer._driver.gerencial_report_close() def _on_GerencialReportPrintEvent(self, receipt, close_previous=False): try: self._validate_printer() except DeviceError as e: warning(str(e)) return if close_previous: # FIXME: dont call _driver directly # Try closing any previously opened report. This is TEF specific, to # workaround the tests where they turn off the printer self._printer._driver.gerencial_report_close() self._printer.print_report(receipt) def _on_CheckECFStateEvent(self): self._check_ecf_state()
class ECFUI(object): def __init__(self): self._ui = None self.default_store = get_default_store() self._printer_verified = False # Delay printer creation until we are accessing pos or till app. Other # applications should still be accessible without a printer self._printer = None SaleStatusChangedEvent.connect(self._on_SaleStatusChanged) SaleAvoidCancelEvent.connect(self._on_SaleAvoidCancel) TillOpenEvent.connect(self._on_TillOpen) TillCloseEvent.connect(self._on_TillClose) TillAddCashEvent.connect(self._on_TillAddCash) TillAddTillEntryEvent.connect(self._on_AddTillEntry) TillRemoveCashEvent.connect(self._on_TillRemoveCash) StartApplicationEvent.connect(self._on_StartApplicationEvent) StopApplicationEvent.connect(self._on_StopApplicationEvent) CouponCreatedEvent.connect(self._on_CouponCreatedEvent) GerencialReportPrintEvent.connect(self._on_GerencialReportPrintEvent) GerencialReportCancelEvent.connect(self._on_GerencialReportCancelEvent) CheckECFStateEvent.connect(self._on_CheckECFStateEvent) HasPendingReduceZ.connect(self._on_HasPendingReduceZ) HasOpenCouponEvent.connect(self._on_HasOpenCouponEvent) EditorCreateEvent.connect(self._on_EditorCreateEvent) self._till_summarize_action = gtk.Action("Summary", _("Summary"), None, None) self._till_summarize_action.connect("activate", self._on_TillSummary__activate) add_bindings([("plugin.ecf.read_memory", "<Primary>F9"), ("plugin.ecf.summarize", "<Primary>F11")]) # # Private # def _create_printer(self): if self._printer: return self._printer station = get_current_station(self.default_store) printer = self.default_store.find(ECFPrinter, station=station, is_active=True).one() if not printer: return None try: self._printer = CouponPrinter(printer) except SerialException as e: warning(_("Error opening serial port"), str(e)) except DriverError as e: warning(str(e)) return None def _validate_printer(self): if self._printer is None: raise DeviceError(_("This operation requires a connected fiscal printer")) # Check fiscal printer setup. If something went wrong with the setup, # block the till if not self._printer._driver.setup_complete(): raise DeviceError(_("An error occurred during fiscal printer setup")) if not self._printer_verified: log.info("ecfui._validate_printer") if not self._printer.check_serial(): raise DeviceError(_("Fiscalprinters serial number is different!")) self._printer_verified = True def _add_ui_menus(self, appname, app, uimanager): self._current_app = app if appname == "pos": self._create_printer() self._add_pos_menus(uimanager) elif appname == "till": self._create_printer() self._add_till_menus(uimanager) elif appname == "sales": # The sales app needs the printer to check if the # sale being returned is the last sale on the ECF. self._create_printer() elif appname == "admin": self._add_admin_menus(uimanager) app.tasks.add_item(_("Fiscal Printers"), "fiscal-printer", "printer", self._on_ConfigurePrinter__activate) def _remove_ui_menus(self, uimanager): if not self._ui: return uimanager.remove_ui(self._ui) self._ui = None def _add_admin_menus(self, uimanager): ui_string = """<ui> <menubar name="menubar"> <placeholder action="AppMenubarPH"> <menu action="ConfigureMenu"> <placeholder name="ConfigurePH"> <menuitem action="ConfigurePrinter"/> </placeholder> </menu> </placeholder> </menubar> </ui>""" ag = gtk.ActionGroup("ECFMenuActions") ag.add_actions( [ ("ECFMenu", None, _("ECF")), ( "ConfigurePrinter", None, _("Configure fiscal printer..."), None, None, self._on_ConfigurePrinter__activate, ), ] ) uimanager.insert_action_group(ag, 0) self._ui = uimanager.add_ui_from_string(ui_string) def _add_ecf_menu(self, uimanager): ui_string = """<ui> <menubar name="menubar"> <placeholder name="ExtraMenubarPH"> <menu action="ECFMenu"> <menuitem action="CancelLastDocument"/> <menuitem action="Summary"/> <menuitem action="ReadMemory"/> </menu> </placeholder> </menubar> </ui>""" group = get_accels("plugin.ecf") ag = gtk.ActionGroup("ECFMenuActions") ag.add_actions( [ ("ECFMenu", None, _("ECF")), ("ReadMemory", None, _("Read Memory"), group.get("read_memory"), None, self._on_ReadMemory__activate), ( "CancelLastDocument", None, _("Cancel Last Document"), None, None, self._on_CancelLastDocument__activate, ), ] ) ag.add_action_with_accel(self._till_summarize_action, group.get("summarize")) can_cancel = sysparam.get_bool("ALLOW_CANCEL_LAST_COUPON") cancel_coupon_menu = ag.get_action("CancelLastDocument") cancel_coupon_menu.set_visible(can_cancel) uimanager.insert_action_group(ag, 0) self._ui = uimanager.add_ui_from_string(ui_string) def _add_pos_menus(self, uimanager): if sysparam.get_bool("POS_SEPARATE_CASHIER"): return self._add_ecf_menu(uimanager) def _add_till_menus(self, uimanager): self._add_ecf_menu(uimanager) def _check_ecf_state(self): log.info("ecfui._check_printer") try: self._validate_printer() except (DriverError, DeviceError): warning(_("It was not possible to communicate with the printer")) raise SystemExit self._has_open_coupon() def _has_open_coupon(self): self._validate_printer() if self._printer.has_open_coupon(): warning(_("The ECF has an open coupon. It will be canceled now.")) self._printer.cancel() def _open_till(self, till): log.info("ECFCouponPrinter.open_till(%r)" % (till,)) # Callsite catches DeviceError self._validate_printer() retval = True while True: try: self._printer.open_till() except CouponOpenError: self._printer.cancel() retval = False except DriverError: response = warning( _( u"It's not possible to emit a read X for the " "configured printer.\nWould you like to ignore " "this error and continue?" ), buttons=( (_(u"Cancel"), gtk.RESPONSE_CANCEL), (_(u"Ignore"), gtk.RESPONSE_YES), (_(u"Try Again"), gtk.RESPONSE_NONE), ), ) if response == gtk.RESPONSE_NONE: continue elif response == gtk.RESPONSE_CANCEL: retval = False break return retval def _has_pending_reduce(self): self._validate_printer() return self._printer.has_pending_reduce() def _close_till(self, till, previous_day): log.info("ECFCouponPrinter.close_till(%r, %r)" % (till, previous_day)) # XXX: this is so ugly, but the printer stops responding # if its printing something. We should wait a little bit... # This only happens if closing the till from the current day. if not previous_day: time.sleep(4) # Callsite catches DeviceError self._validate_printer() printer = self._printer.get_printer() if sysparam.get_bool("ENABLE_DOCUMENT_ON_INVOICE") and not ( printer.user_number and printer.register_date and printer.register_cro ): response = warning( short=_( u"You need to set some details about your ECF " "if you want to save the paulista invoice file. " "Go to the admin application and fill the " "required information for the ECF." ), buttons=((_(u"Cancel Close Till"), gtk.RESPONSE_CANCEL),), ) return False retval = True while True: try: self._printer.close_till(previous_day=previous_day) except CouponOpenError: self._printer.cancel() retval = False except DriverError: response = warning( short=_( u"It's not possible to emit a reduce Z for the " "configured printer.\nWould you like to ignore " "this error and continue?" ), buttons=( (_(u"Cancel"), gtk.RESPONSE_CANCEL), (_(u"Ignore"), gtk.RESPONSE_YES), (_(u"Try Again"), gtk.RESPONSE_NONE), ), ) if response == gtk.RESPONSE_NONE: continue elif response == gtk.RESPONSE_CANCEL: retval = False break if self._needs_cat52(printer): day = datetime.date.today() if previous_day: # XXX: Make sure this is tested day = till.opening_date dir = sysparam.get_string("CAT52_DEST_DIR") dir = os.path.expanduser(dir) if not os.path.exists(dir): os.mkdir(dir) generator = StoqlibCATGenerator(self.default_store, day, printer) generator.write(dir) self._reset_last_doc() return retval def _needs_cat52(self, printer): # If the param is not enabled, we dont need. if not sysparam.get_bool("ENABLE_DOCUMENT_ON_INVOICE"): return False # Even if the parameter is enabled, we can only generate cat52 for # the printer we support, and that dont have MFD: # If the printer has an MFD, it should not be present in the # MODEL_CODES variable model = MODEL_CODES.get((printer.brand, printer.model)) if not model: return False return True def _set_last_sale(self, sale, store): printer = store.fetch(self._printer._printer) printer.last_sale = sale printer.last_till_entry = None def _set_last_till_entry(self, till_entry, store): printer = store.fetch(self._printer._printer) printer.last_till_entry = till_entry printer.last_sale = None def _reset_last_doc(self): # Last ecf document is not a sale or a till_entry anymore. store = new_store() printer = store.fetch(self._printer._printer) printer.last_till_entry = None printer.last_sale = None store.commit() def _add_cash(self, till, value): log.info("ECFCouponPrinter.add_cash(%r, %r)" % (till, value)) # XXX: this is so ugly, but the printer stops responding # if its printing something. We should wait a little bit... time.sleep(4) # Callsite catches DeviceError self._validate_printer() self._printer.add_cash(value) def _remove_cash(self, till, value): log.info("ECFCouponPrinter.remove_cash(%r, %r)" % (till, value)) # Callsite catches DeviceError self._validate_printer() self._printer.remove_cash(value) def _confirm_sale(self, sale): log.info("ECFCouponPrinter.confirm_sale(%r)" % (sale,)) self._validate_printer() def _coupon_create(self, fiscalcoupon, sale): # External sales are an exception to the general rule and should not # generate an ecf. if sale and sale.is_external(): return # Callsite catches DeviceError self._validate_printer() coupon = self._printer.create_coupon(fiscalcoupon) assert coupon for signal, callback in [ ("open", self._on_coupon__open), ("identify-customer", self._on_coupon__identify_customer), ("customer-identified", self._on_coupon__customer_identified), ("add-item", self._on_coupon__add_item), ("remove-item", self._on_coupon__remove_item), ("add-payments", self._on_coupon__add_payments), ("totalize", self._on_coupon__totalize), ("close", self._on_coupon__close), ("cancel", self._on_coupon__cancel), ("get-coo", self._on_coupon__get_coo), ("get-supports-duplicate-receipt", self._on_coupon__get_supports_duplicate), ("print-payment-receipt", self._on_coupon__print_payment_receipt), ]: fiscalcoupon.connect_object(signal, callback, coupon) return coupon def _get_last_document(self, store): station = api.get_current_station(store) return ECFPrinter.get_last_document(station=station, store=store) def _is_ecf_last_sale(self, sale): store = sale.store is_last_sale = store.find(ECFPrinter, last_sale=sale).count() > 0 return is_last_sale def _confirm_last_document_cancel(self, last_doc): if last_doc.last_sale is None and last_doc.last_till_entry is None: info(_("There is no sale nor till entry to cancel")) return if last_doc.last_sale: msg = _("Do you really want to cancel the sale number %s " "and value %.2f ?") % ( last_doc.last_sale.identifier, last_doc.last_sale.total_amount, ) elif last_doc.last_till_entry.value > 0: msg = _("Do you really want to cancel the last cash added " "number %d and value %.2f ?") % ( last_doc.last_till_entry.identifier, last_doc.last_till_entry.value, ) else: msg = _("Do you really want to cancel the last cash removed " "number %d and value %.2f ?") % ( last_doc.last_till_entry.identifier, last_doc.last_till_entry.value, ) return yesno(msg, gtk.RESPONSE_NO, _("Cancel Last Document"), _("Not now")) def _cancel_last_till_entry(self, last_doc, store): last_till_entry = store.fetch(last_doc.last_till_entry) value = last_till_entry.value till = Till.get_current(store) try: self._printer.cancel_last_coupon() if last_till_entry.value > 0: till_entry = till.add_debit_entry( # TRANSLATORS: cash out = sangria, cash in = suprimento value, _(u"Cash out: last cash in cancelled"), ) self._set_last_till_entry(till_entry, store) else: till_entry = till.add_credit_entry( # TRANSLATORS: cash out = sangria, cash in = suprimento -value, _(u"Cash in: last cash out cancelled"), ) self._set_last_till_entry(till_entry, store) info(_("Document was cancelled")) except: info(_("Cancelling failed, nothing to cancel")) return def _cancel_last_sale(self, last_doc, store): if last_doc.last_sale.status == Sale.STATUS_RETURNED: return sale = store.fetch(last_doc.last_sale) value = sale.total_amount sale.cancel(force=True) comment = _(u"Cancelling last document on ECF") SaleComment(store=store, sale=sale, comment=comment, author=api.get_current_user(store)) till = Till.get_current(store) # TRANSLATORS: cash out = sangria till.add_debit_entry(value, _(u"Cash out: last sale cancelled")) last_doc.last_sale = None info(_("Document was cancelled")) def _cancel_last_document(self): try: self._validate_printer() except DeviceError as e: warning(str(e)) return store = new_store() last_doc = self._get_last_document(store) if not self._confirm_last_document_cancel(last_doc): store.close() return if last_doc.last_till_entry: self._cancel_last_till_entry(last_doc, store) elif last_doc.last_sale: # Verify till balance before cancel the last sale. till = Till.get_current(store) if last_doc.last_sale.total_amount > till.get_balance(): warning(_("You do not have this value on till.")) store.close() return cancelled = self._printer.cancel_last_coupon() if not cancelled: info(_("Cancelling sale failed, nothing to cancel")) store.close() return else: self._cancel_last_sale(last_doc, store) store.commit() def _till_summarize(self): try: self._validate_printer() except DeviceError as e: warning(str(e)) return self._printer.summarize() self._reset_last_doc() def _fiscal_memory_dialog(self): try: self._validate_printer() except DeviceError as e: warning(str(e)) return retval = run_dialog(FiscalMemoryDialog, None, self.default_store, self._printer) if retval: self._reset_last_doc() def _get_client_document(self, sale): """Returns a Settable with two attributes: document, a string with the client cpf or cnpj and document_type, being one of (FiscalSaleHistory.TYPE_CPF, FiscalSaleHistory.TYPE_CNPJ ) """ client_role = sale.get_client_role() if isinstance(client_role, Individual): document_type = FiscalSaleHistory.TYPE_CPF document = client_role.cpf elif isinstance(client_role, Company): document_type = FiscalSaleHistory.TYPE_CNPJ document = client_role.cnpj else: return if document: return Settable(document_type=document_type, document=document) def _identify_customer(self, coupon, sale=None): if not sysparam.get_bool("ENABLE_DOCUMENT_ON_INVOICE"): return model = None initial_client_document = None if sale: model = self._get_client_document(sale) # Sale may have no client. if model: initial_client_document = model.document model = run_dialog(PaulistaInvoiceDialog, self._current_app, self.default_store, model) # The user has chosen the 'without cpf' option, but we still need to # inform a invalid CPF, otherwise the current client's cpf will be used if not model: coupon.identify_customer("-", "-", u"", None) return # The document didn't change. if model.document == initial_client_document: return coupon.identify_customer("-", "-", model.document, model.document_type) # # Events # def _on_StartApplicationEvent(self, appname, app): self._add_ui_menus(appname, app, app.window.uimanager) def _on_StopApplicationEvent(self, appname, app): self._remove_ui_menus(app.window.uimanager) def _on_SaleStatusChanged(self, sale, old_status): if sale.status == Sale.STATUS_CONFIRMED: self._confirm_sale(sale) self._set_last_sale(sale, sale.store) def _on_SaleAvoidCancel(self, sale): if self._is_ecf_last_sale(sale): info(_("That is last sale in ECF. Return using the menu " "ECF - Cancel Last Document")) return True return False def _on_TillOpen(self, till): return self._open_till(till) def _on_TillClose(self, till, previous_day): return self._close_till(till, previous_day) def _on_TillAddCash(self, till, value): self._add_cash(till, value) def _on_TillRemoveCash(self, till, value): self._remove_cash(till, value) def _on_CouponCreatedEvent(self, coupon, sale): self._coupon_create(coupon, sale) def _on_AddTillEntry(self, till_entry, store): self._set_last_till_entry(till_entry, store) def _on_HasPendingReduceZ(self): return self._has_pending_reduce() def _on_HasOpenCouponEvent(self): self._has_open_coupon() # # Callbacks # def _on_coupon__open(self, coupon): self._validate_printer() if not coupon.identify_customer_at_end: self._identify_customer(coupon) coupon.open() def _on_coupon__identify_customer(self, coupon, person): if person.individual: individual = person.individual document_type = FiscalSaleHistory.TYPE_CPF document = individual.cpf elif person.company: company = person.company document_type = FiscalSaleHistory.TYPE_CNPJ document = company.cnpj else: raise TypeError("identify_customer needs a individual or a company") name = person.name address = person.get_address_string() coupon.identify_customer(name, address, document, document_type) def _on_coupon__customer_identified(self, coupon): return coupon.is_customer_identified() def _on_coupon__add_item(self, coupon, item): self._validate_printer() return coupon.add_item(item) def _on_coupon__remove_item(self, coupon, item_id): coupon.remove_item(item_id) def _on_coupon__add_payments(self, coupon, sale): coupon.add_payments(sale) def _on_coupon__totalize(self, coupon, sale): coupon.totalize(sale) def _on_coupon__close(self, coupon, sale): if coupon.identify_customer_at_end: self._identify_customer(coupon, sale) return coupon.close(sale) def _on_coupon__cancel(self, coupon): coupon.cancel() def _on_coupon__get_coo(self, coupon): return coupon.get_coo() def _on_coupon__get_supports_duplicate(self, coupon): return coupon.supports_duplicate_receipt def _on_coupon__print_payment_receipt(self, coupon, coo, payment, value, receipt): coupon.print_payment_receipt(coo, payment, value, receipt) def _on_TillSummary__activate(self, action): self._till_summarize() def _on_ReadMemory__activate(self, action): self._fiscal_memory_dialog() def _on_CancelLastDocument__activate(self, action): self._cancel_last_document() def _on_ConfigurePrinter__activate(self, action=None): run_dialog(ECFListDialog, None) def _on_GerencialReportCancelEvent(self): self._printer._driver.gerencial_report_close() def _on_GerencialReportPrintEvent(self, receipt, close_previous=False): try: self._validate_printer() except DeviceError as e: warning(str(e)) return if close_previous: # FIXME: dont call _driver directly # Try closing any previously opened report. This is TEF specific, to # workaround the tests where they turn off the printer self._printer._driver.gerencial_report_close() self._printer.print_report(receipt) def _on_CheckECFStateEvent(self): self._check_ecf_state() def _on_EditorCreateEvent(self, editor, model, store, *args): from stoqlib.gui.dialogs.saledetails import SaleDetailsDialog manager = get_plugin_manager() nfe_active = manager.is_active("nfe") if not nfe_active and isinstance(editor, SaleDetailsDialog): # Only display the coupon number if the nfe is not active. editor.invoice_label.set_text(_("Coupon Number")) editor.invoice_number.update(model.coupon_id)
def create_coupon_printer(self, printer=None): return CouponPrinter(printer or self.create_ecf_printer())