示例#1
0
文件: test_pos.py 项目: pkaislan/stoq
    def _open_till(self, store):
        till = Till(store=store, station=api.get_current_station(store))
        till.open_till()

        TillOpenEvent.emit(till=till)
        self.assertEquals(till, Till.get_current(store))
        return till
示例#2
0
 def before_start(self, store):
     till = Till.get_current(store)
     if till is None:
         till = Till(store=store,
                     station=get_current_station(store))
         till.open_till()
         assert till == Till.get_current(store)
示例#3
0
    def testTillOpenOnce(self):
        station = get_current_station(self.store)
        till = Till(store=self.store, station=station)

        till.open_till()
        till.close_till()

        self.assertRaises(TillError, till.open_till)
示例#4
0
 def testTillClose(self):
     station = self.create_station()
     till = Till(store=self.store, station=station)
     till.open_till()
     self.assertEqual(till.status, Till.STATUS_OPEN)
     till.close_till()
     self.assertEqual(till.status, Till.STATUS_CLOSED)
     self.assertRaises(TillError, till.close_till)
示例#5
0
文件: test_till.py 项目: rg3915/stoq
 def test_needs_closing(self):
     till = Till(station=self.create_station(), store=self.store)
     self.failIf(till.needs_closing())
     till.open_till()
     self.failIf(till.needs_closing())
     till.opening_date = localnow() - datetime.timedelta(1)
     self.failUnless(till.needs_closing())
     till.close_till()
     self.failIf(till.needs_closing())
示例#6
0
文件: test_till.py 项目: rg3915/stoq
    def test_get_balance(self):
        till = Till(store=self.store, station=self.create_station())
        till.open_till()

        old = till.get_balance()
        till.add_credit_entry(currency(10), u"")
        self.assertEqual(till.get_balance(), old + 10)
        till.add_debit_entry(currency(5), u"")
        self.assertEqual(till.get_balance(), old + 5)
示例#7
0
文件: test_till.py 项目: rg3915/stoq
    def test_get_debits_total(self):
        till = Till(store=self.store, station=self.create_station())
        till.open_till()

        old = till.get_debits_total()
        till.add_debit_entry(currency(10), u"")
        self.assertEqual(till.get_debits_total(), old - 10)
        # This should not affect the debit
        till.add_credit_entry(currency(5), u"")
        self.assertEqual(till.get_debits_total(), old - 10)
示例#8
0
    def test_till_history_report(self):
        from stoqlib.gui.dialogs.tillhistory import TillHistoryDialog

        dialog = TillHistoryDialog(self.store)

        till = Till(station=get_current_station(self.store), store=self.store)
        till.open_till()

        sale = self.create_sale()
        sellable = self.create_sellable()
        sale.add_sellable(sellable, price=100)
        method = PaymentMethod.get_by_name(self.store, u"bill")
        payment = method.create_payment(Payment.TYPE_IN, sale.group, sale.branch, Decimal(100))
        TillEntry(
            value=25,
            identifier=20,
            description=u"Cash In",
            payment=None,
            till=till,
            branch=till.station.branch,
            date=datetime.date(2007, 1, 1),
            store=self.store,
        )
        TillEntry(
            value=-5,
            identifier=21,
            description=u"Cash Out",
            payment=None,
            till=till,
            branch=till.station.branch,
            date=datetime.date(2007, 1, 1),
            store=self.store,
        )

        TillEntry(
            value=100,
            identifier=22,
            description=sellable.get_description(),
            payment=payment,
            till=till,
            branch=till.station.branch,
            date=datetime.date(2007, 1, 1),
            store=self.store,
        )
        till_entry = list(self.store.find(TillEntry, till=till))
        today = datetime.date.today().strftime("%x")
        for item in till_entry:
            if today in item.description:
                date = datetime.date(2007, 1, 1).strftime("%x")
                item.description = item.description.replace(today, date)

            item.date = datetime.date(2007, 1, 1)
            dialog.results.append(item)

        self._diff_expected(TillHistoryReport, "till-history-report", dialog.results, list(dialog.results))
示例#9
0
    def on_Edit__activate(self, action):
        try:
            Till.get_current(self.store)
        except TillError as e:
            warning(str(e))
            return

        store = api.new_store()
        views = self.results.get_selected_rows()
        sale = store.fetch(views[0].sale)
        retval = run_dialog(SalePaymentsEditor, self, store, sale)

        if store.confirm(retval):
            self.refresh()
        store.close()
示例#10
0
文件: ecfui.py 项目: LeonamSilva/stoq
    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 _receive(self):
        with api.new_store() as store:
            till = Till.get_current(store)
            assert till

            in_payment = self.results.get_selected()
            payment = store.fetch(in_payment.payment)
            assert self._can_receive(payment)

            retval = run_dialog(SalePaymentConfirmSlave, self, store,
                                payments=[payment], show_till_info=False)
            if not retval:
                return

            try:
                TillAddCashEvent.emit(till=till, value=payment.value)
            except (TillError, DeviceError, DriverError) as e:
                warning(str(e))
                return

            till_entry = till.add_credit_entry(payment.value,
                                               _(u'Received payment: %s') % payment.description)

            TillAddTillEntryEvent.emit(till_entry, store)

        if store.committed:
            self.search.refresh()
示例#12
0
    def open_till(self):
        """Opens the till
        """
        try:
            current_till = Till.get_current(self.store)
        except TillError as e:
            warning(str(e))
            return False

        if current_till is not None:
            warning(_("You already have a till operation opened. "
                      "Close the current Till and open another one."))
            return False

        store = api.new_store()
        try:
            model = run_dialog(TillOpeningEditor, self._parent, store)
        except TillError as e:
            warning(str(e))
            model = None

        retval = store.confirm(model)
        store.close()
        if retval:
            self._till_status_changed(closed=False, blocked=False)
        return retval
