"""Members modelsmain page."""
from django.db import models
from outils.common_imports import *
from outils.images_imports import *

from outils.common import OdooAPI
from outils.common import CouchDB
from outils.common import Verification
from products.models import OFF
from envelops.models import CagetteEnvelops
import shifts.fonctions

import sys
import pytz
import locale
import re
import dateutil.parser
from datetime import date

FUNDRAISING_CAT_ID = {'A': 1, 'B': 2, 'C': 3}

class CagetteMember(models.Model):
    """Class to handle cagette Odoo member."""
    m_default_fields = ['name', 'parent_name', 'sex', 'image_medium', 'active',
                        'barcode_base', 'barcode', 'shift_type',
                        'is_associated_people', 'is_member', 'shift_type',
                        'display_ftop_points', 'display_std_points',
                        'is_exempted', 'cooperative_state', 'date_alert_stop']

    m_short_default_fields = ['name', 'barcode_base']

    def __init__(self, id):
        """Init with odoo id."""
        self.id = int(id)
        self.o_api = OdooAPI()

    @staticmethod
    def get_new_password_link(data):
        result = {}
        if 'email' in data:
            email = data['email'].strip()
            validator = validators.EmailValidator()
            try:
                validator(email)
                api = OdooAPI()
                cond = [['email', '=', email]]
                m_res = api.search_read('res.partner', cond, ['id'])
                if m_res and 'id' in m_res[0]:
                    res = api.execute('res.partner', 'send_new_password_email', [m_res[0]['id']])
                    if 'error' in res:
                        result['error'] = res['error']
                else:
                    result['error'] = 'get_new_password_link django error : res_partner not found'

            except Exception as e:
                result['error'] = 'get_new_password_link error while calling odoo api : ' + str(e)
                if "Only users with the following access level are currently allowed to do that" in str(e):
                    result['error'] += (" Il s'agit peut-être d'un problème de permissions de l'utilisateur api : "
                                        "donnez lui le droit Administration/Settings (Configuration en français)")
        else:
            result['error'] = 'get_new_password_link django error : no email in data'
        return result

    @staticmethod
    def set_new_password(received_pwd, token):
        if len(token) > 32 and len(received_pwd) >= 10:
            api = OdooAPI()
            db_uuid = api.get_system_param('database.uuid')
            cond = [['reset_password_token', '=', token.replace(db_uuid,'')]]
            res_m = api.search_read('res.partner', cond, ['id'])
            if res_m:
                import argon2
                ph = argon2.PasswordHasher()
                fields = {'hashed_password': ph.hash(received_pwd),
                          'reset_password_token': ''}
                api.update('res.partner', [res_m[0]['id']], fields)
            else:
                raise Exception('Invalid token')
        else:
            raise Exception('Invalid arguments')
        return 'successful_reset_password'

    def get_preferences(self, key=None):
        preferences = {}
        
        try:
            cond = [['id', '=', self.id]]
            fields = ['external_apps_preferences']
            res = self.o_api.search_read('res.partner', cond, fields)
            if res:
                stored_pref = res[0]['external_apps_preferences']
                if stored_pref:
                    p = json.loads(stored_pref)
                    if key is None:
                        preferences = p
                    elif key in p:
                        preferences = p[key]
        except Exception as e:
            preferences['error'] = str(e)
        coop_logger.info("retrieved pref = %s", str(preferences))
        return preferences

    def set_preferences(self, data):
        return self.o_api.update('res.partner', [self.id], {'external_apps_preferences': json.dumps(data)})

    def update_from_ajax(self, request):
        result = {}
        try:
            fields = {}
            for key, value in request.POST.items():
                fields[key] = value
            result['update'] = self.o_api.update('res.partner', [self.id], fields)
        except Exception as e:
            result['error'] = str(e)
        return result

    def set_odoo_image(self, image):
        """Record base64 image associated to member."""
        api = OdooAPI()
        f = {'image': image}
        return api.update('res.partner', [self.id], f)

    def attach_message(self, body):
        params = {'message_type': 'comment', 'subtype': 'mail.mt_comment', 'body': body}
        return self.o_api.execute('res.partner', 'message_post', [self.id], params)

    def get_image(self):
        image = ''
        cond = [['id', '=', self.id]]
        fields = ['image_medium']
        res = self.o_api.search_read('res.partner', cond, fields)
        if res and len(res) == 1:
            image = res[0]['image_medium']
        return image

    def get_member_points(self, shift_type):
        points_field = 'final_standard_point' if shift_type == "standard" else 'final_ftop_point'

        cond = [['id', '=', self.id]]
        fields = ['id', points_field]
        res = self.o_api.search_read('res.partner', cond, fields)

        if res and len(res) == 1:
            return int(res[0][points_field])
        else:
            return None

    def update_member_points(self, data):
        """
            ex:
            data = {
                'name': reason,
                'shift_id': False,
                'type': stype,
                'partner_id': self.id,
                'point_qty': pts
            }
        """

        try:
            return self.o_api.create('shift.counter.event', data)
        except Exception as e:
            print(str(e))


