diff --git a/auquotidien/auquotidien.py b/auquotidien/auquotidien.py index b847803..9a5384f 100644 --- a/auquotidien/auquotidien.py +++ b/auquotidien/auquotidien.py @@ -13,9 +13,6 @@ from modules import template from modules import root from modules import payments from modules import connectors -from modules import abelium_domino_ui -from modules import abelium_domino_vars -from modules import abelium_domino_synchro get_publisher_class().register_translation_domain('auquotidien') get_publisher_class().default_configuration_path = 'au-quotidien-wcs-settings.xml' @@ -32,10 +29,6 @@ rdb.register_directory('settings', admin.SettingsDirectory()) import wcs.admin.forms wcs.admin.forms.FormsDirectory.categories = categories_admin.CategoriesDirectory() -import wcs.admin.settings -wcs.admin.settings.SettingsDirectory.domino = abelium_domino_ui.AbeliumDominoDirectory() -wcs.admin.settings.SettingsDirectory._q_exports.append('domino') - import wcs.categories wcs.categories.Category.XML_NODES = [('name', 'str'), ('url_name', 'str'), ('description', 'str'), ('position', 'int'), diff --git a/auquotidien/modules/abelium_domino_synchro.py b/auquotidien/modules/abelium_domino_synchro.py deleted file mode 100644 index e7d7a89..0000000 --- a/auquotidien/modules/abelium_domino_synchro.py +++ /dev/null @@ -1,165 +0,0 @@ -import sys -from datetime import datetime -import collections -from decimal import Decimal - -from wcs.qommon import _ -from wcs.qommon.cron import CronJob -from wcs.qommon.publisher import get_publisher_class -from wcs.qommon import get_logger - -from wcs.users import User - -from .abelium_domino_ui import (get_client, is_activated, get_invoice_regie, - abelium_domino_ws) -from .payments import Invoice, Transaction - -DOMINO_ID_PREFIX = 'DOMINO-' - -def synchronize_domino(publisher): - regie = get_invoice_regie(publisher) - logger = get_logger() - if not is_activated(publisher) or not regie: - return - client = get_client(publisher) - if client is None: - logger.warning('Unable to create a DominoWS object') - return - client.clear_cache() - users = User.values() - users_by_mail = dict(((user.email, user) for user in users)) - users_by_code_interne = {} - for user in users: - if hasattr(user, 'abelium_domino_code_famille'): - users_by_code_interne[user.abelium_domino_code_famille] = user - try: - invoices = client.invoices - except abelium_domino_ws.DominoException as e: - publisher.notify_of_exception(sys.exc_info(), context='[DOMINO]') - logger.error('domino cron: failure to retrieve invoice list from domino ' - 'for synchronization [error:%s]', e) - return - # import new invoices - logger.info('domino cron: retrieved %i invoices', len(invoices)) - for invoice_id, invoice in invoices.items(): - user = None - if invoice.family.code_interne in users_by_code_interne: - user = users_by_code_interne[invoice.family.code_interne] - if user is None: - for email in (invoice.family.email_pere, invoice.family.email_mere, - invoice.family.adresse_internet): - user = users_by_mail.get(email) - if user: - break - else: - continue - external_id = '%s%s' % (DOMINO_ID_PREFIX, invoice.id) - payment_invoice = Invoice.get_on_index(external_id, 'external_id', ignore_errors=True) - if payment_invoice: - continue - if invoice.reste_du == Decimal(0) or invoice.reste_du < Decimal(0): - continue - payment_invoice = Invoice() - payment_invoice.user_id = user.id - payment_invoice.external_id = external_id - payment_invoice.regie_id = regie.id - payment_invoice.formdef_id = None - payment_invoice.formdata_id = None - payment_invoice.amount = invoice.reste_du - payment_invoice.date = invoice.creation - payment_invoice.domino_synchro_date = datetime.now() - if 'etablissement' in invoice._detail: - etablissement = invoice._detail['etablissement'].encode('utf-8') - payment_invoice.subject = _('%s - Childcare services') % etablissement - else: - payment_invoice.subject = _('Childcare services') - if invoice._detail.get('lignes'): - details = [] - details.append('') - tpl = ''' - - - - -''' - captions = { - 'designation': _('Caption'), - 'quantite': _('Quantity'), - 'prix': _('Price'), - 'amount': _('Amount') - } - details.append(tpl % captions) - details.append('') - details.append('') - for ligne in invoice._detail['lignes']: - def encode(x): - a, b = x - b = b.encode('utf-8') - return (a,b) - ligne = map(encode, ligne) - ligne = dict(ligne) - base = collections.defaultdict(lambda:'') - base.update(ligne) - details.append(tpl % base) - details.append('
%(designation)s%(quantite)s%(prix)s%(montant)s
') - payment_invoice.details = '\n'.join(details) - payment_invoice.store() - logger.info('domino cron: remote invoice %s for family %s added to user %s invoices with id %s', - invoice.id, invoice.family.id, user.id, payment_invoice.id) - - # update invoices - invoices_ids = dict(invoices.items()) - for payment_invoice in Invoice.values(): - if payment_invoice.external_id is None or not payment_invoice.external_id.startswith(DOMINO_ID_PREFIX): - continue # not a payment related to domino we skip - i = payment_invoice.external_id[len(DOMINO_ID_PREFIX):] - i = int(i) - invoice = invoices_ids.get(i) - if payment_invoice.paid: - if not invoice: - # invoice has been paid (locally or not) but remote invoice has - # been deleted do, we do nothing. - continue - if getattr(payment_invoice, 'domino_knows_its_paid', None) or getattr(payment_invoice, 'paid_by_domino', None): - # synchronization of payment already done, skip - continue - transactions = list(Transaction.get_with_indexed_value('invoice_ids', - payment_invoice.id)) - if not transactions: - logger.warning("domino cron: invoice %s is marked paid but does " - "not have any linked transaction.", payment_invoice.id) - details = '' # no details about the payment, problem - else: - details = repr(transactions[0].__dict__) - if invoice.montant != payment_invoice.amount: - pass # add warning logs - try: - client.pay_invoice([invoice], invoice.montant, details, - payment_invoice.paid_date) - except abelium_domino_ws.DominoException as e: - logger.error('domino cron: invoice %s has been paid, but the remote system ' - 'is unreachable, notification will be done again ' - 'later [error: %s]', invoice.id, e) - else: - # memorize the date of synchronization - payment_invoice.domino_knows_its_paid = datetime.now() - payment_invoice.store() - logger.info('domino cron: domino: invoice %s has been paid; remote system has been ' - 'notified', payment_invoice.id) - else: # unpaid - if not invoice: - logger.info('domino cron: remote invoice %s disapearred, so its ' - 'still-unpaid local counterpart invoice %s was deleted.', - i, payment_invoice.id) - payment_invoice.remove_self() - elif invoice.paid(): - payment_invoice.paid_by_domino = True - payment_invoice.pay() - logger.info('domino cron: remote invoice %s has beend paid, ' - 'local invoice %s of user %s is now marked as paid.', - invoice.id, payment_invoice.id, payment_invoice.user_id) - else: # not invoice.paid() - pass # still waiting for the payment - -get_publisher_class().register_cronjob(CronJob(function=synchronize_domino, - hours=range(0, 24), minutes=range(0, 60, 30))) diff --git a/auquotidien/modules/abelium_domino_ui.py b/auquotidien/modules/abelium_domino_ui.py deleted file mode 100644 index c6c6531..0000000 --- a/auquotidien/modules/abelium_domino_ui.py +++ /dev/null @@ -1,170 +0,0 @@ -from quixote import get_publisher, redirect, get_request -from quixote.directory import Directory, AccessControlled -from quixote.html import TemplateIO, htmltext - -from wcs.qommon import _, N_ -from wcs.qommon import get_cfg, get_logger -from wcs.qommon.form import Form, StringWidget, CheckboxWidget, SingleSelectWidget -from wcs.qommon.backoffice.menu import html_top -from quixote.html import htmltext - -from .payments import Regie - - -# constants -ABELIUM_DOMINO = 'abelium_domino' -ACTIVATED = 'activated' -WSDL_URL = 'wsdl_url' -SERVICE_URL = 'service_url' -DOMAIN = 'domain' -LOGIN = 'login' -PASSWORD = 'password' -INVOICE_REGIE = 'invoice_regie' - -try: - import abelium_domino_ws -except ImportError as e: - abelium_domino_ws = None - import_error = e - -def get_abelium_cfg(publisher): - if not publisher: - publisher = get_publisher() - return publisher.cfg.get(ABELIUM_DOMINO, {}) - -def is_activated(publisher=None): - cfg = get_abelium_cfg(publisher) - return cfg.get(ACTIVATED, False) and abelium_domino_ws is not None - -def get_client(publisher=None): - publisher = publisher or get_publisher() - - cfg = get_abelium_cfg(publisher) - try: - publisher._ws_cache = abelium_domino_ws.DominoWs( - url=cfg.get(WSDL_URL, ''), - domain=cfg.get(DOMAIN,''), - login=cfg.get(LOGIN, ''), - password=cfg.get(PASSWORD, ''), - location=cfg.get(SERVICE_URL), - logger=get_logger()) - except IOError: - return None - return publisher._ws_cache - -def get_family(user, publisher=None): - family = None - if user is None: - return None - client = get_client(publisher) - if not client: - return None - if hasattr(user, 'abelium_domino_code_famille'): - family = client.get_family_by_code_interne( - user.abelium_domino_code_famille) - if family is None and user.email: - family = client.get_family_by_mail(user.email) - return family - -def get_invoice_regie(publisher=None): - cfg = get_abelium_cfg(publisher) - regie_id = cfg.get(INVOICE_REGIE) - if not regie_id: - return None - return Regie.get(regie_id, ignore_errors=True) - -class AbeliumDominoDirectory(Directory): - _q_exports = [ '' , 'debug' ] - label = N_('Domino') - - def debug(self): - from abelium_domino_vars import SESSION_CACHE - html_top(ABELIUM_DOMINO) - r = TemplateIO(html=True) - r += htmltext('
') - if get_request().get_method() == 'POST': - try: - from abelium_domino_synchro import synchronize_domino - synchronize_domino(get_publisher()) - except Exception as e: - r += htmltext('
%s
') % repr(e) - r += htmltext('

code interne: %s

') % getattr(get_request().user, str('abelium_domino_code_famille'), None) - r += htmltext('
') - context = get_publisher().substitutions.get_context_variables() - for var in sorted(context.keys()): - value = context[var] - if value: - r += htmltext('
%s
') % var - r += htmltext('
%s') % value - r += htmltext('
') - delattr(get_request().session, SESSION_CACHE) - - def _q_index(self): - publisher = get_publisher() - cfg = get_cfg(ABELIUM_DOMINO, {}) - form = self.form(cfg) - - title = _('Abelium Domino') - html_top(ABELIUM_DOMINO, title = title) - r = TemplateIO(html=True) - r += htmltext('

%s

') % title - - if form.is_submitted() and not form.has_errors(): - if form.get_widget('cancel').parse(): - return redirect('..') - if form.get_widget('submit').parse(): - for name in [f[0] for f in self.form_desc] + [INVOICE_REGIE]: - widget = form.get_widget(name) - if widget: - cfg[name] = widget.parse() - publisher.cfg[ABELIUM_DOMINO] = cfg - publisher.write_cfg() - return redirect('.') - - if abelium_domino_ws: - r += form.render() - else: - message = _('The Abelium Domino module is not ' - 'activated because of this error when ' - 'loading it: %r') % import_error - r += htmltext('

%s

') % message - r += htmltext('
') - context = get_publisher().substitutions.get_context_variables() - for var in sorted(context.keys()): - value = context[var] - if value: - r += htmltext('
%s
') % var - r += htmltext('
%s') % value - r += htmltext('
') - return r.getvalue() - - form_desc = ( - # name, required, title, kind - (ACTIVATED, False, _('Activated'), bool), - (WSDL_URL, True, _('WSDL URL'), str), - (SERVICE_URL, False, _('Service URL'), str), - (DOMAIN, True, _('Domain'), str), - (LOGIN, True, _('Login'), str), - (PASSWORD, True, _('Password'), str), - ) - - - def form(self, initial_value={}): - form = Form(enctype='multipart/form-data') - kinds = { str: StringWidget, bool: CheckboxWidget } - for name, required, title, kind in self.form_desc: - widget = kinds[kind] - form.add(widget, name, required=required, title=title, - value=initial_value.get(name, '')) - options = [(regie.id, regie.label) \ - for regie in Regie.values()] - options.insert(0, (None, _('None'))) - form.add(SingleSelectWidget, INVOICE_REGIE, - title=_('Regie which will receive payments'), - value=initial_value.get(INVOICE_REGIE), - options=options) - - form.add_submit('submit', _('Submit')) - form.add_submit('cancel', _('Cancel')) - - return form diff --git a/auquotidien/modules/abelium_domino_vars.py b/auquotidien/modules/abelium_domino_vars.py deleted file mode 100644 index 7a5178c..0000000 --- a/auquotidien/modules/abelium_domino_vars.py +++ /dev/null @@ -1,86 +0,0 @@ -from decimal import Decimal -import logging - -from quixote.publish import get_publisher - -from wcs.qommon import _ -from wcs.qommon.substitution import Substitutions -from wcs.publisher import WcsPublisher - -from .abelium_domino_ui import (is_activated, abelium_domino_ws, get_client, get_family) - -SESSION_CACHE = 'abelium_domino_variable_cache' - -class DominoVariables(object): - VARIABLE_TEMPLATE = 'domino_var_%s' - CHILD_VARIABLE_TEMPLATE = 'domino_var_%s_enfant%s' - - @classmethod - def CHILD_COLUMNS(cls): - return abelium_domino_ws.Child.COLUMNS - - @classmethod - def FAMILY_COLUMNS(cls): - return abelium_domino_ws.Family.COLUMNS + abelium_domino_ws.Family.MORE_COLUMNS - - def __init__(self, publisher=None, request=None): - self.publisher = publisher - self.request = request - - def get_substitution_variables(self): - vars = {} - if not is_activated() or not self.request or not self.request.user \ - or not getattr(self.request.user, 'email'): - return vars - - # test cache - cache = getattr(self.request.session, SESSION_CACHE, None) - if cache is not None: - return cache - # call the web service - try: - charset = get_publisher().site_charset - family = get_family(self.request.user) - if family: - family.complete() - for i, child in enumerate(family.children): - for remote_name, name, converter, desc in self.CHILD_COLUMNS(): - v = getattr(child, name, None) - if v is None: - continue - if hasattr(v, 'encode'): - v = v.encode(charset) - vars[self.CHILD_VARIABLE_TEMPLATE % (name, i+1)] = v - vars[self.VARIABLE_TEMPLATE % 'nombre_enfants'] = len(family.children) - for remote_name, name, converted, desc in self.FAMILY_COLUMNS(): - if hasattr(family, name): - v = getattr(family, name) - if v is None: - continue - if hasattr(v, 'encode'): - v = v.encode(charset) - vars[self.VARIABLE_TEMPLATE % name] = v - amount = Decimal(0) - for invoice in family.invoices: - amount += invoice.reste_du - if amount: - vars['user_famille_reste_du'] = str(amount) - except abelium_domino_ws.DominoException: - logging.exception('unable to call the domino ws for user %s', self.request.user.id) - setattr(self.request.session, SESSION_CACHE, vars) - self.request.session.store() - return vars - - def get_substitution_variables_list(cls): - if not is_activated(): - return () - vars = [] - for remote_name, name, converted, desc in cls.FAMILY_COLUMNS(): - vars.append((_('Domino'), cls.VARIABLE_TEMPLATE % name, desc)) - for remote_name, name, converted, desc in cls.CHILD_COLUMNS(): - vars.append((_('Domino'), cls.CHILD_VARIABLE_TEMPLATE % (name, '{0,1,2,..}'), desc)) - return vars - get_substitution_variables_list = classmethod(get_substitution_variables_list) - -Substitutions.register_dynamic_source(DominoVariables) -WcsPublisher.register_extra_source(DominoVariables) diff --git a/auquotidien/modules/abelium_domino_workflow.py b/auquotidien/modules/abelium_domino_workflow.py index 113459f..40737c7 100644 --- a/auquotidien/modules/abelium_domino_workflow.py +++ b/auquotidien/modules/abelium_domino_workflow.py @@ -1,148 +1,4 @@ -import re -import time +from wcs.workflows import WorkflowStatusItem -from quixote import get_request, get_publisher, get_session -from quixote.directory import Directory - -from wcs.qommon import _, N_ -from wcs.qommon.substitution import Substitutions -from wcs.qommon.form import Form, ValidatedStringWidget -from wcs.qommon import get_logger - -from wcs.workflows import Workflow, WorkflowStatusJumpItem, register_item_class -from wcs.forms.common import FormStatusPage - -from .abelium_domino_ui import (is_activated, abelium_domino_ws, get_client, get_family) -from . import abelium_domino_ws - -class InternalCodeStringWidget(ValidatedStringWidget): - regex = '\d*' - -class AbeliumDominoRegisterFamilyWorkflowStatusItem(WorkflowStatusJumpItem): - status = None - description = N_('Abelium Domino: Register a Family') +class AbeliumDominoRegisterFamilyWorkflowStatusItem(WorkflowStatusItem): key = 'abelium-domino-register-family' - category = 'interaction' - label = None - - def render_as_line(self): - return _('Register a Family into Abelium Domino') - - def get_family(self, formdata): - try: - user = formdata.get_user() - if user: - family = get_family(user) - if family: - family.complete() - return family - except abelium_domino_ws.DominoException: - pass - return None - - def fill_form(self, form, formdata, user): - family = self.get_family(formdata) - if 'family_id' not in form._names: - form.add(InternalCodeStringWidget, 'family_id', - title=_('Family internal code'), - value=family and family.code_interne.encode('utf8'), - hint=_('If a family internal code is present, the ' - 'family is updated, if not it is created')) - form.add_submit('create_update_button%s' % self.id, - _('Create or update the family')) - - def update(self, form, formdata, user, evo): - fid_widget = form.get_widget('family_id') - code_interne = fid_widget.parse() - try: - code_interne = int(code_interne) - except ValueError: - raise ValueError('Le code interne est invalide') - code_interne = '%05d' % code_interne - family = get_client().get_family_by_code_interne(code_interne) - if not family: - raise ValueError('Le code interne est invalide') - family.complete() - self.extract_family(form, formdata, user, evo, family) - family.save() - return family - - def create(self, form, formdata, user, evo): - family = abelium_domino_ws.Family(client=get_client()) - self.extract_family(form, formdata, user, evo, family) - return family - - def extract_family(self, form, formdata, user, evo, family): - formdef = formdata.formdef - children = [abelium_domino_ws.Child() for i in range(5)] - max_i = 0 - for field in formdef.fields: - value = formdata.data.get(field.id) - if value in (None, ''): - continue - if hasattr(field, 'date_in_the_past'): - value = time.strftime('%Y%m%d', value) - value = unicode(value, 'utf8') - if field.prefill and \ - field.prefill.get('type') == 'formula': - v = field.prefill.get('value', '').strip() - i = None - name = None - m = re.search('domino_var_([^ ]*)_enfant([0-9]*)', v) - m2 = re.search('domino_var_([^ ]*)', v) - if m: - name, i = m.groups() - try: - i = int(i) - except ValueError: - continue - max_i = max(i, max_i) - setattr(children[i-1], name, value) - elif m2: - name = m2.group(1) - setattr(family, name, value) - for child1, child2 in zip(family.children, children[:max_i]): - child1.__dict__.update(child2.__dict__) - family.save() - if max_i > len(family.children): # add new children - for child in children[len(family.children):max_i]: - family.add_child(child) - - def submit_form(self, form, formdata, user, evo): - logger = get_logger() - if form.get_submit() != 'create_update_button%s' % self.id: - return - try: - if form.get_widget('family_id').parse(): - family = self.update(form, formdata, user, evo) - msg = _('Sucessfully updated the family %s') - log_msg = _('Sucessfully updated the family %(code)s of %(user)s') - else: - family = self.create(form, formdata, user, evo) - msg = _('Sucessfully created the family %s') - log_msg = _('Sucessfully created the family %(code)s of %(user)s') - code_interne = family.code_interne.encode('utf8') - msg = msg % code_interne - logger.info(log_msg, {'code': code_interne, 'user': formdata.get_user()}) - form_user = formdata.get_user() - form_user.abelium_domino_code_famille = code_interne - form_user.store() - except Exception as e: - if form.get_widget('family_id').parse(): - msg = _('Unable to update family: %s') % str(e) - else: - msg = _('Unable to create family: %s') % str(e) - evo.comment = msg - logger.exception(msg % formdata.get_user()) - else: - evo.comment = msg - wf_status = self.get_target_status() - if wf_status: - evo.status = 'wf-%s' % wf_status[0].id - return False - - def is_available(self, workflow=None): - return get_publisher().has_site_option('domino') - is_available = classmethod(is_available) - -register_item_class(AbeliumDominoRegisterFamilyWorkflowStatusItem) diff --git a/auquotidien/modules/abelium_domino_ws.py b/auquotidien/modules/abelium_domino_ws.py deleted file mode 100644 index fc68b61..0000000 --- a/auquotidien/modules/abelium_domino_ws.py +++ /dev/null @@ -1,647 +0,0 @@ -# -*- coding: utf-8 -*- -from decimal import Decimal -import time -import datetime -from xml.etree import ElementTree as etree -import logging - -try: - from suds.client import Client - from suds.bindings.binding import Binding - # Webdev is bugged and using an HTML generator to produce XML content, - Binding.replyfilter = lambda self, x: x.replace(' ', ' ') -except ImportError: - Client = None - Binding = None - -logger = logging.getLogger(__name__) - -# cleaning and parsing functions - -LINE_SEPARATOR = '\n' -COLUMN_SEPARATOR = '\t' - -def unicode_and_strip(x): - return unicode(x).strip() - -def strip_and_int(x): - try: - return int(x.strip()) - except ValueError: - return None - -def strip_and_date(x): - try: - return datetime.datetime.strptime(x.strip(), '%Y%m%d').date() - except ValueError: - return None - -def parse_date(date_string): - if date_string: - return datetime.datetime.strptime(date_string, "%Y%m%d") - else: - None - -class DominoException(Exception): - pass - -def object_cached(function): - '''Decorate an object method so that its results is cached on the object - instance after the first call. - ''' - def decorated_function(self, *args, **kwargs): - cache_name = '__%s_cache' % function.__name__ - if not hasattr(self, cache_name): - setattr(self, cache_name, (time.time(), {})) - t, d = getattr(self, cache_name) - if time.time() - t > 80: - setattr(self, cache_name, (time.time(), {})) - t, d = getattr(self, cache_name) - k = tuple(*args) + tuple(sorted(kwargs.items())) - if not k in d: - d[k] = function(self, *args, **kwargs) - return d[k] - return decorated_function - -# Data model -class SimpleObject(object): - '''Base class for object returned by the web service''' - - '''Describe basic columns''' - COLUMNS = () - '''Describe extended object columns''' - MORE_COLUMNS = () - - def __init__(self, **kwargs): - self.__dict__.update(kwargs) - - def __repr__(self): - c = {} - for remote_name, name, converter, desc in self.COLUMNS: - if hasattr(self, name): - c[name] = getattr(self, name) - return str(c) - - def serialize(self): - l = [] - for remote_name, local_name, converter, desc in self.COLUMNS + self.MORE_COLUMNS: - if local_name == 'id': - continue - v = getattr(self, local_name, None) - if v is None: - continue - if isinstance(v, (datetime.date, datetime.datetime)): - v = v.strftime('%Y%m%d') - if remote_name.endswith('_DA') and '-' in v: - v = v.replace('-', '') - l.append(u'{0}: "{1}"'.format(remote_name, v)) - return u','.join(l) - - def debug(self): - '''Output a debugging view of this object''' - res = '' - for remote_name, name, converter, desc in self.MORE_COLUMNS or self.COLUMNS: - if hasattr(self, name): - res += name + ':' + repr(getattr(self, name)) + '\n' - return res - - def __int__(self): - '''Return the object id''' - return self.id - -class UrgentContact(SimpleObject): - COLUMNS = ( - ('IDENFANTS', 'id_enfant', strip_and_int, 'IDENFANTS'), - ('IDCONTACT_AUTORISE', 'id', strip_and_int, 'IDCONTACT_AUTORISE'), - ('LIENFAMILLE_CH', 'lien_de_famille', unicode_and_strip, 'LIENFAMILLE_CH'), - ('PERE_MERE_CH', 'lien_pere_ou_pere', unicode_and_strip, 'PERE_MERE_CH'), - ('IDFAMILLES', 'id_famille', unicode_and_strip, 'IDFAMILLES'), - ('TYPE_CH', 'type', unicode_and_strip, 'TYPE_CH'), - ('NOM_CH', 'nom', unicode_and_strip, 'NOM_CH'), - ('PRENOM_CH', 'prenom', unicode_and_strip, 'PRENOM_CH'), - ('RUE_CH', 'rue', unicode_and_strip, 'RUE_CH'), - ('RUE2_CH', 'rue2', unicode_and_strip, 'RUE2_CH'), - ('RUE3_CH', 'rue3', unicode_and_strip, 'RUE3_CH'), - ('CODEPOSTAL_CH', 'code_postal', unicode_and_strip, 'CODEPOSTAL_CH'), - ('VILLE_CH', 'ville', unicode_and_strip, 'VILLE_CH'), - ('TELEPHONE_CH', 'telephone', unicode_and_strip, 'TELEPHONE_CH'), - ('TELEPHONE2_CH', 'telephone2', unicode_and_strip, 'TELEPHONE2_CH'), - ('ADRESSEINT_CH', 'adresse_internet', unicode_and_strip, 'ADRESSEINT_CH'), - ) - -class Child(SimpleObject): - COLUMNS = ( - ('IDENFANTS', 'id', strip_and_int, 'Identifiant de ENFANTS'), - ('NOM_CH', 'nom', unicode_and_strip, 'Nom'), - ('PRENOM_CH', 'prenom', unicode_and_strip, 'Prénom'), - ('NAISSANCE_DA', 'date_naissance', strip_and_date, 'Date de Naissance'), - ('COMMENTAIRE_ME', 'commentaire', unicode_and_strip, 'Commentaires / Notes'), - ('IDFAMILLES', 'id_famille', unicode_and_strip, 'IDFAMILLES'), - ('CODEPOSTAL_CH', 'code_postal', unicode_and_strip, 'Code Postal'), - ('VILLE_CH', 'ville', unicode_and_strip, 'Ville'), - ('CODEINTERNE_CH', 'code_interne', unicode_and_strip, 'Code Interne'), - ('LIEUNAISSANCE_CH', 'lieu_naissance', unicode_and_strip, 'Lieu de Naissance'), - ('DEPNAISSANCE_CH', 'departement_naissance', unicode_and_strip, 'Département Naissance'), - ('NUMSECU_CH', 'num_securite_sociale', unicode_and_strip, 'N° de SECU'), - ('NATIONALITE_CH', 'nationalite', unicode_and_strip, 'Nationalité'), - ('PRENOM2_CH', 'prenom2', unicode_and_strip, 'Prénom 2'), - ('SEXE_CH', 'sexe', unicode_and_strip, 'Sexe'), - ('IDTABLELIBRE1', 'IDTABLELIBRE1', unicode_and_strip, 'IDTABLELIBRE1'), - ('IDTABLELIBRE2', 'IDTABLELIBRE2', unicode_and_strip, 'IDTABLELIBRE2'), - ('IDTABLELIBRE3', 'IDTABLELIBRE3', unicode_and_strip, 'IDTABLELIBRE3'), - ('IDTABLELIBRE4', 'IDTABLELIBRE4', unicode_and_strip, 'IDTABLELIBRE4'), - ('CHAMPLIBRE1_CH', 'CHAMPLIBRE1_CH', unicode_and_strip, 'Valeur Champ Libre 1'), - ('CHAMPLIBRE2_CH', 'CHAMPLIBRE2_CH', unicode_and_strip, 'Valeur Champ Libre 2'), - ('CHAMPCALCULE1_CH', 'CHAMPCALCULE1_CH', unicode_and_strip, 'Valeur Champ Calculé 1'), - ('CHAMPCALCULE2_CH', 'CHAMPCALCULE2_CH', unicode_and_strip, 'Valeur Champ Calculé 2'), - ('SOMMEIL_ME', 'sommeil', unicode_and_strip, 'Sommeil'), - ('ACTIVITE_ME', 'activite', unicode_and_strip, 'Activités'), - ('HABITUDE_ME', 'habitude', unicode_and_strip, 'Habitudes'), - ('PHOTO_CH', 'photographie', unicode_and_strip, 'Photographie'), - ('NUMCOMPTE_CH', 'numcompte', unicode_and_strip, 'N° Compte Comptable'), - ('TELEPHONE_CH', 'telephone', unicode_and_strip, 'Téléphone'), - ('IDFAMILLES2', 'id_famille2', unicode_and_strip, 'Identifiant famille 2'), - ('PERE_CH', 'pere', unicode_and_strip, 'Nom du père'), - ('MERE_CH', 'mere', unicode_and_strip, 'Nom de la mère'), - ('AUTOPARENTALEMERE_IN', 'autorisation_parentale_mere', unicode_and_strip, 'Autorisation Parentale Mère'), - ('AUTOPARENTALEPERE_IN', 'autorisation_parentale_pere', unicode_and_strip, 'Autorisation Parentale de Père'), - ('IDPORTAIL_ENFANTS', 'id_portail_enfants', unicode_and_strip, 'Identifiant de PORTAIL_ENFANTS'), - ('ADRESSEINT_CH', 'adresse_internet', unicode_and_strip, 'Adresse Internet'), - ) - - def save(self): - if hasattr(self, 'id'): - self.client.update_child(self) - else: - self.id = self.client.add_child(self) - self.client.clear_cache() - -class Family(SimpleObject): - COLUMNS = ( - ('IDFAMILLES', 'id', strip_and_int, 'identifiant de famille'), - ('NOMFAMILLE_CH', 'famille_nom', unicode_and_strip, 'nom de famille'), - ('EMAILPERE_CH', 'email_pere', unicode_and_strip, 'email du père'), - ('EMAILMERE_CH', 'email_mere', unicode_and_strip, 'email de la mère'), - ('ADRESSEINT_CH', 'adresse_internet', unicode_and_strip, 'adresse internet'), - ('CODEINTERNE_CH', 'code_interne', unicode_and_strip, 'code interne'), - ) - - MORE_COLUMNS = ( - ('IDFAMILLES', 'id', strip_and_int, 'identifiant de famille'), - ('CODEINTERNE_CH', 'code_interne', unicode_and_strip, 'code interne'), - ('CIVILITE_CH', 'civilite', unicode_and_strip, 'civilité'), - ('NOMFAMILLE_CH', 'famille_nom', unicode_and_strip, 'nom de famille'), - ('RUE_CH', 'rue', unicode_and_strip, 'rue'), - ('RUE2_CH', 'rue2', unicode_and_strip, 'rue 2'), - ('RUE3_CH', 'rue3', unicode_and_strip, 'rue 3'), - ('CODEPOSTAL_CH', 'code_postal', unicode_and_strip, 'code postal'), - ('VILLE_CH', 'ville', unicode_and_strip, 'ville'), - ('TELEPHONE_CH', 'telephone', unicode_and_strip, 'téléphone'), - ('TELEPHONE2_CH', 'telephone2', unicode_and_strip, 'téléphone 2'), - ('TELECOPIE_CH', 'telecopie', unicode_and_strip, 'télécopie'), - ('TELECOPIE2_CH', 'telecopie2', unicode_and_strip, 'télécopie 2'), - ('ADRESSEINT_CH', 'adresse_internet', unicode_and_strip, 'adresse internet'), - ('SITUATION_CH', 'situation', unicode_and_strip, 'situation familiale'), - ('REVENUMENSUEL_MO', 'revenu_mensuel', unicode_and_strip, 'revenu mensuel de la famille'), - ('REVENUANNUEL_MO', 'revenu_annuel', unicode_and_strip, 'revenu annuel de la famille'), - ('QUOTIENTFAMILIAL_MO', 'quotient_familial', unicode_and_strip, 'quotient familial'), - ('NBTOTALENFANTS_EN', 'nb_total_enfants', unicode_and_strip, 'nombre total d\'enfants'), - ('NBENFANTSACHARGE_EN', 'nb_enfants_a_charge', unicode_and_strip, 'nombre d\'enfants à charge'), - ('NOMPERE_CH', 'nom_pere', unicode_and_strip, 'monsieur'), - ('PRENOMPERE_CH', 'prenom_pere', unicode_and_strip, 'prénom monsieur'), - ('AUTOPARENTALEPERE_IN', 'autoparentale_pere', unicode_and_strip, 'autorisation parentale de père'), - ('DATENAISPERE_DA', 'date_naissance_pere', strip_and_date, 'date de naisance du père'), - ('DEPNAISPERE_EN', 'departement_naissance_pere', unicode_and_strip, 'département de naissance du père'), - ('LIEUNAISPERE_CH', 'lieu_naissance_pere', unicode_and_strip, 'lieu de naissance du père'), - ('RUEPERE_CH', 'rue_pere', unicode_and_strip, 'rue père'), - ('RUE2PERE_CH', 'rue2_pere', unicode_and_strip, 'rue 2 père'), - ('RUE3PERE_CH', 'rue3_pere', unicode_and_strip, 'rue 3 père'), - ('CODEPOSTALPERE_CH', 'code_postal_pere', unicode_and_strip, 'code postal père'), - ('VILLEPERE_CH', 'ville_pere', unicode_and_strip, 'ville père'), - ('TELEPHONEPERE_CH', 'telephone_pere', unicode_and_strip, 'téléphone père'), - ('TELEPHONE2PERE_CH', 'telephone2_pere', unicode_and_strip, 'téléphone 2 père'), - ('TELPERE_LR_IN', 'tel_pere_liste_rouge', unicode_and_strip, 'téléphone liste rouge père'), - ('TEL2PERE_LR_IN', 'tel2_pere_liste_rouge', unicode_and_strip, 'téléphone 2 liste rouge père'), - ('TEL_LR_IN', 'tel_liste_rourge', unicode_and_strip, 'téléphone liste rouge'), - ('TEL2_LR_IN', 'tel2_liste_rouge', unicode_and_strip, 'téléphone 2 liste rouge'), - ('NOMMERE_CH', 'nom_mere', unicode_and_strip, 'madame'), - ('PRENOMMERE_CH', 'prenom_mere', unicode_and_strip, 'prénom madame'), - ('AUTOPARENTALEMERE_IN', 'autoparentale_mere', unicode_and_strip, 'autorisation parentale mère'), - ('DATENAISMERE_DA', 'date_naissance_mere', strip_and_date, 'date de naissance de la mère'), - ('DEPNAISMERE_EN', 'departement_naissance_mere', unicode_and_strip, 'département de naissance de la mère'), - ('LIEUNAISMERE_CH', 'lieu_naissance_mere', unicode_and_strip, 'lieu de naissance de la mère'), - ('RUEMERE_CH', 'rue_mere', unicode_and_strip, 'rue mère'), - ('REVMENSUELPERE_MO', 'revenu_mensuel_pere', unicode_and_strip, 'revenu mensuel du père'), - ('RUE2MERE_CH', 'rue2_mere', unicode_and_strip, 'rue 2 mère'), - ('RUE3MERE_CH', 'rue3_mere', unicode_and_strip, 'rue 3 mère'), - ('CODEPOSTALMERE_CH', 'code_postal_mere', unicode_and_strip, 'code postal de la mère'), - ('VILLEMERE_CH', 'ville_mere', unicode_and_strip, 'ville de la mère'), - ('REVMENSUELMERE_MO', 'revenu_mensuel_mere', unicode_and_strip, 'revenu mensuel mère'), - ('REVANNUELPERE_MO', 'revenu_annuel_pere', unicode_and_strip, 'revenu annuel père'), - ('REVANNUELMERE_MO', 'revenu_annuel_mere', unicode_and_strip, 'revenu annuel mère'), - ('TELEPHONEMERE_CH', 'telephone_mere', unicode_and_strip, 'téléphone mère'), - ('TELEPHONE2MERE_CH', 'telephone2_mere', unicode_and_strip, 'téléphone 2 mère'), - ('TELMERE_LR_IN', 'telephone_mere_liste_rouge', unicode_and_strip, 'téléphone liste rouge mère'), - ('TEL2MERE_LR_IN', 'telephone2_mere_liste_rouge', unicode_and_strip, 'téléphone 2 liste rouge mère'), - ('TELECOPIEPERE_CH', 'telecopie_pere', unicode_and_strip, 'télécopie du père'), - ('TELECOPIE2PERE_CH', 'telecopie2_pere', unicode_and_strip, 'télécopie 2 du père'), - ('TELECOPIEMERE_CH', 'telecopie_mere', unicode_and_strip, 'télécopie de la mère'), - ('TELECOPIE2MERE_CH', 'telecopie2_mere', unicode_and_strip, 'télécopie 2 de la mère'), - ('PROFPERE_CH', 'profession_pere', unicode_and_strip, 'profession du père'), - ('PROFMERE_CH', 'profession_mere', unicode_and_strip, 'profession de la mère'), - ('LIEUTRAVPERE_CH', 'lieu_travail_pere', unicode_and_strip, 'lieu de travail du père'), - ('LIEUTRAVMERE_CH', 'lieu_travail_mere', unicode_and_strip, 'lieu de travail de la mère'), - ('RUETRAVPERE_CH', 'rue_travail_pere', unicode_and_strip, 'rue travail père'), - ('RUE2TRAVPERE_CH', 'rue2_travail_pere', unicode_and_strip, 'rue 2 travail père'), - ('RUE3TRAVPERE_CH', 'rue3_travail_pere', unicode_and_strip, 'rue 3 travail père'), - ('CPTRAVPERE_CH', 'code_postal_travail_pere', unicode_and_strip, 'code postal travail père'), - ('VILLETRAVPERE_CH', 'ville_travail_pere', unicode_and_strip, 'ville travail père'), - ('RUETRAVMERE_CH', 'rue_travail_mere', unicode_and_strip, 'rue travail mère'), - ('RUE2TRAVMERE_CH', 'rue2_travail_mere', unicode_and_strip, 'rue 2 travail mère'), - ('RUE3TRAVMERE_CH', 'rue3_travail_mere', unicode_and_strip, 'rue 3 travail mère'), - ('CPTRAVMERE_CH', 'code_postal_travail_mere', unicode_and_strip, 'code postal travail mère'), - ('VILLETRAVMERE_CH', 'ville_travail_mere', unicode_and_strip, 'ville travail mère'), - ('TELPROFPERE_CH', 'telephone_travail_pere', unicode_and_strip, 'téléphone professionnel père'), - ('TEL2PROFPERE_CH', 'telephone2_travail_pere', unicode_and_strip, 'téléphone 2 professionnel père'), - ('TELMOBILPERE_CH', 'telephone_mobile_pere', unicode_and_strip, 'téléphone mobile'), - ('TELPROFMERE_CH', 'telephone_travail_mere', unicode_and_strip, 'téléphone travail mère'), - ('TEL2PROFMERE_CH', 'telephone2_travail_mere', unicode_and_strip, 'téléphone 2 travail mère'), - ('TELMOBILMERE_CH', 'telephone_mobile_mere', unicode_and_strip, 'téléphone mobile mère'), - ('TOTALDU_MO', 'total_du', unicode_and_strip, 'total dû'), - ('TOTALREGLE_MO', 'total_regle', unicode_and_strip, 'total réglé'), - ('NUMCENTRESS_CH', 'num_centre_securite_sociale', unicode_and_strip, 'n° centre sécurité sociale'), - ('NOMCENTRESS_CH', 'nom_centre_securite_sociale', unicode_and_strip, 'nom centre sécurité sociale'), - ('NUMASSURANCE_CH', 'num_assurance', unicode_and_strip, 'n° assurance'), - ('NOMASSURANCE_CH', 'nom_assurance', unicode_and_strip, 'nom assurance'), - ('RIVOLI_EN', 'code_rivoli', unicode_and_strip, 'identifiant code rivoli'), - ('NUMCOMPTE_CH', 'numero_compte_comptable', unicode_and_strip, 'n° compte comptable'), - ('EMAILPERE_CH', 'email_pere', unicode_and_strip, 'email du père'), - ('EMAILMERE_CH', 'email_mere', unicode_and_strip, 'email de la mère'), - ('NUMALLOCATAIRE_CH', 'numero_allocataire', unicode_and_strip, 'n° allocataire'), - ('COMMENTAIRE_ME', 'commentaire', unicode_and_strip, 'commentaires / notes'), - ('IDCSPPERE', 'identifiant_csp_pere', unicode_and_strip, 'référence identifiant csp'), - ('IDCSPMERE', 'identifiant_csp_mere', unicode_and_strip, 'référence identifiant csp'), - ('IDSECTEURS', 'identifiant_secteurs', unicode_and_strip, 'référence identifiant secteurs'), - ('IDZONES', 'identifiant_zones', unicode_and_strip, 'référence identifiant zones'), - ('IDRUES', 'identifiant_rues', unicode_and_strip, 'référence identifiant rues'), - ('IDVILLES', 'identifiant_villes', unicode_and_strip, 'référence identifiant villes'), - ('IDREGIMES', 'identifiant_regimes', unicode_and_strip, 'référence identifiant regimes'), - ('IDSITUATIONFAMILLE', 'identifiant_situation_famille', unicode_and_strip, 'référence identifiant situationfamille'), - ('NUMSECUPERE_CH', 'num_securite_sociale_pere', unicode_and_strip, 'n° secu père'), - ('NUMSECUMERE_CH', 'num_securite_sociale_mere', unicode_and_strip, 'n° secu mère'), - ('NATIONPERE_CH', 'nation_pere', unicode_and_strip, 'nationalité père'), - ('NATIONMERE_CH', 'nation_mere', unicode_and_strip, 'nationalité mère'), - ('NOMJEUNEFILLE_CH', 'nom_jeune_fille', unicode_and_strip, 'nom jeune fille'), - ('IDCAFS', 'idcafs', unicode_and_strip, 'référence identifiant cafs'), - ('CHAMPLIBRE1_CH', 'champ_libre1', unicode_and_strip, 'valeur champ libre 1'), - ('CHAMPLIBRE2_CH', 'champ_libre2', unicode_and_strip, 'valeur champ libre 2'), - ('CHAMPCALCULE1_CH', 'champ_calcule1', unicode_and_strip, 'valeur champ calculé 1'), - ('CHAMPCALCULE2_CH', 'champ_calcule2', unicode_and_strip, 'valeur champ calculé 2'), - ('IDTABLELIBRE1', 'id_table_libre1', unicode_and_strip, 'idtablelibre1'), - ('IDTABLELIBRE3', 'id_table_libre3', unicode_and_strip, 'idtablelibre3'), - ('IDTABLELIBRE2', 'id_table_libre2', unicode_and_strip, 'idtablelibre2'), - ('IDTABLELIBRE4', 'id_table_libre4', unicode_and_strip, 'idtablelibre4'), - ('NOMURSSAF_CH', 'nom_urssaf', unicode_and_strip, 'nom urssaf'), - ('NUMURSSAF_CH', 'num_urssaf', unicode_and_strip, 'n° urssaf'), - ('IDPROFPERE', 'identifiant_profession_pere', unicode_and_strip, 'référence identifiant profession'), - ('IDPROFMERE', 'identifiant_profession_mere', unicode_and_strip, 'référence identifiant profession'), - ('ALLOCATAIRE_CH', 'allocataire', unicode_and_strip, 'allocataire père ou mère (p,m)'), -# ('PHOTOPERE_CH', 'photo_pere', unicode_and_strip, 'photographie père'), -# ('PHOTOMERE_CH', 'photo_mere', unicode_and_strip, 'photographie mère'), - ('NUMRUE_CH', 'numero_rue', unicode_and_strip, 'numéro de rue'), - ('NUMRUEPERE_CH', 'numero_rue_pere', unicode_and_strip, 'numéro de rue père'), - ('NUMRUEMERE_CH', 'numero_rue_mere', unicode_and_strip, 'numéro de rue mère'), - ('IDPORTAIL_FAMILLES', 'identifiant_portail_familles', unicode_and_strip, 'identifiant de portail_familles'), - ('ECHEANCEASSURANCE_DA', 'echeance_assurance', unicode_and_strip, 'date echéance assurance'), - ('RM_MIKADO_MO', 'rm_mikado', unicode_and_strip, 'revenus mensuels mikado'), - ('RA_MIKADO_MO', 'ra_mikado', unicode_and_strip, 'revenus annuels mikado'), - ('QF_MIKADO_MO', 'qf_mikado', unicode_and_strip, 'quotient familial mikado'), - ('RM_DIABOLO_MO', 'rm_diabolo', unicode_and_strip, 'revenus mensuels diabolo'), - ('RA_DIABOLO_MO', 'ra_diabolo', unicode_and_strip, 'revenus annuels diabolo'), - ('QF_DIABOLO_MO', 'qf_diabolo', unicode_and_strip, 'quotient familial diabolo'), - ('RM_OLIGO_MO', 'rm_oligo', unicode_and_strip, 'revenus mensuels oligo'), - ('RA_OLIGO_MO', 'ra_oligo', unicode_and_strip, 'revenus annuels oligo'), - ('QF_OLIGO_MO', 'qf_oligo', unicode_and_strip, 'quotient familial oligo'), - ('APPLICATION_REV_MIKADO_DA', 'application_rev_mikado', unicode_and_strip, 'date d\'application des revenus de mikado'), - ('APPLICATION_REV_DIABOLO_DA', 'application_rev_diabolo', unicode_and_strip, 'date d\'application des revenus de diabolo'), - ('APPLICATION_REV_OLIGO_DA', 'application_rev_oligo', unicode_and_strip, 'date d\'application des revenus de oligo'), - ) - - def __init__(self, *args, **kwargs): - self.children = [] - super(Family, self).__init__(*args, **kwargs) - - def complete(self): - k = [a for a,b,c,d in self.MORE_COLUMNS] - list(self.client('LISTER_FAMILLES', args=(','.join(k), self.id), - columns=self.MORE_COLUMNS, instances=(self,))) - l = self.client.get_children(self.id).values() - self.children = sorted(l, key=lambda c: c.id) - return self - - @property - def invoices(self): - return [invoice for id, invoice in self.client.invoices.items() if invoice.id_famille == self.id] - - def add_child(self, child): - if hasattr(self, 'id'): - child.id_famille = self.id - child.client = self.client - self.children.append(child) - - def save(self): - if hasattr(self, 'id'): - self.client.update_family(self) - else: - self.code_interne = self.client.new_code_interne() - self.id = self.client.add_family(self) - for child in self.children: - child.id_famille = self.id - child.save() - self.client.clear_cache() - -class Invoice(SimpleObject): - COLUMNS = ( - ('', 'id_famille', int, ''), - ('', 'id', int, ''), - ('', 'numero', str, ''), - ('', 'debut_periode', parse_date, ''), - ('', 'fin_periode', parse_date, ''), - ('', 'creation', parse_date, ''), - ('', 'echeance', parse_date, ''), - ('', 'montant', Decimal, ''), - ('', 'reste_du', Decimal, ''), - ) - _detail = {} - - def detail(self): - if not self._detail: - self.client.factures_detail([self]) - return self._detail - - @property - def family(self): - return self.client.families[self.id_famille] - - def paid(self): - return self.reste_du == Decimal(0) - -class DominoWs(object): - '''Interface to the WebService exposed by Abelium Domino. - - It allows to retrieve family and invoices. - - Beware that it does a lot of caching to limit call to the webservice, so - if you need fresh data, call clear_cache() - - All family are in the families dictionnary and all invoices in the - invoices dictionnary. - ''' - - def __init__(self, url, domain, login, password, location=None, - logger=logger): - if not Client: - raise ValueError('You need python suds') - self.logger = logger - self.logger.debug('creating DominoWs(%r, %r, %r, %r, location=%r)', - url, domain, login, password, location) - self.url = url - self.domain = domain - self.login = login - self.password = password - self.client = Client(url, location=location, timeout=60) - self.client.options.cache.setduration(seconds=60) - - def clear_cache(self): - '''Remove cached attributes from the instance.''' - - for key, value in self.__dict__.items(): - if key.startswith('__') and key.endswith('_cache'): - del self.__dict__[key] - - def call(self, function_name, *args): - '''Call SOAP method named function_name passing args list as parameters. - - Any error is converted into the DominoException class.''' - - try: - self.logger.debug(('soap call to %s(%s)' % (function_name, args)).encode('utf-8')) - data = getattr(self.client.service, function_name)(self.domain, self.login, self.password, *args) - self.logger.debug((u'result: %s' % data).encode('utf-8')) - self.data = data - except IOError as e: - raise DominoException('Erreur IO', e) - if data is None: - data = '' - if data.startswith('ERREUR'): - raise DominoException(data[9:].encode('utf8')) - return data - - def parse_tabular_data(self, data): - '''Row are separated by carriage-return, ASCII #13, characters and columns by tabs. - Empty lines (ignoring spaces) are ignored. - ''' - - rows = data.split(LINE_SEPARATOR) - rows = [[cell.strip() for cell in row.split(COLUMN_SEPARATOR)] for row in rows if row.strip() != ''] - return rows - - def __call__(self, function_name, cls=None, args=[], instances=None, columns=None): - '''Call SOAP method named function_name, splitlines, map tab separated - values to _map keys in a dictionnary, and use this dictionnary to - initialize an object of class cls. - - - If instances is present, the given instances are updated with the - returned content, in order, row by row. - - If cls is not None and instances is None, a new instance of the class - cls is instancied for every row and initialized with the content of - the row. - - If cls and instances are None, the raw data returned by the SOAP call - is returned. - ''' - - data = self.call(function_name, *args) - if cls or instances: - rows = self.parse_tabular_data(data) - kwargs = {} - if instances: - rows = zip(rows, instances) - for row in rows: - if instances: - row, instance = row - if not row[0]: - continue - for a, b in zip(columns or cls.COLUMNS, row): - x, name, converter, desc = a - kwargs[name] = converter(b.strip()) - if instances: - instance.__dict__.update(kwargs) - yield instance - else: - yield cls(client=self, **kwargs) - else: - yield data - - def add_family(self, family): - result = self.call('AJOUTER_FAMILLE', family.serialize()) - return int(result.strip()) - - def update_family(self, family): - if not hasattr(family, 'id'): - raise DominoException('Family lacks an "id" attribute, it usually means that it is new.') - result = self.call('MODIFIER_FAMILLE', unicode(family.id), family.serialize()) - return result.strip() == 'OK' - - def add_child(self, child): - result = self.call('AJOUTER_ENFANT', child.serialize()) - return int(result.strip()) - - def update_child(self, child): - if not hasattr(child, 'id'): - raise DominoException('Family lacks an "id" attribute, it usually means that it is new.') - result = self.call('MODIFIER_ENFANT', unicode(child.id), child.serialize()) - return result.strip() == 'OK' - - @property - @object_cached - def families(self): - '''Dictionary of all families indexed by their id. - - After the first use, the value is cached. Use clear_cache() to reset - it. - ''' - - return self.get_families() - - def get_families(self, id_famille=0, full=False): - '''Get families informations. - There is no caching. - - id_famille - if not 0, the family with this id is retrieved. If 0 - all families are retrieved. Default to 0. - full - If True return all the columns of the family table. Default - to False. - ''' - columns = Family.MORE_COLUMNS if full else Family.COLUMNS - families = self('LISTER_FAMILLES', - Family, - args=(','.join([x[0] for x in columns]), id_famille)) - return dict([(int(x), x) for x in families]) - - def get_children(self, id_famille=0): - columns = Child.COLUMNS - if id_famille == 0: - children = self('LISTER_ENFANTS', - Child, - args=((','.join([x[0] for x in columns])),)) - else: - children = self('LISTER_ENFANTS_FAMILLE', - Child, - args=(id_famille, (','.join([x[0] for x in columns])))) - return dict([(int(x), x) for x in children]) - - def get_urgent_contacts(self, id_enfant): - columns = UrgentContact.COLUMNS - urgent_contacts = self('LISTER_PERSONNES_URGENCE', - UrgentContact, - args=((id_enfant, ','.join([x[0] for x in columns])))) - return dict([(int(x), x) for x in urgent_contacts]) - - @property - @object_cached - def invoices(self): - '''Dictionnary of all invoices indexed by their id. - - After the first use, the value is cached. Use clear_cache() to reset - it. - ''' - invoices = self.get_invoices() - for invoice in invoices.values(): - invoice.famille = self.families[invoice.id_famille] - return invoices - - def new_code_interne(self): - max_ci = 0 - for family in self.families.values(): - try: - max_ci = max(max_ci, int(family.code_interne)) - except: - pass - return '%05d' % (max_ci+1) - - def get_invoices(self, id_famille=0, state='TOUTES'): - '''Get invoices informations. - - id_famille - If value is not 0, only invoice for the family with - this id are retrieved. If value is 0, invoices for all families are - retrieved. Default to 0. - etat - state of the invoices to return, possible values are - 'SOLDEES', 'NON_SOLDEES', 'TOUTES'. - ''' - invoices = self('LISTER_FACTURES_FAMILLE', Invoice, - args=(id_famille, state)) - invoices = list(invoices) - for invoice in invoices: - invoice.famille = self.families[invoice.id_famille] - return dict(((int(x), x) for x in invoices)) - - FACTURE_DETAIL_HEADERS = ['designation', 'quantite', 'prix', 'montant'] - def factures_detail(self, invoices): - '''Retrieve details of some invoice''' - data = self.call('DETAILLER_FACTURES', (''.join(("%s;" % int(x) for x in invoices)),)) - try: - tree = etree.fromstring(data.encode('utf8')) - for invoice, facture_node in zip(invoices, tree.findall('facture')): - rows = [] - for ligne in facture_node.findall('detail_facture/ligne'): - row = [] - rows.append(row) - for header in self.FACTURE_DETAIL_HEADERS: - if header in ligne.attrib: - row.append((header, ligne.attrib[header])) - etablissement = facture_node.find('detail_etablissements/etablissement') - if etablissement is not None: - nom = etablissement.get('nom').strip() - else: - nom = '' - d = { 'etablissement': nom, 'lignes': rows } - invoice._detail = d - except Exception as e: - raise DominoException('Exception when retrieving invoice details', e) - - def get_family_by_mail(self, email): - '''Return the first whose one email attribute matches the given email''' - for famille in self.families.values(): - if email in (famille.email_pere, famille.email_mere, - famille.adresse_internet): - return famille - return None - - def get_family_by_code_interne(self, code_interne): - '''Return the first whose one email attribute matches the given email''' - for famille in self.families.values(): - if getattr(famille, 'code_interne', None) == code_interne: - return famille - return None - - def pay_invoice(self, id_invoices, amount, other_information, date=None): - '''Notify Domino of the payment of some invoices. - - id_invoices - integer if of the invoice or Invoice instances - amount - amount as a Decimal object - other_information - free content to attach to the payment, like a - bank transaction number for correlation. - date - date of the payment, must be a datetime object. If None, - now() is used. Default to None. - ''' - - if not date: - date = datetime.datetime.now() - due = sum([self.invoices[int(id_invoice)].reste_du - for id_invoice in id_invoices]) - if Decimal(amount) == Decimal(due): - return self('SOLDER_FACTURE', None, args=(str(amount), - ''.join([ '%s;' % int(x) for x in id_invoices]), - date.strftime('%Y-%m-%d'), other_information)) - else: - raise DominoException('Amount due and paid do not match', { 'due': due, 'paid': amount}) diff --git a/auquotidien/modules/admin.py b/auquotidien/modules/admin.py index 7785097..050f18d 100644 --- a/auquotidien/modules/admin.py +++ b/auquotidien/modules/admin.py @@ -18,15 +18,12 @@ from wcs.categories import Category from wcs.qommon.backoffice.menu import html_top import re -from .abelium_domino_ui import AbeliumDominoDirectory class PanelDirectory(Directory): - _q_exports = ['', 'update', 'permissions', 'domino'] + _q_exports = ['', 'update', 'permissions'] label = N_('Control Panel') - domino = AbeliumDominoDirectory() - def _verify_mask(self, form): if form.is_submitted(): if not re.match("[0-9Xx]*$", form.get('mobile_mask') or ''): @@ -76,8 +73,6 @@ class SettingsDirectory(wcs.admin.settings.SettingsDirectory): r += htmltext('

%s

') % _('Extra Options') r += htmltext('') r += htmltext('') r += htmltext('') diff --git a/auquotidien/modules/connectors.py b/auquotidien/modules/connectors.py index c1ebec6..3a418a7 100644 --- a/auquotidien/modules/connectors.py +++ b/auquotidien/modules/connectors.py @@ -1,2 +1 @@ -from . import abelium_domino_workflow from . import clicrdv