示例#13
0
    def needs_closing(self):
        """Checks if the last opened till was closed and asks the
        user if he wants to close it

        :returns:
            - CLOSE_TILL_BOTH if both DB and ECF needs closing.
            - CLOSE_TILL_DB if only DB needs closing.
            - CLOSE_TILL_ECF if only ECF needs closing.
            - CLOSE_TILL_NONE if both ECF and DB are consistent (they may be
                  closed, or open for the current day)
        """
        ecf_needs_closing = HasPendingReduceZ.emit()

        last_till = Till.get_last(self.store)
        if last_till:
            db_needs_closing = last_till.needs_closing()
        else:
            db_needs_closing = False

        if db_needs_closing and ecf_needs_closing:
            return CLOSE_TILL_BOTH
        elif db_needs_closing and not ecf_needs_closing:
            return CLOSE_TILL_DB
        elif ecf_needs_closing and not db_needs_closing:
            return CLOSE_TILL_ECF
        else:
            return CLOSE_TILL_NONE
示例#14
0
    def create_sale(self, id_=None, branch=None, client=None):
        from stoqlib.domain.sale import Sale
        from stoqlib.domain.till import Till

        till = Till.get_current(self.store)
        if till is None:
            till = self.create_till()
            till.open_till()
        salesperson = self.create_sales_person()
        group = self.create_payment_group()
        if client:
            group.payer = client.person

        sale = Sale(
            coupon_id=0,
            open_date=TransactionTimestamp(),
            salesperson=salesperson,
            branch=branch or get_current_branch(self.store),
            cfop=sysparam(self.store).DEFAULT_SALES_CFOP,
            group=group,
            client=client,
            store=self.store,
        )
        if id_:
            sale.id = id_
            sale.identifier = id_
        return sale
示例#15
0
    def test_sales_person_report(self):
        sysparam.set_bool(self.store, "SALE_PAY_COMMISSION_WHEN_CONFIRMED", True)
        salesperson = self.create_sales_person()
        product = self.create_product(price=100)
        sellable = product.sellable

        sale = self.create_sale()
        sale.salesperson = salesperson
        sale.add_sellable(sellable, quantity=1)

        self.create_storable(product, get_current_branch(self.store), stock=100)

        CommissionSource(sellable=sellable, direct_value=Decimal(10), installments_value=1, store=self.store)

        sale.order()

        method = PaymentMethod.get_by_name(self.store, u"money")
        till = Till.get_last_opened(self.store)
        method.create_payment(Payment.TYPE_IN, sale.group, sale.branch, sale.get_sale_subtotal(), till=till)
        sale.confirm()
        sale.group.pay()

        salesperson = salesperson
        commissions = list(self.store.find(CommissionView))
        commissions[0].identifier = 1
        commissions[1].identifier = 139

        self._diff_expected(SalesPersonReport, "sales-person-report", commissions, salesperson)

        # Also test when there is no salesperson selected
        self._diff_expected(SalesPersonReport, "sales-person-report-without-salesperson", commissions, None)
示例#16
0
文件: till.py 项目: pkaislan/stoq
    def _update_till_status(self, closed, blocked):
        # Three different situations;
        #
        # - Till is closed
        # - Till is opened
        # - Till was not closed the previous fiscal day (blocked)

        self.set_sensitive([self.TillOpen], closed)
        self.set_sensitive([self.TillClose, self.PaymentReceive],
                           not closed or blocked)

        widgets = [self.TillVerify, self.TillAddCash, self.TillRemoveCash,
                   self.SearchTillHistory, self.app_vbox]
        self.set_sensitive(widgets, not closed and not blocked)

        if closed:
            text = _(u"Till closed")
            self.clear()
            self.setup_focus()
        elif blocked:
            text = _(u"Till blocked from previous day")
        else:
            till = Till.get_current(self.store)
            text = _(u"Till opened on %s") % till.opening_date.strftime('%x')

        self.till_status_label.set_text(text)

        self._update_toolbar_buttons()
        self._update_total()
示例#17
0
    def testSalesPersonReport(self):
        sysparam(self.store).SALE_PAY_COMMISSION_WHEN_CONFIRMED = 1
        salesperson = self.create_sales_person()
        product = self.create_product(price=100)
        sellable = product.sellable

        sale = self.create_sale()
        sale.salesperson = salesperson
        sale.add_sellable(sellable, quantity=1)

        self.create_storable(product, get_current_branch(self.store), stock=100)

        CommissionSource(sellable=sellable,
                         direct_value=Decimal(10),
                         installments_value=1,
                         store=self.store)

        sale.order()

        method = PaymentMethod.get_by_name(self.store, u'money')
        till = Till.get_last_opened(self.store)
        method.create_inpayment(sale.group, sale.branch,
                                sale.get_sale_subtotal(),
                                till=till)
        sale.confirm()
        sale.set_paid()

        salesperson_name = salesperson.person.name
        commissions = list(self.store.find(CommissionView))
        commissions[0].identifier = 1
        commissions[1].identifier = 139

        self._diff_expected(SalesPersonReport, 'sales-person-report', commissions,
                            salesperson_name)
