510 lines
18 KiB
Python
510 lines
18 KiB
Python
'''
|
|
Usage :
|
|
|
|
wcs-manage runscript -d wcs.dev.publik.love cr-reunion/run.py import --filepath ~/notes/clients/cr-reunion/aide-voyage/demande.csv --api-url=https://wcs.dev.publik.love/api/ --key=cr-reunion-voyage --passwd=29a7182f-e3f0-4044-b6d4-163d16bbb86e
|
|
|
|
wcs-manage runscript -d wcs.dev.publik.love cr-reunion/run.py reset
|
|
'''
|
|
|
|
import argparse
|
|
import base64
|
|
import collections
|
|
import csv
|
|
import datetime
|
|
import hashlib
|
|
import hmac
|
|
import random
|
|
import time
|
|
import urllib.parse
|
|
from pprint import pprint
|
|
|
|
import requests
|
|
from requests.auth import HTTPBasicAuth
|
|
|
|
from wcs.carddef import CardDef
|
|
import wcs.sql_criterias as st
|
|
|
|
|
|
CARD_BENEFICIAIRE_SLUG = 'beneficiaire-ct'
|
|
CARD_BON_SLUG = 'bon-ct'
|
|
CARD_COMPAGNIE_AERIENNE_SLUG = 'cta-referentiel-compagnies-aeriennes'
|
|
CARD_DEROGATION_SLUG = 'referentiel-beneficiaires-de-derogation'
|
|
CARD_STATUT_BON_SLUG = 'ct-statuts-des-bons'
|
|
CARD_TYPE_AIDE_SLUG = 'referentiel-type-d-aide'
|
|
|
|
BENEFICIAIRES = {}
|
|
COMPAGNIES_AERIENNES = {}
|
|
DEROGATIONS = {}
|
|
STATUT_BON = {}
|
|
TYPE_AIDES = {}
|
|
|
|
|
|
|
|
def build_cache(slug, cache):
|
|
cardef = CardDef.get_by_urlname(slug)
|
|
libelle_field = get_field_by_varname(cardef, 'libelle')
|
|
for card in cardef.data_class().select():
|
|
cache[card.data[libelle_field.id].upper()] = str(card.id)
|
|
|
|
|
|
def get_beneficiaire_hash(prenom, nom, numero_fiscal):
|
|
return '%s##%s##%s' % (prenom.strip().upper(), nom.strip().upper(), numero_fiscal)
|
|
|
|
|
|
def get_beneficiaire_hash_from_row(row):
|
|
return get_beneficiaire_hash(row['prenom'], row['nom'], row['numero fiscal'])
|
|
|
|
|
|
def get_field_by_varname(cardef, varname):
|
|
for field in cardef.fields:
|
|
if field.varname == varname:
|
|
return field
|
|
raise ValueError('Missing field: %s' % varname)
|
|
|
|
|
|
def get_type_aide_cardddata(label):
|
|
if not TYPE_AIDES:
|
|
build_cache(CARD_TYPE_AIDE_SLUG, TYPE_AIDES)
|
|
return TYPE_AIDES[label.upper()]
|
|
|
|
|
|
def get_derogation_cardddata(label):
|
|
if not DEROGATIONS:
|
|
build_cache(CARD_DEROGATION_SLUG, DEROGATIONS)
|
|
return DEROGATIONS[label.upper()]
|
|
|
|
def get_statut_bon_cardddata(label):
|
|
if not STATUT_BON:
|
|
build_cache(CARD_STATUT_BON_SLUG, STATUT_BON)
|
|
return STATUT_BON[label.upper()]
|
|
|
|
def get_compagnie_aerienne(label):
|
|
if not COMPAGNIES_AERIENNES:
|
|
build_cache(CARD_COMPAGNIE_AERIENNE_SLUG, COMPAGNIES_AERIENNES)
|
|
if label.upper() not in COMPAGNIES_AERIENNES:
|
|
print('missing company : %s' % label.upper())
|
|
return ''
|
|
return COMPAGNIES_AERIENNES[label.upper()]
|
|
|
|
|
|
def parse_date(date_str, date_format='%Y-%m-%d'):
|
|
if not date_str or date_str == 'NULL':
|
|
return ''
|
|
return datetime.datetime.strptime(date_str, date_format).date().strftime('%Y-%m-%d')
|
|
|
|
|
|
def api_call(args, path, data=None, user_email=None, method='post'):
|
|
basic = HTTPBasicAuth(args.key, args.passwd)
|
|
url = args.api_url + path
|
|
if method == 'post':
|
|
post_data = {'data': data}
|
|
if user_email:
|
|
post_data['user'] = {'email': user_email}
|
|
resp = requests.post(url, json=post_data, auth=basic)
|
|
elif method == 'get':
|
|
resp = requests.get(url, auth=basic)
|
|
resp.raise_for_status()
|
|
if resp.json()['err'] != 0:
|
|
raise ValueError()
|
|
return resp
|
|
|
|
|
|
def get_rows(args):
|
|
for filepath in args.filepath:
|
|
with open(filepath) as csvfile:
|
|
reader = csv.DictReader(csvfile, delimiter=',', quotechar='"')
|
|
numline = 0
|
|
for i, row in enumerate(reader):
|
|
if i < args.start_line:
|
|
continue
|
|
if args.mode != 'full' and numline > args.sample_numlines:
|
|
break
|
|
numline += 1
|
|
yield i, row
|
|
|
|
|
|
def import_data(args):
|
|
benef_path = 'cards/%s/submit' % CARD_BENEFICIAIRE_SLUG
|
|
bon_path = 'cards/%s/submit' % CARD_BON_SLUG
|
|
|
|
# list of (criteria, result) tuples
|
|
type_aide_mapping = [
|
|
(
|
|
{
|
|
'volet': 'VOLET B - Métropole / Réunion',
|
|
'type demande': 'BON',
|
|
'type aide': 'RESSOURCEMENT ETUDIANT',
|
|
'mesure specifique': 'NULL',
|
|
},
|
|
{
|
|
'bon_type_aide': get_type_aide_cardddata('Ressourcement étudiant')
|
|
},
|
|
),
|
|
(
|
|
{
|
|
'volet': 'VOLET B - Métropole / Réunion',
|
|
'type demande': 'REMBOURSEMENT',
|
|
'type aide': 'RESSOURCEMENT ETUDIANT',
|
|
'mesure specifique': 'NULL',
|
|
},
|
|
{
|
|
'bon_type_aide': get_type_aide_cardddata('Ressourcement étudiant')
|
|
},
|
|
),
|
|
(
|
|
{
|
|
'volet': 'VOLET A - Réunion / Métropole',
|
|
'type demande': 'REMBOURSEMENT',
|
|
'type aide': 'MESURES SPECIFIQUES',
|
|
'mesure specifique': 'Cas particulier du deuil en Métropole',
|
|
},
|
|
{'bon_type_aide': get_type_aide_cardddata('Remboursement (deuil)')}
|
|
),
|
|
(
|
|
{
|
|
'volet': 'VOLET A - Réunion / Métropole',
|
|
'type demande': 'BON',
|
|
'type aide': 'GRAND PUBLIC',
|
|
'mesure specifique': 'NULL',
|
|
},
|
|
{
|
|
'bon_type_aide': get_type_aide_cardddata('Grand Public'),
|
|
},
|
|
),
|
|
(
|
|
{
|
|
'volet': 'VOLET A - Réunion / Métropole',
|
|
'type demande': 'BON',
|
|
'type aide': 'CONTINUITE FUNERAIRE',
|
|
'mesure specifique': 'NULL',
|
|
},
|
|
{
|
|
'bon_type_aide': get_type_aide_cardddata(
|
|
'Continuité Territoriale Funéraire'
|
|
)
|
|
},
|
|
),
|
|
(
|
|
{
|
|
'volet': 'VOLET A - Réunion / Métropole',
|
|
'type demande': 'BON',
|
|
'type aide': 'MESURES SPECIFIQUES',
|
|
'mesure specifique': 'Voyage pédagogique pour les publics scolaires',
|
|
},
|
|
{
|
|
'bon_type_aide': get_type_aide_cardddata('Voyage pédagogique (élève)'),
|
|
},
|
|
),
|
|
(
|
|
{
|
|
'volet': 'VOLET A - Réunion / Métropole',
|
|
'type demande': 'BON',
|
|
'type aide': 'MESURES SPECIFIQUES',
|
|
'mesure specifique': 'Accompagnateur voyage pédagogique pour les publics scolaires mineurs.',
|
|
},
|
|
{
|
|
'bon_type_aide': get_type_aide_cardddata(
|
|
'Voyage pédagogique (accompagnateur)'
|
|
),
|
|
},
|
|
),
|
|
(
|
|
{
|
|
'volet': 'VOLET A - Réunion / Métropole',
|
|
'type demande': 'BON',
|
|
'type aide': 'MESURES SPECIFIQUES',
|
|
'mesure specifique': 'Sportif de haut niveau national ou régional',
|
|
},
|
|
{
|
|
'bon_type_aide': get_type_aide_cardddata(
|
|
'Compétition sportive (sportif)'
|
|
),
|
|
},
|
|
),
|
|
(
|
|
{
|
|
'volet': 'VOLET A - Réunion / Métropole',
|
|
'type demande': 'BON',
|
|
'type aide': 'MESURES SPECIFIQUES',
|
|
'mesure specifique': 'Accompagnateur de sportif(s) de haut niveau',
|
|
},
|
|
{
|
|
'bon_type_aide': get_type_aide_cardddata(
|
|
'Compétition sportive (accompagnateur)'
|
|
),
|
|
},
|
|
),
|
|
(
|
|
{
|
|
'volet': 'VOLET A - Réunion / Métropole',
|
|
'type demande': 'BON',
|
|
'type aide': 'MESURES SPECIFIQUES',
|
|
'mesure specifique': "Accompagnateur majeur d'un jeune de moins de 26 ans pour un premier départ de La Réunion pour les études, ou lycéen ou apprenti.",
|
|
},
|
|
{
|
|
'bon_type_aide': get_type_aide_cardddata(
|
|
"Accompagnateur d'un jeune de moins de 26 ans pour un premier départ de La Réunion"
|
|
),
|
|
},
|
|
),
|
|
(
|
|
{
|
|
'volet': 'VOLET A - Réunion / Métropole',
|
|
'type demande': 'BON',
|
|
'type aide': 'MESURES SPECIFIQUES',
|
|
'mesure specifique': 'Lycéens, apprenti, étudiant en France métropolitaine',
|
|
},
|
|
{
|
|
'bon_type_aide': get_type_aide_cardddata(
|
|
'Lycéen, apprenti, étudiant en France métropolitaine'
|
|
),
|
|
},
|
|
),
|
|
(
|
|
{
|
|
'volet': 'VOLET A - Réunion / Métropole',
|
|
'type demande': 'BON',
|
|
'type aide': 'MESURES SPECIFIQUES',
|
|
'mesure specifique': 'Acteurs culturels',
|
|
},
|
|
{
|
|
'bon_type_aide': get_type_aide_cardddata(
|
|
'Voyage artistique (artistes et acteurs culturels)'
|
|
),
|
|
},
|
|
),
|
|
(
|
|
{
|
|
'volet': 'VOLET A - Réunion / Métropole',
|
|
'type demande': 'BON',
|
|
'type aide': 'MESURES SPECIFIQUES',
|
|
'mesure specifique': 'Accompagnateur acteurs culturels',
|
|
},
|
|
{
|
|
'bon_type_aide': get_type_aide_cardddata(
|
|
'Voyage artistique (accompagnateur)'
|
|
),
|
|
},
|
|
),
|
|
(
|
|
{
|
|
'volet': 'VOLET A - Réunion / Métropole',
|
|
'type demande': 'BON',
|
|
'type aide': 'MESURES SPECIFIQUES',
|
|
'mesure specifique': "Patient lors d'un rapatriement sanitaire pris en charge à 65% maximum par la CGSS après réponse négative des organismes sollicités",
|
|
},
|
|
{
|
|
'bon_type_aide': get_type_aide_cardddata(
|
|
'Patient en transfert sanitaire'
|
|
),
|
|
},
|
|
),
|
|
(
|
|
{
|
|
'volet': 'VOLET A - Réunion / Métropole',
|
|
'type demande': 'BON',
|
|
'type aide': 'MESURES SPECIFIQUES',
|
|
'mesure specifique': "Accompagnateur d'un patient lors d'un rapatriement sanitaire pris en charge à 65% maximum par la CGSS après réponse négative des organismes sollicités",
|
|
},
|
|
{
|
|
'bon_type_aide': get_type_aide_cardddata(
|
|
"Accompagnateur d'un patient en transfert sanitaire"
|
|
),
|
|
},
|
|
),
|
|
(
|
|
{
|
|
'volet': 'VOLET A - Réunion / Métropole',
|
|
'type demande': 'BON',
|
|
'type aide': 'MESURES SPECIFIQUES',
|
|
'mesure specifique': 'Doctorant et post-doctorant pour des travaux de recherche',
|
|
},
|
|
{
|
|
'bon_type_aide': get_type_aide_cardddata('Doctorant ou post-doctorant'),
|
|
},
|
|
),
|
|
(
|
|
{
|
|
'volet': 'VOLET A - Réunion / Métropole',
|
|
'type demande': 'BON',
|
|
'type aide': 'MESURES SPECIFIQUES',
|
|
'mesure specifique': "Salarié non fonctionnaire ou demandeur d'emploi non aidé par d'autres organismes",
|
|
},
|
|
{
|
|
'bon_type_aide': get_type_aide_cardddata(
|
|
"Épreuves d'admissibilité ou d'admission de concours de la fonction publique"
|
|
),
|
|
},
|
|
),
|
|
(
|
|
{
|
|
'volet': 'VOLET A - Réunion / Métropole',
|
|
'type demande': 'BON',
|
|
'type aide': 'MESURES SPECIFIQUES',
|
|
'mesure specifique': 'Cas particulier du deuil en Métropole',
|
|
},
|
|
{
|
|
'bon_type_aide': get_type_aide_cardddata(
|
|
'Deuil et raisons funéraires (hors CTF et remboursement)'
|
|
),
|
|
},
|
|
),
|
|
(
|
|
{
|
|
'volet': 'VOLET B - Métropole / Réunion',
|
|
'type demande': 'BON',
|
|
'type aide': 'AIDE ETUDIANT SPECIAL COVID',
|
|
'mesure specifique': 'NULL',
|
|
},
|
|
'continue',
|
|
),
|
|
(
|
|
{
|
|
'volet': 'VOLET A - Réunion / Métropole',
|
|
'type demande': 'BON',
|
|
'type aide': 'GRAND PUBLIC',
|
|
'derogation': "Pupilles de la nation ou enfants faisant l'objet d'un placement auprès des structures spécifiques",
|
|
},
|
|
{
|
|
'bon_type_aide': get_type_aide_cardddata('Pupille de la nation'),
|
|
},
|
|
),
|
|
]
|
|
|
|
derogation_mapping = {
|
|
"Personne porteuse d'un handicap": get_derogation_cardddata('Personne en situation de handicap'),
|
|
'Accompagnateur femme enceinte': get_derogation_cardddata("Accompagnateur d'une femme enceinte"),
|
|
'Accompagnateur personne âgée': get_derogation_cardddata("Accompagnateur d'une personne âgée"),
|
|
"Accompagnateur personne porteuse d'un handicap": get_derogation_cardddata("Accompagnateur d'une personne en situation de handicap"),
|
|
'Femme enceinte': get_derogation_cardddata('Femme enceinte'),
|
|
'Personne âgée': get_derogation_cardddata('Personne âgée'),
|
|
"Enfants nés après la déclaration fiscale et n'apparaissant par sur l'avis fiscal de référence": '',
|
|
"Pupilles de la nation ou enfants faisant l'objet d'un placement auprès des structures spécifiques": '',
|
|
'Personnes ne pouvant justifier de leur état civil à La Réunion': '',
|
|
'NULL': ''
|
|
}
|
|
|
|
status_mapping = {
|
|
'CORRIGÉ': get_statut_bon_cardddata('Vérifiable'),
|
|
'VALIDÉ': get_statut_bon_cardddata('Facturable'),
|
|
'MANDATÉ': get_statut_bon_cardddata('En cours de mandatement'),
|
|
'RESERVÉ': get_statut_bon_cardddata('Réservé'),
|
|
'REFUSÉ': get_statut_bon_cardddata('Invalide'),
|
|
'PAYÉ': get_statut_bon_cardddata('Payé'),
|
|
'MANDATEMENT EN COURS': get_statut_bon_cardddata('En cours de mandatement'),
|
|
'PRÉPARATION MANDAT': get_statut_bon_cardddata('En cours de mandatement'),
|
|
}
|
|
|
|
def get_from_mapping(row_data, mapping):
|
|
for criteria, extra_data in mapping:
|
|
for key, value in criteria.items():
|
|
if row_data[key] != value:
|
|
break
|
|
else:
|
|
return extra_data
|
|
return None
|
|
|
|
# Initialize BENEFICIAIRES with carddata
|
|
beneficiaire_carddef = CardDef.get_by_urlname(CARD_BENEFICIAIRE_SLUG)
|
|
prenom_field = get_field_by_varname(beneficiaire_carddef, 'prenom')
|
|
nom_field = get_field_by_varname(beneficiaire_carddef, 'nom_usage')
|
|
numero_fiscal_field = get_field_by_varname(beneficiaire_carddef, 'numero_fiscal')
|
|
|
|
for beneficiaire in beneficiaire_carddef.data_class().select():
|
|
BENEFICIAIRES[get_beneficiaire_hash(beneficiaire.data[prenom_field.id], beneficiaire.data[nom_field.id], beneficiaire.data[numero_fiscal_field.id])] = str(beneficiaire.id)
|
|
|
|
for i, row in get_rows(args):
|
|
|
|
type_aide = get_from_mapping(row, type_aide_mapping)
|
|
if type_aide is None:
|
|
print('No matching criteria for type_aide in row num %s.' % i)
|
|
pprint(row)
|
|
continue
|
|
|
|
if type_aide == 'continue':
|
|
continue
|
|
|
|
if row['derogation'] not in derogation_mapping:
|
|
print('No matching derogation matching in row num %s.' % i)
|
|
pprint(row)
|
|
continue
|
|
derogation = derogation_mapping[row['derogation']]
|
|
|
|
|
|
beneficiaire_hash = get_beneficiaire_hash_from_row(row)
|
|
|
|
if beneficiaire_hash not in BENEFICIAIRES:
|
|
email = ''
|
|
if row['email'] != 'NULL':
|
|
email = row['email'].lower()
|
|
|
|
beneficiaire_data = {
|
|
'courriel_demandeur': email,
|
|
'date_import': datetime.datetime.now().date().strftime('%Y-%m-%d'),
|
|
'date_naissance': parse_date(row['date naissance']),
|
|
'nom_naissance': row['nom naissance'].upper(),
|
|
'nom_usage': row['nom'].upper(),
|
|
'numero_fiscal': row['numero fiscal'],
|
|
'prenom': row['prenom'].upper(),
|
|
}
|
|
resp = api_call(args, path=benef_path, data=beneficiaire_data)
|
|
beneficiaire_id = str(resp.json()['data']['id'])
|
|
BENEFICIAIRES[beneficiaire_hash] = beneficiaire_id
|
|
|
|
beneficiaire_id = BENEFICIAIRES[beneficiaire_hash]
|
|
email = ''
|
|
if row['email'] != 'NULL':
|
|
email = row['email'].lower()
|
|
data = {
|
|
'annee_demande': row['campagne'],
|
|
'beneficiaire': beneficiaire_id,
|
|
'beneficiaire_courriel': email,
|
|
'beneficiaire_numero_cni': row['numero CI/passeport'],
|
|
'bon_numero': row['numero bon'],
|
|
'bon_montant': row['montant region'],
|
|
'bon_statut': status_mapping[row['statut']],
|
|
'bon_type_derogation': derogation,
|
|
'bon_validite': row['date validite'],
|
|
'commentaires_import': 'Trajet : %s.' % row['trajet'],
|
|
'date_import': datetime.datetime.now().date().strftime('%Y-%m-%d'),
|
|
'voyage_date_aller': parse_date(row['date aller'], '%Y-%m-%d %H:%M:%S'),
|
|
'voyage_date_retour': parse_date(row['date retour'], '%Y-%m-%d %H:%M:%S'),
|
|
}
|
|
|
|
if row['compagnie'] and row['compagnie'] != 'NULL':
|
|
data['billet_compagnie'] = get_compagnie_aerienne(row['compagnie'])
|
|
|
|
data.update(type_aide)
|
|
resp = api_call(args, path=bon_path, data=data)
|
|
|
|
|
|
def reset(args):
|
|
for slug in (CARD_BENEFICIAIRE_SLUG, CARD_BON_SLUG):
|
|
cardef = CardDef.get_by_urlname(slug)
|
|
date_timport_field = get_field_by_varname(cardef, 'date_import')
|
|
for carddata in cardef.data_class().select(
|
|
[st.NotNull(st.get_field_id(date_timport_field))]
|
|
):
|
|
carddata.remove_self()
|
|
|
|
|
|
parser = argparse.ArgumentParser()
|
|
subparsers = parser.add_subparsers()
|
|
|
|
parser_import = subparsers.add_parser('import')
|
|
parser_import.set_defaults(func=import_data)
|
|
parser_import.add_argument('--filepath', nargs='+')
|
|
parser_import.add_argument('--mode', default='sample', choices=('sample', 'full'))
|
|
parser_import.add_argument('--sample-numlines', default=100, type=int)
|
|
parser_import.add_argument('--start-line', default=0, type=int)
|
|
parser_import.add_argument('--api-url')
|
|
parser_import.add_argument('--email')
|
|
parser_import.add_argument('--key')
|
|
parser_import.add_argument('--passwd')
|
|
|
|
parser_reset = subparsers.add_parser('reset')
|
|
parser_reset.set_defaults(func=reset)
|
|
|
|
args = parser.parse_args()
|
|
args.func(args)
|