# # # BDM
    def save_partner_info(self, partner_id, fieldsDatas):
        return self.o_api.update('res.partner', partner_id,  fieldsDatas)

    @staticmethod
    def retrieve_data_according_keys(keys, full=False):
        api = OdooAPI()
        cond = []
        for k in keys:
            cond.append([k, '=', keys[k]])
        if full is True:
            fields = ['image_medium', 'barcode_base', 'barcode', 'create_date',
                      'cooperative_state', 'name', 'birthdate_date', 'street', 'street2',
                      'zip', 'city', 'email', 'mobile', 'phone', 'total_partner_owned_share',
                      'amount_subscription', 'active_tmpl_reg_line_count', 'is_exempted',
                      'shift_type', 'current_template_name',
                      'final_standard_point', 'final_ftop_point',
                      'date_alert_stop', 'sex']
        else:
            fields = ['name', 'email', 'birthdate_date',
                      'sex', 'country_id', 'total_partner_owned_share',
                      'barcode_base', 'tmpl_reg_line_ids']
        return api.search_read('res.partner', cond, fields, 1, 0,
                                     'id DESC')

    @staticmethod
    def get_credentials(request, external=False, with_id=False):
        import hashlib

        data = {}

        login = request.POST.get('login')
        password = request.POST.get('password')
        fp = request.POST.get('fp') #  fingerprint (prevent using stolen cookies)
        if login and password:
            api = OdooAPI()
            login = login.strip()
            cond = [['email', '=', login]]
            if getattr(settings, 'ALLOW_NON_MEMBER_TO_CONNECT', False) is False:
                cond.append('|')
                cond.append(['is_member', '=', True])
                # TODO : consider replacing is_associated_people by suppleant_member_id to exclude mineurs rattachés
                cond.append(['is_associated_people', '=', True])

            fields = ['name', 'email', 'birthdate_date', 'create_date', 'cooperative_state', 'is_associated_people', 'barcode_base']
            if getattr(settings, 'USE_MEMBERS_CUSTOM_PASSWORD', False) is True:
                fields.append('hashed_password')
            res = api.search_read('res.partner', cond, fields)
            if (res and len(res) >= 1):
                coop_id = None
                hashed_password = None
                # TODO : add comment to explain why there is a loop here
                for item in res:
                    coop = item
                    if 'hashed_password' in item:
                        hashed_password = item['hashed_password']
                    if item["birthdate_date"] is not False:
                        coop_birthdate = item['birthdate_date']
                        coop_state = item['cooperative_state']
                    # TODO : consider replacing is_associated_people check by suppleant_member_id to exclude mineurs rattachés
                    if item["is_associated_people"] == True:
                        coop_id = item['id']

                secret_verified = False
                if hashed_password:
                    import argon2
                    ph = argon2.PasswordHasher()
                    try:
                        ph.verify(hashed_password, password)
                        secret_verified = True
                    except Exception as e:
                        coop_logger.info("Wrong password : %s", str(e))
                else:
                    y, m, d = coop_birthdate.split('-')
                    password = password.replace('/', '')
                    if (password == d + m + y):
                        secret_verified = True
                if secret_verified is True:
                    if coop_id is None:
                        coop_id = coop['id']
                    data['id'] = coop_id
                    auth_token_seed = fp + coop['create_date']
                    data['auth_token'] = hashlib.sha256(auth_token_seed.encode('utf-8')).hexdigest()
                    data['token'] = hashlib.sha256(coop['create_date'].encode('utf-8')).hexdigest()
                    data['coop_state'] = coop_state
                    if external is True:
                        from outils.functions import extract_firstname_lastname
                        name_sep = getattr(settings, 'SUBSCRIPTION_NAME_SEP', ' ')
                        name_elts = extract_firstname_lastname(coop['name'], name_sep)
                        data['lastname'] = name_elts['lastname']
                        if name_elts['firstname'] != name_elts['lastname']:
                            data['firstname'] = name_elts['firstname']
                        else:
                            data['firstname'] = ''
                        data['coop_num'] = coop['barcode_base']

                if not ('auth_token' in data):
                    data['failure'] = True
                    data['msg'] = "Erreur dans le mail ou le mot de passe"
                    data['errnum'] = 1
            else:
                data['failure'] = True
                data['msg'] = "Erreur dans le mail ou le mot de passe"
                data['errnum'] = 2
                #  data['res'] = res

        elif external is False and 'token' in request.COOKIES and 'id' in request.COOKIES:
            api = OdooAPI()
            cond = [['id', '=', request.COOKIES['id']]]
            fields = ['create_date','email']
            res = api.search_read('res.partner', cond, fields)
            if (res and len(res) == 1):
                login = res[0]['email']
                calc_token = hashlib.sha256(res[0]['create_date'].encode('utf-8')).hexdigest()
                if calc_token == request.COOKIES['token']:
                    data['success'] = True
                    if with_id is True:
                        data['id'] = res[0]['id']
                else:
                    data['failure'] = True
                    data['errnum'] = 3
        else:
            data['failure'] = True
        if not ('failure' in data):
            if external is False:
                data['login'] = login
                c_db_data = CagetteMember.get_couchdb_data(login)
                if len(c_db_data) > 0 and 'validation_state' in c_db_data:
                    data['validation_state'] = c_db_data['validation_state']
        #  print(str(data))
        return data

    @staticmethod
    def send_new_password_link(request):
        result = {}
        email = request.POST.get('email')
        if len(email) > 5 and '@' in email:
            api = OdooAPI()
            cond = [['email', '=', email]]
            fields = ['create_date']
            res = api.search_read('res.partner', cond, fields)
            if (res and len(res) == 1):

                result['msg'] = 'Trouvé ' + str(res[0]['create_date'])
        else:
            result['error'] = 'Email non valide'

        return result

    def get_data(self, full=False):
        """Get member data using Odoo API."""
        return CagetteMember.retrieve_data_according_keys({'id':self.id}, full)

    @staticmethod
    def standalone_create_envelops(request):
        res = {}
        fields = {'checks': []}
        try:
            checks = request.POST.getlist("checks[]")
            if len(checks) > 0:
                for c in checks:
                    fields['checks'].append(int(c))
            for key, value in request.POST.items():
                if key != "checks[]":
                    fields[key] = value
            res = CagetteEnvelops().create_or_update_envelops(fields)
        except Exception as e:
            res['error'] = str(e)
        return res

    def add_pts(self, stype, pts, reason):
        fields = {'name': reason,
                  'shift_id': False,
                  'type': stype,
                  'partner_id': self.id,
                  'point_qty': pts
                 }
        return self.o_api.create('shift.counter.event', fields)

    def add_first_point(self, stype):
        """To prevent members to have -1 point if service is too close to subscription"""
        ltype = 'standard'
        if (stype == 2):
            ltype = 'ftop'
        self.add_pts(ltype, 1, 'Point de bienvenue')

    def generate_barcode(self):
        return self.o_api.execute('res.partner', 'generate_barcode', [self.id])

    def generate_base_and_barcode(self, data=None):
        """Call Odoo methods to generate base and barcode numbers."""
        res1 = res2 = 0
        try:
            if hasattr(settings, 'SUBSCRIPTION_INPUT_BARCODE') and not (data is None):
                # Below code no more useful since LGDS use random barcode
                # import re
                # p = '0420(......)00.'
                # exp = re.compile(p)
                # match = exp.match(data['m_barcode'])
                # base = int(match.group(1))
                f = {'barcode': data['m_barcode']}
                res1 = self.o_api.execute('res.partner', 'generate_base', [self.id])
                res2 = self.o_api.update('res.partner', [self.id], f)

            else:
                res1 = self.o_api.execute('res.partner', 'generate_base', [self.id])
                res2 = self.generate_barcode()
        except Exception as e:
            print(str(e))
        return (res1 and res2)

    def create_capital_subscription_invoice(self, amount, date):
        """Make CapitalFundraisingWizard entities creation."""
        api = OdooAPI()

        if getattr(settings, 'ASK_FOR_CAPITAL_PAYMENT', True) is True:
            shares_qty = int(int(amount) / settings.PARTS_A_PRICE_UNIT)
        else:
            shares_qty = 1

        f1 = {'type': 'out_invoice',
              'date_invoice': date,
              'journal_id': settings.CAP_JOURNAL_ID,
              'account_id': settings.CAP_APPELE_NON_VERSE_ACCOUNT_ID,
              # 'payment_term_id':
              'partner_id': self.id,
              'is_capital_fundraising': True,
              'fundraising_category_id': settings.FUNDRAISING_CAT_ID,
              'state': 'open'
              }
        invoice_id = api.create('account.invoice', f1)
        f2 = {'invoice_id': invoice_id,
              'uom_id': settings.UNITE_UOM_ID,
              'product_id': settings.PARTS_A_PRODUCT_ID,
              'price_unit': settings.PARTS_A_PRICE_UNIT,
              'name': 'Parts A',
              'quantity': shares_qty,
              'account_id': settings.CAP_INVOICE_LINE_ACCOUNT_ID
              }
        invoice_line_id = api.create('account.invoice.line', f2)
        api.execute('account.invoice',
                    'action_move_create',
                    [invoice_id])
        api.execute('account.invoice',
                    'assign_ownshare_to_invoice',
                    [invoice_id])
        return [invoice_id, invoice_line_id]

    def create_coop_shift_subscription(self, shift_t_id, stype, call_nb=1):
        """Store coop shift subscription."""
        # Get shift template ticket corresponding to given shift temp. id
        sti = None
        shift_type = 'standard'
        if stype == 2:
            shift_type = 'ftop'
        cond = [['shift_template_id', '=', int(shift_t_id)],
                ['shift_type', '=', shift_type]]
        fields = ['seats_reserved', 'seats_max']
        stt = self.o_api.search_read('shift.template.ticket', cond, fields)
        if len(stt) > 0:
            sti = stt[0]['id']  # shift_ticket_id
            seats_reserved = int(stt[0]['seats_reserved'])
            seats_max = int(stt[0]['seats_max'])
            # if (seats_reserved == seats_max):
            # TODO
        if not (sti is None):
            try:
                today = datetime.date.today().strftime("%Y-%m-%d")
                st_r_fields = {'partner_id': self.id,
                               'shift_template_id': int(shift_t_id),
                               'shift_ticket_id': int(sti),
                               'state': 'open',
                               'date_begin': today
                               }
                st_r_id = self.o_api.create('shift.template.registration',
                                            st_r_fields)
            except Exception as ex:
                if 'seules les inscriptions ABCD sont possibles' in str(ex) and call_nb < 2:
                    return self.create_coop_shift_subscription(shift_t_id, 1, call_nb + 1)
                else:
                    coop_logger.error("Error while creating shift.template.registration : %s, (fields =%s)", str(ex), str(st_r_fields))
                    st_r_id = None

        return st_r_id

    @staticmethod
    def exists(mail):
        api = OdooAPI()
        cond = [['email', 'ilike', str(mail)]]
        fields = ['email']
        res = api.search_read('res.partner', cond, fields, 1, 0, 'id DESC')
        if (res and len(res) == 1):
            answer = True
        else:
            answer = False
        return answer

    @staticmethod
    def is_associated(id_parent):
        api = OdooAPI()
        cond = [['parent_id', '=', int(id_parent)]]
        fields = ['id','name','parent_id','birthdate_date','suppleant_member_id']
        res = api.search_read('res.partner', cond, fields, 10, 0, 'id DESC')
        for partner in res:
            if partner['suppleant_member_id']:
                return True
        return False

    @staticmethod
    def finalize_coop_creation(post_data):
        """ Update coop data. """
        res = {}
        # First, update couchdb data
        c_db = CouchDB(arg_db='member')
        # shift_template = json.loads(post_data['shift_template'])
        received_data = {'birthdate': post_data['birthdate'],
                         'city': post_data['city'],
                         'zip': post_data['zip'],
                         'firstname': post_data['firstname'],
                         'lastname': post_data['lastname'],
                         'odoo_id': post_data['odoo_id'],
                         'address': post_data['address'],
                         'mobile': post_data['mobile'],
                         'country': post_data['country'],
                         'validation_state': 'done'
                         }
        if ('sex' in post_data):
            received_data['sex'] = post_data['sex']
        if ('function' in post_data):
            received_data['function'] = post_data['function']
        if 'street2' in post_data:
            received_data['street2'] = post_data['street2']
        if 'phone' in post_data:
            received_data['phone'] = format_phone_number(post_data['phone'])
        r = c_db.updateDoc(received_data, 'odoo_id')
        if r:
            if ('odoo_id' in r):
                try:
                    # Update res.partner with received data
                    partner_id = CagetteMember.create_from_buffered_data(received_data)

                except Exception as e:
                    res['error'] = 'Erreur maj membre dans Odoo'
                    coop_logger.error("Pb avec couchDB (1): %s \n %s", str(received_data), str(e))
        else:
            res['error'] = 'Pb avec couchDB'
            coop_logger.error("Pb avec couchDB (B): %s", str(received_data))
        return res

    @staticmethod
    def latest_coop_id():
        """Return the last barcode base recorded in Odoo database."""
        api = OdooAPI()
        cond = [['is_member', '=', True],
                ['total_partner_owned_share', '>', 0]]
        fields = ['barcode_base']
        return api.search_read('res.partner', cond, fields, 1, 0, 'id DESC')

    @staticmethod
    def create_from_buffered_data(post_data):
        """
            Create member or update its data in odoo and return its partner_id.
            At creation:
             Capital subscription and shift subscription will be stored!
             Fill accounting envelops with payment data.
             Send welcome mail for new members.

        """
        # WARNING : Very sensitive data
        #           Very touching step :
        #           couchdb data should reflect Odoo state
        res = {}
        api = OdooAPI()
        c_db = CouchDB(arg_db='member')
        partner_id = None
        name_sep = getattr(settings, 'SUBSCRIPTION_NAME_SEP', ' ')
        ask_4_sex = getattr(settings, 'SUBSCRIPTION_ASK_FOR_SEX', False)
        ask_4_job = getattr(settings, 'SUBSCRIPTION_ASK_FOR_JOB', False)
        concat_order = getattr(settings, 'CONCAT_NAME_ORDER', 'FL')
        ask_for_capital_payment = getattr(settings, 'ASK_FOR_CAPITAL_PAYMENT', True)
        sex = 'o'
        function = ''

        if ask_4_sex is True:
            sex = post_data['sex']
        if ask_4_job is True:
            function = post_data['function']

        # With input type="date", transmitted value is YYYY-mm-dd
        # But, it could be dd/mm/YYYY if not supported by browser
        well_formatted_dob = True
        birthdate = post_data['birthdate']
        try:
            i = birthdate.index('/')
            b_elts = birthdate.split('/')
            birthdate = b_elts[2] + '-' + b_elts[1] + '-' + b_elts[0]
        except:
            # Birthdate should be '-' separated values
            try:
                i = birthdate.index('-')
            except:
                well_formatted_dob = False

        if (well_formatted_dob is True):
            # Prepare data for odoo
            if concat_order == 'LF':
                name = post_data['lastname'] + name_sep + post_data['firstname']
            else:
                name = post_data['firstname'] + name_sep + post_data['lastname']
            f = {'name': name,
                 'birthdate_date': birthdate,
                 'sex': sex,
                 'street': post_data['address'],
                 'zip': post_data['zip'],
                 'city': post_data['city'],
                 'phone': format_phone_number(post_data['mobile']), # Because list view default show Phone and people mainly gives mobile
                 'barcode_rule_id': settings.COOP_BARCODE_RULE_ID,
                 'function': function
                 }
            if ('_id' in post_data):
                f['email'] = post_data['_id']
            if ('country' in post_data):
                if (post_data['country'].lower() == 'france' or
                   post_data['country'] == ''):
                    f['country_id'] = getattr(settings, 'FRANCE_ID', 76)
            if 'street2' in post_data:
                f['street2'] = post_data['street2']
            if ('phone' in post_data) and len(post_data['phone']) > 0:
                if len(f['phone']) == 0:
                    f['phone'] = format_phone_number(post_data['phone'])
                else:
                    f['mobile'] = f['phone']
                    f['phone'] = format_phone_number(post_data['phone'])

            # Create coop
            if not ('odoo_id' in post_data):
                partner_id = api.create('res.partner', f)
                try:
                    id = int(partner_id)
                except:
                    id = 0
                if (id > 0):    # Coop succesfuly created
                    # Update couchdb (rest of data updated on client side)
                    update_data = {'_id': post_data['_id'],
                                   'email': post_data['_id'],
                                   'odoo_id': id}

                    r = c_db.updateDoc(update_data, '_id')
                    if r:
                        # Check if we proceed to capital subscription
                        m = CagetteMember(partner_id)
                        owned_share = 0
                        existing_data = m.get_data()

                        if (len(existing_data) > 0):
                            owned_share = existing_data[0]['total_partner_owned_share']

                        if (owned_share > 0):
                            # form has already been submitted
                            # Subscription, shift and payment data won't be saved
                            res['subs'] = [-1, -1]
                            res['bc'] = False
                            res['shift'] = None
                            res['envelop'] = None
                        else:
                            # New member
                            # Create capital subscription, base & barcode
                            if 'shares_euros' in post_data:
                                shares_euros = post_data['shares_euros']
                            elif getattr(settings, 'ASK_FOR_CAPITAL_PAYMENT', True) is False:
                                shares_euros = 0
                            today = datetime.date.today().strftime("%Y-%m-%d")
                            res['subs'] = \
                                m.create_capital_subscription_invoice(shares_euros, today)
                            res['bc'] = m.generate_base_and_barcode(post_data)

                            # if the new member is associated with an already existing member 
                            # then we put the state in "associated" and we create the "associated" member
                            if 'is_associated_people' in post_data and 'parent_id' in post_data :
                                associated_member = {
                                    'email': post_data['_id'],
                                    'name': name,
                                    'birthdate_date': birthdate,
                                    'sex': sex,
                                    'street': post_data['address'],
                                    'zip': post_data['zip'],
                                    'city': post_data['city'],
                                    'phone': format_phone_number(post_data['mobile']), # Because list view default show Phone and people mainly gives mobile
                                    'barcode_rule_id': settings.ASSOCIATE_BARCODE_RULE_ID,
                                    'parent_id': post_data['parent_id'],
                                    'suppleant_member_id': partner_id,
                                    'is_associated_people': True,
                                    'function': function
                                    }
                                associated_member_id = api.create('res.partner', associated_member)
                                fields = {}
                                fields['cooperative_state'] = 'associated'
                                api.update('res.partner', [partner_id], fields)
                                am = CagetteMember(associated_member_id)
                                res['bca'] = am.generate_base_and_barcode(post_data)
                            # If it's an new associated member with a new partner. Link will be made by the user in BDM/admin
                            # We add the associated member to the "associate" shift template so we can find them in Odoo
                            elif 'is_associated_people' not in post_data or 'is_associated_people' in post_data and 'parent_id' not in post_data:
                                # Create shift suscription if is not associated
                                try:
                                    shift_template = json.loads(post_data['shift_template'])
                                    shift_t_id = shift_template['data']['id']
                                    stype = shift_template['data']['type']
                                    res['shift'] = \
                                        m.create_coop_shift_subscription(shift_t_id, stype)
                                except Exception as no_shift_e:
                                    coop_logger.error("Pas de créneau défini : %s \n %s", str(post_data), str(no_shift_e))
                                # m.add_first_point(stype) # Not needed anymore

                            # Update couchdb do with new data
                            try:
                                updated_data = m.get_data()
                                update_data['barcode_base'] = updated_data[0]['barcode_base']
                                res['bc'] = update_data['barcode_base']
                                update_data['odoo_state'] = 'done'
                                c_db.updateDoc(update_data, '_id')
                            except Exception as e:
                                res['error'] = 'Erreur après souscription du capital'
                                coop_logger.error("Erreur après souscription : %s \n %s", str(res), str(e))

                            if ask_for_capital_payment is True:
                                # Create or update envelop(s) with coop payment data
                                payment_data = {
                                    'partner_id': partner_id,
                                    'partner_name': post_data['firstname'] + ' ' + post_data['lastname'],
                                    'payment_meaning': post_data['payment_meaning'],
                                    'shares_euros': post_data['shares_euros'],
                                    'checks_nb': post_data['checks_nb']     # is 0 if payment is cash
                                }

                                if ('checks' in post_data):
                                    payment_data['checks'] = json.loads(post_data['checks'])
                                else:
                                    payment_data['checks'] = []
                                if payment_data['payment_meaning'] == 'cash' or payment_data['payment_meaning'] == 'ch':
                                    res['envelop'] = CagetteEnvelops().create_or_update_envelops(payment_data)
                                else:
                                    p_data = {'partner_id': partner_id, 'type': payment_data['payment_meaning'], 'amount': post_data['shares_euros']}
                                    res['envelop'] = CagetteEnvelops().save_payment(p_data)
                        # Send welcome mail
                        try:
                            api.execute('res.partner', 'send_welcome_email', [partner_id])
                        except Exception as e:
                            res['error'] = 'Erreur envoie mail invitation'
                        # from outils.common import CagetteMail
                        # try:
                        #     CagetteMail.sendWelcome(f['email'])
                        # except Exception as e:
                        #     res['error'] = 'Erreur envoie mail invitation'
                    else:
                        res['error'] = 'Pb avec couchDB'
                        res['data'] = update_data
                        coop_logger.error("Pb couchDB (C) : %s", str(res))
                else:
                    res['error'] = 'Erreur creation membre odoo'
                    coop_logger.error("Pb couchDB (D) : %s", str(res))
            # Update coop data
            else:
                odoo_id = int(post_data['odoo_id'])
                if (api.update('res.partner', [odoo_id], f) is True):
                    partner_id = odoo_id

        return partner_id

    @staticmethod
    def create_from_cvs_row(data):
        """
            Create member or update its data in odoo and return its partner_id.
            At creation:
             Capital subscription
        """
        res = {}
        api = OdooAPI()
        partner_id = None

        well_formatted_dob = True
        birthdate = data['date de naissance']
        try:
            i = birthdate.index('/')
            b_elts = birthdate.split('/')
            birthdate = b_elts[2] + '-' + b_elts[1] + '-' + b_elts[0]
        except:
            # Birthdate should be '-' separated values
            try:
                i = birthdate.index('-')
            except:
                well_formatted_dob = False

        if (well_formatted_dob is True):
            # Prepare data for odoo
            f = {'name': data['Prénom'] + ' ' + data['Nom'],
                 'birthdate_date': birthdate,
                 'sex': 'o',
                 'street': data['adresse rue'],
                 'zip': data['code postal'],
                 'city': data['ville'],
                 'country_id': getattr(settings, 'FRANCE_ID', 75),
                 'phone': data['tel'],
                 'email': data['mail'],
                 'barcode_rule_id': settings.COOP_BARCODE_RULE_ID
                 }
            partner_id = api.create('res.partner', f)
            try:
                id = int(partner_id)
            except:
                id = 0
            if (id > 0):    # Coop succesfuly created
                res['id'] = partner_id
                m = CagetteMember(partner_id)
                shares_euros = int(float(data['Nb de parts']) * 10)
                res['subs'] = m.create_capital_subscription_invoice(shares_euros, data['date inscription'])
                res['bc'] = m.generate_base_and_barcode(data)
            else:
                res['error'] = 'Unable to create member from ' + str(data)
        return res

    @staticmethod
    def store_warning_msg(post_data):
        """Store in couchDB database the warning new coop has written."""
        c_db = CouchDB(arg_db='member')
        data = {
            '_id': post_data['_id'],
            'coop_msg': post_data['msg']
        }
        r = c_db.updateDoc(data, '_id')
        return r

    @staticmethod
    def get_couchdb_data(email):
        """Retrieve couchDB data corresponding to given email."""
        c_db = CouchDB(arg_db='member')
        try:
            doc = c_db.getDocById(email)
        except:
            doc = []
        return doc

    @staticmethod
    def get_state_fr(coop_state):
        """Return french version of given coop_state."""

        if coop_state == 'alert':
            fr_state = 'En alerte'
        elif coop_state == 'delay':
            fr_state = 'Délai accordé'
        elif coop_state == 'suspended':
            fr_state = 'Rattrapage'
        elif coop_state == 'not_concerned':
            fr_state = 'Non concerné(e)'
        elif coop_state == 'blocked':
            fr_state = 'Bloqué(e)'
        elif coop_state == 'unpayed':
            fr_state = 'Impayé constaté'
        elif coop_state == 'unsubscribed':
            fr_state = 'Désinscrit(e)'
        elif coop_state == 'up_to_date':
            fr_state = 'A jour'
        elif coop_state == 'exempted':
            fr_state = 'Exempté(e)'
        elif coop_state == 'associated':
            fr_state = 'En binôme'
        elif coop_state == 'gone':
            fr_state = 'Parti(e)'
        elif coop_state == 'vacation':
            fr_state = 'En vacances'
        else:
            fr_state = 'Inconnu'
        return fr_state

    @staticmethod
    def get_members_next_shift(ids):
        """Retrieve next shift for given members ids."""
        api = OdooAPI()
        cond = [['partner_id', 'in', ids],
                ['date_begin', '>=', datetime.datetime.now().isoformat()],
                ['state', '=', 'open']]
        fields = ['shift_type', 'date_begin', 'partner_id', 'date_end', 'shift_ticket_id']

        res = api.search_read('shift.registration', cond, fields, 2500, 0, 'date_begin ASC')
        shifts = {}
        locale.setlocale(locale.LC_ALL, 'fr_FR.utf8')

        if len(res) > 0:
            local_tz = pytz.timezone('Europe/Paris')
            for s in res:
                date, t = s['date_begin'].split(' ')
                year, month, day = date.split('-')
                hour, minute, second = t.split(':')
                if int(hour) < 21:
                    start = datetime.datetime(int(year), int(month), int(day), int(hour), int(minute), int(second), tzinfo=pytz.utc)
                    start_date = start.astimezone(local_tz)
                    s['start'] = start_date.strftime("%A %d %B %Y à %Hh%M")
                    if not (s['partner_id'][0] in shifts):
                        shifts[s['partner_id'][0]] = []
                    shifts[s['partner_id'][0]].append(s)

        return shifts

    @staticmethod
    def add_next_shifts_to_members(members):
        """Next shifts are added to members data."""
        ids = []
        m_list = []
        for m in members:
            ids.append(m['id'])

        shifts = CagetteMember.get_members_next_shift(ids)

        for m in members:
            s = []
            if m['id'] in shifts:
                s = shifts[m['id']]
            m['shifts'] = s
            m_list.append(m)
        return m_list

    @staticmethod
    def search(k_type, key, shift_id=None, search_type="full"):
        """Search member according 3 types of key."""
        api = OdooAPI()
        if k_type == 'id':
            cond = [['id', '=', int(key)]]
        elif k_type == 'barcode_base':
            cond = [['barcode_base', '=', str(key)]]
        elif k_type == 'barcode':
            cond = [['barcode', '=', str(key)]]
        else:
            cond = [['name', 'ilike', str(key)]]
        cond.append('|')
        cond.append(['is_member', '=', True])
        #TODO : replace is_associated_people check by suppleant_member_id check to exclude mineurs rattachés
        if search_type != 'members' and search_type != 'envelops':
            cond.append(['is_associated_people', '=', True])
        else:
            cond.append(['is_associated_people', '=', False])
        if search_type != 'envelops':
            cond.append(['cooperative_state', '!=', 'associated'])
        # cond.append(['cooperative_state', '!=', 'unsubscribed'])

        if search_type == "full" or search_type == 'members' or search_type == "manage_shift_registrations":
            fields = CagetteMember.m_default_fields
            if not shift_id is None:
                CagetteMember.m_default_fields.append('tmpl_reg_line_ids')
            res = api.search_read('res.partner', cond, fields)
            members = []
            if len(res) > 0:
                for m in res:
                    keep_it = False
                    if not shift_id is None and len(shift_id) > 0:
                        # Only member registred to shift_id will be returned
                        if len(m['tmpl_reg_line_ids']) > 0:
                            cond = [['id', '=', m['tmpl_reg_line_ids'][0]]]
                            fields = ['shift_template_id']
                            shift_templ_res = api.search_read('shift.template.registration.line', cond, fields)
                            if (len(shift_templ_res) > 0
                                and
                                int(shift_templ_res[0]['shift_template_id'][0]) == int(shift_id)):
                                keep_it = True
                    else:
                        keep_it = True
                    if keep_it is True:
                        try:
                            img_code = base64.b64decode(m['image_medium'])
                            extension = imghdr.what('', img_code)
                            m['image_extension'] = extension
                        except Exception as e:
                            coop_logger.info("Img error : %s", e)
                        m['state'] = m['cooperative_state']
                        m['cooperative_state'] = \
                            CagetteMember.get_state_fr(m['cooperative_state'])
                        # member = CagetteMember(m['id'], m['email'])
                        # m['next_shifts'] = member.get_next_shift()
                        if not m['parent_name'] is False:
                            m['name'] += ' (suppléant.e de son binôme ' + m['parent_name'] + ')'
                            del m['parent_name']
                        members.append(m)

            return CagetteMember.add_next_shifts_to_members(members)
        elif search_type == "makeups_data":
            fields = CagetteMember.m_short_default_fields
            fields = fields + ['shift_type', 'makeups_to_do', 'display_ftop_points', 'display_std_points', 'shift_type']
            cond.append(['shift_type', '=', 'standard'])
            res = api.search_read('res.partner', cond, fields)
            CagetteMembers.add_makeups_to_come_to_member_data(api, res)
            return res
        elif search_type == "shift_template_data":
            fields = CagetteMember.m_short_default_fields
            fields = fields + ['id', 'makeups_to_do', 'cooperative_state','parent_name']
            res = api.search_read('res.partner', cond, fields)

            if res:
                for partner in res:
                    c = [['partner_id', '=', int(partner['id'])], ['state', 'in', ('draft', 'open')]]
                    f = ['shift_template_id']
                    shift_template_reg = api.search_read('shift.template.registration', c, f)

                    if shift_template_reg:
                        partner['shift_template_id'] = shift_template_reg[0]['shift_template_id']
                    else:
                        partner['shift_template_id'] = None
                    if not partner['parent_name'] is False:
                        partner['name'] += ' (suppléant.e de son binôme ' + partner['parent_name'] + ')'
                        del partner['parent_name']

            return res
        else:
            # TODO differentiate envelops & subscription_data searches
            fields = CagetteMember.m_short_default_fields
            fields = fields + ['total_partner_owned_share', 'amount_subscription']
            res = api.search_read('res.partner', cond, fields)
            return res

    @staticmethod
    def is_partner_involved_in_binom(partner):
        api = OdooAPI()
        # First make sure the selected partner "is not already a suppleant"
        # Let's be prudent : cover both case of partner being suppleant member,
        # and case of partner being the contact of a suppleant member
        contact_of_partner_suppleant = api.search_read('res.partner', [['suppleant_member_id', '=', partner['id']]])
        if contact_of_partner_suppleant:
            # partner has a suppleant contact therefore it is a suppleant member
            return "MEMBER_SUPPLEANT_OF_BINOM"
        if partner['parent_id'] and partner['suppleant_member_id']:
            # partner is the contact of a suppleant member
            return "CONTACT_OF_MEMBER_SUPPLEANT_OF_BINOM"

        # Second make sure the selected partner is not a titulaire
        children_of_partner = api.search_read('res.partner', [['parent_id', '=', partner['id']]])
        suppleant_of_partner = [x for x in children_of_partner if x['suppleant_member_id']]
        if suppleant_of_partner:
            # partner is titulaire
            return "TITULAIRE_OF_BINOM"

        return None

    @staticmethod
    def can_become_titulaire(parent_id):
        parent_id = int(parent_id) # mandatory
        api = OdooAPI()
        parent = api.search_read('res.partner',
                                [['id', '=', parent_id]],
                                ['parent_id', 'suppleant_member_id', 'cooperative_state'])[0]
        parent['id'] = parent_id  # needed ?
        case = CagetteMember.is_partner_involved_in_binom(parent)

        errors = []
        if case == "MEMBER_SUPPLEANT_OF_BINOM":
            errors.append("Le titulaire sélectionné est déjà suppléant d'un binôme.")
        elif case == "CONTACT_OF_MEMBER_SUPPLEANT_OF_BINOM":
            errors.append("Le titulaire sélectionné est déjà (le contact d'un membre) suppléant d'un binôme.")
        elif case == "TITULAIRE_OF_BINOM":
            errors.append("Le titulaire sélectionné est déjà titulaire d'un binôme.")

        if parent['cooperative_state'] == "exempted":
            errors.append("Le titulaire séléctionné est exempté.")

        return errors


    @staticmethod
    def can_become_suppleant(child_id):
        child_id = int(child_id) # mandatory
        api = OdooAPI()
        child = api.search_read('res.partner',
                                 [['id', '=', child_id]],
                                 ['parent_id','suppleant_member_id','cooperative_state'])[0]
        child['id'] = child_id # needed ?
        case = CagetteMember.is_partner_involved_in_binom(child)

        errors = []
        if case == "MEMBER_SUPPLEANT_OF_BINOM":
            errors.append("Le suppléant sélectionné est déjà suppléant d'un binôme.")
        elif case == "CONTACT_OF_MEMBER_SUPPLEANT_OF_BINOM":
            errors.append("Le suppléant sélectionné est déjà (le contact d'un membre) suppléant d'un binôme.")
        elif case == "TITULAIRE_OF_BINOM":
            errors.append("Le suppléant sélectionné est déjà titulaire d'un binôme.")

        if child['cooperative_state'] == "exempted":
            errors.append("Le membre suppléant séléctionné est exempté.")

        return errors


    @staticmethod
    def remove_data_from_CouchDB(request):
        res = {}
        try:
            email = request.POST.get("email","")
            if len(email) > 0:
                is_connected_user = CagetteUser.are_credentials_ok(request)
                can_be_deleted = False
                coop = CagetteMember.retrieve_data_according_keys({'email':email})
                if len(coop) > 0:
                    coop = coop[0]
                    if (len(coop['tmpl_reg_line_ids']) > 0 and coop['total_partner_owned_share']):
                        #no need to be connected to delete it
                        can_be_deleted = True
                    else:
                        if not is_connected_user:
                            message = 'L\'inscription de ce membre n\'a pas été correctement finalisée, il ne peut être archivé.' + "\n"
                            if len(coop['tmpl_reg_line_ids']) == 0:
                                message += 'Il n\'est inscrit à aucun créneau. Veuillez en ajouter un sur Odoo.'+ "\n"
                            if coop['total_partner_owned_share'] == 0:
                                message += 'Le capital souscrit n\'a pas été enregistré. Veuillez le faire sur Odoo.'
                            res['msg'] = message

                else:
                    if is_connected_user is False:
                        res['msg'] = 'Aucun membre connu avec cet email'

                # if demand comes from a connected user, it can be deleted
                if is_connected_user:
                    can_be_deleted = True
                else:
                    if can_be_deleted is False:
                        res['msg'] = 'Seul un utilisateur connecté peut faire cette suppression'

                if can_be_deleted is True:
                    c_db = CouchDB(arg_db='member')
                    doc = c_db.getDocById(email)    # email is the id for members doc
                    res['action'] = c_db.delete(doc)
            else:
                res['msg'] = 'No email'
        except Exception as e:
            coop_logger.error("Remove data from couchDB : %s", str(e))
            res['msg'] = 'Oups ! Erreur Remove data from couchDB'
        return res

    @staticmethod
    def remove_from_mess_list(request):
        res = {}
        try:
            _id = request.POST.get("id", "")
            c_db = CouchDB(arg_db='member_mess')
            doc = c_db.getDocById(_id)
            res['action'] = c_db.delete(doc)
        except Exception as e:
            res['error'] = str(e)
        return res

    def search_associated_people(self):
        """ Search for an associated partner """
        res = {}

        c = [["parent_id", "=", self.id]]
        f = ["id", "name", "barcode_base", 'suppleant_member_id']

        res = self.o_api.search_read('res.partner', c, f)

        for partner in res:
            if partner['suppleant_member_id']:
                return partner
        return None

    def update_member_makeups(self, member_data):
        api = OdooAPI()
        makeups_to_do = int(member_data["target_makeups_nb"])
        if makeups_to_do < 0:
            coop_logger.error(
                "Erreur : dans update_member_makeups, target makeups_to_do = %s.",
                makeups_to_do
            )
        f = {'makeups_to_do': makeups_to_do}
        res_item = api.update('res.partner', [self.id], f)
        res = {
            'mid': self.id,
            'update': res_item
        }
        # No need to manually call status recompute after that because when we use this method,
        # we always call an odoo method that triggers status update after that
        return res


    def get_makeup_registrations_ids_on_shift_template(self, shift_template_id):
        """ Get the makeup registrations that are on a shift template """
        makeup_reg_ids = []
        res_shift_ids = shifts.fonctions.get_scheduled_makeups(self.o_api, partner_ids=[self.id])
        for shift_reg in res_shift_ids:
            c = [["id", "=", int(shift_reg['shift_id'][0])]]
            f = ['shift_template_id']
            shift = self.o_api.search_read("shift.shift", c, f)[0]
            if shift['shift_template_id'][0] == shift_template_id:
                makeup_reg_ids.append(shift_reg["id"])
        return makeup_reg_ids


    def get_shift_template_registration_ids(self):
        c = [['partner_id', '=', self.id]]
        f = ['id']
        return [x['id'] for x in self.o_api.search_read("shift.template.registration", c, f)]


    def unsubscribe_member_but_exogenous_makeups(self):
        return self.o_api.execute(
            'shift.template.registration',
            'unlink_but_exogenous_makeups',
            self.get_shift_template_registration_ids()
        )


    def unsubscribe_member(self):
        return self.o_api.execute(
            'shift.template.registration',
            'unlink',
            self.get_shift_template_registration_ids()
        )

    def set_cooperative_state(self, state):
        f = {'cooperative_state': state}
        return self.o_api.update('res.partner', [self.id], f)

    def update_extra_shift_done(self, value):
        api = OdooAPI()
        res = {}

        f = { 'extra_shift_done': value }
        res_item = api.update('res.partner', [self.id], f)
        res = {
            'mid': self.id,
            'update': res_item
        }

        return res


    def has_state_unsubscribed_gone_or_associated(self):
        c = [['id', '=', self.id]]
        f = ['cooperative_state']
        state = self.o_api.search_read("res.partner", c, f)[0]["cooperative_state"]
        return state in ("unsubscribed", "gone", "associated")