示例#18
0
    def testGetCurrentTillClose(self):
        station = get_current_station(self.store)
        self.assertEqual(Till.get_current(self.store), None)
        till = Till(store=self.store, station=station)
        till.open_till()

        self.assertEqual(Till.get_current(self.store), till)
        till.close_till()
        self.assertEqual(Till.get_current(self.store), None)
示例#19
0
    def close_till(self, close_db=True, close_ecf=True):
        """Closes the till

        There are 3 possibilities for parameters combination:
          * *total close*: Both *close_db* and *close_ecf* are ``True``.
            The till on both will be closed.
          * *partial close*: Both *close_db* and *close_ecf* are ``False``.
            It's more like a till verification. The actual user will do it
            to check and maybe remove money from till, leaving it ready
            for the next one. Note that this will not emit
            'till-status-changed' event, since the till will not
            really close.
          * *fix conflicting status*: *close_db* and *close_ecf* are
            different. Use this only if you need to fix a conflicting
            status, like if the DB is open but the ECF is closed, or
            the other way around.

        :param close_db: If the till in the DB should be closed
        :param close_ecf: If the till in the ECF should be closed
        :returns: True if the till was closed, otherwise False
        """
        is_partial = not close_db and not close_ecf
        manager = get_plugin_manager()

        # This behavior is only because of ECF
        if not is_partial and not self._previous_day:
            if (manager.is_active('ecf') and
                not yesno(_("You can only close the till once per day. "
                            "You won't be able to make any more sales today.\n\n"
                            "Close the till?"),
                          Gtk.ResponseType.NO, _("Close Till"), _("Not now"))):
                return
        elif not is_partial:
            # When closing from a previous day, close only what is needed.
            close_db = self._close_db
            close_ecf = self._close_ecf

        if close_db:
            till = Till.get_last_opened(self.store)
            assert till

        store = api.new_store()
        editor_class = TillVerifyEditor if is_partial else TillClosingEditor
        model = run_dialog(editor_class, self._parent, store,
                           previous_day=self._previous_day, close_db=close_db,
                           close_ecf=close_ecf)

        if not model:
            store.confirm(model)
            store.close()
            return

        # TillClosingEditor closes the till
        retval = store.confirm(model)
        store.close()
        if retval and not is_partial:
            self._till_status_changed(closed=True, blocked=False)

        return retval
示例#20
0
文件: test_till.py 项目: rg3915/stoq
    def test_add_debit_entry(self):
        till = Till(store=self.store, station=self.create_station())
        till.open_till()

        self.assertEqual(till.get_balance(), 0)
        till.add_debit_entry(10)
        self.assertEqual(till.get_balance(), -10)
示例#21
0
    def test_till_daily_movement(self):
        date = datetime.date(2013, 1, 1)
        # create sale payment
        sale = self.create_sale()
        sellable = self.create_sellable()
        sale.add_sellable(sellable, price=100)
        sale.identifier = 1000
        sale.order()

        method = PaymentMethod.get_by_name(self.store, u'money')
        till = Till.get_last_opened(self.store)
        payment = method.create_payment(Payment.TYPE_IN, sale.group, sale.branch,
                                        sale.get_sale_subtotal(),
                                        till=till)
        sale.confirm()
        sale.group.pay()

        sale.confirm_date = date

        payment.identifier = 1010
        payment.paid_date = date

        # create lonely input payment
        payer = self.create_client()
        address = self.create_address()
        address.person = payer.person

        method = PaymentMethod.get_by_name(self.store, u'money')
        group = self.create_payment_group()
        branch = self.create_branch()
        payment_lonely_input = method.create_payment(Payment.TYPE_IN, group, branch, Decimal(100))
        payment_lonely_input.description = u"Test receivable account"
        payment_lonely_input.group.payer = payer.person
        payment_lonely_input.set_pending()
        payment_lonely_input.pay()
        payment_lonely_input.identifier = 1001
        payment_lonely_input.paid_date = date

        # create purchase payment
        drawee = self.create_supplier()
        address = self.create_address()
        address.person = drawee.person

        method = PaymentMethod.get_by_name(self.store, u'money')
        group = self.create_payment_group()
        branch = self.create_branch()
        payment = method.create_payment(Payment.TYPE_OUT, group, branch, Decimal(100))
        payment.description = u"Test payable account"
        payment.group.recipient = drawee.person
        payment.set_pending()
        payment.pay()
        payment.identifier = 1002
        payment.paid_date = date

        # create lonely output payment
        self._diff_expected(TillDailyMovementReport,
                            'till-daily-movement-report', self.store, date)
示例#22
0
 def create_model(self, store):
     till = Till.get_current(self.store)
     return Settable(employee=None,
                     payment=None,
                     # FIXME: should send in consts.now()
                     open_date=None,
                     till=till,
                     balance=till.get_balance(),
                     value=currency(0))
示例#23
0
文件: test_till.py 项目: rg3915/stoq
    def test_add_entry_out_payment(self):
        till = Till(store=self.store, station=self.create_station())
        till.open_till()

        payment = self._create_outpayment()
        self.assertEqual(till.get_balance(), 0)
        till.add_entry(payment)
        self.assertEqual(till.get_balance(), -10)
示例#24
0
文件: test_till.py 项目: rg3915/stoq
    def test_till_open_other_station(self):
        till = Till(station=self.create_station(), store=self.store)
        till.open_till()

        till = Till(station=get_current_station(self.store), store=self.store)
        till.open_till()

        self.assertEqual(Till.get_last_opened(self.store), till)
