FabLabKasse.shopping.backend package

Submodules

FabLabKasse.shopping.backend.abstract module

abstract implementations of shopping and clients

base class and interface definition for specific implementations (see other files in this folder)

class FabLabKasse.shopping.backend.abstract.AbstractClient(client_id=None, name='')[source]

Bases: object

a client that can pay by pin

get_debt()[source]

how much is the current debt (<0 = client has pre-paid)

get_debt_limit()[source]

how much is the limit for the debt that may not be exceeded

test_pin(pin)[source]

is the given pin (4-digit string) correct and the client enabled for paying?

class FabLabKasse.shopping.backend.abstract.AbstractShoppingBackend(cfg)[source]

Bases: object

manages products, categories and orders (cart)

add_order_line(prod_id, qty, comment=None)[source]

add product to cart

if not all values are allowed, qty is rounded up to the next possible amount.

The user should only be asked for a comment by the GUI if self.product_requires_text_entry(prod_id) == True

Parameters:
  • prod_id (int) – product
  • qty (Decimal) – amount of product
  • comment ((basestring, None)) – textual comment from the user, or None.
Raise:

ProductNotFound

create_order()[source]

create a new order and return its id

delete_current_order()[source]

delete currently selected order, implies set_current_order(None)

delete_order_line(order_line_id)[source]

delete product from cart

format_money(amount)[source]
format_qty(qty)[source]
get_category_path(current_category)[source]

return the category path from the root to the current category, excluding the root category

[child_of_root, …, parent_of_current, current_category]

Return type:list(Category)
get_current_order()[source]

get selected order (or return 0 if switching between multiple orders is not supported)

get_current_total()[source]
Returns:total sum of current order
Return type:Decimal

Note: The internal rounding must be consistent, which is needed by :class: FabLabKasse.shopping.payment_methods. That means that x,xx5 € must always be rounded up or always down. “Fair rounding” like Decimal.ROUND_HALF_EVEN is not allowed.

For example:

  • add article costing 1,015 € -> get_current_total == x
  • add article costing 0,990 € -> get_current_total == x + 0,99

This would not be true with the fair strategy “round second digit to even value if the third one is exactly 5” (1,02€ and 2,00€).

get_order_line(order_line_id)[source]

get order line of current order

get_order_lines()[source]

return current order lines

Return type:OrderLine
get_products(current_category)[source]

return products in current category

Return type:list(Product)
get_root_category()[source]

return id of root category

get_subcategories(current_category)[source]

return list(Category) of subclasses of the given category-id.

list_clients()[source]

returns all selectable clients in a dict {id: Client(id), …}

pay_order(method)[source]

store payment of current order to database :param method: payment method object, whose type is used to determine where the order should be stored in the database method.amount_paid - method.amount_returned is how much money was gained by this sale, must be equal to self.get_current_total()

pay_order_on_client(client)[source]

charge the order on client’s account

Parameters:client – AbstractClient
Raises:DebtLimitExceeded when the client’s debt limit would be exceeded
print_receipt(order_id)[source]

print the receipt for a given, already paid order_id

The receipt data must be stored in the backend, because for accountability reasons all receipt texts need to be stored anyway.

product_requires_text_entry(prod_id)[source]

when adding prod_id, should the user be asked for a text entry for entering comments like his name?

static round_money(value)[source]

rounds money in Decimal representation to 2 places

Main purpose is shopping.backend.abstract.AbstractShoppingBackend.get_current_total(), since round() does behave weird. But maybe there are other applications too.

Parameters:value (float | Decimal) – an amount of money to be rounded
Returns:money, rounded to 2 digits
Return type:Decimal
>>> AbstractShoppingBackend.round_money(Decimal('0.005'))
Decimal('0.01')
>>> AbstractShoppingBackend.round_money(Decimal('0.004'))
Decimal('0.00')
search_from_text(searchstr)[source]

search searchstr in products and categories :return: tuple (list of categories, products for table)

Return type:list(Product)
search_product_from_code(code)[source]

search via barcode, PLU or similar unique-ID entry. code may be any string

Returns:product id
Raises:ProductNotFound() if nothing found
set_current_order(order_id)[source]

