149 lines
5.7 KiB
Python
149 lines
5.7 KiB
Python
# barbacompta - accounting for dummies
|
|
# Copyright (C) 2010-2022 Entr'ouvert
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it
|
|
# under the terms of the GNU Affero General Public License as published
|
|
# by the Free Software Foundation, either version 3 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
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import io
|
|
import itertools
|
|
import logging
|
|
import time
|
|
|
|
from django.conf import settings
|
|
from django.db import transaction
|
|
|
|
from eo_gestion.chorus.api import ChorusAPI
|
|
from eo_gestion.chorus.models import Structure
|
|
from eo_gestion.eo_facture.models import Client
|
|
|
|
logger = logging.getLogger('barbacompta')
|
|
|
|
|
|
def get_api():
|
|
return ChorusAPI(
|
|
platform=settings.CHORUS_PLATFORM,
|
|
piste_client_id=settings.CHORUS_PISTE_CLIENT_ID,
|
|
piste_client_secret=settings.CHORUS_PISTE_CLIENT_SECRET,
|
|
chorus_tech_username=settings.CHORUS_TECH_USER_LOGIN,
|
|
chorus_tech_password=settings.CHORUS_TECH_USER_PASSWORD,
|
|
)
|
|
|
|
|
|
def push_to_chorus(pdf: bytes, name: str):
|
|
'''Wrapper around eo_gestion.chorus.ChorusAPI.deposer_flux_facturx'''
|
|
api = get_api()
|
|
data = {}
|
|
try:
|
|
data = api.deposer_flux_facturx(pdf=pdf, name=name)
|
|
except api.Error as e:
|
|
data['http.requests_error'] = str(e)
|
|
if e.response is not None:
|
|
data['http.response.status-code'] = e.response.status_code
|
|
data['http.response.headers'] = dict(e.response.headers)
|
|
data['http.response.body'] = repr(e.response.content[:1024])
|
|
return data
|
|
|
|
|
|
def _grouper(it, size):
|
|
'''Split iterator in equal size chunk of `size` elements.'''
|
|
it = iter(it)
|
|
return iter(lambda: tuple(itertools.islice(it, size)), ())
|
|
|
|
|
|
def _convert_annuaire_to_models(api, content):
|
|
with io.BytesIO(content) as fd:
|
|
for structure in api.Annuaire.parse(fd):
|
|
if not structure.structure_active:
|
|
continue
|
|
if structure.gestion_service:
|
|
for service in structure.services:
|
|
if not service.service_actif:
|
|
continue
|
|
yield Structure(
|
|
name=structure.raison_sociale[:80],
|
|
siret=structure.identifiant,
|
|
service_code=service.code,
|
|
service_name=service.nom[:80] or service.code,
|
|
email=service.adresse_postale.get('courriel')
|
|
or structure.adresse_postale.get('courriel'),
|
|
engagement_obligatoire=service.gestion_egmt or structure.gestion_engagement,
|
|
)
|
|
else:
|
|
yield Structure(
|
|
name=structure.raison_sociale[:80],
|
|
siret=structure.identifiant,
|
|
email=structure.adresse_postale.get('courriel'),
|
|
engagement_obligatoire=structure.gestion_engagement,
|
|
)
|
|
|
|
|
|
def _update_structures():
|
|
api = get_api()
|
|
content = api.telecharger_annuaire_destinataire()
|
|
known_identifiers = set()
|
|
structure_fields = ['name', 'siret', 'service_code', 'service_name', 'email', 'engagement_obligatoire']
|
|
count_updated = 0
|
|
count_inserted = 0
|
|
for structures in _grouper(_convert_annuaire_to_models(api, content), 1000):
|
|
inserts = []
|
|
updates = []
|
|
identifiers = {structure.full_identifier for structure in structures}
|
|
qs = Structure.objects.filter(full_identifier__in=identifiers)
|
|
identifier_to_structure = {structure.full_identifier: structure for structure in qs}
|
|
for structure in structures:
|
|
known_identifiers.add(structure.full_identifier)
|
|
if structure.full_identifier in identifier_to_structure:
|
|
old_structure = identifier_to_structure[structure.full_identifier]
|
|
equals = all(
|
|
getattr(structure, key) == getattr(old_structure, key) for key in structure_fields
|
|
)
|
|
if equals:
|
|
continue
|
|
structure.id = old_structure.id
|
|
updates.append(structure)
|
|
else:
|
|
inserts.append(structure)
|
|
with transaction.atomic():
|
|
count_updated += len(updates)
|
|
count_inserted += len(inserts)
|
|
if inserts:
|
|
Structure.objects.bulk_create(inserts)
|
|
if updates:
|
|
Structure.objects.bulk_update(updates, fields=structure_fields)
|
|
# delete obsolete structures
|
|
qs = Structure.objects.exclude(full_identifier__in=known_identifiers)
|
|
count_deleted, _ = qs.delete()
|
|
|
|
# update Client objects
|
|
Client.update_siret_and_service_code()
|
|
return {'deleted': count_deleted, 'updated': count_updated, 'created': count_inserted}
|
|
|
|
|
|
def update_structures():
|
|
start = time.time()
|
|
logger.debug('chorus: update structures started.')
|
|
try:
|
|
result = _update_structures()
|
|
except Exception:
|
|
logger.exception('chorus: update structures finished with error (%.1f seconds).', time.time() - start)
|
|
else:
|
|
report = []
|
|
for key, value in result.items():
|
|
if value:
|
|
report.append(f'{value} {key}')
|
|
if report:
|
|
msg = ', '.join(report)
|
|
else:
|
|
msg = 'nothing done'
|
|
logger.info('chorus: update structures finished (%s, %.1f seconds).', msg, time.time() - start)
|