示例#25
0
    def testCreateInPayment(self):
        payment = self.createInPayment()
        self.failUnless(isinstance(payment, Payment))
        self.assertEqual(payment.value, Decimal(100))
        self.assertEqual(payment.till, Till.get_current(self.store))

        payment_without_till = self.createInPayment(till=None)
        self.failUnless(isinstance(payment, Payment))
        self.assertEqual(payment_without_till.value, Decimal(100))
        self.assertEqual(payment_without_till.till, None)
示例#26
0
    def testAddEntryInPayment(self):
        till = Till(store=self.store,
                    station=self.create_station())
        till.open_till()

        payment = self._create_inpayment()
        self.assertEqual(till.get_balance(), 0)
        till.add_entry(payment)
        self.assertEqual(till.get_balance(), 10)
示例#27
0
 def when_done(self, store):
     # This is sort of hack, set the opening/closing dates to the date before
     # it's run, so we can open/close the till in the tests, which uses
     # the examples.
     till = Till.get_current(store)
     # Do not leave anything in the till.
     till.add_debit_entry(till.get_balance(), _(u"Amount removed from Till"))
     till.close_till()
     yesterday = localtoday() - datetime.timedelta(1)
     till.opening_date = yesterday
     till.closing_date = yesterday
示例#28
0
文件: till.py 项目: esosaja/stoq
    def _get_till_balance(self):
        """Returns the balance of till operations"""
        try:
            till = Till.get_current(self.store)
        except TillError:
            till = None

        if till is None:
            return currency(0)

        return till.get_balance()
示例#29
0
 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"))
示例#30
0
    def on_Renegotiate__activate(self, action):
        try:
            Till.get_current(self.store)
        except TillError as e:
            warning(str(e))
            return
        receivable_views = self.results.get_selected_rows()
        if not self._can_renegotiate(receivable_views):
            warning(_('Cannot renegotiate selected payments'))
            return
        store = api.new_store()

        groups = list(set([store.fetch(v.group) for v in receivable_views]))
        retval = run_dialog(PaymentRenegotiationWizard, self, store,
                            groups)

        if store.confirm(retval):
            # FIXME: Storm is not expiring the groups correctly.
            # Figure out why. See bug 5087
            self.refresh()
            self._update_widgets()
        store.close()
示例#31
0
    def _update_till_status(self, closed, blocked):
        # Three different situations:
        #
        # - Till is closed
        # - Till is opened
        # - Till was not closed the previous fiscal day (blocked)

        self.set_sensitive([self.TillOpen], closed)
        self.set_sensitive([self.TillClose], not closed or blocked)
        widgets = [self.TillVerify, self.TillAddCash, self.TillRemoveCash,
                   self.SearchTillHistory, self.search_holder, self.PaymentReceive]
        self.set_sensitive(widgets, not closed and not blocked)

        def large(s):
            return '<span weight="bold" size="xx-large">%s</span>' % (
                stoq_api.escape(s), )

        if closed:
            text = large(_(u"Till closed"))
            self.search_holder.hide()
            self.footer_hbox.hide()
            self.large_status.show()
            self.clear()
            self.setup_focus()
            # Adding the label on footer without the link
            self.small_status.set_text(text)

            if not blocked:
                text += '\n\n<span size="large"><a href="open-till">%s</a></span>' % (
                    stoq_api.escape(_('Open till')))
            self.status_link.set_markup(text)
        elif blocked:
            self.search_holder.hide()
            self.footer_hbox.hide()
            text = large(_(u"Till blocked"))
            self.status_link.set_markup(text)
            self.small_status.set_text(text)
        else:
            self.search_holder.show()
            self.footer_hbox.show()
            self.large_status.hide()
            till = Till.get_current(self.store, api.get_current_station(self.store))
            text = _(u"Till opened on %s") % till.opening_date.strftime('%x')
            self.small_status.set_text(text)
        self._update_toolbar_buttons()
        self._update_total()
        if sysparam.get_bool('SHOW_TOTAL_PAYMENTS_ON_TILL'):
            self._update_payment_total()
示例#32
0
    def _update_widgets(self, sale_view):
        sale = sale_view and sale_view.sale
        try:
            till = Till.get_current(self.store)
        except TillError:
            till = None

        can_edit = bool(sale and sale.can_edit())
        # We need an open till to return sales
        if sale and till:
            can_return = sale.can_return() or sale.can_cancel()
        else:
            can_return = False

        self._sale_toolbar.return_sale_button.set_sensitive(can_return)
        self._sale_toolbar.edit_button.set_sensitive(can_edit)
示例#33
0
 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"))
示例#34
0
 def _open_till(self, store, initial_cash_amount=0):
     station = get_current_station(store)
     last_till = Till.get_last(store)
     if not last_till or last_till.status != Till.STATUS_OPEN:
         # Create till and open
         till = Till(store=store, station=station)
         till.open_till()
         till.initial_cash_amount = decimal.Decimal(initial_cash_amount)
     else:
         # Error, till already opened
         assert False
示例#35
0
    def next_step(self):
        self.wizard.create_payments = self.create_payments.read()

        if self.wizard.create_payments:
            try:
                till = Till.get_current(self.store)
            except TillError as e:
                warning(str(e))
                return self

            if not till:
                warning(
                    _("You need to open the till before doing this operation.")
                )
                return self

        return DecreaseItemStep(self.wizard, self, self.store, self.model)
示例#36
0
 def __init__(self,
              store,
              model=None,
              previous_day=False,
              close_db=True,
              close_ecf=True):
     """
     Create a new TillClosingEditor object.
     :param previous_day: If the till wasn't closed previously
     """
     self._previous_day = previous_day
     self.till = Till.get_last(store)
     if close_db:
         assert self.till
     self._close_db = close_db
     self._close_ecf = close_ecf
     BaseEditor.__init__(self, store, model)
     self._setup_widgets()