switch to another order (when the backend supports multiple orders)

update_quantity(order_line_id, amount)[source]

change quantity of order-line.

if not all float values are allowed, round upvalue to the next possible one

class FabLabKasse.shopping.backend.abstract.AbstractShoppingBackendTest(methodName='runTest')[source]

Bases: unittest.case.TestCase

test the AbstractShoppingBackend class

TODO extend this test

test_round_money_subcent_values()[source]

test the money-rounding function

the test checks the rounding of subcent values

class FabLabKasse.shopping.backend.abstract.Category(categ_id, name, parent_id=None)[source]

Bases: object

represents a category of Products

exception FabLabKasse.shopping.backend.abstract.DebtLimitExceeded[source]

Bases: exceptions.Exception

exception raised by pay_order_on_client: order not paid because the debt limit would have been exceeded

class FabLabKasse.shopping.backend.abstract.OrderLine(order_line_id, qty, unit, name, price_per_unit, price_subtotal, delete_if_zero_qty=True)[source]

Bases: object

one order line (roughly equal to a product in a shopping cart, although there may be multiple entries for one product)

Parameters:
  • id – id of order-line, must be unique and non-changing inside one Order() (if None: autogenerate id)
  • qty (Decimal) – amount (“unlimited” number of digits is okay)
  • unit (unicode) – product unit of sale
  • name (unicode) – product name
  • price_per_unit (Decimal) – price for one unit
  • price_subtotal (Decimal) – price for qty * unit of this product
  • delete_if_zero_qty (boolean) –

    if the qty is zero and the user starts adding something else, then remove this line

    [ usually True, set to False for products that also may as comment limes costing nothing ]

exception FabLabKasse.shopping.backend.abstract.PrinterError[source]

Bases: exceptions.Exception

cannot print receipt

class FabLabKasse.shopping.backend.abstract.Product(prod_id, name, price, unit, location, categ_id=None, qty_rounding=0, text_entry_required=False)[source]

Bases: object

simple representation for a product

Parameters:
  • prod_id (int) – numeric unique product ID
  • categ_id (int | None) –

    category ID of product, or None if the product is not directly visible

    TODO hide these products from search, or a more explicit solution

  • name (unicode) – Name of product
  • location (unicode) – Location of product (shown to the user)
  • unit (unicode) – Unit of sale for this product (e.g. piece, kilogram)
  • price (Decimal) – price for one unit of this product
  • qty_rounding (int | Decimal) –

    Product can only be bought in multiples of this quantity, user (GUI) input will be rounded/truncated to the next multiple of this.

    Set to 0 so that the product can be bought in arbitrarily small quantities.

    example: you cannot buy half a t-shirt, so you set qty_rounding = 1

    handling this is responsibility of the shopping backend

exception FabLabKasse.shopping.backend.abstract.ProductNotFound[source]

Bases: exceptions.Exception

requested product not found

FabLabKasse.shopping.backend.abstract.basicUnitTests(shopping_backend)[source]
FabLabKasse.shopping.backend.abstract.float_to_decimal(number, digits)[source]

convert float to decimal with rounding and strict error tolerances

If the given number cannot be represented as decimal with an error within 1/1000 of the last digit, ValueError is raised.

Parameters:
  • number (float | Decimal) – a float that is nearly equal to a decimal number
  • digits (int) – number of decimal places of the resulting value (max. 9)
Raise:

ValueError

>>> float_to_decimal(1.424, 3)
Decimal('1.424')
>>> float_to_decimal(0.7, 1)
Decimal('0.7')
FabLabKasse.shopping.backend.abstract.format_money(amount)[source]

format float as money string

You should best use Decimal as input. TODO: make moneysign interchangeable

Parameters:amount (float|Decimal) – amount of money
Returns:amount formatted as string with Euro-Sign
Return type:unicode
>>> format_money(1.23)
u'1,23 \u20ac'
>>> format_money(3.741)
u'3,741 \u20ac'
>>> format_money(42.4242)
u'42,424 \u20ac'
>>> format_money(5.8899)
u'5,89 \u20ac'
>>> format_money(Decimal('1.23'))
u'1,23 \u20ac'
>>> format_money(Decimal('3.741'))
u'3,741 \u20ac'
>>> format_money(Decimal('42.4242'))
u'42,424 \u20ac'
>>> format_money(Decimal('5.8899'))
u'5,89 \u20ac'
FabLabKasse.shopping.backend.abstract.format_qty(qty)[source]