class CagetteMembers(models.Model):
    """Class to manage operations on all members or part of them."""

    @staticmethod
    def get_problematic_members():
        """Search partner with missing elements"""
        api = OdooAPI()
        return []


    @staticmethod
    def raw_search(needle):
        """Search partner with missing elements"""
        res = []
        try:
            api = OdooAPI()
            cond = ['|', ('email', 'ilike', needle), ('display_name', 'ilike', needle)]
            fields = ['barcode_base', 'barcode', 'create_date',
                      'cooperative_state', 'name', 'birthdate_date', 'street', 'street2',
                      'zip', 'city', 'email', 'mobile', 'phone', 'total_partner_owned_share',
                      'amount_subscription', 'active_tmpl_reg_line_count',
                      'shift_type', 'current_template_name', 'sex']
            api_res = api.search_read('res.partner', cond, fields)
            if api_res:
                ids_in_env = CagetteEnvelops().get_ids_in_all()
                for c in api_res:
                    if not (str(c['id']) in ids_in_env):
                        c['envelops'] = False
                    else:
                        c['envelops'] = True
                res = api_res
        except Exception as e:
            res['error'] = str(e)
        return res

    @staticmethod
    def verify_subscription_state():
        """Verify couchDB and Odoo DB coherence."""
        c_db = CouchDB(arg_db='member')
        # Get all members with unregistrated shifts
        unregistrated_members = c_db.getAllDocs('odoo_state', 'done', False)
        partner_ids = []
        to_modify_in_couchDB = []
        for m in unregistrated_members:
            if 'odoo_id' in m:
                partner_ids.append(int(m['odoo_id']))
        if len(partner_ids) > 0:
            api = OdooAPI()
            cond = [['id', 'in', partner_ids]]
            f = ['total_partner_owned_share', 'name', 'barcode_base', 'tmpl_reg_line_ids']
            res = api.search_read('res.partner', cond, f)
            for p in res:
                if p['total_partner_owned_share'] > 0:
                    to_modify_in_couchDB.append(p)
        if len(to_modify_in_couchDB) > 0:
            for p in to_modify_in_couchDB:
                if len(p['tmpl_reg_line_ids']) > 0:
                    r = {'odoo_id': p['id'], 'odoo_state': 'done', 'barcode_base': p['barcode_base']}
                    c_db.updateDoc(r, 'odoo_id', ['shift_template'])

        return to_modify_in_couchDB

    @staticmethod
    def update_couchdb_barcodes():
        c_db = CouchDB(arg_db='member')
        # Get all members with 'done' state
        all_done = c_db.getAllDocs('odoo_state', 'done')
        partner_ids = []
        to_modify_in_couchDB = []
        for m in all_done:
            partner_ids.append(int(m['odoo_id']))
        if len(partner_ids) > 0:
            api = OdooAPI()
            cond = [['id', 'in', partner_ids]]
            f = ['barcode_base']
            res = api.search_read('res.partner', cond, f)

            for p in res:
                for m in all_done:
                    if (int(m['odoo_id']) == int(p['id'])):
                        try:
                            if int(m['barcode_base']) != int(p['barcode_base']):
                                m['barcode_base'] = p['barcode_base']
                                to_modify_in_couchDB.append(m)
                        except:
                            pass
            if len(to_modify_in_couchDB) > 0:
                for p in to_modify_in_couchDB:
                    r = {'odoo_id': p['odoo_id'],
                         'barcode_base': p['barcode_base']}
                    c_db.updateDoc(r, 'odoo_id')

        return to_modify_in_couchDB

    @staticmethod
    def _generate_inra_csv_data(odoo_result):
        data = {'lines': [], 'sum_up': ''}
        current_p = ''
        try:
            if (('purchases' in odoo_result) and len(odoo_result['purchases']) > 0):
                off = OFF()
                off_products = off.get_products()
                headers = ['date', 'coop_id', 'coop_num', 'coop_naissance', 'coop_ville',
                           'code-barre', 'nom_produit', 'qte', 'prix', 'remise',
                           'categ_id', 'nom_cat_cagette',
                           'off_qte', 'off_cat', 'off_labels',
                           'off_nutriscore', 'off_nova', 'off_nrj_100g',
                           'off_manufacture_places', 'off_origins']
                data['lines'].append(headers)
                coop_nums = []
                products = []
                remises = {}
                ca_ttc_panel = 0
                for p in odoo_result['purchases']:
                    current_p = p
                    if p['product_barcode'] in off_products:
                        off_pdt = off_products[p['product_barcode']]
                    else:
                        off_pdt = {'quantity': '', 'categories': '',
                                   'labels': '', 'nutrition_grade_fr': '', 'nova_group': '', 'energy_100g': '',
                                   'manufacturing_places': '', 'origins': ''}
                    pname = p['product_name'].replace(';', ' ')
                    cat_name = ''
                    if str(p['product_categ_id']) in odoo_result['pcat']:
                        cat_name = odoo_result['pcat'][str(p['product_categ_id'])]
                    line = [p['date_order'], p['coop_id'], p['coop_num'], p['coop_birthdate'], p['coop_city'],
                            p['product_barcode'], pname, p['product_qty'], p['product_price'], p['product_discount'],
                            p['product_categ_id'], cat_name,
                            off_pdt['quantity'], off_pdt['categories'], off_pdt['labels'],
                            off_pdt['nutrition_grade_fr'], off_pdt['nova_group'], off_pdt['energy_100g'],
                            off_pdt['manufacturing_places'], off_pdt['origins']]

                    ca_ttc_line = float(p['product_qty']) * float(p['product_price'])
                    if (int(p['product_discount']) > 0):
                        ca_ttc_line *= (100 - int(p['product_discount'])) / 100
                        if not (p['product_discount'] in remises):
                            remises[p['product_discount']] = 0
                        remises[p['product_discount']] += ca_ttc_line

                    ca_ttc_panel += ca_ttc_line
                    data['lines'].append(line)
                    if not (p['coop_num'] in coop_nums):
                        coop_nums.append(p['coop_num'])
                    if not (p['product_id'] in products):
                        products.append(p['product_id'])

                data['sum_up'] =  'Coopérateurs du panel ayant fait au moins 1 achat : ' + str(len(coop_nums)) + "\n"
                data['sum_up'] += 'Nb de références de produits achetés : ' + str(len(products)) + "\n"
                data['sum_up'] += 'CA TTC réalisé : ' + "{:.2f}".format(ca_ttc_panel) + "\n"
                for pc, val in remises.items():
                    data['sum_up'] += 'dont CA TTC remises ' + str(int(pc)) + '% : ' + "{:.2f}".format(val) + "\n"
        except Exception as e:
            data['error'] = str(e) + ' : produit en cours = ' + str(current_p)
        return data

    @staticmethod
    def get_inra_panel_purchases(request):
        api = OdooAPI()
        list_fpath = 'members/panel.csv'
        res = {'error': ''}
        try:
            nums = []
            with open(list_fpath) as fp:
                line = fp.readline()
                while line:
                    nums.append(line.strip())
                    line = fp.readline()
            if len(nums) > 0:
                params = {'partners_coop_num': nums}
                # num_slice = nums[0:1]
                month = request.POST.get('mois_month')
                year = request.POST.get('mois_year')
                try:
                    m = int(month)
                    y = int(year)
                    if (m < 10):
                        month = '0' + month
                    if (m > 0 and y > 0):
                        params['month'] = year + '-' + month
                    else:
                        today = datetime.date.today()
                        year = str(today.year)
                        month = str(today.month)
                        if (len(month) == 1):
                            month = '0' + month
                        params['month'] = year + '-' + month
                except Exception as e2:
                    res['error'] += str(e2)
                    pass
                odoo_result = api.execute('lacagette.pos_member_purchases', 'get_members_purchases', params)
                res['data'] = CagetteMembers._generate_inra_csv_data(odoo_result)
                if 'error' in res['data']:
                    res['error'] += res['data']['error']
                res['params'] = params

        except Exception as e:
            res['error'] += str(e)

        return res

    @staticmethod
    def get(cond, fields, o=0, l=5000):
        res = {}
        try:
            api = OdooAPI()
            res = api.search_read('res.partner', cond, fields, offset=o, limit=l)
        except Exception as e:
            res['error'] = str(e)
        return res

    @staticmethod
    def add_pts_to_everyone(mtype, ids, pts, reason):
        res = {}
        try:
            for mid in ids:
                m = CagetteMember(mid)
                res[str(mid)] = m.add_pts(mtype, pts, reason)
        except Exception as e:
            res['error'] = str(e)
        return res

    @staticmethod
    def get_makeups_members(ids=[]):
        # 0 : fetch members with makeups_to_do > 0
        api = OdooAPI()
        cond = [['makeups_to_do','>', 0]]

        if len(ids) > 0:
            cond.append(['id','in', ids])

        fields = ['id', 'name', 'display_std_points', 'display_ftop_points', 'shift_type', 'makeups_to_do']
        res = api.search_read('res.partner', cond, fields)

        # There are two things we need to do now :
        # 1 : fetching members with no makeups to do but with some makeups to come
        # 2 : providing makeups to come to all members

        makeups_to_come_per_partner = shifts.fonctions.get_partners_with_makeups_to_come(api)

        # 1 : fetching members with no makeups to do but with some makeups to come
        cond = [['makeups_to_do', '=', 0], ['id', 'in', list(makeups_to_come_per_partner.keys())]]

        if len(ids) > 0:
            cond.append(['id','in', ids])

        res = res + api.search_read('res.partner', cond, fields)

        # 2 : providing makeups to come to all members
        for idx, partner in enumerate(res):
            if partner['id'] in makeups_to_come_per_partner:
                res[idx]['makeups_to_come'] = makeups_to_come_per_partner[partner['id']]
            else:
                res[idx]['makeups_to_come'] = 0

        return res

    @staticmethod
    def add_makeups_to_come_to_member_data(api, res):
        if res:
            for idx, partner in enumerate(res):
                res[idx]['makeups_to_come'] = len(shifts.fonctions.get_scheduled_makeups(api, partner_ids=[int(partner['id'])]))

    @staticmethod
    def get_attached_members():
        api = OdooAPI()
        cond = [['is_associated_people', '=', True]]
        fields = ['id', 'name', 'parent_name', 'suppleant_member_id']
        res = api.search_read('res.partner', cond, fields)
        # Exclude mineurs rattachés
        return [x for x in res if x['suppleant_member_id']]