示例#37
0
 def __init__(self,
              store,
              model=None,
              previous_day=False,
              close_db=True,
              close_ecf=True):
     """
     Create a new TillClosingEditor object.
     :param previous_day: If the till wasn't closed previously
     """
     self._previous_day = previous_day
     self.till = Till.get_last(store, api.get_current_station(store))
     if close_db:
         assert self.till
     self._close_db = close_db
     self._close_ecf = close_ecf
     self._blind_close = sysparam.get_bool('TILL_BLIND_CLOSING')
     BaseEditor.__init__(self, store, model)
     self._setup_widgets()
示例#38
0
    def get(self):
        # Retrieve Till data
        with api.new_store() as store:
            till = Till.get_last(store)

            if not till:
                return None

            till_data = {
                'status': till.status,
                'opening_date': till.opening_date.strftime('%Y-%m-%d'),
                'closing_date': (till.closing_date.strftime('%Y-%m-%d') if
                                 till.closing_date else None),
                'initial_cash_amount': str(till.initial_cash_amount),
                'final_cash_amount': str(till.final_cash_amount),
                # Get payments data that will be used on 'close_till' action.
                'entry_types': till.status == 'open' and self._get_till_summary(store, till) or [],
            }

        return till_data
示例#39
0
 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
示例#40
0
    def open_till(self):
        """Opens the till
        """
        if Till.get_current(self.store) is not None:
            warning("You already have a till operation opened. "
                    "Close the current Till and open another one.")
            return False

        store = api.new_store()
        try:
            model = run_dialog(TillOpeningEditor, self._parent, store)
        except TillError as e:
            warning(str(e))
            model = None

        retval = store.confirm(model)
        store.close()
        if retval:
            self._till_status_changed(closed=False, blocked=False)
        return retval
示例#41
0
    def testSalesPersonReport(self):
        sysparam(self.store).SALE_PAY_COMMISSION_WHEN_CONFIRMED = 1
        salesperson = self.create_sales_person()
        product = self.create_product(price=100)
        sellable = product.sellable

        sale = self.create_sale()
        sale.salesperson = salesperson
        sale.add_sellable(sellable, quantity=1)

        self.create_storable(product,
                             get_current_branch(self.store),
                             stock=100)

        CommissionSource(sellable=sellable,
                         direct_value=Decimal(10),
                         installments_value=1,
                         store=self.store)

        sale.order()

        method = PaymentMethod.get_by_name(self.store, u'money')
        till = Till.get_last_opened(self.store)
        method.create_payment(Payment.TYPE_IN,
                              sale.group,
                              sale.branch,
                              sale.get_sale_subtotal(),
                              till=till)
        sale.confirm()
        sale.group.pay()

        salesperson_name = salesperson.person.name
        commissions = list(self.store.find(CommissionView))
        commissions[0].identifier = 1
        commissions[1].identifier = 139

        self._diff_expected(SalesPersonReport, 'sales-person-report',
                            commissions, salesperson_name)
示例#42
0
    def _check_needs_closing(self):
        needs_closing = self.needs_closing()

        # DB and ECF are ok
        if needs_closing is CLOSE_TILL_NONE:
            self._previous_day = False
            # We still need to check if the till is open or closed.
            till = Till.get_current(self.store,
                                    api.get_current_station(self.store))
            self._till_status_changed(closed=not till, blocked=False)
            return True

        close_db = needs_closing in (CLOSE_TILL_DB, CLOSE_TILL_BOTH)
        close_ecf = needs_closing in (CLOSE_TILL_ECF, CLOSE_TILL_BOTH)

        # DB or ECF is open from a previous day
        self._till_status_changed(closed=False, blocked=True)
        self._previous_day = True

        # Save this statuses in case the user chooses not to close now.
        self._close_db = close_db
        self._close_ecf = close_ecf

        manager = get_plugin_manager()
        if close_db and (close_ecf or not manager.is_active('ecf')):
            msg = _("You need to close the till from the previous day before "
                    "creating a new order.\n\nClose the Till?")
        elif close_db and not close_ecf:
            msg = _("The till in Stoq is opened, but in ECF "
                    "is closed.\n\nClose the till in Stoq?")
        elif close_ecf and not close_db:
            msg = _("The till in stoq is closed, but in ECF "
                    "is opened.\n\nClose the till in ECF?")

        if yesno(msg, Gtk.ResponseType.NO, _("Close Till"), _("Not now")):
            return self.close_till(close_db, close_ecf)

        return False
示例#43
0
    def on_confirm(self):
        till = self.model.till
        # Using api.get_default_store instead of self.store
        # or it will return self.model.till
        last_opened = Till.get_last_opened(api.get_default_store())
        if (last_opened and
            last_opened.opening_date.date() == till.opening_date.date()):
            warning(_("A till was opened earlier this day."))
            self.retval = False
            return

        try:
            TillOpenEvent.emit(till=till)
        except (TillError, DeviceError) as e:
            warning(str(e))
            self.retval = False
            return

        value = self.proxy.model.value
        if value:
            TillAddCashEvent.emit(till=till, value=value)
            till_entry = till.add_credit_entry(value, _(u'Initial Cash amount'))
            _create_transaction(self.store, till_entry)
