Skip to content

Commit

Permalink
Support running without a receipt printer or cash drawer
Browse files Browse the repository at this point in the history
If no printer is configured, prevent the user from doing things that
require printing.

If no cash drawer is configured, prevent the user from taking cash.

There are payment method and general settings to override these
restrictions.
  • Loading branch information
sde1000 committed Jan 22, 2024
1 parent 1f872fa commit 0aa9dfd
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 55 deletions.
9 changes: 7 additions & 2 deletions quicktill/card.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@ def enter(self):
r.append(pt.driver._cashback_method.driver.add_payment(
trans, f"{pt.description} cashback",
zero - cashback))
if cashback > zero or pt.driver._kickout:
printer.kickout()
if (cashback > zero or pt.driver._kickout) and tillconfig.cash_drawer:
printer.kickout(tillconfig.cash_drawer)
td.s.flush()
self.reg.add_payments(self.transid, r)

Expand Down Expand Up @@ -366,6 +366,11 @@ def start_payment(self, reg, transid, amount, outstanding):
f"the till later."],
title=f"{self.paytype} transactions not allowed")
return
if self._kickout and not tillconfig.cash_drawer:
ui.infopopup(["This till doesn't have a cash drawer for you "
"to put the card receipt in. Use a different till "
"to take the card payment."], title="Error")
return
if amount < zero:
if amount < outstanding:
ui.infopopup(
Expand Down
9 changes: 8 additions & 1 deletion quicktill/cash.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def read_config(self):
self._change_description = c.get('change_description', 'Change')
self._drawers = c.get('drawers', 1)
self._countup = c.get('countup', _default_countup)
self._require_cash_drawer = c.get('require-cash-drawer', True)
self._total_fields = [
(f"Tray {t + 1}", ui.validate_float, self._countup)
for t in range(self._drawers)]
Expand All @@ -67,6 +68,11 @@ def add_payment(self, transaction, description, amount):
return payment.pline(p)

def start_payment(self, reg, transid, amount, outstanding):
if self._require_cash_drawer and not tillconfig.cash_drawer:
ui.infopopup(["This till doesn't have a cash drawer. Use "
"a till with a cash drawer to take a cash payment."],
title="Error")
return
trans = td.s.query(Transaction).get(transid)
description = self.paytype.description
if amount < zero:
Expand All @@ -93,7 +99,8 @@ def start_payment(self, reg, transid, amount, outstanding):
r = [payment.pline(p)]
if c:
r.append(payment.pline(c))
printer.kickout()
if tillconfig.cash_drawer:
printer.kickout(tillconfig.cash_drawer)
reg.add_payments(transid, r)

@user.permission_required("cancel-cash-payment", "Cancel a cash payment")
Expand Down
6 changes: 5 additions & 1 deletion quicktill/managetill.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ class receiptprint(user.permission_checked, ui.dismisspopup):
'Print any receipt given the transaction number')

def __init__(self):
if not tillconfig.receipt_printer:
ui.infopopup(["This till does not have a receipt printer."],
title="Error")
return
super().__init__(5, 30, title="Receipt print",
dismiss=keyboard.K_CLEAR,
colour=ui.colour_input)
Expand Down Expand Up @@ -46,7 +50,7 @@ def enter(self):
user.log(f"Printed {trans.state} transaction {trans.logref} "
f"from transaction number")
with ui.exception_guard("printing the receipt", title="Printer error"):
printer.print_receipt(rn)
printer.print_receipt(tillconfig.receipt_printer, rn)


@user.permission_required('version', 'See version information')
Expand Down
31 changes: 18 additions & 13 deletions quicktill/printer.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@
# This should be the case if called during a keypress! If being used
# in any other context, use with td.orm_session(): around the call.

def print_receipt(transid):
def print_receipt(printer, transid):
trans = td.s.query(Transaction).get(transid)
if trans is None:
return
if not trans.lines:
return
with tillconfig.receipt_printer as d:
with printer as d:
d.printline(f"\t{tillconfig.pubname}", emph=1)
for i in tillconfig.pubaddr().splitlines():
d.printline(f"\t{i}", colour=1)
Expand Down Expand Up @@ -104,8 +104,11 @@ def print_receipt(transid):
d.printline(f"\t{ui.formatdate(trans.session.date)}")


def print_sessioncountup(s):
with tillconfig.receipt_printer as d:
def print_sessioncountup(printer, sessionid):
s = td.s.query(Session).get(sessionid)
if s is None:
return
with printer as d:
d.printline(f"\t{tillconfig.pubname}", emph=1)
d.printline(f"\tSession {s.id}", colour=1)
d.printline(f"\t{ui.formatdate(s.date)}", colour=1)
Expand Down Expand Up @@ -141,17 +144,19 @@ def print_sessioncountup(s):
d.printline("management menu option 1,3.")


def print_sessiontotals(session_id):
def print_sessiontotals(printer, sessionid):
"""Print a session totals report given a Session id.
"""
s = td.s.query(Session).get(session_id)
s = td.s.query(Session).get(sessionid)
if s is None:
return
printtime = ui.formattime(now())
depts = s.dept_totals
# Let's use the payment type as the dict key
till_totals = dict(s.payment_totals)
actual_totals = dict((x.paytype, x.amount) for x in s.actual_totals)

