Source code for stoqlib.lib.parameters

# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4

# Copyright (C) 2005-2013 Async Open Source
# 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
# 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:
# Author(s): Stoq Team <>
""" Parameters and system data for applications"""

from decimal import Decimal
from uuid import uuid4
import logging

from kiwi.datatypes import ValidationError
from kiwi.python import namedAny
from stoqdrivers.enum import TaxType

from stoqlib.database.runtime import get_default_store
from stoqlib.domain.parameter import ParameterData
from stoqlib.enums import (LatePaymentPolicy, ReturnPolicy,
from stoqlib.l10n.l10n import get_l10n_field
from stoqlib.lib.barcode import BarcodeInfo
from stoqlib.lib.countries import get_countries
from stoqlib.lib.defaults import MAX_INT
from stoqlib.lib.threadutils import threadit
from stoqlib.lib.translation import stoqlib_gettext
from stoqlib.lib.validators import (validate_int,

_ = stoqlib_gettext
log = logging.getLogger(__name__)

def _credit_limit_salary_changed(new_value, store):
    from stoqlib.domain.person import Client

    old_value = sysparam.get_decimal('CREDIT_LIMIT_SALARY_PERCENT')
    if new_value == old_value:

    new_value = Decimal(new_value)
    Client.update_credit_limit(new_value, store)

[docs]class ParameterDetails(object): def __init__(self, key, group, short_desc, long_desc, type, initial=None, options=None, combo_data=None, range=None, multiline=False, validator=None, change_callback=None, editor=None, wrap=True, allow_none=False, is_editable=True, check_missing=None): """ :param check_missing: A callable that should return True or False indicating if the parameter is missing configuration. If missing, it will be highlighted in the parameters search """ self.key = key = group self.short_desc = short_desc self.long_desc = long_desc self.type = type self.initial = initial self.options = options self.combo_data = combo_data self.range = range self.multiline = multiline self.validator = validator self.change_callback = change_callback self.editor = editor self.wrap = wrap self.allow_none = allow_none self.is_editable = is_editable self.check_missing = check_missing # # Public API #
[docs] def get_parameter_type(self): if isinstance(self.type, basestring): return namedAny('stoqlib.domain.' + self.type) else: return self.type
[docs] def get_parameter_validator(self): return self.validator or self._get_generic_parameter_validator()
[docs] def get_change_callback(self): return self.change_callback
# # Staticmethods # @staticmethod
[docs] def validate_int(value): if not validate_int(value): return ValidationError(_("This parameter only accepts " "integer values."))
[docs] def validate_decimal(value): if not validate_decimal(value): return ValidationError(_("This parameter only accepts " "decimal values."))
[docs] def validate_area_code(code): if not validate_area_code(code): return ValidationError(_("'%s' is not a valid area code.\n" "Valid area codes are on 10-99 range.") % code)
[docs] def validate_percentage(value): if not validate_percentage(value): return ValidationError(_("'%s' is not a valid percentage.") % value)
[docs] def validate_cnpj(value): if not validate_cnpj(value): return ValidationError(_("'%s' is not a valid CNPJ"))
[docs] def validate_state(value): state_l10n = get_l10n_field('state') if not state_l10n.validate(value): return ValidationError( _("'%s' is not a valid %s.") % (value, state_l10n.label.lower(), ))
[docs] def validate_city(value): city_l10n = get_l10n_field('city') state = sysparam.get_string('STATE_SUGGESTED') country = sysparam.get_string('COUNTRY_SUGGESTED') if not city_l10n.validate(value, state=state, country=country): return ValidationError(_("'%s' is not a valid %s.") % (value, city_l10n.label.lower()))
# # Private API # def _get_generic_parameter_validator(self): p_type = self.get_parameter_type() if issubclass(p_type, int): return ParameterDetails.validate_int elif issubclass(p_type, Decimal): return ParameterDetails.validate_decimal
DEFAULT_LOAN_NOTICE = _( 'I inform and sign up to receive the items in full ' 'working order and I am aware of the responsability ' 'that I have for returning them, as well as the ' 'return of the amounts involved, in case of loss, ' 'damage or any event that make the product unusable.') _details = [ ParameterDetails( u'EDIT_CODE_PRODUCT', _(u'Products'), _(u'Disable edit code products'), _(u'Disable edit code products on purchase application'), bool, initial=False), ParameterDetails( u'SUGGEST_PRODUCT_CODE_BASED_CATEGORY', _(u'Products'), _(u'Suggest product code based on the selected category'), _(u'If the next product code should be based on the category selected or not'), bool, initial=False), ParameterDetails( u'DEFAULT_PRODUCT_PIS_TEMPLATE', _(u'Products'), _(u'Default PIS template'), _(u'Use this information when try to emmit a fiscal document and the ' u'product has no configured PIS'), 'taxes.ProductPisTemplate'), ParameterDetails( u'DEFAULT_PRODUCT_COFINS_TEMPLATE', _(u'Products'), _(u'Default COFINS template'), _(u'Use this information when try to emmit a fiscal document and the ' u'product has no configured COFINS'), 'taxes.ProductCofinsTemplate'), ParameterDetails( u'MAIN_COMPANY', _(u'General'), _(u'Primary company'), _(u'The primary company which is the owner of all other ' u'branch companies'), u'person.Branch'), ParameterDetails( u'CUSTOM_LOGO_FOR_REPORTS', _(u'General'), _(u'Custom logotype for reports'), _(u'Defines a custom logo for all the reports generated by Stoq. ' u'The recommended image dimension is 170x65 (pixels), if needed, ' u'the image will be resized. In order to use the default logotype ' u'leave this field blank'), u'image.Image'), ParameterDetails( u'DISABLE_COOKIES', _(u'General'), _(u'Disable cookies'), _(u'Disable the ability to use cookies in order to automatic log in ' u'the system. If so, all the users will have to provide the password ' u'everytime they log in. Requires restart to take effect.'), bool, initial=False), ParameterDetails( u'DEFAULT_SALESPERSON_ROLE', _(u'Sales'), _(u'Default salesperson role'), _(u'Defines which of the employee roles existent in the system is the ' u'salesperson role'), u'person.EmployeeRole'), # FIXME: s/SUGGESTED/DEFAULT/ ParameterDetails( u'SUGGESTED_SUPPLIER', _(u'Purchase'), _(u'Suggested supplier'), _(u'The supplier suggested when we are adding a new product in the ' u'system'), u'person.Supplier', allow_none=True), ParameterDetails( u'SUGGESTED_UNIT', _(u'Purchase'), _(u'Suggested unit'), _(u'The unit suggested when we are adding a new product in the ' u'system'), u'sellable.SellableUnit'), ParameterDetails( u'ALLOW_OUTDATED_OPERATIONS', _(u'General'), _(u'Allow outdated operations'), _(u'Allows the inclusion of purchases and payments done previously than the ' u'current date.'), bool, initial=False), ParameterDetails( u'DELIVERY_SERVICE', _(u'Sales'), _(u'Delivery service'), _(u'The default delivery service in the system.'), u'service.Service'), # XXX This parameter is POS-specific. How to deal with that # in a better way? ParameterDetails( u'POS_FULL_SCREEN', _(u'Sales'), _(u'Show POS application in Fullscreen'), _(u'Once this parameter is set the Point of Sale application ' u'will be showed as full screen'), bool, initial=False), ParameterDetails( u'POS_SEPARATE_CASHIER', _(u'Sales'), _(u'Exclude cashier operations in Point of Sale'), _(u'If you have a computer that will be a Point of Sales and have a ' u'fiscal printer connected, set this False, so the Till menu will ' u'appear on POS. If you prefer to separate the Till menu from POS ' u'set this True.'), bool, initial=False), ParameterDetails( u'USE_SALE_TOKEN', _(u'Sales'), _(u'Use tokens to control sales'), _(u'A token is a phisical object that will be attached to a sale ' u'during its lifetime. For example, it can be a bar table, a ' u'hotel room, a real token in a convenience store and so on. '), bool, initial=False), ParameterDetails( u'DEFAULT_PAYMENT_METHOD', _(u'Sales'), _(u'Default payment method selected'), _(u'The default method to select when doing a checkout on POS'), u'payment.method.PaymentMethod'), ParameterDetails( u'ALLOW_CANCEL_CONFIRMED_SALES', _(u'Sales'), _(u'Allow to cancel confirmed sales'), _(u'When this parameter is True, allow the user to cancel confirmed and' u' paid sales'), bool, initial=False), ParameterDetails( u'CITY_SUGGESTED', _(u'General'), _(u'Default city'), _(u'When adding a new address for a certain person we will always ' u'suggest this city.'), unicode, initial=u'São Carlos', validator=ParameterDetails.validate_city), ParameterDetails( u'STATE_SUGGESTED', _(u'General'), _(u'Default state'), _(u'When adding a new address for a certain person we will always ' u'suggest this state.'), unicode, initial=u'SP', validator=ParameterDetails.validate_state), ParameterDetails( u'COUNTRY_SUGGESTED', _(u'General'), _(u'Default country'), _(u'When adding a new address for a certain person we will always ' u'suggest this country.'), # FIXME: When fixing bug 5100, change this to BR unicode, initial=u'Brazil', combo_data=get_countries), ParameterDetails( u'ALLOW_REGISTER_NEW_LOCATIONS', _(u'General'), _(u'Allow registration of new city locations'), # Change the note here when we have more locations to reflect it _(u'Allow to register new city locations. A city location is a ' u'single set of a country + state + city.\n' u'NOTE: Right now this will only work for brazilian locations.'), bool, initial=False), ParameterDetails( u'HAS_DELIVERY_MODE', _(u'Sales'), _(u'Has delivery mode'), _(u'Does this branch work with delivery service? If not, the ' u'delivery option will be disable on Point of Sales Application.'), bool, initial=True), ParameterDetails( u'SHOW_COST_COLUMN_IN_SALES', _(u'Sales'), _(u'Show cost column in sales'), _(u'should the cost column be displayed when creating a new sale quote.'), bool, initial=False), ParameterDetails( u'MAX_SEARCH_RESULTS', _(u'General'), _(u'Max search results'), _(u'The maximum number of results we must show after searching ' u'in any dialog.'), int, initial=600, range=(1, MAX_INT)), ParameterDetails( u'CONFIRM_SALES_ON_TILL', _(u'Sales'), _(u'Confirm sales in Till'), _(u'Once this parameter is set, the sales confirmation are only made ' u'on till application and the fiscal coupon will be printed on ' u'that application instead of Point of Sales'), bool, initial=False), ParameterDetails( u'CONFIRM_QTY_ON_BARCODE_ACTIVATE', _(u'Sales'), _(u'Requires confirmation of quantity after barcode activation'), _(u'The system will always require the quantity of products ' u'before adding a sale item on Point of Sale'), bool, initial=False), ParameterDetails( u'ACCEPT_CHANGE_SALESPERSON', _(u'Sales'), _(u'Change salesperson'), _(u'Determines weather we are able or not to change the salesperson ' u'on the sale checkout dialog. Both "Allowed" and "Disallowed" will ' u'select the current user by default, but only the former will ' u'allow it to be changed. "Choose" will force the current user to ' u'indicate the correct salesperson.'), int, initial=int(ChangeSalespersonPolicy.DISALLOW), options={ int(ChangeSalespersonPolicy.DISALLOW): _(u'Disallowed'), int(ChangeSalespersonPolicy.ALLOW): _(u"Allowed"), int(ChangeSalespersonPolicy.FORCE_CHOOSE): _(u'Choose'), }), ParameterDetails( u'RETURN_POLICY_ON_SALES', _(u'Sales'), _(u'Return policy on sales'), _(u'This parameter sets if the salesperson must return money, credit ' u'or if the client can choose when there is overpaid values in ' u'sales.'), int, initial=int(ReturnPolicy.CLIENT_CHOICE), options={ int(ReturnPolicy.CLIENT_CHOICE): _(u"Client's choice"), int(ReturnPolicy.RETURN_MONEY): _(u'Always return money'), int(ReturnPolicy.RETURN_CREDIT): _(u'Always create credit for ' u'future sales'), }), ParameterDetails( u'ACCEPT_SALE_RETURN_WITHOUT_DOCUMENT', _(u'Sales'), _(u'Allow sale return from clients without document'), _(u'If this parameter is set it will not be possible to accept ' u'returned sales from clients without document.'), bool, initial=True), ParameterDetails( u'MAX_SALE_DISCOUNT', _(u'Sales'), _(u'Max discount for sales'), _(u'The max discount for salesperson in a sale'), Decimal, initial=Decimal(5), range=(0, 100), validator=ParameterDetails.validate_percentage), ParameterDetails( u'REUTILIZE_DISCOUNT', _(u'Sales'), _(u'Reutilize not used discounts on sale quotes'), _(u'Whether we should reutilize the discount not used on some ' u'products to other products. For instance, if two products with ' u'a price of 100,00 are on a sale, and they both have a max ' u'discount of 10%, that means we could sell each one for 90,00. ' u'If this parameter is true, we could still sell one of those ' u'items for 100,00 and reutilize it\'s not used discount on the ' u'other product, selling it for 80,00'), bool, initial=False), ParameterDetails( u'SALE_PAY_COMMISSION_WHEN_CONFIRMED', _(u'Sales'), _(u'Commission Payment At Sale Confirmation'), _(u'Define whether the commission is paid when a sale is confirmed. ' u'If True pay the commission when a sale is confirmed, ' u'if False, pay a relative commission for each commission when ' u'the sales payment is paid.'), bool, initial=False), ParameterDetails( u'ALLOW_TRADE_NOT_REGISTERED_SALES', _(u"Sales"), _(u"Allow trade not registered sales"), _(u"If this is set to True, you will be able to trade products " u"from sales not registered on Stoq. Use this option only if " u"you need to trade itens sold on other stores."), bool, initial=False), ParameterDetails( u'USE_TRADE_AS_DISCOUNT', _(u'Sales'), _(u'Use trade value as a discount'), _(u'The traded value will be used as discount ' u'when confirm a new sale. Otherwise, the trade ' u'will be registred as a new payment for that new sale.'), bool, initial=False), ParameterDetails( u'DEFAULT_OPERATION_NATURE', _(u'Sales'), _(u'Default operation nature'), _(u'When adding a new sale quote, we will always suggest ' u'this operation nature'), unicode, initial=_(u'Sale')), ParameterDetails( u'ASK_SALES_CFOP', _(u'Sales'), _(u'Ask for Sale Order C.F.O.P.'), _(u'Once this parameter is set to True we will ask for the C.F.O.P. ' u'when creating new sale orders'), bool, initial=False), ParameterDetails( u'ALLOW_HIGHER_SALE_PRICE', _(u'Sales'), _(u'Allow product sale with a higher price'), _(u'When this parameter is set, we will allow the sales person to add ' u'items to a quote with a price higher than the default price for ' u'the product.'), bool, initial=True), ParameterDetails( u'DEFAULT_SALES_CFOP', _(u'Sales'), _(u'Default Sales C.F.O.P.'), _(u'Default C.F.O.P. (Fiscal Code of Operations) used when generating ' u'fiscal book entries.'), u'fiscal.CfopData'), ParameterDetails( u'DEFAULT_RETURN_SALES_CFOP', _(u'Sales'), _(u'Default Return Sales C.F.O.P.'), _(u'Default C.F.O.P. (Fiscal Code of Operations) used when returning ' u'sale orders '), u'fiscal.CfopData'), ParameterDetails( u'TOLERANCE_FOR_LATE_PAYMENTS', _(u'Sales'), _(u'Tolerance for a payment to be considered as a late payment.'), _(u'How many days Stoq should allow a client to not pay a late ' u'payment without considering it late.'), int, initial=0, range=(0, 365)), ParameterDetails( u'EXPIRATION_SALE_QUOTE_DATE', _(u'Sales'), _(u'Period of time in days to calculate expiration date of a sale quote'), _(u'How many days Stoq should consider to calculate the default ' u'expiration day of a sale quote'), int, initial=0, range=(0, 365)), ParameterDetails( u'LATE_PAYMENTS_POLICY', _(u'Sales'), _(u'Policy for customers with late payments.'), _(u'How should Stoq behave when creating a new sale for a client with ' u'late payments'), int, initial=int(LatePaymentPolicy.ALLOW_SALES), options={int(LatePaymentPolicy.ALLOW_SALES): _(u'Allow sales'), int(LatePaymentPolicy.DISALLOW_STORE_CREDIT): _(u'Allow sales except with store credit'), int(LatePaymentPolicy.DISALLOW_SALES): _(u'Disallow sales')}), ParameterDetails( u'CHANGE_CLIENT_AFTER_CONFIRMED', _(u'Sales'), _(u'Allow client change after sale\'s confirmation'), _(u'This parameter allows to change the client after a sale\'s confirmation.'), bool, initial=False), ParameterDetails( u'CHANGE_SALESPERSON_AFTER_CONFIRMED', _(u'Sales'), _(u'Allow salesperson change after sale\'s confirmation'), _(u'This parameter allows to change the salesperson after a sale\'s confirmation.'), bool, initial=False), ParameterDetails( u'DEFAULT_RECEIVING_CFOP', _(u'Purchase'), _(u'Default Receiving C.F.O.P.'), _(u'Default C.F.O.P. (Fiscal Code of Operations) used when receiving ' u'products in the stock application.'), u'fiscal.CfopData'), ParameterDetails( u'DEFAULT_STOCK_DECREASE_CFOP', _(u'Stock'), _(u'Default C.F.O.P. for Stock Decreases'), _(u'Default C.F.O.P. (Fiscal Code of Operations) used when performing a ' u'manual stock decrease.'), u'fiscal.CfopData'), ParameterDetails( u'ICMS_TAX', _(u'Sales'), _(u'Default ICMS tax'), _(u'Default ICMS to be applied on all the products of a sale. ') + u' ' + _(u'This is a percentage value and must be between 0 and 100.') + u' ' + _(u'E.g: 18, which means 18% of tax.'), Decimal, initial=Decimal(18), range=(0, 100), validator=ParameterDetails.validate_percentage), ParameterDetails( u'ISS_TAX', _(u'Sales'), _(u'Default ISS tax'), _(u'Default ISS to be applied on all the services of a sale. ') + u' ' + _(u'This is a percentage value and must be between 0 and 100.') + u' ' + _(u'E.g: 12, which means 12% of tax.'), Decimal, initial=Decimal(18), range=(0, 100), validator=ParameterDetails.validate_percentage), ParameterDetails( u'SUBSTITUTION_TAX', _(u'Sales'), _(u'Default Substitution tax'), _(u'The tax applied on all sale products with substitution tax type.') + u' ' + _(u'This is a percentage value and must be between 0 and 100.') + u' ' + _(u'E.g: 16, which means 16% of tax.'), Decimal, initial=Decimal(18), range=(0, 100), validator=ParameterDetails.validate_percentage), ParameterDetails( u'DEFAULT_AREA_CODE', _(u'General'), _(u'Default area code'), _(u'This is the default area code which will be used when ' u'registering new clients, users and more to the system'), int, initial=16, validator=ParameterDetails.validate_area_code), ParameterDetails( u'CREDIT_LIMIT_SALARY_PERCENT', _(u'General'), _(u"Client's credit limit automatic calculation"), _(u"This is used to calculate the client's credit limit according" u"to the client's salary. If this percent is changed it will " u"automatically recalculate the credit limit for all clients."), Decimal, initial=Decimal(0), range=(0, 100), validator=ParameterDetails.validate_percentage, change_callback=_credit_limit_salary_changed), ParameterDetails( u'DEFAULT_PRODUCT_TAX_CONSTANT', _(u'Sales'), _(u'Default tax constant for products'), _(u'This is the default tax constant which will be used ' u'when adding new products to the system'), u'sellable.SellableTaxConstant'), ParameterDetails( u'SUGGEST_BATCH_NUMBER', _(u'General'), _(u'Suggest batch number'), _(u"If false, you should enter the batch number by hand. That's " u"useful if the batch number is already present on the barcode " u"of the products for instance. If true a sequencial number will " u"be used for suggestion when registering new batches. That's " u"useful if you generate your own batches."), bool, initial=False), ParameterDetails( u'LABEL_TEMPLATE_PATH', _(u'General'), _(u'Glabels template file'), _(u'The glabels file that will be used to print the labels. Check the ' u'documentation to see how to setup this file.'), unicode, initial=u"", editor='file-chooser'), ParameterDetails( u'COST_PRECISION_DIGITS', _(u'General'), _(u'Number of digits to use for product cost'), _(u'Set this parameter accordingly to the number of digits of the ' u'products you purchase'), int, initial=2, range=(2, 8)), ParameterDetails( u'SCALE_BARCODE_FORMAT', _(u'Sales'), _(u'Scale barcode format'), _(u'Format used by the barcode printed by the scale. This format always' u' starts with 2 followed by 4,5 or 6 digits product code and by a 5' u' digit weight or a 6 digit price. Check or scale documentation and' u' configuration to see the best option.'), int, initial=0, options=BarcodeInfo.options), ParameterDetails( u'BANKS_ACCOUNT', _(u'Accounts'), _(u'Parent bank account'), _(u'Newly created bank accounts will be placed under this account.'), u'account.Account'), ParameterDetails( u'TILLS_ACCOUNT', _(u'Accounts'), _(u'Parent till account'), _(u'Till account transfers will be placed under this account'), u'account.Account'), ParameterDetails( u'IMBALANCE_ACCOUNT', _(u'Accounts'), _(u'Imbalance account'), _(u'Account used for unbalanced transactions'), u'account.Account'), ParameterDetails( u'DEMO_MODE', _(u'General'), _(u'Demonstration mode'), _(u'If Stoq is used in a demonstration mode'), bool, initial=False, is_editable=False), ParameterDetails( u'BLOCK_INCOMPLETE_PURCHASE_PAYMENTS', _(u'Payments'), _(u'Block incomplete purchase payments'), _(u'Do not allow confirming a account payable if the purchase is not ' u'completely received.'), bool, initial=False), ParameterDetails( u'CREATE_PAYMENTS_ON_STOCK_DECREASE', _(u'Payments'), _(u'Create payments for a stock decrease'), _(u'When this paramater is True, Stoq will allow to create payments for ' u'stock decreases.'), bool, initial=False), ParameterDetails( u'SHOW_TOTAL_PAYMENTS_ON_TILL', _(u'Till'), _(u'Show total received payments of the day on till'), _(u'When this paramater is True, show total of received payments.'), bool, initial=False), # This parameter is tricky, we want to ask the user to fill it in when # upgrading from a previous version, but not if the user installed Stoq # from scratch. Some of the hacks involved with having 3 boolean values # ("", True, False) can be removed if we always allow None and treat it like # and unset value in the database. ParameterDetails( u'ONLINE_SERVICES', _(u'General'), _(u'Online services'), _(u'If online services such as upgrade notifications, automatic crash reports ' u'should be enabled.'), bool, initial=True), ParameterDetails( u'BILL_INSTRUCTIONS', _(u'Sales'), _(u'Bill instructions '), # Translators: do not translate $DATE, $INTEREST, $PENALTY # and $INVOICE_NUMBER _(u'When printing bills, include the first 3 lines of these on ' u'the bill itself. This usually includes instructions on how ' u'to pay the bill and the validity and the terms. The following ' u'placeholders will be replaced by:\n\n' u'$DATE: Replaced with the due date of the bill\n' u'$PENALTY: The calculated penalty based on the parameter aliquot\n' u'$INTEREST: The calculated interest based on the parameter aliquot\n' u'$DISCOUNT: The calculated discount based on the parameter aliquot\n' u'$INVOICE_NUMBER: The sale invoice number\n'), unicode, multiline=True, initial=u""), ParameterDetails( u'BILL_PAYMENT_PLACE', _(u'Sales'), _(u'Bill payment place'), _(u'Payment place instructions to be printed on bill slip'), unicode, multiline=True, initial=_(u'Payable in any bank until due date')), ParameterDetails( u'BILL_INTEREST', _(u'Sales'), _(u'Bill interest aliquot'), _(u'The aliquot to calculate the daily interest on the bill. ' u'See "Bill instructions" parameter for more information on how ' u'this is used'), Decimal, initial=Decimal(0), range=(0, 100), validator=ParameterDetails.validate_percentage), ParameterDetails( u'BILL_PENALTY', _(u'Sales'), _(u'Bill penalty aliquot'), _(u'The aliquot to calculate the penalty on the bill. ' u'See "Bill instructions" parameter for more information on how ' u'this is used'), Decimal, initial=Decimal(0), range=(0, 100), validator=ParameterDetails.validate_percentage), ParameterDetails( u'BILL_DISCOUNT', _(u'Sales'), _(u'Bill discount aliquot'), _(u'The aliquot to calculate the discount on the bill. ' u'See "Bill instructions" parameter for more information on how ' u'this is used'), Decimal, initial=Decimal(0), range=(0, 100), validator=ParameterDetails.validate_percentage), ParameterDetails( u'BOOKLET_INSTRUCTIONS', _(u'Sales'), _(u'Booklet instructions '), _(u'When printing booklets, include the first 4 lines of these on it. ' u'This usually includes instructions on how to pay the booklet and ' u'the validity and the terms.'), unicode, multiline=True, initial=_(u"Payable at any branch on presentation of this booklet")), ParameterDetails( u'SMART_LIST_LOADING', _(u'Smart lists'), _(u'Load items intelligently from the database'), _(u'This is useful when you have several thousand items, but it may cause ' u'some problems as it\'s new and untested. If you want to preserve the old ' u'list behavior in the payable and receivable applications, ' u'disable this parameter.'), bool, initial=True), ParameterDetails( u'LOCAL_BRANCH', _(u'General'), _(u'Current branch for this database'), _(u'When operating with synchronized databases, this parameter will be ' u'used to restrict the data that will be sent to this database.'), u'person.Branch', is_editable=False), ParameterDetails( u'SYNCHRONIZED_MODE', _(u'General'), _(u'Synchronized mode operation'), _(u'This parameter indicates if Stoq is operating with synchronized ' u'databases. When using synchronized databases, some operations with ' u'branches different than the current one will be restriced.'), bool, initial=False, is_editable=False), ParameterDetails( u'PRINT_PROMISSORY_NOTES_ON_BOOKLETS', _(u'Payments'), _(u'Printing of promissory notes on booklets'), _(u'This parameter indicates if Stoq should print promissory notes when' u' printing booklets for payments.'), bool, initial=True), ParameterDetails( u'PRINT_PROMISSORY_NOTE_ON_LOAN', _(u'Loan'), _(u'Printing of promissory notes on loans'), _(u'This parameter indicates if Stoq should print a promissory note ' u'when printing a loan receipt.'), bool, initial=False), ParameterDetails( u'LOAN_NOTICE', _(u'Loan'), _(u'Notice that will be added to the loan report'), _(u'This notice will be added to the loan receipt and can be used to ' 'warn the client that he is responsible for the items he is loaning'), unicode, multiline=True, initial=DEFAULT_LOAN_NOTICE), ParameterDetails( u'PRINT_SALE_DETAILS_ON_POS', _(u'Sales'), _(u'Printing of sale details on point of sales'), _(u'This parameter indicates if Stoq should print the sale details' u'when finishing a sale on point of sales.'), bool, initial=False), ParameterDetails( u'MANDATORY_CHECK_NUMBER', _(u'Payments'), _(u'Mandatory check number'), _(u'This parameter indicates if the check number on check payments is ' u'mandatory.'), bool, initial=False), ParameterDetails( u'ALLOW_CREATE_PAYMENT_ON_SALE_QUOTE', _(u'Sales'), _(u'Allow to add payment on sale quote'), _(u'When enabled, the sale quote wizard will have an extra step to ' u'configure the payments for the sale.'), bool, initial=False), ParameterDetails( u'MANDATORY_CARD_AUTH_NUMBER', _(u'Sales'), _(u'Set authorization number mandatory'), _(u'Set the authorization number on card payments as mandatory or not.'), bool, initial=False), ParameterDetails( u'DEFECT_DETECTED_TEMPLATE', _(u'Work order'), _(u'Defect detected template for work orders'), _(u'A template to be used to fill the "Defect detected" field when ' u'creating a new work order.'), unicode, multiline=True, initial=u""), ParameterDetails( u'WORK_ORDER_NOTICE', _(u'Work order'), _(u'Notice that will be added to the work order report'), _(u'This notice will be added to the work order receipt and can be used to ' 'warn the client about warranties and limitations'), unicode, multiline=True, initial=u''), ParameterDetails( u'AUTOMATIC_LOGOUT', _(u'General'), _(u'Automatic logout after inactivity period'), _(u'Set the maximum time in minutes for the user to remain idle, before being ' u'automatically logout. \nSet to zero to disable the funcionality. ' u'Requires restart to take effect.'), int, initial=0), ParameterDetails( u'UPDATE_PRODUCTS_COST_ON_PURCHASE', _(u'Purchase'), _(u'Automatic update of products cost when making a new purchase.'), _(u'When a new purchase is made, it\'s products cost are set to ' u'the purchase\'s items cost.'), bool, initial=False), ParameterDetails( u'UPDATE_PRODUCT_COST_ON_COMPONENT_UPDATE', _(u'Production'), _(u'Automatic update the cost of a production product'), _(u'When the list of components is updated or the cost of a component ' 'is updated, the cost of a production product will be automatically ' 'updated'), bool, initial=True), ParameterDetails( u'BIRTHDAY_NOTIFICATION', _(u'General'), _(u'Client birthday notification'), _(u'Notify about clients birthdays on the current day.'), bool, initial=True), # This parameter is used for communication with stoq api and stoq link lite. ParameterDetails( u'USER_HASH', _(u'General'), _(u'User hash'), _(u'This hash value is used for integration and communication with stoq api ' u'and stoq link. It will be added on ping requests, tef requests and ' u'feedbacks data sent to stoq api and on the stoq statistics data sent ' u'to stoq link lite.'), unicode, initial=unicode(uuid4().get_hex())), ParameterDetails( u'SHOW_FULL_DATETIME_ON_RECEIVABLE', _(u'Receivable'), _(u'Show full time of the sale on receivable payments'), _(u'Beyond date, display exactly the hour and minute of the sale on the ' u'receivable payments list.'), bool, initial=False), ]
[docs]class ParameterAccess(object): """ API for accessing and updating system parameters """ def __init__(self): # Mapping of details, name -> ParameterDetail self._details = {} for detail in _details: self.register_param(detail) self._values_cache = None # Lazy Mapping of database raw database values, name -> database value @property def _values(self): if self._values_cache is None: self._values_cache = dict( (p.field_name, p.field_value) for p in get_default_store().find(ParameterData)) return self._values_cache def _create_default_values(self, store): """Create default values for parameters that take objects""" self._set_default_value(store, u'USER_HASH') self._set_default_method_default(store) self._set_cfop_default(store, u"DEFAULT_SALES_CFOP", u"Venda de Mercadoria Adquirida", u"5.102") self._set_cfop_default(store, u"DEFAULT_RETURN_SALES_CFOP", u"Devolucao", u"5.202") self._set_cfop_default(store, u"DEFAULT_RECEIVING_CFOP", u"Compra para Comercializacao", u"1.102") self._set_cfop_default(store, u"DEFAULT_STOCK_DECREASE_CFOP", u"Outra saída de mercadoria ou " u"prestação de serviço não especificado", u"5.949") self._set_delivery_default(store) self._set_sales_person_role_default(store) self._set_product_tax_constant_default(store) def _set_default_method_default(self, store): from stoqlib.domain.payment.method import PaymentMethod if self.has_object("DEFAULT_PAYMENT_METHOD"): return method = PaymentMethod.get_by_name(store, u'money') self.set_object(store, u"DEFAULT_PAYMENT_METHOD", method) def _set_cfop_default(self, store, param_name, description, code): from stoqlib.domain.fiscal import CfopData if self.has_object(param_name): return data = self.get_object(store, param_name) if not data: # There is no unique code constraint in the cfop_data table! data = store.find(CfopData, code=code).any() if data is None: data = CfopData(code=code, description=description, store=store) self.set_object(store, param_name, data) def _set_sales_person_role_default(self, store): if self.has_object("DEFAULT_SALESPERSON_ROLE"): return from stoqlib.domain.person import EmployeeRole role = EmployeeRole.get_or_create(store, name=_(u'Salesperson')) self.set_object(store, "DEFAULT_SALESPERSON_ROLE", role) def _set_product_tax_constant_default(self, store): if self.has_object("DEFAULT_PRODUCT_TAX_CONSTANT"): return from stoqlib.domain.sellable import SellableTaxConstant tax_constant = SellableTaxConstant.get_by_type(TaxType.NONE, store) self.set_object(store, "DEFAULT_PRODUCT_TAX_CONSTANT", tax_constant) def _set_delivery_default(self, store): if self.has_object("DELIVERY_SERVICE"): return from stoqlib.domain.sellable import (Sellable, SellableTaxConstant) from stoqlib.domain.service import Service tax_constant = SellableTaxConstant.get_by_type(TaxType.SERVICE, store) sellable = store.find(Sellable, description=_(u'Delivery')).any() if not sellable: sellable = Sellable(store=store, description=_(u'Delivery')) sellable.tax_constant = tax_constant service = sellable.service or Service(sellable=sellable, store=store) self.set_object(store, "DELIVERY_SERVICE", service) def _verify_detail(self, field_name, expected_type=None): detail = self._details.get(field_name) if detail is None: raise ValueError("%s is not a valid parameter" % (field_name, )) if expected_type is not None and detail.type != expected_type: raise ValueError("%s is not a %s parameter" % ( field_name, expected_type.__name__)) return detail def _set_param_internal(self, store, param_name, value, expected_type): self._verify_detail(param_name, expected_type) param = ParameterData.get_or_create(store, field_name=unicode(param_name)) if value is not None and not type(value) is expected_type: raise TypeError("%s must be a %s, not %r" % ( param_name, expected_type, type(value).__name__)) # bool are represented as 1/0 if expected_type is bool: value = int(value) param.field_value = unicode(value) self.set_value_generic(param_name, param.field_value) def _set_default_value(self, store, param_name): """Sets the default initial value for a param in the database If the param is not present in the ParameterData table, it will be created with the default initial value. """ if self._values.get(param_name, None) is not None: return detail = self.get_detail_by_name(param_name) value = detail.initial if value is None: return if detail.type is bool: value = int(value) if value is not None: value = unicode(value) data = ParameterData(store=store, field_name=param_name, field_value=value, is_editable=detail.is_editable) self._values[param_name] = data.field_value return data.field_value def _remove_unused_parameters(self, store): """ Remove any parameter found in ParameterData table which is not used any longer. """ for param_name in self._values: if param_name not in self._details: param = store.find(ParameterData, field_name=param_name).one() store.remove(param) # # Public API #
[docs] def register_param(self, detail): self._details[detail.key] = detail
[docs] def clear_cache(self): """Clears the internal cache so it can be rebuilt on next access""" self._values_cache = None
[docs] def ensure_system_parameters(self, store, update=False): """ :param update: ``True`` if we're upgrading a database, otherwise ``False`` """ # This is called when creating a new database or # updating an existing one # Clear cached values to ensure the parameters updates # will be used correctly. If there any change in name, these values # will differ from database. if update: self.clear_cache() self._create_default_values(store)
[docs] def get(self, param_name, expected_type=None, store=None): detail = self._verify_detail(param_name, expected_type) value = self._values.get(param_name) if value is None: # This parameter should be created on read and not on edit. if param_name == 'USER_HASH': from stoqlib.database.runtime import new_store new_store = new_store() value = self._set_default_value(new_store, u'USER_HASH') new_store.commit() return value # initial value is already of the correct type return detail.initial if expected_type is bool: return value == u'1' elif expected_type in (Decimal, int): try: return expected_type(value) except ValueError: return expected_type(detail.initial) elif isinstance(expected_type, basestring): field_type = detail.get_parameter_type() return store.get(field_type, unicode(value)) return value
[docs] def set_bool(self, store, param_name, value): """ Updates a database bool value for a given parameter. :param store: a database store :param param_name: the parameter name :param value: the value to set :type value: bool """ self._set_param_internal(store, param_name, value, bool)
[docs] def get_bool(self, param_name): """ Fetches a bool database value. :param param_name: the parameter name :returns: the database value :rtype: bool """ return self.get(param_name, bool)
[docs] def set_decimal(self, store, param_name, value): """ Updates a database decimal value for a given parameter. :param store: a database store :param param_name: the parameter name :param value: the value to set :type value: decimal.Decimal """ self._set_param_internal(store, param_name, value, Decimal)
[docs] def get_decimal(self, param_name): """ Fetches a decimal database value. :param param_name: the parameter name :returns: the database value :rtype: decimal.Decimal """ return self.get(param_name, Decimal)
[docs] def set_int(self, store, param_name, value): """ Updates a database int value for a given parameter. :param store: a database store :param param_name: the parameter name :param value: the value to set :type value: int """ self._set_param_internal(store, param_name, value, int)
[docs] def get_int(self, param_name): """ Fetches an int database value. :param param_name: the parameter name :returns: the database value :rtype: int """ return self.get(param_name, int)
[docs] def set_string(self, store, param_name, value): """ Updates a database unicode value for a given parameter. :param store: a database store :param param_name: the parameter name :param value: the value to set :type value: unicode """ self._set_param_internal(store, param_name, unicode(value or ''), unicode)
[docs] def get_string(self, param_name): """ Fetches a unicode database value. :param param_name: the parameter name :returns: the database value :rtype: unicode """ return self.get(param_name, unicode)
[docs] def set_object(self, store, param_name, value): """ Updates a database object. :param store: a database store :param param_name: the parameter name :param value: the value to set :type value: a domain object """ detail = self._details.get(param_name) if detail is None: raise ValueError("%s is not a valid parameter" % (param_name, )) field_type = detail.get_parameter_type() if (value is not None and not isinstance(value, field_type)): raise TypeError("%s must be a %s instance, not %r" % ( param_name, field_type.__name__, type(value).__name__)) param = ParameterData.get_or_create(store, field_name=unicode(param_name)) if value is not None: value = unicode( param.field_value = value param.is_editable = detail.is_editable self._values[param_name] = value
[docs] def get_object(self, store, param_name): """ Fetches an object from the database. ..note..:: This has to query the database to build an object and it is slower than other getters, avoid it if you can. :param store: a database store :param param_name: the parameter name :returns: the object """ detail = self._verify_detail(param_name) return self.get(param_name, detail.type, store)
[docs] def get_object_id(self, param_name): """ Fetches the database object id :param param_name: the parameter name :returns: the object id """ return self.get(param_name)
[docs] def has_object(self, param_name): """ Check if an object is set. :param param_name: the parameter name """ value = self.get(param_name) return value is not None
[docs] def compare_object(self, param_name, other_object): """ Compare the currently set value of a parameter with a specified object. :param param_name: the parameter name :param other_object: object to compare """ object_id = self.get(param_name) if object_id is None and other_object is None: return True if other_object is None: return False # FIXME: Enable this type checking in the future # if type(other_object) != detail.get_parameter_type(): # raise TypeError("Expected an object of type %s, but got a %s" % ( # detail.get_parameter_type().__name__, # type(other_object).__name__)) return object_id ==
[docs] def set_value_generic(self, param_name, value): """Update the internal cache for a parameter :param param_name: the parameter name :param value: value :type value: unicode """ # FIXME: Find a better way of doing this after we integrate # better with Stoq. if param_name == 'ONLINE_SERVICES': from import ServerProxy p = ServerProxy(timeout=5) threadit(lambda: p.check_running() and'restart')) self._values[param_name] = value
[docs] def get_details(self): return self._details.values()
[docs] def get_detail_by_name(self, param_name): """ Returns a ParameterDetails class for the given parameter name :param param_name: the parameter name :returns: the detail """ detail = self._details.get(param_name) if detail is None: raise KeyError("Unknown parameter: %r" % (param_name, )) return detail
sysparam = ParameterAccess()