# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
##
## Copyright (C) 2005-2012 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>
##
##
""" Sale wizard definition """
import decimal
import gtk
from kiwi.component import get_utility
from kiwi.currency import currency, format_price
from kiwi.datatypes import ValidationError
from kiwi.ui.objectlist import Column
from stoqlib.api import api
from stoqlib.domain.costcenter import CostCenter
from stoqlib.domain.events import CreatePaymentEvent
from stoqlib.domain.fiscal import CfopData
from stoqlib.domain.payment.card import CreditProvider
from stoqlib.domain.payment.method import PaymentMethod
from stoqlib.domain.payment.payment import Payment
from stoqlib.domain.person import SalesPerson, Transporter, Person, Client
from stoqlib.domain.sale import Sale, SaleComment
from stoqlib.enums import CreatePaymentStatus, ChangeSalespersonPolicy
from stoqlib.exceptions import SellError, StoqlibError, PaymentMethodError
from stoqlib.lib.formatters import get_formatted_cost
from stoqlib.lib.message import warning, marker, yesno
from stoqlib.lib.parameters import sysparam
from stoqlib.lib.pluginmanager import get_plugin_manager
from stoqlib.lib.translation import stoqlib_gettext
from stoqlib.gui.base.wizards import WizardEditorStep, BaseWizard, BaseWizardStep
from stoqlib.gui.base.dialogs import run_dialog
from stoqlib.gui.dialogs.batchselectiondialog import BatchDecreaseSelectionDialog
from stoqlib.gui.dialogs.missingitemsdialog import (get_missing_items,
MissingItemsDialog)
from stoqlib.gui.editors.fiscaleditor import CfopEditor
from stoqlib.gui.editors.noteeditor import NoteEditor
from stoqlib.gui.editors.personeditor import ClientEditor, TransporterEditor
from stoqlib.gui.events import (ConfirmSaleWizardFinishEvent,
StockOperationPersonValidationEvent,
InvoiceSetupEvent)
from stoqlib.gui.interfaces import IDomainSlaveMapper
from stoqlib.gui.slaves.cashchangeslave import CashChangeSlave
from stoqlib.gui.slaves.paymentmethodslave import SelectPaymentMethodSlave
from stoqlib.gui.slaves.paymentslave import (register_payment_slaves,
MultipleMethodSlave)
from stoqlib.gui.slaves.saleslave import SaleDiscountSlave
from stoqlib.gui.utils.printing import print_report
from stoqlib.gui.widgets.queryentry import ClientEntryGadget
from stoqlib.gui.wizards.personwizard import run_person_role_dialog
from stoqlib.reporting.sale import SaleOrderReport
N_ = _ = stoqlib_gettext
class _TemporarySaleItem(object):
def __init__(self, item):
sellable = item.sellable
self.storable = sellable.product_storable
assert self.storable and self.storable.is_batch
self.code = sellable.code
self.barcode = sellable.barcode
self.sale_item = item
self.description = sellable.description
self.category_description = sellable.get_category_description()
self.price = item.price
self.original_quantity = item.quantity
self.batches = {}
@property
def quantity(self):
return sum(quantity for quantity in self.batches.values())
@property
def total(self):
return currency(self.price * self.quantity)
@property
def need_adjust_batch(self):
return self.original_quantity != self.quantity
class _SaleBatchDecreaseSelectionDialog(BatchDecreaseSelectionDialog):
# We are finishing the sale here and this dialog will be used to select
# batches to decrease. We cannot decrease more than we sold
validate_max_quantity = True
#
# Wizard Steps
#
[docs]class PaymentMethodStep(BaseWizardStep):
gladefile = 'HolderTemplate'
slave_holder = 'place_holder'
def __init__(self, wizard, previous, store, model, method,
outstanding_value=None, finish_on_total=True):
"""
:param wizard: the wizard this step is in
:param previous: the previous step if there is any
:param store: the store this step is executed
:param model: the model of this step
:param method: the payment method
:param finish_on_total: if it is ``True`` automatically closes
the wizard when payments total is equals to the total cost
of the operation. When it is ``False``, waits for the user to
click the finish button
:param outstanding_value: if this value is not ``None``, it will
be used as the total value of the payment
"""
self._method_name = method
self._method_slave = None
self.model = model
if outstanding_value is None:
outstanding_value = currency(0)
self._outstanding_value = outstanding_value
self._finish_on_total = finish_on_total
BaseWizardStep.__init__(self, store, wizard, previous)
register_payment_slaves()
self._create_ui()
def _create_ui(self):
slave = self._create_slave(self._method_name)
self._attach_slave(slave)
def _create_slave(self, method):
dsm = get_utility(IDomainSlaveMapper)
slave_class = dsm.get_slave_class(method)
assert slave_class
method = self.store.fetch(method)
if slave_class is MultipleMethodSlave:
slave = slave_class(self.wizard, self, self.store, self.model,
method, outstanding_value=self._outstanding_value,
finish_on_total=self._finish_on_total,
allow_remove_paid=False)
else:
slave = slave_class(self.wizard, self, self.store, self.model,
method, outstanding_value=self._outstanding_value)
self._method_slave = slave
return slave
def _attach_slave(self, slave):
if self.get_slave(self.slave_holder):
self.detach_slave(self.slave_holder)
self.attach_slave(self.slave_holder, slave)
#
# WizardStep hooks
#
[docs] def validate_step(self):
return self._method_slave.finish()
[docs] def has_next_step(self):
return self._method_slave.has_next_step()
[docs] def next_step(self):
return self._method_slave.next_step()
[docs] def post_init(self):
self._method_slave.update_view()
[docs]class BaseMethodSelectionStep(object):
"""Base class for method selection when doing client sales
Classes using this base class should have a select_method_holder EventBox
and a cash_change_holder EventBox in the glade file
"""
#
# Private API
#
def _update_next_step(self, method):
received_value = self.cash_change_slave.received_value
if method and method.method_name == u'money':
self.wizard.enable_finish()
if self.wizard.need_create_payment():
self.cash_change_slave.enable_cash_change()
else:
# In this case, the user has already paid more than the total
# sale amount.
self.cash_change_slave.disable_cash_change()
elif method and method.method_name == u'credit':
self.wizard.enable_finish()
if self.wizard.need_create_payment():
received_value.set_text(format_price(self.get_remaining_value()))
self.cash_change_slave.disable_cash_change()
else:
self.wizard.disable_finish()
if self.wizard.need_create_payment():
received_value.set_text(format_price(self.get_remaining_value()))
else:
self.wizard.enable_finish()
self.cash_change_slave.disable_cash_change()
def _create_change_payment(self):
if self.cash_change_slave.credit_checkbutton.get_active():
method_name = u'credit'
else:
method_name = u'money'
payments_value = self.model.group.get_total_confirmed_value()
sale_total = self.model.get_total_sale_amount()
# To have reached this far, the payments value must be greater than the
# sale total
assert payments_value > sale_total, (payments_value, sale_total)
method = PaymentMethod.get_by_name(self.store, method_name)
description = _(u'%s returned for sale %s') % (method.description,
self.model.identifier)
payment = method.create_payment(Payment.TYPE_OUT,
payment_group=self.model.group,
branch=self.model.branch,
value=(payments_value - sale_total),
description=description)
payment.set_pending()
if method_name == u'credit':
payment.pay()
#
# Public API
#
# FIXME This should be on Sale domain but the domain needs to be refactored
[docs] def get_remaining_value(self):
sale_total = self.model.get_total_sale_amount()
payments_value = self.model.group.get_total_confirmed_value()
return sale_total - payments_value
[docs] def get_selected_method(self):
return self.pm_slave.get_selected_method()
[docs] def setup_cash_payment(self, total=None):
money_method = PaymentMethod.get_by_name(self.store, u'money')
total = total or self.wizard.get_total_to_pay()
try:
return money_method.create_payment(Payment.TYPE_IN, self.model.group,
self.model.branch, total)
except PaymentMethodError as err:
warning(str(err))
#
# WizardStep hooks
#
[docs] def post_init(self):
if not self.wizard.need_create_payment():
for widget in [self.select_method_holder,
self.subtotal_expander]:
widget.hide()
self.pm_slave.connect('method-changed', self.on_payment_method_changed)
self._update_next_step(self.pm_slave.get_selected_method())
[docs] def setup_slaves(self):
marker('SelectPaymentMethodSlave')
self.pm_slave = SelectPaymentMethodSlave(store=self.store,
payment_type=Payment.TYPE_IN)
self.attach_slave('select_method_holder', self.pm_slave)
marker('CashChangeSlave')
self.cash_change_slave = CashChangeSlave(self.store, self.model, self.wizard)
self.attach_slave('cash_change_holder', self.cash_change_slave)
self.cash_change_slave.received_value.connect(
'activate', lambda entry: self.wizard.go_to_next())
[docs] def next_step(self):
if not self.wizard.need_create_payment():
if self.cash_change_slave.credit_checkbutton.get_active():
self._create_change_payment()
return
selected_method = self.get_selected_method()
if selected_method.method_name == u'money':
if not self.cash_change_slave.can_finish():
warning(_(u"Invalid value, please verify if it was "
"properly typed."))
self.cash_change_slave.received_value.select_region(
0, len(self.cash_change_slave.received_value.get_text()))
self.cash_change_slave.received_value.grab_focus()
return self
# We have to modify the payment, so the fiscal printer can
# calculate and print the payback, if necessary.
payment = self.setup_cash_payment()
if payment is None:
return
total = self.cash_change_slave.get_received_value()
payment.base_value = total
# Return None here means call wizard.finish, which is exactly
# what we need
return None
elif selected_method.method_name == u'credit':
client = self.model.client
total = self.wizard.get_total_to_pay()
assert client.can_purchase(selected_method, total)
try:
payment = selected_method.create_payment(
Payment.TYPE_IN, self.model.group, self.model.branch, total)
except PaymentMethodError as err:
warning(str(err))
return self
# Return None here means call wizard.finish, which is exactly
# what we need
return None
elif selected_method.method_name == u'store_credit':
client = self.model.client
total = self.wizard.get_total_to_pay()
assert client.can_purchase(selected_method, total)
step_class = PaymentMethodStep
elif selected_method.method_name == 'card':
providers = CreditProvider.get_card_providers(self.store)
if providers.is_empty():
warning(_("You need active credit providers to use the "
"card payment method."))
return self
step_class = PaymentMethodStep
else:
step_class = PaymentMethodStep
retval = CreatePaymentEvent.emit(selected_method, self.model,
self.store)
# None means no one catched this event
if retval is None or retval == CreatePaymentStatus.UNHANDLED:
# FIXME: We cannot send outstanding_value to multiple editor
# since if we have a trade going on, it will be calculated wrong
if selected_method.method_name == 'multiple':
outstanding_value = None
else:
outstanding_value = self.wizard.get_total_to_pay()
manager = get_plugin_manager()
return step_class(self.wizard, self, self.store, self.model,
selected_method,
outstanding_value=outstanding_value,
finish_on_total=manager.is_active('tef'))
# finish the wizard
if retval == CreatePaymentStatus.SUCCESS:
return None
# returning self to stay on this step
return self
#
# Callbacks
#
[docs] def on_payment_method_changed(self, slave, method_name):
self._update_next_step(method_name)
[docs]class ConfirmSaleBatchStep(WizardEditorStep):
"""Step for selecting |batches| for sale items
Before going to :class:`.SalesPersonStep`, if a product is controlling
batch and that information is not available (probably because the
sale is quoted) this step will set it for you.
Note that each item can produce n items, n being the number of
batches used for it. All of those items will be already on the
sale and adjusted properly, tough.
"""
gladefile = 'ConfirmSaleBatchStep'
model_type = Sale
#
# WizardEditorStep
#
[docs] def post_init(self):
# If the user goes back from the previous step, make sure
# things don't get messed
if self.store.savepoint_exists('before_salesperson_step'):
self.store.rollback_to_savepoint('before_salesperson_step')
self.register_validate_function(self._validation_func)
self.force_validation()
[docs] def setup_proxies(self):
self._setup_widgets()
self.force_validation()
[docs] def next_step(self):
self.store.savepoint('before_salesperson_step')
marker('running SalesPersonStep')
self._update_sale_items()
step = SalesPersonStep(self.wizard, self.store, self.model,
self.wizard.payment_group)
marker('finished creating SalesPersonStep')
return step
#
# Private
#
def _setup_widgets(self):
self.sale_items.set_columns([
Column('code', title=_('Code'),
data_type=str, visible=False),
Column('barcode', title=_('Barcode'),
data_type=str, visible=False),
Column('description', title=_('Description'),
data_type=str, expand=True),
Column('category_description', title=_('Category'),
data_type=str),
Column('original_quantity', title=_('Quantity'),
data_type=decimal.Decimal),
Column('quantity', title=_('Adjusted qty'),
data_type=decimal.Decimal),
Column('price', title=_('price'), data_type=currency,
format_func=get_formatted_cost),
Column('total', title=_('Total'), data_type=currency)])
self.sale_items.extend(self._get_sale_items())
self.sale_items.set_cell_data_func(self._on_sale_items__cell_data_func)
def _get_sale_items(self):
for item in self.model.get_items_missing_batch():
yield _TemporarySaleItem(item)
def _edit_item(self, item):
retval = run_dialog(_SaleBatchDecreaseSelectionDialog, self.wizard,
store=self.store, model=item.storable,
quantity=item.original_quantity,
original_batches=item.batches)
item.batches = retval or item.batches
self.sale_items.update(item)
self.force_validation()
def _validation_func(self, value):
need_adjust_batch = any(i.need_adjust_batch for i in self.sale_items)
self.wizard.refresh_next(value and not need_adjust_batch)
def _update_sale_items(self):
for temp_item in self.sale_items:
sale_item = temp_item.sale_item
if temp_item.batches:
sale_item.set_batches(temp_item.batches)
#
# Callbacks
#
def _on_sale_items__cell_data_func(self, column, renderer, obj, text):
if not isinstance(renderer, gtk.CellRendererText):
return text
# Set red to provide a visual indication for the user that
# the item needs to be adjusted
renderer.set_property('foreground', 'red')
renderer.set_property('foreground-set', obj.need_adjust_batch)
return text
[docs] def on_sale_items__selection_changed(self, sale_items, item):
self.edit_btn.set_sensitive(bool(item))
[docs] def on_sale_items__row_activated(self, sale_items, item):
self._edit_item(item)
[docs] def on_edit_btn__clicked(self, button):
item = self.sale_items.get_selected()
self._edit_item(item)
[docs]class SalesPersonStep(BaseMethodSelectionStep, WizardEditorStep):
""" An abstract step which allows to define a salesperson, the sale's
discount and surcharge, when it is needed.
"""
gladefile = 'SalesPersonStep'
model_type = Sale
proxy_widgets = ['salesperson',
'client',
'transporter',
'cost_center']
cfop_widgets = ('cfop', )
def __init__(self, wizard, store, model, payment_group, previous=None):
self.pm_slave = None
self.payment_group = payment_group
BaseMethodSelectionStep.__init__(self)
marker("WizardEditorStep.__init__")
WizardEditorStep.__init__(self, store, wizard, model,
previous=previous)
self._update_totals()
self.update_discount_and_surcharge()
#
# Private API
#
def _update_totals(self):
subtotal = self.wizard.get_subtotal()
self.subtotal_lbl.update(subtotal)
total_paid = self.wizard.get_total_paid()
self.total_paid_lbl.update(total_paid)
to_pay = self.model.get_total_sale_amount(subtotal=subtotal) - total_paid
self.cash_change_slave.update_total_sale_amount(to_pay)
self.total_lbl.update(to_pay)
def _update_sale_client(self):
"""Update the sale client based on the informed document
If the sale does not have a client yet, and the current_document (informed by
the ecf plugin) is set, and a person with the given document exists, that client
will be associated with this sale.
"""
if self.model.client or not self.wizard._current_document:
return
person = Person.get_by_document(self.store,
unicode(self.wizard._current_document))
if not person:
return
if person.client:
self.model.client = person.client
else:
self.model.client = Client(store=self.store, person=person)
def _setup_clients_widget(self):
self._update_sale_client()
marker('Filling clients')
self.client_gadget = ClientEntryGadget(
entry=self.client,
store=self.store,
initial_value=self.model.client,
parent=self.wizard,
run_editor=self._run_client_editor)
marker('Filled clients')
def _run_client_editor(self, store, model, description=None,
visual_mode=False):
return run_person_role_dialog(ClientEditor, self.wizard, store, model,
document=self.wizard._current_document,
description=description,
visual_mode=visual_mode)
def _fill_transporter_combo(self):
marker('Filling transporters')
transporters = Transporter.get_active_transporters(self.store)
items = api.for_person_combo(transporters)
self.transporter.prefill(items)
self.transporter.set_sensitive(len(items))
marker('Filled transporters')
def _fill_cost_center_combo(self):
marker('Filling cost centers')
cost_centers = CostCenter.get_active(self.store)
# we keep this value because each call to is_empty() is a new sql query
# to the database
cost_centers_exists = not cost_centers.is_empty()
if cost_centers_exists:
self.cost_center.prefill(api.for_combo(cost_centers, attr='name',
empty=_('No cost center.')))
self.cost_center.set_visible(cost_centers_exists)
self.cost_center_lbl.set_visible(cost_centers_exists)
marker('Filled cost centers')
def _fill_cfop_combo(self):
marker('Filling CFOPs')
cfops = CfopData.get_for_sale(self.store)
self.cfop.prefill(api.for_combo(cfops))
marker('Filled CFOPs')
#
# Public API
#
[docs] def update_discount_and_surcharge(self):
marker("update_discount_and_surcharge")
# Here we need avoid to reset sale data defined when creating the
# Sale in the POS application, i.e, we should not reset the
# discount and surcharge if they are already set (this is the
# case when one of the parameters, CONFIRM_SALES_ON_TILL or
# USE_TRADE_AS_DISCOUNT is enabled).
if (not sysparam.get_bool('CONFIRM_SALES_ON_TILL') and
not sysparam.get_bool('USE_TRADE_AS_DISCOUNT')):
self.model.discount_value = currency(0)
self.model.surcharge_value = currency(0)
def _refresh_next(self, validation_value):
self.client.validate(force=True)
client_valid = self.client.is_valid()
self.wizard.refresh_next(validation_value and client_valid)
#
# WizardStep hooks
#
[docs] def post_init(self):
BaseMethodSelectionStep.post_init(self)
marker('Entering post_init')
if self.wizard.need_create_payment():
self.wizard.payment_group.clear_unused()
self.register_validate_function(self._refresh_next)
self._update_next_step(self.get_selected_method())
# If there's no salesperson, keep the focus there as it should be
# selected first to have a nice flow
if (hasattr(self, 'cash_change_slave') and
self.model.salesperson is not None):
self.cash_change_slave.received_value.grab_focus()
self.force_validation()
self.data_table.set_focus_chain([
self.salesperson,
self.invoice_number_holder,
self.client_gadget.box,
self.transporter,
self.create_transporter,
self.cfop,
self.create_cfop,
])
marker('Leaving post_init')
[docs] def setup_slaves(self):
marker('Setting up slaves')
BaseMethodSelectionStep.setup_slaves(self)
marker('Finished parent')
self.pm_slave.set_client(self.model.client,
total_amount=self.wizard.get_total_to_pay())
marker('Setting discount')
self.discount_slave = SaleDiscountSlave(self.store, self.model,
self.model_type)
if sysparam.get_bool('USE_TRADE_AS_DISCOUNT'):
self.subtotal_expander.set_expanded(True)
self.discount_slave.discount_value_ck.set_active(True)
self.discount_slave.update_sale_discount()
marker('Finshed setting up discount')
self.discount_slave.connect('discount-changed',
self.on_discount_slave_changed)
slave_holder = 'discount_surcharge_slave'
if self.get_slave(slave_holder):
self.detach_slave(slave_holder)
self.attach_slave(slave_holder, self.discount_slave)
marker('Finished setting up slaves')
[docs] def setup_proxies(self):
marker('Setting up proxies')
self.setup_widgets()
self.proxy = self.add_proxy(self.model, self.proxy_widgets)
if self.model.client:
self.client_gadget.set_editable(False)
if sysparam.get_bool('ASK_SALES_CFOP'):
self.add_proxy(self.model, SalesPersonStep.cfop_widgets)
marker('Finished setting up proxies')
#
# Callbacks
#
[docs] def key_F5(self):
self.salesperson.grab_focus()
[docs] def key_F6(self):
self.client.grab_focus()
[docs] def key_F7(self):
self.transporter.grab_focus()
[docs] def key_F8(self):
self.cash_change_slave.received_value.grab_focus()
[docs] def on_client__content_changed(self, entry):
# This gets called before setup_slaves, but we must wait until slaves
# are setup correctly
if not self.pm_slave:
return
self.discount_slave.update_max_discount()
self.pm_slave.set_client(
client=self.model.client,
total_amount=self.wizard.get_total_to_pay())
[docs] def on_payment_method_changed(self, slave, method):
self.force_validation()
self._update_next_step(method)
[docs] def on_client__validate(self, widget, client):
if not client:
return
# this is used to avoid some tests from crashing
if self.pm_slave is None:
return
method = self.pm_slave.get_selected_method()
try:
client.can_purchase(method, self.get_remaining_value())
except SellError as e:
return ValidationError(e)
return StockOperationPersonValidationEvent.emit(client.person, type(client))
[docs] def on_create_transporter__clicked(self, button):
store = api.new_store()
transporter = store.fetch(self.model.transporter)
model = run_person_role_dialog(TransporterEditor, self.wizard, store,
transporter)
rv = store.confirm(model)
store.close()
if rv:
self._fill_transporter_combo()
model = self.store.fetch(model)
self.transporter.select(model)
[docs] def on_discount_slave_changed(self, slave):
self._update_totals()
self.client.validate()
[docs] def on_create_cfop__clicked(self, widget):
self.store.savepoint('before_run_editor_cfop')
cfop = run_dialog(CfopEditor, self.wizard, self.store, None)
if cfop:
self.cfop.append_item(cfop.get_description(), cfop)
self.cfop.select_item_by_data(cfop)
else:
self.store.rollback_to_savepoint('before_run_editor_cfop')
[docs] def on_invoice_number__validate(self, widget, value):
if not 0 < value <= 999999999:
return ValidationError(
_("Invoice number must be between 1 and 999999999"))
invoice = self.model.invoice
branch = self.model.branch
if invoice.check_unique_invoice_number_by_branch(value, branch):
return ValidationError(_(u'Invoice number already used.'))
[docs] def on_transporter__validate(self, widget, transporter):
return StockOperationPersonValidationEvent.emit(transporter.person,
type(transporter))
#
# Wizards for sales
#
[docs]class ConfirmSaleWizard(BaseWizard):
"""A wizard used when confirming a sale order. It means generate
payments, fiscal data and update stock
"""
size = (700, 400)
title = _("Sale Checkout")
help_section = 'sale-confirm'
# FIXME: In the long term, we should only create the sale at the end
# of this process, but that requires major surgery of the
# interaction between salewizard.py, pos.py and fiscalprinter.py
def __init__(self, store, model, subtotal, total_paid=0,
current_document=None):
"""Creates a new SaleWizard that confirms a sale.
To avoid excessive querying of the database we pass
some data already queried/calculated before hand.
:param store: a store
:param model: a |sale|
:param subtotal: subtotal of the sale
:param total_paid: totaly value already paid
:param current_document: the current document of the identified client,
if any
"""
marker('ConfirmSaleWizard')
self._check_payment_group(model, store)
self._subtotal = subtotal
self._total_paid = total_paid
self._current_document = current_document
self.model = model
adjusted_batches = model.check_and_adjust_batches()
if not adjusted_batches:
first_step = ConfirmSaleBatchStep(store, self, model, None)
else:
marker('running SalesPersonStep')
first_step = SalesPersonStep(self, store, model, self.payment_group)
marker('finished creating SalesPersonStep')
BaseWizard.__init__(self, store, first_step, model)
if not sysparam.get_bool('CONFIRM_SALES_ON_TILL'):
# This was added to allow us to work even if an error
# happened while adding a payment, where we already order
# but cannot confirm and are thrown back to the main
# POS interface
if self.model.can_order():
self.model.order()
marker('leaving ConfirmSaleWizard.__init__')
def _check_payment_group(self, model, store):
if not isinstance(model, Sale):
raise StoqlibError("Invalid datatype for model, it should be "
"of type Sale, got %s instead" % model)
self.payment_group = model.group
[docs] def get_subtotal(self):
"""Fetch the sale subtotal without querying the database.
The subtotal is the value of all items that are being sold
:returns: the subtotal of the current sale
"""
return self._subtotal
[docs] def get_total_amount(self):
"""Fetch the total sale amount without querying the database.
The total sale amount is the subtotal with discount and markups
taken into account.
:returns: the total amount of the current sale
"""
return self.model.get_total_sale_amount(subtotal=self._subtotal)
[docs] def get_total_paid(self):
"""Fetch the value already paid for this sale.
This is only used when we return a project we already paid for.
:returns: the total paid value for the current sale
"""
return self._total_paid
[docs] def get_total_to_pay(self):
"""Fetch the value the client still needs to pay.
This is a short hand for self.get_total_amount() - self.get_total_paid()
"""
return self.get_total_amount() - self.get_total_paid()
[docs] def need_create_payment(self):
return self.get_total_to_pay() > 0
[docs] def print_sale_details(self):
if yesno(_("Do you want to print this sale's details?"), gtk.RESPONSE_YES,
_("Print Details"), _("Don't Print")):
print_report(SaleOrderReport, self.model)
[docs] def finish(self):
missing = get_missing_items(self.model, self.store)
if missing:
# We want to close the checkout, so the user will be back to the
# list of items in the sale.
self.close()
run_dialog(MissingItemsDialog, self, self.model, missing)
return False
group = self.model.group
# FIXME: This is set too late on Sale.confirm(). If PaymentGroup don't
# have a payer, we won't be able to print bills/booklets.
group.payer = self.model.client and self.model.client.person
invoice_ok = InvoiceSetupEvent.emit()
if invoice_ok is False:
# If there is any problem with the invoice, the event will display an error
# message and the dialog is kept open so the user can fix whatever is wrong.
# If this is the second time the user is trying to confirm, an
# error message is being displayed saying that the payment can't be
# created twice, so we clear the payments created to avoid the message
# TODO: Create the payments on the wizard finish event (here)
self.payment_group.clear_unused()
return
self.retval = True
self.close()
retval = ConfirmSaleWizardFinishEvent.emit(self.model)
if retval is not None:
self.retval = retval
if sysparam.get_bool('PRINT_SALE_DETAILS_ON_POS'):
self.print_sale_details()
[docs]def test(): # pragma nocover
creator = api.prepare_test()
sale_item = creator.create_sale_item()
retval = run_dialog(ConfirmSaleWizard, None, creator.store,
sale_item.sale)
creator.store.confirm(retval)
if __name__ == '__main__': # pragma nocover
test()