361 lines
12 KiB
Python
361 lines
12 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright (C) 2019 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 csv
|
|
import io
|
|
from collections import OrderedDict
|
|
|
|
from django.contrib.postgres.fields import JSONField
|
|
from django.db import IntegrityError, models, transaction
|
|
from django.http import Http404
|
|
from django.utils.translation import ugettext_lazy as _
|
|
|
|
from passerelle.base.models import BaseResource
|
|
from passerelle.utils import SFTPField
|
|
from passerelle.utils.api import APIError, endpoint
|
|
|
|
CSV_DELIMITER = ';'
|
|
FILES_ENCODING = 'latin-1'
|
|
|
|
APPLICANT_SCHEMA = OrderedDict(
|
|
(
|
|
(
|
|
"form_id",
|
|
{
|
|
"type": "string",
|
|
},
|
|
),
|
|
(
|
|
"demand_date",
|
|
{
|
|
"type": "string",
|
|
"pattern": "^[0-9]{8}$",
|
|
},
|
|
),
|
|
(
|
|
"demand_time",
|
|
{
|
|
"type": "string",
|
|
"pattern": "^[0-9]{6}$",
|
|
},
|
|
),
|
|
(
|
|
"producer_code",
|
|
{
|
|
"type": "integer",
|
|
},
|
|
),
|
|
("invariant_number", {"type": "string", "maxLength": 10, "default": ""}),
|
|
(
|
|
"city_insee_code",
|
|
{
|
|
"type": "string",
|
|
},
|
|
),
|
|
(
|
|
"street_rivoli_code",
|
|
{
|
|
"type": "string",
|
|
},
|
|
),
|
|
(
|
|
"street_name",
|
|
{
|
|
"type": "string",
|
|
},
|
|
),
|
|
("address_complement", {"type": "string", "maxLength": 32, "default": ""}),
|
|
("street_number", {"type": "integer", "default": 0}),
|
|
("bis_ter", {"type": "string", "maxLength": 3, "default": ""}),
|
|
("building", {"type": "string", "maxLength": 5, "default": ""}),
|
|
("hall", {"type": "string", "maxLength": 5, "default": ""}),
|
|
("appartment_number", {"type": "string", "maxLength": 5, "default": ""}),
|
|
("producer_social_reason", {"type": "string", "maxLength": 38, "default": ""}),
|
|
("producer_title_code", {"type": "integer", "default": 0}),
|
|
("producer_last_name", {"type": "string", "maxLength": 38, "default": ""}),
|
|
("producer_first_name", {"type": "string", "maxLength": 32, "default": ""}),
|
|
("producer_phone", {"type": "string", "maxLength": 20, "default": ""}),
|
|
("producer_email", {"type": "string", "maxLength": 50, "default": ""}),
|
|
("owner_last_name", {"type": "string", "maxLength": 38, "default": ""}),
|
|
("owner_first_name", {"type": "string", "maxLength": 32, "default": ""}),
|
|
("owner_phone", {"type": "string", "maxLength": 20, "default": ""}),
|
|
("owner_email", {"type": "string", "maxLength": 50, "default": ""}),
|
|
("activity_code", {"type": "integer", "default": 0}),
|
|
("family_members_number", {"type": "integer", "default": 0}),
|
|
("houses_number", {"type": "integer", "default": 0}),
|
|
("t1_flats_number", {"type": "integer", "default": 0}),
|
|
("t2_flats_number", {"type": "integer", "default": 0}),
|
|
("t3_flats_number", {"type": "integer", "default": 0}),
|
|
("t4_flats_number", {"type": "integer", "default": 0}),
|
|
("t5_flats_number", {"type": "integer", "default": 0}),
|
|
("t6_flats_number", {"type": "integer", "default": 0}),
|
|
("shops_number", {"type": "integer", "default": 0}),
|
|
("garden_size", {"type": "integer", "default": 0}),
|
|
("expected_date", {"type": "string", "pattern": "^[0-9]{8}$", "default": ""}),
|
|
("expected_time", {"type": "string", "pattern": "^[0-9]{4}$", "default": ""}),
|
|
("modification_code", {"type": "integer", "default": 0}),
|
|
("demand_reason_label", {"type": "string", "default": ""}),
|
|
("comment", {"type": "string", "maxLength": 500, "default": ""}),
|
|
)
|
|
)
|
|
|
|
CARD_SCHEMA = OrderedDict(
|
|
(
|
|
(
|
|
"card_subject",
|
|
{
|
|
"type": "integer",
|
|
},
|
|
),
|
|
(
|
|
"card_type",
|
|
{
|
|
"type": "integer",
|
|
},
|
|
),
|
|
(
|
|
"card_demand_reason",
|
|
{
|
|
"type": "integer",
|
|
},
|
|
),
|
|
(
|
|
"cards_quantity",
|
|
{
|
|
"type": "integer",
|
|
},
|
|
),
|
|
(
|
|
"card_number",
|
|
{
|
|
"type": "string",
|
|
"maxLength": 20,
|
|
},
|
|
),
|
|
(
|
|
"card_bar_code",
|
|
{
|
|
"type": "string",
|
|
"maxLength": 20,
|
|
"default": "",
|
|
},
|
|
),
|
|
(
|
|
"card_code",
|
|
{
|
|
"type": "string",
|
|
"maxLength": 20,
|
|
"default": "",
|
|
},
|
|
),
|
|
(
|
|
"card_validity_start_date",
|
|
{
|
|
"type": "string",
|
|
"pattern": "^[0-9]{8}$",
|
|
"default": "",
|
|
},
|
|
),
|
|
(
|
|
"card_validity_end_date",
|
|
{
|
|
"type": "string",
|
|
"pattern": "^[0-9]{8}$",
|
|
"default": "",
|
|
},
|
|
),
|
|
(
|
|
"card_comment",
|
|
{
|
|
"type": "string",
|
|
"maxLength": 100,
|
|
"default": "",
|
|
},
|
|
),
|
|
)
|
|
)
|
|
|
|
DEMAND_SCHEMA = APPLICANT_SCHEMA.copy()
|
|
DEMAND_SCHEMA.update(CARD_SCHEMA)
|
|
|
|
SCHEMA = {
|
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
|
"title": "Gesbac",
|
|
"description": "",
|
|
"type": "object",
|
|
"required": [
|
|
"form_id",
|
|
"demand_date",
|
|
"demand_time",
|
|
"producer_code",
|
|
"city_insee_code",
|
|
"street_rivoli_code",
|
|
"street_name",
|
|
"card_subject",
|
|
"card_type",
|
|
"card_demand_reason",
|
|
"cards_quantity",
|
|
],
|
|
"properties": DEMAND_SCHEMA,
|
|
}
|
|
|
|
|
|
class Gesbac(BaseResource):
|
|
outcoming_sftp = SFTPField(verbose_name=_('Outcoming SFTP'))
|
|
incoming_sftp = SFTPField(verbose_name=_('Incoming SFTP'))
|
|
output_files_prefix = models.CharField(_('Output files prefix'), blank=False, max_length=32)
|
|
input_files_prefix = models.CharField(_('Input files prefix'), blank=False, max_length=32)
|
|
|
|
category = _('Business Process Connectors')
|
|
|
|
class Meta:
|
|
verbose_name = 'Gesbac'
|
|
|
|
def check_status(self):
|
|
with self.outcoming_sftp.client() as out_sftp:
|
|
out_sftp.listdir()
|
|
with self.incoming_sftp.client() as in_sftp:
|
|
in_sftp.listdir()
|
|
|
|
def get_responses(self):
|
|
data = []
|
|
with self.incoming_sftp.client() as client:
|
|
for csv_file in client.listdir():
|
|
if not csv_file.startswith(self.input_files_prefix):
|
|
continue
|
|
with client.open(csv_file, 'rb') as fd:
|
|
content = io.TextIOWrapper(fd, encoding=FILES_ENCODING)
|
|
for row in csv.reader(content, delimiter=CSV_DELIMITER):
|
|
data.append(row)
|
|
for card_data in data:
|
|
for form in self.form_set.filter(status='sent'):
|
|
if card_data[1] == form.get_gesbac_id():
|
|
form.card_data = card_data
|
|
form.status = 'closed'
|
|
form.save()
|
|
|
|
def hourly(self):
|
|
super().hourly()
|
|
self.get_responses()
|
|
|
|
def send_demand(self, form_id):
|
|
form = Form.objects.get(id=form_id)
|
|
form.send()
|
|
|
|
@endpoint(
|
|
name='create-demand',
|
|
perm='can_access',
|
|
description=_('Create demand'),
|
|
post={
|
|
'description': _('Creates a demand file'),
|
|
'request_body': {'schema': {'application/json': SCHEMA}},
|
|
},
|
|
)
|
|
def create_demand(self, request, post_data):
|
|
form_id = post_data['form_id']
|
|
for counter in range(20):
|
|
try:
|
|
with transaction.atomic():
|
|
form = Form.objects.create(resource=self, form_id=form_id, counter=counter)
|
|
break
|
|
except IntegrityError:
|
|
continue
|
|
else:
|
|
raise APIError('fail: more than 20 demands')
|
|
post_data['form_id'] = form.get_gesbac_id()
|
|
data = []
|
|
applicant_data = ['E']
|
|
# get applicant attributes
|
|
for name, value in APPLICANT_SCHEMA.items():
|
|
if value['type'] == 'string':
|
|
item = post_data.get(name, '')
|
|
elif value['type'] == 'integer':
|
|
item = post_data.get(name, 0)
|
|
applicant_data.append(item)
|
|
data.append(applicant_data)
|
|
# get card attributes
|
|
card_data = ['CARTE', post_data['form_id']]
|
|
for name, value in CARD_SCHEMA.items():
|
|
if value['type'] == 'string':
|
|
item = post_data.get(name, '')
|
|
elif value['type'] == 'integer':
|
|
item = post_data.get(name, 0)
|
|
card_data.append(item)
|
|
data.append(card_data)
|
|
form.demand_data = data
|
|
form.save()
|
|
self.add_job('send_demand', form_id=form.id)
|
|
return {'data': {'filename': form.get_filename(), 'gesbac_id': form.get_gesbac_id()}}
|
|
|
|
@endpoint(
|
|
name='get-response',
|
|
perm='can_access',
|
|
description=_('Get response'),
|
|
parameters={'gesbac_id': {'description': _('Gesbac demand identifier'), 'example_value': '420001'}},
|
|
)
|
|
def get_response(self, request, gesbac_id):
|
|
try:
|
|
response = self.form_set.filter(status='closed', gesbac_id=gesbac_id).latest()
|
|
return {'data': response.card_data}
|
|
except Form.DoesNotExist:
|
|
raise Http404('No response found')
|
|
|
|
|
|
FORM_STATUSES = (('new', 'New'), ('sent', 'Sent'), ('closed', 'Closed'))
|
|
|
|
|
|
class Form(models.Model):
|
|
resource = models.ForeignKey(Gesbac, on_delete=models.CASCADE)
|
|
form_id = models.CharField(max_length=64)
|
|
gesbac_id = models.CharField(max_length=64)
|
|
counter = models.IntegerField(default=0)
|
|
creation_datetime = models.DateTimeField(auto_now_add=True)
|
|
filename = models.CharField(max_length=128, null=True)
|
|
status = models.CharField(max_length=8, default='new', choices=FORM_STATUSES)
|
|
demand_data = JSONField(default=dict)
|
|
card_data = JSONField(default=dict)
|
|
|
|
class Meta:
|
|
get_latest_by = 'creation_datetime'
|
|
unique_together = ('form_id', 'counter')
|
|
|
|
def get_gesbac_id(self):
|
|
if not self.gesbac_id:
|
|
self.gesbac_id = self.form_id.replace('-', '0') + '%02d' % self.counter
|
|
self.save()
|
|
return self.gesbac_id
|
|
|
|
def get_filename(self):
|
|
if not self.filename:
|
|
timestamp = self.creation_datetime.strftime('%y%m%d-%H%M%S')
|
|
self.filename = '%s%s-%s.csv' % (
|
|
self.resource.output_files_prefix,
|
|
timestamp,
|
|
self.get_gesbac_id(),
|
|
)
|
|
self.save()
|
|
return self.filename
|
|
|
|
def send(self):
|
|
with self.resource.outcoming_sftp.client() as client:
|
|
with client.open(self.get_filename(), mode='wb') as fd:
|
|
fd = io.TextIOWrapper(fd, encoding=FILES_ENCODING)
|
|
writer = csv.writer(fd, delimiter=CSV_DELIMITER)
|
|
for row in self.demand_data:
|
|
writer.writerow(row)
|
|
self.status = 'sent'
|
|
self.save()
|