class CagetteUser(models.Model):

    @staticmethod
    def get_credentials(request):
        import hashlib

        data = {}
        api = OdooAPI()
        login = request.POST.get('login')
        password = request.POST.get('password')

        if login and password:
            uid = api.authenticate(login, password)
            if not(uid is False):
                cond = [['id', '=', uid]]
                fields = ['active', 'cooperative_state', 'create_date', 'groups_id']
                try:
                    res = api.search_read('res.users', cond, fields)
                    if (res[0]['active'] is True):
                        tocode = res[0]['create_date'] + request.META.get('HTTP_USER_AGENT')
                        data['authtoken'] = hashlib.sha256(tocode.encode('utf-8')).hexdigest()
                        data['uid'] = uid
                        data['cooperative_state'] = res[0]['cooperative_state']
                        cond = [['id', 'in', res[0]['groups_id']]]
                        fields = ['full_name']
                        data['groups'] = api.search_read('res.groups', cond, fields)

                except Exception as e:
                    data['error'] = str(e)

        return data

    @staticmethod
    def are_credentials_ok(request):
        import hashlib
        answer = False
        if 'authtoken' in request.COOKIES and 'uid' in request.COOKIES:
            api = OdooAPI()
            cond = [['id', '=', request.COOKIES['uid']]]
            fields = ['active','create_date']
            try:
                res = api.search_read('res.users', cond, fields)
                if (res[0]['active'] is True):
                    tocode = res[0]['create_date'] + request.META.get('HTTP_USER_AGENT')
                    calc_authtoken = hashlib.sha256(tocode.encode('utf-8')).hexdigest()
                    if calc_authtoken == request.COOKIES['authtoken']:
                        answer = True
            except:
                pass

        return answer

    @staticmethod
    def get_preferences(request, key=None):
        preferences = {}
        try:
            api = OdooAPI()
            cond = [['id', '=', request.COOKIES['uid']]]
            fields = ['partner_id']
            res = api.search_read('res.users', cond, fields)
            if res:
                preferences = CagetteMember(res[0]['partner_id'][0]).get_preferences(key)
        except Exception as e:
            preferences['error'] = str(e)
        
        return preferences

    @staticmethod
    def set_preferences(request, data, key=None):
        """Be careful : if key is None, preferences will be overwritten by received data."""    
        res = {}
        if CagetteUser.are_credentials_ok(request):
            try:
                api = OdooAPI()
                cond = [['id', '=', request.COOKIES['uid']]]
                fields = ['partner_id']
                res_user = api.search_read('res.users', cond, fields)
                if res_user:
                    if key is None:
                        external_apps_preferences = data
                    else:
                        external_apps_preferences = CagetteUser.get_preferences(request)
                        external_apps_preferences[key] = data
                    CagetteMember(res_user[0]['partner_id'][0]).set_preferences(external_apps_preferences)
                    res['success'] = True
            except Exception as e:
                res['error'] = str(e)
        
        return res