"""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 products.models import OFF from envelops.models import CagetteEnvelops 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() 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 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', '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','date_delay_stop', 'sex'] else: fields = ['name', 'email', 'birthdate', '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]) cond.append(['is_associated_people', '=', True]) fields = ['name', 'email', 'birthdate', 'create_date', 'cooperative_state', 'is_associated_people', 'barcode_base'] res = api.search_read('res.partner', cond, fields) if (res and len(res) >= 1): coop_id = None for item in res: coop = item if item["birthdate"] is not False: coop_birthdate = item['birthdate'] coop_state = item['cooperative_state'] if item["is_associated_people"] == True: coop_id = item['id'] y, m, d = coop_birthdate.split('-') password = password.replace('/', '') if (password == d + m + y): 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() 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': int(int(amount) / settings.PARTS_A_PRICE_UNIT), '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'] res = api.search_read('res.partner', cond, fields, 10, 0, 'id DESC') already_have_adult_associated = False for partner in res: birthdate = partner['birthdate'] if(birthdate): today = date.today() date1 = datetime.datetime.strptime(birthdate, "%Y-%m-%d") age = today.year - date1.year - ((today.month, today.day) < (date1.month, date1.day)) if age > 17 : already_have_adult_associated = True return already_have_adult_associated @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') 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': 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'] = 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 today = datetime.date.today().strftime("%Y-%m-%d") res['subs'] = \ m.create_capital_subscription_invoice(post_data['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 : fields = {} fields['cooperative_state'] = 'associated' api.update('res.partner', [partner_id], fields) associated_member = { 'email': post_data['_id'], 'name': name, 'birthdate': 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'], 'is_associated_people': True, 'function': function } associated_member_id = api.create('res.partner', associated_member) 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 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) # 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)) # 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': birthdate, 'sex': 'o', 'street': data['adresse rue'], 'zip': data['code postal'], 'city': data['ville'], 'country_id': 76, '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.""" company = getattr(settings, 'COMPANY_CODE', '') if coop_state == 'alert': fr_state = 'En alerte' elif coop_state == 'delay': fr_state = 'Délai accordé' elif coop_state == 'suspended': if company == 'lacagette': fr_state = 'Rattrapage' else: fr_state = 'Suspendu(e)' 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)' 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]) if search_type != 'members': cond.append(['is_associated_people', '=', True]) else: cond.append(['is_associated_people', '=', False]) 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(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 short & 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 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"] res = self.o_api.search_read('res.partner', c, f) try: return res[0] except: return None def update_member_makeups(self, member_data): api = OdooAPI() res = {} f = { 'makeups_to_do': int(member_data["target_makeups_nb"]) } res_item = api.update('res.partner', [self.id], f) res = { 'mid': self.id, 'update': res_item } return res def get_member_selected_makeups(self): res = {} c = [["partner_id", "=", self.id], ["is_makeup", "=", True], ["state", "=", "open"]] f=['id'] res = self.o_api.search_read("shift.registration", c, f) return res def is_member_registered_to_makeup_shift_template(self, shift_template_id): """ Given a shift template, check if the member is registered to a makeup on this shift template """ try: c = [["partner_id", "=", self.id], ["is_makeup", "=", True], ["state", "=", "open"]] f=['shift_id'] res_shift_ids = self.o_api.search_read("shift.registration", c, f) if res_shift_ids: shift_ids = [int(d['shift_id'][0]) for d in res_shift_ids] c = [["id", "in", shift_ids]] f=['shift_template_id'] stis = self.o_api.search_read("shift.shift", c, f) for sti in stis: if sti['shift_template_id'][0] == shift_template_id: return True except Exception as e: print(str(e)) return False def unsubscribe_member(self, changing_shift = False): """ If changing_shift, don't delete makeups registrations & don't close extension """ res = {} now = datetime.datetime.now().isoformat() # Get and then delete shift template registration c = [['partner_id', '=', self.id]] f = ['id'] res_ids = self.o_api.search_read("shift.template.registration", c, f) ids = [d['id'] for d in res_ids] if ids: res["delete_shift_template_reg"] = self.o_api.execute('shift.template.registration', 'unlink', ids) # Get and then delete shift registrations c = [['partner_id', '=', self.id], ['date_begin', '>', now]] if changing_shift is True: c.append(['is_makeup', '!=', True]) f = ['id'] res_ids = self.o_api.search_read("shift.registration", c, f) ids = [d['id'] for d in res_ids] if ids: res["delete_shifts_reg"] = self.o_api.execute('shift.registration', 'unlink', ids) if changing_shift is False: # Close extensions if just unsubscribing, else keep it res["close_extensions"] = self.close_extension() return res 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 close_extension(self): now = datetime.datetime.now().isoformat() c = [['partner_id', '=', self.id], ['date_start', '<=', now], ['date_stop', '>=', now]] f = ['id'] res_ids = self.o_api.search_read("shift.extension", c, f) ids = [d['id'] for d in res_ids] if ids: f = {'date_stop': now} return self.o_api.update('shift.extension', ids, f) else: return False 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', '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=[]): 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', 'date_delay_stop'] 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 cs = CagetteShift() makeups_to_come_per_partner = cs.get_partners_with_makeups_to_come() # 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(res): if res: cs = CagetteShift() for idx, partner in enumerate(res): shift_data = cs.get_shift_partner(int(partner['id'])) res[idx]['makeups_to_come'] = sum(1 for value in shift_data if value['is_makeup']) @staticmethod def get_attached_members(): api = OdooAPI() cond = [['is_associated_people','=', True]] fields = ['id', 'name', 'parent_name'] res = api.search_read('res.partner', cond, fields) return res 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 from shifts.models import CagetteShift