with tillconfig.receipt_printer as d:
with printer as d:
d.printline(f"\t{tillconfig.pubname}", emph=1)
d.printline(f"\tSession {s.id}", colour=1)
d.printline(f"\t{ui.formatdate(s.date)}", colour=1)
Expand Down Expand Up @@ -199,13 +204,13 @@ def print_sessiontotals(session_id):
d.printline(f"\tPrinted {printtime}")


def print_deferred_payment_wrapper(trans, paytype, amount, user_name):
def print_deferred_payment_wrapper(printer, trans, paytype, amount, user_name):
"""Print a wrapper for a deferred payment
Print a wrapper for money (cash, etc.) to be set aside to use
towards paying a part-paid transaction in a future session.
"""
with tillconfig.receipt_printer as d:
with printer as d:
for i in range(4):
d.printline(f"\t{tillconfig.pubname}", emph=1)
d.printline(f"\tDeferred transaction {trans.id}", emph=1)
Expand Down Expand Up @@ -282,15 +287,15 @@ def stocklabel_print(p, sl):
stock_label(d, sd)


def print_restock_list(rl):
def print_restock_list(printer, rl):
"""
Print a list of (stockline,stockmovement) tuples.
A stockmovement tuple is (stockitem,fetchqty,newdisplayqty,qtyremain).
We can't assume that any of these objects are in the current
session.
"""
with tillconfig.receipt_printer as d:
with printer as d:
d.printline(f"\t{tillconfig.pubname}", emph=1)
d.printline("\tRe-stock list")
d.printline(f"\tPrinted {ui.formattime(now())}")
Expand All @@ -313,15 +318,15 @@ def print_restock_list(rl):
d.printline("\tEnd of list")


def kickout():
def kickout(drawer):
"""Kick out the cash drawer.
Returns True if successful.
"""
with ui.exception_guard("kicking out the cash drawer",
title="Printer error"):
try:
tillconfig.cash_drawer.kickout()
drawer.kickout()
except pdrivers.PrinterError as e:
ui.infopopup([f"Could not kick out the cash drawer: {e.desc}"],
title="Printer problem")
Expand Down
42 changes: 32 additions & 10 deletions quicktill/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -1855,13 +1855,16 @@ def cashkey(self):
if self.user.may('nosale'):
if self.hook("nosale"):
return
if printer.kickout():
ui.toast("No Sale has been recorded.")
# Finally! We're not lying any more!
user.log("No Sale")
if lock_after_nosale():
self.locked = True
self._redraw()
if tillconfig.cash_drawer:
if printer.kickout(tillconfig.cash_drawer):
ui.toast("No Sale has been recorded.")
# Finally! We're not lying any more!
user.log("No Sale")
else:
ui.toast("No Sale")
if lock_after_nosale():
self.locked = True
self._redraw()
else:
ui.infopopup(["You don't have permission to use "
"the No Sale function."], title="No Sale")
Expand Down Expand Up @@ -2185,12 +2188,16 @@ def printkey(self):
"if you have its number using the option under "
"'Manage Till'."], title="Error")
return
if not tillconfig.receipt_printer:
ui.infopopup(["This till does not have a receipt printer."],
title="Error")
return
log.info("Register: printing transaction %d", trans.id)
user.log(f"Printed {trans.state} transaction {trans.logref} "
f"from register")
ui.toast("The receipt is being printed.")
with ui.exception_guard("printing the receipt", title="Printer error"):
printer.print_receipt(trans.id)
printer.print_receipt(tillconfig.receipt_printer, trans.id)

