443 lines
12 KiB
Python
443 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.db import models, transaction, IntegrityError
|
|
from django.utils import six
|
|
from django.utils.encoding import force_bytes
|
|
from django.utils.translation import ugettext_lazy as _
|
|
from django.http import Http404
|
|
|
|
from passerelle.base.models import BaseResource
|
|
from passerelle.utils.api import endpoint, APIError
|
|
from passerelle.utils import SFTPField
|
|
|
|
from jsonfield import JSONField
|
|
|
|
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 = u'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:
|
|
if six.PY3:
|
|
content = io.TextIOWrapper(fd, encoding=FILES_ENCODING)
|
|
else:
|
|
content = io.BytesIO(force_bytes(fd.read().decode(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(Gesbac, self).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 as e:
|
|
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()
|
|
card_data = JSONField()
|
|
|
|
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:
|
|
if six.PY3:
|
|
fd = io.TextIOWrapper(fd, encoding=FILES_ENCODING)
|
|
writer = csv.writer(fd, delimiter=CSV_DELIMITER)
|
|
for row in self.demand_data:
|
|
# encode strings to ASCII
|
|
if six.PY2:
|
|
row = [
|
|
item.encode(FILES_ENCODING) if isinstance(item, six.string_types)
|
|
else item for item in row
|
|
]
|
|
writer.writerow(row)
|
|
self.status = 'sent'
|
|
self.save()
|