示例#44
0
    def create_sale(self, id_=None, branch=None, client=None):
        from stoqlib.domain.sale import Sale
        from stoqlib.domain.till import Till
        till = Till.get_current(self.store)
        if till is None:
            till = self.create_till()
            till.open_till()
        salesperson = self.create_sales_person()
        group = self.create_payment_group()
        if client:
            group.payer = client.person

        sale = Sale(coupon_id=0,
                    open_date=TransactionTimestamp(),
                    salesperson=salesperson,
                    branch=branch or get_current_branch(self.store),
                    cfop=sysparam(self.store).DEFAULT_SALES_CFOP,
                    group=group,
                    client=client,
                    store=self.store)
        if id_:
            sale.id = id_
            sale.identifier = id_
        return sale
示例#45
0
    def close_till(self, close_db=True, close_ecf=True):
        """Closes the till

        There are 3 possibilities for parameters combination:
          * *total close*: Both *close_db* and *close_ecf* are ``True``.
            The till on both will be closed.
          * *partial close*: Both *close_db* and *close_ecf* are ``False``.
            It's more like a till verification. The actual user will do it
            to check and maybe remove money from till, leaving it ready
            for the next one. Note that this will not emit
            'till-status-changed' event, since the till will not
            really close.
          * *fix conflicting status*: *close_db* and *close_ecf* are
            different. Use this only if you need to fix a conflicting
            status, like if the DB is open but the ECF is closed, or
            the other way around.

        :param close_db: If the till in the DB should be closed
        :param close_ecf: If the till in the ECF should be closed
        :returns: True if the till was closed, otherwise False
        """
        is_partial = not close_db and not close_ecf
        manager = get_plugin_manager()

        # This behavior is only because of ECF
        if not is_partial and not self._previous_day:
            if (manager.is_active('ecf') and not yesno(
                    _("You can only close the till once per day. "
                      "You won't be able to make any more sales today.\n\n"
                      "Close the till?"), Gtk.ResponseType.NO, _("Close Till"),
                    _("Not now"))):
                return
        elif not is_partial:
            # When closing from a previous day, close only what is needed.
            close_db = self._close_db
            close_ecf = self._close_ecf

        if close_db:
            till = Till.get_last_opened(self.store,
                                        api.get_current_station(self.store))
            assert till

        store = api.new_store()
        editor_class = TillVerifyEditor if is_partial else TillClosingEditor
        model = run_dialog(editor_class,
                           self._parent,
                           store,
                           previous_day=self._previous_day,
                           close_db=close_db,
                           close_ecf=close_ecf)

        if not model:
            store.confirm(model)
            store.close()
            return

        # TillClosingEditor closes the till
        retval = store.confirm(model)
        store.close()
        if retval and not is_partial:
            self._till_status_changed(closed=True, blocked=False)

        return retval
示例#46
0
 def create_till(self):
     from stoqlib.domain.till import Till
     station = get_current_station(self.store)
     return Till(store=self.store, station=station)
示例#47
0
 def test_needs_closing(self):
     till = Till(station=self.create_station(), store=self.store)
     self.failIf(till.needs_closing())
     till.open_till()
     self.failIf(till.needs_closing())
     till.opening_date = localnow() - datetime.timedelta(1)
     self.failUnless(till.needs_closing())
     till.close_till()
     self.failIf(till.needs_closing())
示例#48
0
    def post(self, store):
        # FIXME: Check branch state and force fail if no override for that product is present.
        self.ensure_printer()

        data = request.get_json()
        client_id = data.get('client_id')
        products = data['products']
        payments = data['payments']
        client_category_id = data.get('price_table')

        document = raw_document(data.get('client_document', '') or '')
        if document:
            document = format_document(document)

        if client_id:
            client = store.get(Client, client_id)
        elif document:
            person = Person.get_by_document(store, document)
            client = person and person.client
        else:
            client = None

        # Create the sale
        branch = api.get_current_branch(store)
        group = PaymentGroup(store=store)
        user = api.get_current_user(store)
        sale = Sale(
            store=store,
            branch=branch,
            salesperson=user.person.sales_person,
            client=client,
            client_category_id=client_category_id,
            group=group,
            open_date=localnow(),
            coupon_id=None,
        )
        # Add products
        for p in products:
            sellable = store.get(Sellable, p['id'])
            item = sale.add_sellable(sellable, price=currency(p['price']),
                                     quantity=decimal.Decimal(p['quantity']))
            # XXX: bdil has requested that when there is a special discount, the discount does
            # not appear on the coupon. Instead, the item wil be sold using the discount price
            # as the base price. Maybe this should be a parameter somewhere
            item.base_price = item.price

        # Add payments
        sale_total = sale.get_total_sale_amount()
        money_payment = None
        payments_total = 0
        for p in payments:
            method_name = p['method']
            tef_data = p.get('tef_data', {})
            if method_name == 'tef':
                p['provider'] = tef_data['card_name']
                method_name = 'card'

            method = PaymentMethod.get_by_name(store, method_name)
            installments = p.get('installments', 1) or 1

            due_dates = list(create_date_interval(
                INTERVALTYPE_MONTH,
                interval=1,
                start_date=localnow(),
                count=installments))

            payment_value = currency(p['value'])
            payments_total += payment_value

            p_list = method.create_payments(
                Payment.TYPE_IN, group, branch,
                payment_value, due_dates)

            if method.method_name == 'money':
                # FIXME Frontend should not allow more than one money payment. this can be changed
                # once https://gitlab.com/stoqtech/private/bdil/issues/75 is fixed?
                if not money_payment or payment_value > money_payment.value:
                    money_payment = p_list[0]
            elif method.method_name == 'card':
                for payment in p_list:
                    card_data = method.operation.get_card_data_by_payment(payment)

                    card_type = p['mode']
                    # Stoq does not have the voucher comcept, so register it as a debit card.
                    if card_type == 'voucher':
                        card_type = 'debit'
                    device = self._get_card_device(store, 'TEF')
                    provider = self._get_provider(store, p['provider'])

                    if tef_data:
                        card_data.nsu = tef_data['aut_loc_ref']
                        card_data.auth = tef_data['aut_ext_ref']
                    card_data.update_card_data(device, provider, card_type, installments)
                    card_data.te.metadata = tef_data

        # If payments total exceed sale total, we must adjust money payment so that the change is
        # correctly calculated..
        if payments_total > sale_total and money_payment:
            money_payment.value -= (payments_total - sale_total)
            assert money_payment.value >= 0

        # Confirm the sale
        group.confirm()
        sale.order()

        till = Till.get_last(store)
        sale.confirm(till)

        # Fiscal plugins will connect to this event and "do their job"
        # It's their responsibility to raise an exception in case of
        # any error, which will then trigger the abort bellow

        # FIXME: Catch printing errors here and send message to the user.
        SaleConfirmedRemoteEvent.emit(sale, document)

        # This will make sure we update any stock or price changes products may
        # have between sales
        return True