format quantity (number) as string

Parameters:qty – quantity in numbers
Returns:string-representation of qty, decimal sep is dependent on locale
Return type:unicode
>>> format_qty(5)
u'5'
FabLabKasse.shopping.backend.abstract.load_tests(loader, tests, ignore)[source]

loader function to load the doctests in this module into unittest

FabLabKasse.shopping.backend.dummy module

FabLabKasse.shopping.backend.legacy_offline_kassenbuch module

FabLabKasse.shopping.backend.oerp module

class FabLabKasse.shopping.backend.oerp.Client(client_id=None, name='')[source]

Bases: FabLabKasse.shopping.backend.abstract.AbstractClient

oerp implementation of AbstractClient. do not instantiate this yourself, but please rather use Client.from_oerp or ShoppingBackend.list_clients

classmethod from_oerp(client_id, oerp)[source]

read raw data from oerp record

get_debt()[source]

how much is the current debt (<0 = client has pre-paid)

get_debt_limit()[source]

how much is the limit for the debt that may not be exceeded

class FabLabKasse.shopping.backend.oerp.ShoppingBackend(cfg)[source]

Bases: FabLabKasse.shopping.backend.abstract.AbstractShoppingBackend

OpenERP implementation of AbstractShoppingBackend

add_order_line(prod_id, qty, comment=None)[source]

add product to cart

if not all values are allowed, qty is rounded up to the next possible amount.

The user should only be asked for a comment by the GUI if self.product_requires_text_entry(prod_id) == True

Parameters:
  • prod_id (int) – product
  • qty (Decimal) – amount of product
  • comment ((basestring, None)) – textual comment from the user, or None.
Raise:

ProductNotFound

create_order()[source]

create a new order and return its id

delete_current_order()[source]

delete currently selected order, implies set_current_order(None)

delete_order_line(order_line_id)[source]

delete product from cart

get_category_path(current_category)[source]

return the category path from the root to the current category, excluding the root category

[child_of_root, …, parent_of_current, current_category]

Return type:list(Category)
get_current_order()[source]

get selected order (or return 0 if switching between multiple orders is not supported)

get_current_total()[source]
Returns:total sum of current order
Return type:Decimal

Note: The internal rounding must be consistent, which is needed by :class: FabLabKasse.shopping.payment_methods. That means that x,xx5 € must always be rounded up or always down. “Fair rounding” like Decimal.ROUND_HALF_EVEN is not allowed.

For example:

  • add article costing 1,015 € -> get_current_total == x
  • add article costing 0,990 € -> get_current_total == x + 0,99

This would not be true with the fair strategy “round second digit to even value if the third one is exactly 5” (1,02€ and 2,00€).

get_order_line(order_line_id)[source]

get order line of current order

get_order_lines()[source]

return current order lines

Return type:OrderLine
get_products(current_category)[source]

return products in current category

Return type:list(Product)
get_root_category()[source]

return id of root category

get_subcategories(current_category)[source]

return list(Category) of subclasses of the given category-id.

list_clients()[source]

returns all selectable clients in a dict {id: Client(id), …}

pay_order(method)[source]

store payment of current order to database :param method: payment method object, whose type is used to determine where the order should be stored in the database method.amount_paid - method.amount_returned is how much money was gained by this sale, must be equal to self.get_current_total()

search_from_text(searchstr)[source]

search searchstr in products and categories :return: tuple (list of categories, products for table)

Return type:list(Product)
search_product_from_code(code)[source]

search via barcode, PLU or similar unique-ID entry. code may be any string

Returns:product id
Raises:ProductNotFound() if nothing found
set_current_order(order_id)[source]

switch to another order (when the backend supports multiple orders)

update_quantity(order_line_id, amount)[source]

change quantity of order-line.

if not all float values are allowed, round upvalue to the next possible one

FabLabKasse.shopping.backend.offline_base module

Module contents