def cancelkey(self):
"""The cancel key was pressed.
Expand Down Expand Up @@ -2414,7 +2421,10 @@ def canceltrans(self):
self.transid = None
td.s.flush()
if payments > zero:
printer.kickout()
# XXX this is looking at all payments, not just those that
# go in the cash drawer.
if tillconfig.cash_drawer:
printer.kickout(tillconfig.cash_drawer)
refundtext = f"{tillconfig.fc(payments)} had already been "\
f"put in the cash drawer."
else:
Expand Down Expand Up @@ -2761,6 +2771,17 @@ def defertrans(self):
# cash to use towards paying it

if nds != zero:
if not tillconfig.receipt_printer:
ui.infopopup(
["The till needs to print a ticket to wrap the "
"money for this part-paid transaction, but it "
"doesn't have a receipt printer.",
"",
"Use a till with a receipt printer to defer "
"this transaction."],
title="Error - no printer")
td.s.rollback()
return
user.log(f"Deferred transaction {trans.logref} which was "
f"part-paid by {tillconfig.fc(nds)}")
defer_pm.driver.add_payment(ptrans, "Deferred", zero - nds)
Expand All @@ -2774,8 +2795,9 @@ def defertrans(self):
f"the customer.")
try:
printer.print_deferred_payment_wrapper(
tillconfig.receipt_printer,
trans, defer_pm, nds, self.user.fullname)
printer.kickout()
printer.kickout(tillconfig.cash_drawer)
except Exception as e:
ui.infopopup(
["There was an error printing the deferred payment "
Expand Down
68 changes: 49 additions & 19 deletions quicktill/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
description="Should session totals be printed after they have been "
"confirmed?")

sessioncountup_print = config.BooleanConfigItem(
'core:sessioncountup_print', True, display_name="Print countup sheets?",
description="Should a counting-up sheet be printed when a session "
"is closed?")


def trans_restore():
"""Restore deferred transactions
Expand Down Expand Up @@ -75,7 +80,8 @@ def key_enter(self):
log.info("Started session number %d", sc.id)
user.log(f"Started session {sc.logref}")
payment.notify_session_start(sc)
printer.kickout()
if tillconfig.cash_drawer:
printer.kickout(tillconfig.cash_drawer)
if deferred:
deferred = [
"",
Expand Down Expand Up @@ -103,6 +109,13 @@ def start():


def checkendsession():
if sessioncountup_print() and not tillconfig.receipt_printer:
log.info("End session: no receipt printer")
ui.infopopup(["This till does not have a receipt printer. Use "
"a different till to close the session and print "
"the counting-up sheet."],
title="Error")
return
sc = Session.current(td.s)
if sc is None:
log.info("End session: no session in progress")
Expand All @@ -124,12 +137,13 @@ def confirmendsession():
r = checkendsession()
if not r:
return
# Check that the printer has paper before ending the session
pp = tillconfig.receipt_printer.offline()
if pp:
ui.infopopup(["Could not end the session: there is a problem with "
f"the printer: {pp}"], title="Printer problem")
return
if sessioncountup_print():
# Check that the printer has paper before ending the session
pp = tillconfig.receipt_printer.offline()
if pp:
ui.infopopup(["Could not end the session: there is a problem with "
f"the printer: {pp}"], title="Printer problem")
return
r.endtime = datetime.datetime.now()
log.info("End of session %d confirmed.", r.id)
user.log(f"Ended session {r.logref}")
Expand All @@ -139,11 +153,13 @@ def confirmendsession():
"actual amounts using management option 1, 3."],
title="Session Ended", colour=ui.colour_info,
dismiss=keyboard.K_CASH)
ui.toast("Printing the countup sheet.")
with ui.exception_guard("printing the session countup sheet",
title="Printer error"):
printer.print_sessioncountup(r)
printer.kickout()
if sessioncountup_print():
ui.toast("Printing the countup sheet.")
with ui.exception_guard("printing the session countup sheet",
title="Printer error"):
printer.print_sessioncountup(tillconfig.receipt_printer, r.id)
if tillconfig.cash_drawer:
printer.kickout(tillconfig.cash_drawer)
managestock.stock_purge_internal(source="session end")
payment.notify_session_end(r)

Expand Down Expand Up @@ -249,6 +265,12 @@ class record(ui.dismisspopup):
ffw = 13

def __init__(self, sessionid):
if sessiontotal_print() and not tillconfig.receipt_printer:
ui.infopopup(["This till does not have a receipt printer "
"to print the session totals after they have "
"been recorded. Use a till that has a printer."],
title="Error")
return
log.info("Record session takings popup: session %d", sessionid)
self.sessionid = sessionid
s = td.s.query(Session).get(sessionid)
Expand Down Expand Up @@ -429,17 +451,24 @@ def finish(self):
self.dismiss()
for i in SessionHooks.instances:
i.postRecordSessionTakings(session.id)
if sessiontotal_print():
if sessiontotal_print() and tillconfig.receipt_printer:
ui.toast("Printing the confirmed session totals.")
with ui.exception_guard("printing the confirmed session totals",
title="Printer error"):
printer.print_sessiontotals(session.id)
printer.print_sessiontotals(
tillconfig.receipt_printer, session.id)
else:
ui.toast(f"Totals for session {session.id} confirmed.")


@user.permission_required('record-takings', "Record takings for a session")
def recordtakings():
if sessiontotal_print() and not tillconfig.receipt_printer:
ui.infopopup(["This till does not have a receipt printer "
"to print the session totals after they have "
"been recorded. Use a till that has a printer."],
title="Error")
return
m = sessionlist(record, unpaidonly=True, closedonly=True)
if len(m) == 0:
log.info("Record takings: no sessions available")
Expand Down Expand Up @@ -505,11 +534,12 @@ def totalpopup(sessionid):
dept.id, dept.description, tillconfig.fc(total)))
dt = dt + total
l.append(ui.tableformatter(" l pr ")("Total", tillconfig.fc(dt)))
l.append("")
l.append(" Press Print for a hard copy ")
keymap = {
keyboard.K_PRINT: (printer.print_sessiontotals, (s.id,), False),
}
keymap = {}
if tillconfig.receipt_printer:
l.append("")
l.append(" Press Print for a hard copy ")
keymap[keyboard.K_PRINT] = (printer.print_sessiontotals, (
tillconfig.receipt_printer, s.id), False)
ui.listpopup(l,
title=f"Session number {s.id}",
colour=ui.colour_info, keymap=keymap,
Expand Down
Loading

0 comments on commit 0aa9dfd

Please sign in to comment.