示例#49
0
 def test_till_close_more_than_balance(self):
     station = self.create_station()
     till = Till(store=self.store, station=station)
     till.open_till()
     till.add_debit_entry(currency(20), u"")
     self.assertRaises(ValueError, till.close_till)
示例#50
0
    def test_get_cash_amount(self):
        till = Till(store=self.store, station=self.create_station())
        till.open_till()

        old = till.get_cash_amount()
        # money operations
        till.add_credit_entry(currency(10), u"")
        self.assertEqual(till.get_cash_amount(), old + 10)
        till.add_debit_entry(currency(5), u"")
        self.assertEqual(till.get_cash_amount(), old + 5)
        # non-money operations
        payment1 = self._create_inpayment()
        till.add_entry(payment1)
        self.assertEqual(till.get_cash_amount(), old + 5)
        payment2 = self._create_outpayment()
        till.add_entry(payment2)
        self.assertEqual(till.get_cash_amount(), old + 5)
        # money payment method operation
        payment = self.create_payment()
        payment.due_date = till.opening_date
        payment.set_pending()
        TillEntry(description=u'test',
                  value=payment.value,
                  till=till,
                  branch=till.station.branch,
                  payment=payment,
                  store=self.store)
        payment.pay()
        self.assertEqual(till.get_cash_amount(), old + 5 + payment.value)
示例#51
0
    def test_get_debits_total(self):
        till = Till(store=self.store, station=self.create_station())
        till.open_till()

        old = till.get_debits_total()
        till.add_debit_entry(currency(10), u"")
        self.assertEqual(till.get_debits_total(), old - 10)
        # This should not affect the debit
        till.add_credit_entry(currency(5), u"")
        self.assertEqual(till.get_debits_total(), old - 10)
示例#52
0
 def _createUnclosedTill(self):
     till = Till(station=get_current_station(self.store), store=self.store)
     till.open_till()
     yesterday = localtoday() - datetime.timedelta(1)
     till.opening_date = yesterday
示例#53
0
def current_till(store, example_creator, current_station):
    return Till.get_current(store, current_station) or example_creator.create_till()
示例#54
0
    def confirm(self, sale, store, savepoint=None, subtotal=None):
        """Confirms a |sale| on fiscalprinter and database

        If the sale is confirmed, the store will be committed for you.
        There's no need for the callsite to call store.confirm().
        If the sale is not confirmed, for instance the user cancelled the
        sale or there was a problem with the fiscal printer, then the
        store will be rolled back.

        :param sale: the |sale| to be confirmed
        :param trans: a store
        :param savepoint: if specified, a database savepoint name that
            will be used to rollback to if the sale was not confirmed.
        :param subtotal: the total value of all the items in the sale
        """
        # Actually, we are confirming the sale here, so the sale
        # confirmation process will be available to others applications
        # like Till and not only to the POS.
        payments_total = sale.group.get_total_confirmed_value()
        sale_total = sale.get_total_sale_amount()

        payment = get_formatted_price(payments_total)
        amount = get_formatted_price(sale_total)
        msg = _(u"Payment value (%s) is greater than sale's total (%s). "
                "Do you want to confirm it anyway?") % (payment, amount)
        if (sale_total < payments_total
                and not yesno(msg, gtk.RESPONSE_NO, _(u"Confirm Sale"),
                              _(u"Don't Confirm"))):
            return False

        model = run_dialog(ConfirmSaleWizard,
                           self._parent,
                           store,
                           sale,
                           subtotal=subtotal,
                           total_paid=payments_total)

        if not model:
            CancelPendingPaymentsEvent.emit()
            store.rollback(name=savepoint, close=False)
            return False

        if sale.client and not self.is_customer_identified():
            self.identify_customer(sale.client.person)

        if not self.totalize(sale):
            store.rollback(name=savepoint, close=False)
            return False

        if not self.setup_payments(sale):
            store.rollback(name=savepoint, close=False)
            return False

        if not self.close(sale, store):
            store.rollback(name=savepoint, close=False)
            return False

        # FIXME: This used to be done inside sale.confirm. Maybe it would be
        # better to do a proper error handling
        till = Till.get_current(store)
        assert till

        try:
            sale.confirm(till=till)
        except SellError as err:
            warning(str(err))
            store.rollback(name=savepoint, close=False)
            return False

        if not self.print_receipts(sale):
            warning(_("The sale was cancelled"))
            sale.cancel(force=True)

        print_cheques_for_payment_group(store, sale.group)

        # Only finish the transaction after everything passed above.
        store.confirm(model)

        # Try to print only after the transaction is commited, to prevent
        # losing data if something fails while printing
        group = sale.group
        booklets = list(group.get_payments_by_method_name(u'store_credit'))
        bills = list(group.get_payments_by_method_name(u'bill'))

        if (booklets and yesno(
                _("Do you want to print the booklets for this sale?"),
                gtk.RESPONSE_YES, _("Print booklets"), _("Don't print"))):
            try:
                print_report(BookletReport, booklets)
            except ReportError:
                warning(_("Could not print booklets"))

        if (bills and BillReport.check_printable(bills) and yesno(
                _("Do you want to print the bills for this sale?"),
                gtk.RESPONSE_YES, _("Print bills"), _("Don't print"))):
            try:
                print_report(BillReport, bills)
            except ReportError:
                # TRANSLATORS: bills here refers to "boletos" in pt_BR
                warning(_("Could not print bills"))

        return True
示例#55
0
    def test_till_open_previously_opened(self):
        yesterday = localnow() - datetime.timedelta(1)

        # Open a till, set the opening_date to yesterday
        till = Till(station=get_current_station(self.store), store=self.store)
        till.open_till()
        till.opening_date = yesterday
        till.add_credit_entry(currency(10), u"")
        till.close_till()
        till.closing_date = yesterday

        new_till = Till(station=get_current_station(self.store),
                        store=self.store)
        self.assertTrue(new_till._get_last_closed_till())
        new_till.open_till()
        self.assertEqual(new_till.initial_cash_amount, till.final_cash_amount)
示例#56
0
    def test_needs_closing(self):
        till = Till(station=self.create_station(), store=self.store)
        self.assertFalse(till.needs_closing())

        # Till is opened today, no need to close
        till.open_till()
        self.assertFalse(till.needs_closing())

        # till was onpened yesterday. Should close
        till.opening_date = localnow() - datetime.timedelta(1)
        self.assertTrue(till.needs_closing())

        # Till was opened yesterday, but there is a tolerance
        tolerance = int((localnow() - localtoday()).seconds / (60 * 60)) + 1
        with self.sysparam(TILL_TOLERANCE_FOR_CLOSING=tolerance):
            self.assertFalse(till.needs_closing())

        # Till is now closed, no need to close again
        till.close_till()
        self.assertFalse(till.needs_closing())
示例#57
0
 def create_model(self, store):
     till = Till.get_current(store, api.get_current_station(store))
     return Settable(value=currency(0),
                     reason=u'',
                     till=till,
                     balance=till.get_balance())
示例#58
0
 def test_get_last_closed(self):
     till = Till(store=self.store, station=get_current_station(self.store))
     till.open_till()
     till.close_till()
     self.assertEqual(Till.get_last_closed(self.store), till)
示例#59
0
    def test_get_balance(self):
        till = Till(store=self.store, station=self.create_station())
        till.open_till()

        old = till.get_balance()
        till.add_credit_entry(currency(10), u"")
        self.assertEqual(till.get_balance(), old + 10)
        till.add_debit_entry(currency(5), u"")
        self.assertEqual(till.get_balance(), old + 5)
示例#60
0
    def create_payment(self,
                       payment_type,
                       payment_group,
                       branch,
                       value,
                       due_date=None,
                       description=None,
                       base_value=None,
                       till=ValueUnset,
                       payment_number=None):
        """Creates a new payment according to a payment method interface

        :param payment_type: the kind of payment, in or out
        :param payment_group: a :class:`PaymentGroup` subclass
        :param branch: the :class:`branch <stoqlib.domain.person.Branch>`
          associated with the payment, for incoming payments this is the
          branch receiving the payment and for outgoing payments this is the
          branch sending the payment.
        :param value: value of payment
        :param due_date: optional, due date of payment
        :param details: optional
        :param description: optional, description of the payment
        :param base_value: optional
        :param till: optional
        :param payment_number: optional
        :returns: a :class:`payment <stoqlib.domain.payment.Payment>`
        """
        store = self.store

        if due_date is None:
            due_date = TransactionTimestamp()

        if payment_type == Payment.TYPE_IN:
            query = And(Payment.group_id == payment_group.id,
                        Payment.method_id == self.id,
                        Payment.payment_type == Payment.TYPE_IN,
                        Payment.status != Payment.STATUS_CANCELLED)
            payment_count = store.find(Payment, query).count()
            if payment_count == self.max_installments:
                raise PaymentMethodError(
                    _('You can not create more inpayments for this payment '
                      'group since the maximum allowed for this payment '
                      'method is %d') % self.max_installments)
            elif payment_count > self.max_installments:
                raise DatabaseInconsistency(
                    _('You have more inpayments in database than the maximum '
                      'allowed for this payment method'))

        if not description:
            description = self.describe_payment(payment_group)

        # If till is unset, do some clever guessing
        if till is ValueUnset:
            # We only need a till for inpayments
            if payment_type == Payment.TYPE_IN:
                till = Till.get_current(store)
            elif payment_type == Payment.TYPE_OUT:
                till = None
            else:
                raise AssertionError(payment_type)

        payment = Payment(store=store,
                          branch=branch,
                          payment_type=payment_type,
                          due_date=due_date,
                          value=value,
                          base_value=base_value,
                          group=payment_group,
                          method=self,
                          category=None,
                          till=till,
                          description=description,
                          payment_number=payment_number)
        self.operation.payment_create(payment)
        return payment