import requests
from bs4 import BeautifulSoup
from datetime import datetime
import os.path
import csv
import threading
from queue import Queue

# Proxies for BURP - update requests if you want to use this proxy
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
playersFile = 'sample_corrected.txt'
ids = Queue()
# Please be nice to the PDGA site :)
THREADS = 1

class Player:
    def __init__(self, pdga):
        self.store = []
        self.failure = False
        self.pdga = pdga

        r = requests.get(f'https://www.pdga.com/player/{pdga}')
        soup = BeautifulSoup(r.text, 'html.parser')
        pi = soup.find('ul', class_='player-info info-list')

        self.today = datetime.now().strftime("%d/%m/%Y %H:%M:%S")

        # if access denied|page not found go to next player
        self.check_failures(soup)
        if self.failure:
            return

        player = soup.h1.get_text()

        # Fields that will always exist for all members
        self.name = player.split(' #')[0].replace(',', ' ')
        self.status = pi.find('li', class_='membership-status').text.split('Status:  ')[1].split(' ')[0]

        # The remaining fields may not be on the profile so I had to check to see if they exist before parsing
        expiration = pi.find('li', class_='membership-status').text.split('Status:  ')[1]
        if 'until' in expiration:
            self.expiration = expiration.split('until ')[1].replace(')', '')
        else:
            self.expiration = expiration.split('as of ')[1].replace(')', '')

        self.joindate = pi.find('li', class_='join-date')
        if self.joindate:
            self.joindate = self.joindate.text.split('Member Since: ')[1].split(' ')[0]
        else:
            self.joindate = ''

        try:
            location = pi.find('li', class_='location').text.split('Classification:')[0].split('Location: ')[1].split(
                ',')
        except:
            location = ''

        if location:
            # City, State, Country
            if len(location) >= 3:
                self.city = location[0].lstrip()
                self.state = location[1].lstrip()
                self.country = location[2].split('Member Since: ')[0].lstrip()
            # Only State/Prov, Country
            if len(location) == 2:
                self.city = 'N/A'
                self.state = location[0].lstrip()
                self.country = location[1].split('Member Since: ')[0].lstrip()
            # Country Only
            if len(location) == 1:
                self.city = 'N/A'
                self.state = 'N/A'
                self.country = location[0].split('Member Since: ')[0].lstrip()
            self.loclink = pi.find('li', class_='location').find('a')['href']
        else:
            self.city = ''
            self.state = ''
            self.country = ''
            self.loclink = ''

        self.rating = pi.find('li', class_='current-rating')
        if self.rating:
            self.rating = self.rating.text.split('Current Rating: ')[1].split(' ')[0]
        else:
            self.rating = ''

        self.classification = pi.find('li', class_='classification')
        if self.classification:
            self.classification = self.classification.text.split('Classification:  ')[1]
        else:
            self.classification = ''

        self.events = pi.find('li', class_='career-events')
        if self.events:
            self.events = self.events.text.split('Career Events: ')[1].replace(',', '')
        else:
            self.events = ''

        self.earnings = pi.find('li', class_='career-earnings')
        if self.earnings:
            self.earnings = self.earnings.text.split('Career Earnings: ')[1].replace(',', '').strip('$')
        else:
            self.earnings = '0'

        self.wins = pi.find('li', class_='career-wins disclaimer')
        if self.wins:
            self.wins = self.wins.text.split('Career Wins: ')[1]
        else:
            self.wins = '0'

        self.store_vals()
        self.write_data()

    # Set values to store in file
    def store_vals(self):
        self.store = [self.pdga, self.name, self.city, self.state, self.country, self.loclink, self.classification,
                      self.joindate, self.status, self.expiration, self.rating, self.events, self.wins, self.earnings,
                      self.today]
        print(self.store)

    # Append player to file
    def write_data(self):
        with open(playersFile, 'a+', newline='', encoding='utf-8') as csvfile:
            writer = csv.writer(csvfile, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL)
            writer.writerow(self.store)

    # Display detailed data on each a player
    def verbose(self):
        print(f'Scrape Date: {self.today}')
        print(f"ID: {self.pdga}")
        print(f"Name: {self.name}")
        print(f"Status: {self.status}")
        print(f"Expiration: {self.expiration}")
        print(f"City: {self.city}")
        print(f"State: {self.state}")
        print(f"Location Link: {self.loclink}")
        print(f"Country: {self.country}")
        print(f"Rating: {self.rating}")
        print(f"Classification: {self.classification}")
        print(f"Events: {self.events}")
        print(f"Wins: {self.wins}")
        print(f"Earnings: {self.earnings}")

    # Check if player page exists before trying to scrape profile
    def check_failures(self, soup):
        fail = ['Page not found', 'Access denied']
        if any(x in soup.h1.get_text() for x in fail):
            print(f'Not a valid player: {self.pdga}')
            self.name = ''
            self.status = ''
            self.start = ''
            self.expiration = ''
            self.city = ''
            self.state = ''
            self.country = ''
            self.loclink = ''
            self.rating = ''
            self.classification = ''
            self.earnings = ''
            self.events = ''
            self.wins = ''
            self.joindate = ''
            self.store_vals()
            self.failure = True

# Create player file if it doesn't exist. If it does exist return the next user to scrape.
def check_file():
    header = ['id', 'name', 'city', 'state', 'country', 'loclink', 'classification', 'joindate', 'status', 'expiration',
              'rating', 'events', 'wins', 'earnings', 'scrape date']
    if os.path.exists(playersFile):
        print(f'Appending to already created file - {playersFile}')
        return get_recent_scrape()
    else:
        print(f'File created - {playersFile}')
        with open(playersFile, 'w', newline='') as csvfile:
            writer = csv.writer(csvfile, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL)
            writer.writerow(header)
        return 0

# Get total number of PDGA players to set limit of scraping
def find_last_player():
    print('\nFinding number of registered PDGA members...')
    pl = requests.get(
        'https://www.pdga.com/players?FirstName=&LastName=&PDGANum=&Status=All&Gender=All&Class=All&MemberType=All'
        '&City=&StateProv=All&Country=All&Country_1=All&UpdateDate=&order=PDGANum&sort=desc')
    psoup = BeautifulSoup(pl.text, 'html.parser')
    last_player = psoup.find('table', class_='views-table cols-8').find('td', class_='views-field views-field-PDGANum '
                                                                                'active pdga-number').get_text(

    ).rstrip()
    print(f'There are {int(last_player)} registered PDGA members!!!')
    return int(last_player)

# Get the last PDGA member scraped and saved
def get_recent_scrape():
    # Since threading can cause the last saved player to be out of order check the last THREADS number of lines
    # and find the max PDGA number of the last saved
    with open(playersFile, "r", encoding="utf-8", errors="ignore") as scraped:
        # final_line = (scraped.readlines()[-1].split(',')[0])
        print(f'Cecking last {THREADS} lines to find last saved player')
        last_lines = []
        scrape = scraped.readlines()
        for line in range(1, int(THREADS) + 1):
            # print(f"Line: {line} - {scrape[-line].split(',')[0]}")
            last_lines.append(scrape[-line].split(',')[0])

        nextScrape = int(max(last_lines)) + 1
        print(f'Last lines: {last_lines}')
        print(f"\nThe last player scrapted was PDGA #{max(last_lines)}")
        print(f"Continuing scraping on PDGA #{nextScrape}...")
        return nextScrape

# Return [next player to scrape, most recent registered member]
def get_range():
    return range(check_file(), find_last_player())

# Scrape function for threading
def scrape_player():
    global ids
    while True:
        pdga = ids.get()
        Player(pdga)
        ids.task_done()

# Fill queue with remaining players
def fill_queue():
    id_range = get_range()
    for id in id_range:
        ids.put(id)
    print(f'\nAdding PDGA members from {id_range[0]} to {id_range[1]}')
    print(f'Queue of IDs full with {ids.qsize()} members to go!')

if __name__ == '__main__':
    fill_queue()
    print('Starting scraping of members...')

    for i in range(THREADS):
        print(f'Starting thread #{i}')
        t = threading.Thread(target=scrape_player)
        t.start()


from flask import request
from werkzeug.utils import secure_filename

from db import db
from models import Img

def upload_images(pic_list, private, user_id):
    #import pdb; pdb.set_trace()
    for pic in pic_list:
        filename = secure_filename(pic.filename)
        mimetype = pic.mimetype

        if not filename or not mimetype or "image" not in str(mimetype):
            return False

        img = Img(img=pic.read(), user_id=user_id, private=private, name=filename, mimetype=mimetype)
        db.session.add(img)
        db.session.commit()
    return True


from sklearn.feature_extraction.text import TfidfVectorizer
import nltk
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
import spacy,os
import argparse
import re
from tqdm import tqdm
from collections import OrderedDict
import string
import numpy as np
from spacy.lang.en import English
import time
nl = English()
import sys
import pandas as pd

repeat = 5
data = []
doc = []
l3 = []
summary = []
hypothesis = ""
word_count = []
pair_similarity = []
summary_string = []

def count_word(index):
    global doc
    Doc = nl(doc[index])
    tokens = [t.text for t in Doc]
    tokens = [t for t in tokens if len(t.translate(t.maketrans('', '', string.punctuation + string.whitespace))) > 0] # + string.digits
    return len(tokens)

def store_word_count():
    global word_count,doc
    word_count = []
    for i in range(0,len(doc)):
        word_count.append(count_word(i))

def maximum(index, toPrint=0):
    global summary, pair_similarity
    length = len(summary)
    if(length!=0):
        max=0
        for i in range(length):
            a=pair_similarity[index][summary[i]]
            if(a>max):
                max=a
            if toPrint:
              print(str(summary[i])+" -> "+str(a))
        return max
    else:
        return 0

def count_sum(summary):
    sum=0
    length = len(summary)
    for i in range(length):
        sum+=count_word(summary[i])
    return sum

def mmr_sorted(lambda_, doc, length):
    global word_count, pair_similarity, summary
    #print('Inside MMR')
    print(length)
    l3 = []
    vectorizer = TfidfVectorizer(smooth_idf=False)
    X = vectorizer.fit_transform(doc)
    y = X.toarray()
    rows = y.shape[0]
    cols = y.shape[1]
    pair_similarity = []
    for i in range(rows):
        max=-1
        pair_similarity.append([])
        for j in range(rows):
            if(j!=i):
                a = np.sum(np.multiply(y[i],y[j]))
                pair_similarity[-1].append(a)
                if(a>max):
                    max=a
            else:
                pair_similarity[-1].append(1)
        l3.append(max)
    store_word_count()
    l = len(doc)
    count = 0
    last = -1
    summary = []
    summary_word_count = 0
    while(1):
        if (summary_word_count < length):
            max=-1
            for i in range(l):
                a = maximum(i)
                mmrscore = lambda_*l3[i] - (1-lambda_)*a
                if(mmrscore >= max):
                    max = mmrscore
                    ind = i
            summary.append(ind)
            summary_word_count += word_count[ind]
        else:
            #print('Bye')
            break

def listToString():
    global summary_string, word_count, hypothesis, summary, doc
    summary_string = []
    leng = 0
    for i in summary:
      if doc[i] not in summary_string:
          summary_string.append(doc[i])
          leng += word_count[i]
    hypothesis = "".join(summary_string)

parser = argparse.ArgumentParser()
parser.add_argument('--data_path', default = 'data/text/', type = str, help = 'Folder containing textual data')
parser.add_argument('--summary_path', default = 'data/summaries/', type = str, help = 'Folder to store features of the textual data')
parser.add_argument('--length_file', default = 'data/length.txt', type = str, help = 'Path to file containing summary length')

args = parser.parse_args()

print('Generating summary in  ...'+args.summary_path)
num_docs = len(os.listdir(args.data_path))

X1 = pd.read_csv(args.length_file, sep="\t", header=None)

for i in tqdm(range(0,num_docs)):
  length1=X1[1][i]
  #length2=X2[1][i]
  doc = []
  with open(os.path.join(args.data_path,X1[0][i]), 'r') as file:
    for x in file:
      if x != '\n':
        doc.append(x)
  lamda=0.6
  #for j in lamda:
  mmr_sorted(lamda,doc,length1)
  listToString()
  f= open(os.path.join(args.summary_path,X1[0][i]),"w+")
  n = f.write(hypothesis)
  f.close()
  hypothesis=""


from typing import Any, Optional, TYPE_CHECKING

import logging

from ..common.utils import deepmerge
from .execution_method import ExecutionMethod
from .aws_settings import INFRASTRUCTURE_TYPE_AWS, AwsSettings

if TYPE_CHECKING:
    from ..models import (
      Task,
      TaskExecution
    )

logger = logging.getLogger(__name__)

class AwsBaseExecutionMethod(ExecutionMethod):
    def __init__(self, name: str,
            task: Optional['Task'] = None,
            task_execution: Optional['TaskExecution'] = None,
            aws_settings: Optional[dict[str, Any]] = None) -> None:
        super().__init__(name, task=task,
                task_execution=task_execution)

        if aws_settings is None:
            self.aws_settings = self.merge_aws_settings(task=task,
                task_execution=task_execution)
        else:
            self.aws_settings = AwsSettings.parse_obj(aws_settings)

    @staticmethod
    def merge_aws_settings(task: Optional['Task'],
            task_execution: Optional['TaskExecution']) -> AwsSettings:
        settings_to_merge: list[dict[str, Any]] = [ {} ]

        if task:
            if task.run_environment.aws_settings:
                settings_to_merge.append(task.run_environment.aws_settings)

            if task.infrastructure_settings and \
                    (task.infrastructure_type == INFRASTRUCTURE_TYPE_AWS):
                settings_to_merge.append(task.infrastructure_settings)

        if task_execution and task_execution.infrastructure_settings and \
                (task_execution.infrastructure_type == INFRASTRUCTURE_TYPE_AWS):
            settings_to_merge.append(task_execution.infrastructure_settings)

        return AwsSettings.parse_obj(deepmerge(*settings_to_merge))

    def compute_region(self) -> Optional[str]:
        region = self.aws_settings.region

        if (not region) and self.task:
            infra = self.task.infrastructure_settings
            if infra and (self.task.infrastructure_type == INFRASTRUCTURE_TYPE_AWS):
                region = infra.get('region')

                if (not region) and infra.get('network'):
                    region = infra['network'].get('region')

            if not region:
                run_environment = self.task.run_environment
                re_aws_settings = run_environment.aws_settings
                if re_aws_settings:
                    region = re_aws_settings.get('region')

                    if (not region) and re_aws_settings.get('network'):
                        region = re_aws_settings['network'].get('region')

        return region

    def enrich_task_settings(self) -> None:
        if not self.task:
            raise RuntimeError("enrich_task_settings(): No Task found")

        aws_settings_dict = self.task.infrastructure_settings

        if aws_settings_dict:
            aws_settings = AwsSettings.parse_obj(aws_settings_dict)

            aws_settings.update_derived_attrs(execution_method=self)

            self.task.infrastructure_settings = deepmerge(
                    aws_settings_dict, aws_settings.dict())

        # TODO: scheduling URLs

    def enrich_task_execution_settings(self) -> None:
        if not self.task_execution:
            raise RuntimeError("enrich_task_execution_settings(): No Task Execution found")

        aws_settings_dict = self.task_execution.infrastructure_settings

        if aws_settings_dict:
            aws_settings = AwsSettings.parse_obj(aws_settings_dict)

            aws_settings.update_derived_attrs(execution_method=self)

            self.task_execution.infrastructure_settings = deepmerge(
                    aws_settings_dict, aws_settings.dict())


import sys
sys.path.append('../VQ-VAE')
from auto_encoder2 import VQ_CVAE
import argparse
from torch import optim
from torchvision import transforms
import fpa_dataset

parser = argparse.ArgumentParser(description='Train an autoencoder for hand depth image reconstruction')
parser.add_argument('-r', dest='dataset_root_folder', required=True, help='Root folder for dataset')
parser.add_argument('--split-filename', default='', help='Dataset split filename')
parser.add_argument('-e', dest='num_epochs', type=int, required=True,
                    help='Total number of epochs to train')
parser.add_argument('--use-cuda', dest='use_cuda', action='store_true', default=False,
                    help='Whether to use cuda for training')
parser.add_argument('-l', dest='epoch_log', type=int, default=10,
                    help='Total number of epochs to train')
parser.add_argument('--batch-size', type=int, default=1, help='Batch size')

args = parser.parse_args()
args.use_cuda = True

transform_depth = transforms.Compose([transforms.ToTensor()])

lr = 2e-4
d = 128
k = 256
num_channels_in = 1
num_channels_out = 1

model = VQ_CVAE(d=d, k=k, num_channels_in=num_channels_in, num_channels_out=num_channels_out)
if args.use_cuda:
    model = model.cuda()

optimizer = optim.Adam(model.parameters(), lr=lr)
scheduler = optim.lr_scheduler.StepLR(optimizer, 10, 0.5)

train_loader = fpa_dataset.DataLoaderTracking(root_folder=args.dataset_root_folder,
                                      type='train', transform_color=None,
                                      transform_depth=transform_depth,
                                      batch_size=args.batch_size,
                                      split_filename=args.split_filename,)

for epoch_idx in range(args.num_epochs - 1):
    epoch = epoch_idx + 1
    continue_batch_end_ix = -1
    for batch_idx, (data, _) in enumerate(train_loader):
        if batch_idx < continue_batch_end_ix:
            print('Continuing... {}/{}'.format(batch_idx, continue_batch_end_ix))
            continue
        optimizer.zero_grad()
        if args.use_cuda:
            data = data.cuda()
        outputs = model(data)
        loss = model.loss_function(data, *outputs)
        loss.backward()
        optimizer.step()
        a = 0


# -*- coding: utf-8 -*-

import json
import logging
from datetime import datetime, date, timedelta
from odoo import api, fields, models
from odoo.addons.muk_dms.models import dms_base

logger = logging.getLogger('FOLLOW-UP')

AVAILABLE_PRIORITIES = [
    ('0', u'Normale'),
    ('1', u'Basse'),
    ('2', u'Haute'),
    ('3', u'Très haute'),
]

class OFFollowupProject(models.Model):
    _name = 'of.followup.project'
    _inherit = 'mail.thread'
    _description = "Suivi des projets"
    _order = "priority desc, reference_laying_date, id desc"

    @api.model
    def _init_group_of_followup_project_not_migrated(self):
        # On utilise une fonction déclenchée par le XML plutôt qu'un auto_init, car au moment de l'auto_init le groupe
        # n'existe pas encore.
        # Si la migration n'a pas été faite, ajouter le groupe group_of_followup_project_not_migrated à
        # tous les utilisateurs
        if not self.env['ir.config_parameter'].sudo().get_param('of.followup.migration', False):
            group_not_migrated = self.env.ref('of_followup.group_of_followup_project_not_migrated')
            group_user = self.env.ref('base.group_user')
            if group_not_migrated not in group_user.implied_ids:
                group_user.write({'implied_ids': [(4, group_not_migrated.id)]})

    def _get_order_values(
            self, project, mapping_project_tags, default_of_kanban_step_id, done_of_kanban_step_id,
            in_progress_of_kanban_step_id):
        """Helper that return a dict of values to write on a Sale depending of the Project followup"""
        otag_ids = []
        if project.tag_ids:
            otag_ids = [mapping_project_tags[pt.id] for pt in project.tag_ids]

        kanban_step_id = in_progress_of_kanban_step_id
        if project.state in ('done', 'cancel'):
            kanban_step_id = done_of_kanban_step_id
        elif not project.reference_laying_date:
            kanban_step_id = default_of_kanban_step_id
        if not kanban_step_id:
            kanban_step_id = default_of_kanban_step_id

        return {
            'of_priority': project.priority,
            'of_notes': project.notes,
            'of_info': project.info,
            'of_manual_laying_date': project.manual_laying_date,
            'of_laying_week': project.laying_week,
            'of_reference_laying_date': project.reference_laying_date,
            'of_force_laying_date': project.force_laying_date,
            'of_sale_followup_tag_ids': otag_ids,
            'of_kanban_step_id': kanban_step_id
        }

    def _update_tables_from_sale_values(self, order_values_to_upd):
        def _get_sql_set_string(values):
            order_obj = self.env['sale.order']
            sql_set_str = ""
            len_values = len(values)
            for i, (k, v) in enumerate(values.items(), 1):
                separator = ', ' if i < len_values else ' '
                if not v and order_obj._fields[k].type in ['char', 'text', 'selection', 'date']:
                    sql_set_str += "%s = %s%s" % (k, 'NULL', separator)
                elif v and (order_obj._fields[k].type in ['char', 'text'] or isinstance(v, (str, unicode))):
                    sql_set_str += "%s = '%s'%s" % (k, v.replace("'", "''"), separator)
                else:
                    sql_set_str += "%s = %s%s" % (k, v, separator)
            return sql_set_str

        for order_id, order_values in order_values_to_upd.items():
            partner_id = order_values.pop('partner_id')
            followup_tags_ids = order_values.pop('of_sale_followup_tag_ids')
            activities = order_values.pop('of_crm_activity_ids') if 'of_crm_activity_ids' in order_values else []
            order_upd_sql = "UPDATE sale_order " \
                "SET %s" \
                "WHERE id = %s" % (_get_sql_set_string(order_values), order_id)
            # update the columns of the sale_order
            self._cr.execute(order_upd_sql)
            # insert the tags in the relation table of the M2M
            for tag_id in followup_tags_ids:
                self._cr.execute(
                    'INSERT INTO sale_order_followup_tag_rel (order_id, tag_id) VALUES (%s, %s)',
                    (order_id, tag_id)
                )
            # insert the activities linked to the Sale in their table
            for activity in activities:
                self._cr.execute(
                    'INSERT INTO "of_crm_activity" ("create_uid", "uploaded_attachment_id", "type_id", '
                    '"user_id", "vendor_id", "description", "deadline_date", "sequence", "order_id", '
                    '"title", "write_uid", "state", "write_date", "report", "create_date", "load_attachment", '
                    '"origin", "active", "partner_id", "trigger_type") VALUES '
                    '(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, now(), %s, now(), %s, %s, %s, %s, %s);',
                    (
                        activity['create_uid'], None, activity['activity_id'], activity['create_uid'],
                        activity['vendor_id'], activity['summary'], activity['date_deadline'] or None,
                        activity['sequence'], order_id, activity['summary'] or None, activity['write_uid'],
                        activity['state'], None, False, 'sale_order', True, partner_id, 'at_creation'
                    )
                )

    def _followup_data_migration(self):
        """
        Migration of the Project followup data into the new fields of the linked Order.
        Migration of Project followup task and other stuff in the CRM activities, CRM tags, etc.
        """
        cr = self._cr
        # Follow-up data migration
        ir_config_obj = self.env['ir.config_parameter']
        logger.info('_followup_data_migration START')
        if not ir_config_obj.get_param('of.followup.migration', False):
            self = self.with_context(lang='fr_FR')
            crm_activity_obj = self.env['crm.activity']
            followup_task_obj = self.env['of.followup.task']
            followup_task_type_obj = self.env['of.followup.task.type']
            followup_project_obj = self.env['of.followup.project']
            sale_followup_tag_obj = self.env['of.sale.followup.tag']
            followup_project_tag_obj = self.env['of.followup.project.tag']
            sale_order_kanban_obj = self.env['of.sale.order.kanban']

            # of.followup.project.tag -> of.sale.followup.tag
            logger.info('    of.followup.project.tag -> of.sale.followup.tag')
            project_tags = followup_project_tag_obj.search([])
            mapping_project_tags = {ptag.id: sale_followup_tag_obj.create(
                {'name': ptag.name, 'color': ptag.color}).id for ptag in project_tags}

            # of.followup.task.type -> crm.activity (loaded via data XML) so we build a data mapping dict
            logger.info('    of.followup.task.type -> crm.activity')
            task_type_planif_id = self.env.ref('of_followup.of_followup_task_type_planif').id
            task_type_vt_id = self.env.ref('of_followup.of_followup_task_type_vt').id
            followup_type_values = followup_task_type_obj.with_context(active_test=False).search_read(
                ['|', ('predefined_task', '=', False), ('id', 'in', [task_type_planif_id, task_type_vt_id])],
                ['name', 'short_name'])
            mapping_task_type = {}
            for data in followup_type_values:
                act_id = crm_activity_obj.create(
                    {'name': data['name'], 'of_short_name': data['short_name'], 'of_object': 'sale_order'}).id
                k = '%s,%s' % (data['id'], data['short_name'])
                mapping_task_type[k] = act_id

            # of.followup.task -> of.crm.activity
            logger.info('    of.followup.task -> of.crm.activity')
            # get the tasks ids
            tasks_query = "SELECT DISTINCT(OFT.id) " \
                "FROM of_followup_task OFT " \
                "INNER JOIN of_followup_project OFP ON OFP.id = OFT.project_id " \
                "INNER JOIN of_followup_task_type OFTT ON OFTT.id = OFT.type_id " \
                "WHERE OFTT.predefined_task IS NOT TRUE OR OFT.type_id IN %s;"
            cr.execute(tasks_query, (tuple([task_type_planif_id, task_type_vt_id]),))
            tasks_ids = cr.fetchall()
            tasks_ids = map(lambda t: t[0], tasks_ids)
            # create sales activities from followup tasks and prepare orders data from the project linked to the task
            project_data = {}
            for task in followup_task_obj.browse(tasks_ids):
                project = task.project_id
                type_id = task.type_id.id
                partner_id = project.order_id.partner_id.id or False
                short_name = task.type_id.short_name
                k = '%s,%s' % (type_id, short_name)
                activity_state = 'planned'  # default value
                if task.state_id.final_state:
                    activity_state = 'done'
                if project:
                    if not project_data.get(project):
                        project_data[project] = {'partner_id': partner_id, 'of_crm_activity_ids': []}

                    # try to preload the deadline date dependings on the current follow-up's stage
                    date_deadline = False
                    if project.reference_laying_date:
                        stage_code_tr = {
                            's': 0,
                            's+': 1,
                            's-2': -2,
                            's-4': -4,
                            's-6': -6,
                            's-8': -8
                        }
                        reference_laying_date = datetime.strptime(project.reference_laying_date, '%Y-%m-%d')
                        task_stage = task.state_id.stage_id
                        if task_stage and stage_code_tr.get(task_stage.code):
                            days_nbr = stage_code_tr.get(task_stage.code) * 7
                            date_deadline = reference_laying_date + timedelta(days=days_nbr)
                            # take the start day of the week for this date
                            start_date = date_deadline - timedelta(days=date_deadline.weekday())
                            date_deadline = start_date.strftime('%Y-%m-%d')

                    if mapping_task_type.get(k):
                        project_data[project]['of_crm_activity_ids'].append({
                            'sequence': task.sequence,
                            'summary': task.name,
                            'date_deadline': date_deadline,
                            'activity_id': mapping_task_type.get(k),
                            'state': activity_state,
                            'user_id': task.project_id.user_id.id or None,
                            'vendor_id': task.project_id.vendor_id.id or None,
                            'create_uid': task.create_uid.id or None,
                            'write_uid': task.write_uid.id or None
                        })

            order_values_to_upd = {}
            # of.followup.project -> sale.order
            logger.info('    of.followup.project -> sale.order')
            done_project_ids = []
            default_of_kanban_step_id = self.env.ref('of_sale_kanban.of_sale_order_kanban_new').id
            done_of_kanban_step_id = sale_order_kanban_obj.search([('name', '=', u"Terminé")]).id
            in_progress_of_kanban_step_id = sale_order_kanban_obj.search([('name', '=', u"En cours")]).id

            for project, data in project_data.items():
                values = self._get_order_values(
                    project, mapping_project_tags, default_of_kanban_step_id, done_of_kanban_step_id,
                    in_progress_of_kanban_step_id)
                values.update(data)
                order_values_to_upd[project.order_id.id] = values
                done_project_ids.append(project.id)

            # other projets that aren't yet migrated from the followup tasks
            if done_project_ids:
                projects_query = "SELECT DISTINCT(OFP.id) " \
                    "FROM of_followup_project OFP " \
                    "WHERE OFP.id NOT IN %s;"
                cr.execute(projects_query, (tuple(done_project_ids),))
            else:
                projects_query = "SELECT DISTINCT(OFP.id) FROM of_followup_project OFP;"
                cr.execute(projects_query)
            project_ids = cr.fetchall()
            project_ids = map(lambda p: p[0], project_ids)
            followup_projects = followup_project_obj.browse(project_ids)
            for project in followup_projects:
                values = self._get_order_values(
                    project, mapping_project_tags, default_of_kanban_step_id, done_of_kanban_step_id,
                    in_progress_of_kanban_step_id)
                values['partner_id'] = project.order_id.partner_id.id
                order_values_to_upd[project.order_id.id] = values
            self._update_tables_from_sale_values(order_values_to_upd)

            # recompute follow-up fields
            logger.info('    recompute follow-up fields')
            order_ids = order_values_to_upd.keys()
            orders = self.env['sale.order'].browse(order_ids)
            orders._compute_of_activities_state()
            # set the migration as done
            ir_config_obj.set_param('of.followup.migration', 'True')
            # Deactivate view to hide migration button
            self.env.ref('of_followup.view_sales_config_settings_followup').active = False
            # Retirer le groupe group_of_followup_project_not_migrated pour masquer les différents éléments du suivi
            group_not_migrated = self.env.ref('of_followup.group_of_followup_project_not_migrated')
            group_user = self.env.ref('base.group_user')
            if group_not_migrated in group_user.implied_ids:
                group_user.write({'implied_ids': [(3, group_not_migrated.id)]})
        logger.info('_followup_data_migration END')

    stage_id = fields.Many2one(
        compute='_compute_stage_id', comodel_name='of.followup.project.stage', string=u"Etape de suivi", store=True,
        group_expand='_read_group_stage_ids')
    state = fields.Selection(
        [('in_progress', u'En cours'),
         ('late', u'En retard'),
         ('ready', u'Prêt'),
         ('done', u'Terminé'),
         ('cancel', u'Annulé')],
        string=u"Etat du dossier", compute='_compute_state', store=True)
    is_done = fields.Boolean(string=u"Est terminé")
    is_canceled = fields.Boolean(string=u"Est annulé")
    order_id = fields.Many2one(
        comodel_name='sale.order', string=u"Commande", required=True, copy=False, ondelete='cascade')
    partner_id = fields.Many2one(related='order_id.partner_id', string=u"Client", readonly=True)
    intervention_address_id = fields.Many2one(
        related='order_id.partner_shipping_id', string=u"Adresse d'intervention", readonly=True)
    invoice_status = fields.Selection(related='order_id.invoice_status', string=u"État de facturation", readonly=True)
    user_id = fields.Many2one(comodel_name='res.users', string=u"Responsable", default=lambda self: self.env.user)
    vendor_id = fields.Many2one(related='order_id.user_id', string=u"Vendeur", readonly=True)
    reference_laying_date = fields.Date(
        compute='_compute_reference_laying_date', string=u"Date de pose de référence", store=True, compute_sudo=True)
    force_laying_date = fields.Boolean(string=u"Forcer la date de pose")
    manual_laying_date = fields.Date(string=u"Date de pose (manuelle)")
    laying_week = fields.Char(
        compute='_compute_reference_laying_date', string=u"Semaine de pose", store=True, compute_sudo=True)
    task_ids = fields.One2many(comodel_name='of.followup.task', inverse_name='project_id', string=u"Tâches")
    predefined_task_ids = fields.One2many(
        comodel_name='of.followup.task', inverse_name='project_id', string=u"Tâches pré-définies",
        domain=[('predefined_task', '=', True)])
    other_task_ids = fields.One2many(
        comodel_name='of.followup.task', inverse_name='project_id', string=u"Autres tâches",
        domain=[('predefined_task', '=', False)])
    template_id = fields.Many2one(comodel_name='of.followup.project.template', string=u"Modèle")
    color = fields.Char(string=u"Couleur", compute="_compute_color")
    priority = fields.Selection(
        AVAILABLE_PRIORITIES, string=u'Priorité', index=True, default=AVAILABLE_PRIORITIES[0][0])
    main_product_brand_id = fields.Many2one(
        comodel_name='of.product.brand', compute='_compute_main_product_brand_id',
        string=u"Marque de l'article principal", store=True)
    info = fields.Text(string=u"Infos")
    notes = fields.Text(string=u"Notes")
    tag_ids = fields.Many2many(comodel_name='of.followup.project.tag', string=u"Étiquettes")
    alert_ids = fields.Many2many(
        comodel_name='of.followup.project.alert', string=u"Alertes", compute='_compute_alert_ids')

    invoice_count = fields.Integer(string=u"Nombre de factures", related='order_id.invoice_count', readonly=True)
    purchase_count = fields.Integer(string=u"Nombre d'achats", related='order_id.purchase_count', readonly=True)
    intervention_count = fields.Integer(
        string=u"Nombre d'interventions", related='order_id.intervention_count', readonly=True)
    to_schedule_count = fields.Integer(
        string=u"Nombre de DI à programmer", related='order_id.of_service_count', readonly=True)
    delivery_count = fields.Integer(string=u"Nombre de livraisons", related='order_id.delivery_count', readonly=True)
    picking_count = fields.Integer(string=u"Nombre de réceptions", compute='_compute_picking_count')

    # Champs pour affichage vignette kanban
    late_tasks_number = fields.Char(string=u"Nombre de tâches en retard", compute='_compute_late_tasks_number')
    late_tasks = fields.Text(string=u"Tâches en retard", compute='_compute_late_tasks')
    info_display = fields.Text(string=u"Infos pour affichage", compute='_compute_info_display')
    date_alert_display = fields.Text(string=u"Infos pour alerte de dates", compute='_compute_alert_display')
    picking_alert_display = fields.Text(
        string=u"Infos pour alerte de livraison/réception", compute='_compute_alert_display')
    amount_untaxed = fields.Monetary(string=u"Montant HT", related='order_id.amount_untaxed', readonly=True)
    currency_id = fields.Many2one('res.currency', string=u"Devise", related='order_id.currency_id', readonly=True)

    _sql_constraints = [('order_uniq', 'unique (order_id)', u"Un suivi a déjà été créé pour cette commande !")]

    @api.multi
    def name_get(self):
        res = []
        for followup in self:
            name = "Suivi commande %s" % followup.order_id.name
            res.append((followup.id, name))
        return res

    @api.model
    def _read_group_stage_ids(self, stages, domain, order):
        stage_ids = self.env['of.followup.project.stage'].search([])
        return stage_ids

    @api.multi
    @api.depends('reference_laying_date', 'state')
    def _compute_stage_id(self):
        for rec in self:
            if rec.state in ('done', 'cancel'):
                rec.stage_id = self.env['of.followup.project.stage'].search([('code', '=', 'done')], limit=1)
            else:
                laying_date = rec.reference_laying_date
                if laying_date:
                    laying_date = datetime.strptime(laying_date, "%Y-%m-%d").date()
                    today = date.today()
                    if laying_date < today:
                        rec.stage_id = self.env['of.followup.project.stage'].search([('code', '=', 's+')], limit=1)
                    else:
                        monday1 = (laying_date - timedelta(days=laying_date.weekday()))
                        monday2 = (today - timedelta(days=today.weekday()))
                        week_diff = (monday1 - monday2).days / 7
                        rec.stage_id = self.env['of.followup.project.stage'].search(
                            [('week_diff_min', '<=', week_diff), ('week_diff_max', '>=', week_diff)], limit=1)
                else:
                    rec.stage_id = self.env['of.followup.project.stage'].search([('code', '=', 'new')], limit=1)

    @api.multi
    @api.depends('order_id', 'order_id.state', 'is_done', 'is_canceled', 'task_ids', 'task_ids.is_not_processed',
                 'task_ids.is_done', 'task_ids.is_late')
    def _compute_state(self):
        for rec in self:
            # Si commande annulée, le suivi est à l'état annulé également
            if rec.order_id.state == 'cancel':
                rec.state = 'cancel'
            else:
                # Par défaut le projet est en cours
                state = 'in_progress'
                if rec.is_done:
                    # Le projet a été marqué comme terminé
                    state = 'done'
                elif rec.is_canceled:
                    # Le projet a été marqué comme annulé
                    state = 'cancel'
                else:
                    # Correction d'un bug sur l'ordre de calcul des champs compute : Pour savoir si des tâches
                    # sont réellement en retard, il faut recalculer l'étape du suivi au préalable
                    rec.state = state
                    rec._compute_stage_id()
                    # Toutes les tâches sont terminées (excepté les non traitées)
                    if rec.task_ids and not rec.task_ids.filtered(lambda t: not t.is_not_processed and not t.is_done):
                        state = 'ready'
                    # Au moins une tâche est en retard
                    elif rec.task_ids.filtered(lambda t: t.is_late):
                        state = 'late'
                rec.state = state

    @api.multi
    @api.depends('force_laying_date', 'manual_laying_date', 'order_id', 'order_id.intervention_ids',
                 'order_id.intervention_ids.date', 'order_id.intervention_ids.tache_id',
                 'order_id.intervention_ids.tache_id.tache_categ_id')
    def _compute_reference_laying_date(self):
        for rec in self:
            laying_date = False
            if rec.force_laying_date:
                laying_date = rec.manual_laying_date
            elif rec.order_id and rec.order_id.intervention_ids:
                planif_task_type = self.env.ref('of_followup.of_followup_task_type_planif')
                if planif_task_type:
                    planif_planning_tache_categs = planif_task_type.planning_tache_categ_ids
                    interventions = rec.order_id.intervention_ids.filtered(
                        lambda i: i.tache_id.tache_categ_id.id in planif_planning_tache_categs.ids and
                        i.date > fields.Datetime.now())
                    if interventions:
                        laying_date = interventions[0].date_date
                    else:
                        interventions = rec.order_id.intervention_ids.filtered(
                            lambda i: i.tache_id.tache_categ_id.id in planif_planning_tache_categs.ids)
                        if interventions:
                            laying_date = interventions[-1].date_date
            rec.reference_laying_date = laying_date
            if laying_date:
                laying_week = datetime.strptime(laying_date, "%Y-%m-%d").date().isocalendar()[1]
                rec.laying_week = "%02d" % laying_week
            else:
                rec.laying_week = "Non programmée"

    @api.multi
    def _compute_color(self):
        for rec in self:
            state = rec.state
            if state == 'in_progress':
                color = '#ffffff'
            elif state == 'late':
                color = '#ffa8a8'
            elif state == 'ready':
                color = '#bcffa8'
            elif state == 'done':
                color = '#d7d7d7'
            elif state == 'cancel':
                color = '#eeeeee'
            else:
                color = '#ffffff'
            rec.color = color

    @api.multi
    @api.depends('order_id', 'order_id.order_line', 'order_id.order_line.of_article_principal',
                 'order_id.order_line.product_id', 'order_id.order_line.product_id.brand_id')
    def _compute_main_product_brand_id(self):
        for rec in self:
            main_product_lines = rec.order_id.order_line.filtered(lambda l: l.of_article_principal)
            if main_product_lines:
                rec.main_product_brand_id = main_product_lines[0].product_id.brand_id

    @api.multi
    def _compute_alert_ids(self):
        for rec in self:
            if rec.order_id:
                alerts = self.env['of.followup.project.alert']
                # Vérification des dates
                planif_task_type = self.env.ref('of_followup.of_followup_task_type_planif')
                if planif_task_type and rec.force_laying_date:
                    planif_planning_tache_categs = planif_task_type.planning_tache_categ_ids
                    interventions = rec.order_id.intervention_ids.filtered(
                        lambda i: i.tache_id.tache_categ_id.id in planif_planning_tache_categs.ids and
                        i.date > fields.Datetime.now() and i.state != 'cancel')
                    if interventions:
                        intervention = interventions[0]
                        if intervention.date_date != rec.manual_laying_date:
                            alerts |= self.env.ref('of_followup.of_followup_project_alert_date')
                    else:
                        interventions = rec.order_id.intervention_ids.filtered(
                            lambda i:
                            i.tache_id.tache_categ_id.id in planif_planning_tache_categs.ids and i.state != 'cancel')
                        if interventions:
                            intervention = interventions[-1]
                            if intervention.date_date != rec.manual_laying_date:
                                alerts |= self.env.ref('of_followup.of_followup_project_alert_date')
                # Vérification BL
                if rec.order_id.picking_ids:
                    late_delivery_pickings = rec.order_id.picking_ids.filtered(
                        lambda p: p.state not in ['done', 'cancel'] and p.min_date < fields.Datetime.now())
                    if late_delivery_pickings:
                        alerts |= self.env.ref('of_followup.of_followup_project_alert_bl')
                # Vérification BR
                if rec.order_id.purchase_ids.mapped('picking_ids'):
                    late_receipt_pickings = rec.order_id.purchase_ids.mapped('picking_ids').filtered(
                        lambda p: p.state not in ['done', 'cancel'] and p.min_date < fields.Datetime.now())
                    if late_receipt_pickings:
                        alerts |= self.env.ref('of_followup.of_followup_project_alert_br')
                rec.alert_ids = alerts

    @api.multi
    def _compute_picking_count(self):
        for rec in self:
            rec.picking_count = sum(rec.order_id.purchase_ids.mapped('picking_count'))

    @api.multi
    def _compute_late_tasks_number(self):
        for rec in self:
            rec.late_tasks_number = "(%s/%s)" % (len(rec.task_ids.filtered(lambda t: t.is_late)), len(rec.task_ids))

    @api.multi
    def _compute_late_tasks(self):
        for rec in self:
            late_tasks = []
            for late_task in rec.task_ids.filtered(lambda t: t.is_late):
                if len(late_tasks) < 3:
                    late_tasks.append(late_task.type_id.short_name)
                else:
                    late_tasks.append("...")
                    break
            rec.late_tasks = json.dumps(late_tasks) if late_tasks else False

    @api.multi
    def _compute_info_display(self):
        for rec in self:
            info = []
            if rec.info:
                for line in rec.info.split('\n'):
                    info.append(line)
            rec.info_display = json.dumps(info) if info else False

    @api.multi
    def _compute_alert_display(self):
        alert_date = self.env.ref('of_followup.of_followup_project_alert_date')
        for rec in self:
            date_alert = []
            picking_alert = []
            if rec.alert_ids:
                for alert in rec.alert_ids:
                    if alert == alert_date:
                        date_alert.append(alert.name)
                    else:
                        picking_alert.append(alert.name)
            rec.date_alert_display = json.dumps(date_alert) if date_alert else False
            rec.picking_alert_display = json.dumps(picking_alert) if picking_alert else False

    @api.model
    def get_color(self):
        state = self.state
        if state == 'in_progress':
            return 1
        elif state == 'late':
            return 2
        elif state == 'ready':
            return 3
        elif state == 'done':
            return 4
        else:
            return 0

    @api.multi
    def set_to_done(self):
        self.ensure_one()
        self.is_done = True

    @api.multi
    def set_to_canceled(self):
        self.ensure_one()
        self.is_canceled = True

    @api.multi
    def set_to_in_progress(self):
        self.ensure_one()
        self.is_done = False

    @api.multi
    def action_send_email(self):
        self.ensure_one()
        ir_model_data = self.env['ir.model.data']
        try:
            template_id = ir_model_data.get_object_reference('of_followup', 'of_followup_project_email_template')[1]
        except ValueError:
            template_id = False
        try:
            compose_form_id = ir_model_data.get_object_reference('mail', 'email_compose_message_wizard_form')[1]
        except ValueError:
            compose_form_id = False
        ctx = dict()
        ctx.update({
            'default_model': 'of.followup.project',
            'default_res_id': self.ids[0],
            'default_use_template': bool(template_id),
            'default_template_id': template_id,
            'default_partner_ids': self.partner_id.ids,
            'default_composition_mode': 'comment',
        })
        return {
            'type': 'ir.actions.act_window',
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'mail.compose.message',
            'views': [(compose_form_id, 'form')],
            'view_id': compose_form_id,
            'target': 'new',
            'context': ctx,
        }

    @api.onchange('template_id')
    def onchange_template_id(self):
        if not self.template_id:
            return
        predefined_new_tasks = []
        other_new_tasks = []
        for task in self.template_id.task_ids:
            vals = {'sequence': task.sequence, 'type_id': task.type_id.id, 'name': task.name}
            state = self.env['of.followup.task.type.state'].search(
                [('task_type_id', '=', task.type_id.id), ('starting_state', '=', True)], limit=1)
            if state:
                if task.predefined_task:
                    vals.update({'predefined_state_id': state.id})
                    predefined_new_tasks += [(0, 0, vals)]
                else:
                    vals.update({'state_id': state.id})
                    other_new_tasks += [(0, 0, vals)]
        return {'value': {'predefined_task_ids': predefined_new_tasks, 'other_task_ids': other_new_tasks}}

    @api.onchange('force_laying_date')
    def onchange_force_laying_date(self):
        self.manual_laying_date = False

    @api.multi
    def action_view_invoice(self):
        self.ensure_one()
        return self.order_id.action_view_invoice()

    @api.multi
    def action_view_purchase(self):
        self.ensure_one()
        return self.order_id.action_view_achats()

    @api.multi
    def action_view_interventions(self):
        self.ensure_one()
        return self.order_id.action_view_intervention()

    @api.multi
    def action_view_to_schedule(self):
        self.ensure_one()
        return self.order_id.action_view_a_programmer()

    @api.multi
    def action_view_delivery(self):
        self.ensure_one()
        return self.order_id.action_view_delivery()

    @api.multi
    def action_view_picking(self):
        self.ensure_one()
        action = self.env.ref('stock.action_picking_tree')
        result = action.read()[0]
        result.pop('id', None)
        result['context'] = {}
        pick_ids = self.order_id.purchase_ids.mapped('picking_ids').ids or []
        if len(pick_ids) > 1:
            result['domain'] = "[('id', 'in', [" + ','.join(map(str, pick_ids)) + "])]"
        elif len(pick_ids) == 1:
            res = self.env.ref('stock.view_picking_form', False)
            result['views'] = [(res and res.id or False, 'form')]
            result['res_id'] = pick_ids and pick_ids[0] or False
        return result

    @api.model
    def create(self, vals):
        res = super(OFFollowupProject, self).create(vals)
        res.order_id.write({'of_followup_project_id': res.id})
        return res

    @api.model
    def cron_move_project(self):
        for project in self.search([]):
            project._compute_stage_id()

    @api.model
    def cron_recompute_reference_laying_date(self):
        for project in self.search([]):
            project._compute_reference_laying_date()

    @api.multi
    def last_step_for_all(self):
        for followup in self:
            for task in followup.other_task_ids:
                states = self.env['of.followup.task.type.state'].search(
                    [('task_type_id', '=', task.type_id.id), ('sequence', '>', task.state_id.sequence)])
                if states:
                    task.state_id = states[-1].id

class OFFollowupProjectStage(models.Model):
    _name = 'of.followup.project.stage'
    _description = "Etat de suivi des projets"
    _order = 'sequence'

    name = fields.Char(string=u"Nom")
    code = fields.Char(string=u"Code")
    sequence = fields.Integer(string=u"Séquence")
    fold = fields.Boolean(string=u"Replié par défaut")

    week_diff_min = fields.Integer(string=u"Différence de semaine minimale")
    week_diff_max = fields.Integer(string=u"Différence de semaine maximale")

class OFFollowupTask(models.Model):
    _name = 'of.followup.task'
    _description = u"Tâche liée au suivi des projets"
    _order = 'sequence'

    project_id = fields.Many2one(
        comodel_name="of.followup.project", string=u"Projet", required=True, ondelete='cascade')

    sequence = fields.Integer(string=u"Séquence")
    type_id = fields.Many2one(comodel_name='of.followup.task.type', string=u"Type", required=True)
    name = fields.Char(string=u"Nom", required=True)
    state_id = fields.Many2one(comodel_name='of.followup.task.type.state', string=u"Etat")
    predefined_state_id = fields.Many2one(
        comodel_name='of.followup.task.type.state', string=u"Etat", compute='_compute_predefined_state_id')
    global_state = fields.Char(string=u"État", compute='_compute_global_state')
    predefined_task = fields.Boolean(string=u"Tâche pré-définie", related='type_id.predefined_task')
    force_state = fields.Boolean(string=u"Gestion manuelle de l'état")
    is_late = fields.Boolean(string=u"Tâche en retard", compute='_compute_is_late')
    is_done = fields.Boolean(string=u"Tâche terminée", compute='_compute_is_done')
    is_not_processed = fields.Boolean(string=u"Tâche non traitée", compute='_compute_is_not_processed')
    planif_intervention_ids = fields.One2many(
        comodel_name='of.planning.intervention', string=u"RDVs d'intervention planifiés",
        compute='_compute_planif_intervention_ids')
    display_planif_interventions = fields.Boolean(
        string=u"Afficher les RDVs d'intervention planifiés ?", compute='_compute_planif_intervention_ids')
    vt_intervention_ids = fields.One2many(
        comodel_name='of.planning.intervention', string=u"RDVs visite technique",
        compute='_compute_vt_intervention_ids')
    display_vt_interventions = fields.Boolean(
        string=u"Afficher les RDVs visite technique ?", compute='_compute_vt_intervention_ids')
    app_order_line_ids = fields.One2many(
        comodel_name='sale.order.line', string=u"Lignes de commande appareils", compute='_compute_app_order_line_ids')
    display_app_order_lines = fields.Boolean(
        string=u"Afficher les lignes de commande appareils ?", compute='_compute_app_order_line_ids')
    acc_order_line_ids = fields.One2many(
        comodel_name='sale.order.line', string=u"Lignes de commande accessoires", compute='_compute_acc_order_line_ids')
    display_acc_order_lines = fields.Boolean(
        string=u"Afficher les lignes de commande accessoires ?", compute='_compute_acc_order_line_ids')
    app_picking_line_ids = fields.One2many(
        comodel_name='stock.move', string=u"Lignes de BL appareils", compute='_compute_app_picking_line_ids')
    display_app_picking_lines = fields.Boolean(
        string=u"Afficher les lignes de BL appareils ?", compute='_compute_app_picking_line_ids')
    acc_picking_line_ids = fields.One2many(
        comodel_name='stock.move', string=u"Lignes de BL accessoires", compute='_compute_acc_picking_line_ids')
    display_acc_picking_lines = fields.Boolean(
        string=u"Afficher les lignes de BL accessoires ?", compute='_compute_acc_picking_line_ids')

    @api.multi
    def _compute_predefined_state_id(self):
        for rec in self:
            if not rec.type_id.state_ids or not rec.type_id.state_ids.filtered(lambda s: s.starting_state):
                continue
            rec.predefined_state_id = rec.type_id.state_ids.filtered(lambda s: s.starting_state)[0]
            # Planification
            if rec.type_id == self.env.ref('of_followup.of_followup_task_type_planif'):
                interventions = rec.planif_intervention_ids
                # Il existe des RDV d'intervention et ils sont tous au statut 'Réalisé'
                if interventions and not interventions.filtered(lambda i: i.state not in ['done', 'cancel']):
                    rec.predefined_state_id = self.env.ref('of_followup.of_followup_task_type_state_planif_03')
                #  Il existe au moins un RDV d'intervention au statut 'Confirmé'
                elif interventions.filtered(lambda i: i.state == 'confirm'):
                    rec.predefined_state_id = self.env.ref('of_followup.of_followup_task_type_state_planif_02')
            # Visite technique
            elif rec.type_id == self.env.ref('of_followup.of_followup_task_type_vt'):
                interventions = rec.vt_intervention_ids
                # Il existe un RDV d'intervention de tâche "VT" au statut 'Réalisé'
                if interventions.filtered(lambda i: i.state == 'done'):
                    rec.predefined_state_id = self.env.ref('of_followup.of_followup_task_type_state_vt_03')
                #  Il existe un RDV d'intervention de tâche "VT" au statut 'Confirmé'
                elif interventions.filtered(lambda i: i.state == 'confirm'):
                    rec.predefined_state_id = self.env.ref('of_followup.of_followup_task_type_state_vt_02')
            # Appareils
            elif rec.type_id == self.env.ref('of_followup.of_followup_task_type_app'):
                app_order_lines = rec.app_order_line_ids
                po_validated = bool(app_order_lines)
                receipt_validated = bool(app_order_lines)
                # Non kit
                for app_order_line in app_order_lines.filtered(lambda l: not l.of_is_kit):
                    stock_moves = app_order_line.procurement_ids.mapped('move_ids')
                    # On regarde d'abord si les articles sont déjà en stock/réservés
                    qty = sum(self.env['stock.quant'].search([('reservation_id', 'in', stock_moves.ids)]).mapped('qty'))
                    if qty < app_order_line.product_uom_qty:
                        # On récupère la(les) ligne(s) de commande d'achat validée associée(s)
                        purchase_procurement_orders = self.env['procurement.order'].search(
                            [('move_dest_id', 'in', stock_moves.ids)])
                        validated_purchase_lines = purchase_procurement_orders.mapped('purchase_line_id').filtered(
                            lambda l: l.order_id.state == 'purchase')
                        # On contrôle que les quantités commandées correspondent
                        if app_order_line.product_uom_qty - qty <= sum(validated_purchase_lines.mapped('product_qty')):
                            receipts = validated_purchase_lines.mapped('order_id').mapped('picking_ids')
                            if not receipts or receipts != receipts.filtered(lambda r: r.state == 'done'):
                                receipt_validated = False
                        else:
                            po_validated = False
                            break
                # Kit
                for app_order_line in app_order_lines.filtered(lambda l: l.of_is_kit):
                    for kit_line in app_order_line.kit_id.kit_line_ids.filtered(
                            lambda l: l.product_id.type == 'product'):
                        stock_moves = kit_line.procurement_ids.mapped('move_ids')
                        # On regarde d'abord si les articles sont déjà en stock/réservés
                        qty = sum(
                            self.env['stock.quant'].search([('reservation_id', 'in', stock_moves.ids)]).mapped('qty'))
                        if qty < kit_line.qty_per_kit:
                            # On récupère la(les) ligne(s) de commande d'achat validée associée(s)
                            purchase_procurement_orders = self.env['procurement.order'].search(
                                [('move_dest_id', 'in', stock_moves.ids)])
                            validated_purchase_lines = purchase_procurement_orders.mapped('purchase_line_id').filtered(
                                lambda l: l.order_id.state == 'purchase')
                            # On contrôle que les quantités commandées correspondent
                            if kit_line.qty_per_kit - qty <= sum(validated_purchase_lines.mapped('product_qty')):
                                receipts = validated_purchase_lines.mapped('order_id').mapped('picking_ids')
                                if not receipts or receipts != receipts.filtered(lambda r: r.state == 'done'):
                                    receipt_validated = False
                            else:
                                po_validated = False
                                break
                if not app_order_lines:
                    rec.predefined_state_id = self.env.ref('of_followup.of_followup_task_type_state_np')
                else:
                    if po_validated:
                        if receipt_validated:
                            rec.predefined_state_id = self.env.ref('of_followup.of_followup_task_type_state_app_03')
                        else:
                            rec.predefined_state_id = self.env.ref('of_followup.of_followup_task_type_state_app_02')
            # Accessoires
            elif rec.type_id == self.env.ref('of_followup.of_followup_task_type_acc'):
                acc_order_lines = rec.acc_order_line_ids
                po_validated = bool(acc_order_lines)
                receipt_validated = bool(acc_order_lines)
                # Non kit
                for acc_order_line in acc_order_lines.filtered(lambda l: not l.of_is_kit):
                    stock_moves = acc_order_line.procurement_ids.mapped('move_ids')
                    # On regarde d'abord si les articles sont déjà en stock/réservés
                    qty = sum(self.env['stock.quant'].search([('reservation_id', 'in', stock_moves.ids)]).mapped('qty'))
                    if qty < acc_order_line.product_uom_qty:
                        # On récupère la(les) ligne(s) de commande d'achat validée associée(s)
                        purchase_procurement_orders = self.env['procurement.order'].search(
                            [('move_dest_id', 'in', stock_moves.ids)])
                        validated_purchase_lines = purchase_procurement_orders.mapped('purchase_line_id').filtered(
                            lambda l: l.order_id.state == 'purchase')
                        # On contrôle que les quantités commandées correspondent
                        if acc_order_line.product_uom_qty - qty <= sum(validated_purchase_lines.mapped('product_qty')):
                            receipts = validated_purchase_lines.mapped('order_id').mapped('picking_ids')
                            if not receipts or receipts != receipts.filtered(lambda r: r.state == 'done'):
                                receipt_validated = False
                        else:
                            po_validated = False
                            break
                # Kit
                for acc_order_line in acc_order_lines.filtered(lambda l: l.of_is_kit):
                    for kit_line in acc_order_line.kit_id.kit_line_ids.filtered(
                            lambda l: l.product_id.type == 'product'):
                        stock_moves = kit_line.procurement_ids.mapped('move_ids')
                        # On regarde d'abord si les articles sont déjà en stock/réservés
                        qty = sum(
                            self.env['stock.quant'].search([('reservation_id', 'in', stock_moves.ids)]).mapped('qty'))
                        if qty < kit_line.qty_per_kit:
                            # On récupère la(les) ligne(s) de commande d'achat validée associée(s)
                            purchase_procurement_orders = self.env['procurement.order'].search(
                                [('move_dest_id', 'in', stock_moves.ids)])
                            validated_purchase_lines = purchase_procurement_orders.mapped('purchase_line_id').filtered(
                                lambda l: l.order_id.state == 'purchase')
                            # On contrôle que les quantités commandées correspondent
                            if kit_line.qty_per_kit - qty <= sum(validated_purchase_lines.mapped('product_qty')):
                                receipts = validated_purchase_lines.mapped('order_id').mapped('picking_ids')
                                if not receipts or receipts != receipts.filtered(lambda r: r.state == 'done'):
                                    receipt_validated = False
                            else:
                                po_validated = False
                                break
                if not acc_order_lines:
                    rec.predefined_state_id = self.env.ref('of_followup.of_followup_task_type_state_np')
                else:
                    if po_validated:
                        if receipt_validated:
                            rec.predefined_state_id = self.env.ref('of_followup.of_followup_task_type_state_app_03')
                        else:
                            rec.predefined_state_id = self.env.ref('of_followup.of_followup_task_type_state_app_02')
            # Appareils hors commande
            elif rec.type_id == self.env.ref('of_followup.of_followup_task_type_out_app'):
                app_picking_lines = rec.app_picking_line_ids
                po_validated = bool(app_picking_lines)
                receipt_validated = bool(app_picking_lines)
                for app_picking_line in app_picking_lines:
                    # On regarde d'abord si les articles sont déjà en stock/réservés
                    qty = sum(self.env['stock.quant'].search([('reservation_id', '=', app_picking_line.id)]).
                              mapped('qty'))
                    if qty < app_picking_line.product_uom_qty:
                        # On récupère la(les) ligne(s) de commande d'achat validée associée(s)
                        purchase_procurement_orders = self.env['procurement.order'].search(
                            [('move_dest_id', '=', app_picking_line.id)])
                        validated_purchase_lines = purchase_procurement_orders.mapped('purchase_line_id').filtered(
                            lambda l: l.order_id.state == 'purchase')
                        # On contrôle que les quantités commandées correspondent
                        if app_picking_line.product_uom_qty - qty <= sum(validated_purchase_lines.
                                                                         mapped('product_qty')):
                            receipts = validated_purchase_lines.mapped('order_id').mapped('picking_ids')
                            if not receipts or receipts != receipts.filtered(lambda r: r.state == 'done'):
                                receipt_validated = False
                        else:
                            po_validated = False
                            break
                if not app_picking_lines:
                    rec.predefined_state_id = self.env.ref('of_followup.of_followup_task_type_state_np')
                else:
                    if po_validated:
                        if receipt_validated:
                            rec.predefined_state_id = self.env.ref('of_followup.of_followup_task_type_state_out_app_03')
                        else:
                            rec.predefined_state_id = self.env.ref('of_followup.of_followup_task_type_state_out_app_02')
            # Accessoires hors commande
            elif rec.type_id == self.env.ref('of_followup.of_followup_task_type_out_acc'):
                acc_picking_lines = rec.acc_picking_line_ids
                po_validated = bool(acc_picking_lines)
                receipt_validated = bool(acc_picking_lines)
                for acc_picking_line in acc_picking_lines:
                    # On regarde d'abord si les articles sont déjà en stock/réservés
                    qty = sum(self.env['stock.quant'].search([('reservation_id', '=', acc_picking_line.id)]).
                              mapped('qty'))
                    if qty < acc_picking_line.product_uom_qty:
                        # On récupère la(les) ligne(s) de commande d'achat validée associée(s)
                        purchase_procurement_orders = self.env['procurement.order'].search(
                            [('move_dest_id', '=', acc_picking_line.id)])
                        validated_purchase_lines = purchase_procurement_orders.mapped('purchase_line_id').filtered(
                            lambda l: l.order_id.state == 'purchase')
                        # On contrôle que les quantités commandées correspondent
                        if acc_picking_line.product_uom_qty - qty <= sum(validated_purchase_lines.
                                                                         mapped('product_qty')):
                            receipts = validated_purchase_lines.mapped('order_id').mapped('picking_ids')
                            if not receipts or receipts != receipts.filtered(lambda r: r.state == 'done'):
                                receipt_validated = False
                        else:
                            po_validated = False
                            break
                if not acc_picking_lines:
                    rec.predefined_state_id = self.env.ref('of_followup.of_followup_task_type_state_np')
                else:
                    if po_validated:
                        if receipt_validated:
                            rec.predefined_state_id = self.env.ref('of_followup.of_followup_task_type_state_out_acc_03')
                        else:
                            rec.predefined_state_id = self.env.ref('of_followup.of_followup_task_type_state_out_acc_02')

    @api.multi
    @api.depends('state_id', 'predefined_state_id', 'predefined_task', 'force_state')
    def _compute_global_state(self):
        for rec in self:
            if rec.predefined_task and not rec.force_state:
                rec.global_state = rec.predefined_state_id.name
            else:
                rec.global_state = rec.state_id and rec.state_id.name or ""

    @api.multi
    def _compute_is_late(self):
        not_processed_state = self.env.ref('of_followup.of_followup_task_type_state_np')
        for rec in self:
            if rec.predefined_task and not rec.force_state and rec.predefined_state_id == not_processed_state:
                rec.is_late = False
            else:
                if rec.predefined_task and not rec.force_state:
                    late_stage = rec.predefined_state_id.stage_id
                else:
                    late_stage = rec.state_id.stage_id
                if late_stage and late_stage.sequence <= rec.project_id.stage_id.sequence:
                    rec.is_late = True
                else:
                    rec.is_late = False

    @api.multi
    def _compute_is_done(self):
        not_processed_state = self.env.ref('of_followup.of_followup_task_type_state_np')
        for rec in self:
            if rec.predefined_task and not rec.force_state and rec.predefined_state_id == not_processed_state:
                rec.is_done = False
            else:
                if rec.predefined_task and not rec.force_state:
                    final_state = rec.predefined_state_id.final_state
                else:
                    final_state = rec.state_id.final_state
                if final_state:
                    rec.is_done = True
                else:
                    rec.is_done = False

    @api.multi
    def _compute_is_not_processed(self):
        not_processed_state = self.env.ref('of_followup.of_followup_task_type_state_np')
        for rec in self:
            if rec.predefined_task and not rec.force_state and rec.predefined_state_id == not_processed_state:
                rec.is_not_processed = True
            else:
                rec.is_not_processed = False

    @api.multi
    def _compute_planif_intervention_ids(self):
        planif_task_type = self.env.ref('of_followup.of_followup_task_type_planif')
        planning_tache_categs = planif_task_type.planning_tache_categ_ids
        for rec in self:
            if rec.type_id == planif_task_type:
                rec.planif_intervention_ids = rec.project_id.order_id.intervention_ids.filtered(
                    lambda i: i.tache_id.tache_categ_id.id in planning_tache_categs.ids)
                rec.display_planif_interventions = True
            else:
                rec.planif_intervention_ids = False
                rec.display_planif_interventions = False

    @api.multi
    def _compute_vt_intervention_ids(self):
        vt_task_type = self.env.ref('of_followup.of_followup_task_type_vt')
        planning_tache_categs = vt_task_type.planning_tache_categ_ids
        for rec in self:
            if rec.type_id == vt_task_type:
                rec.vt_intervention_ids = rec.project_id.order_id.intervention_ids.filtered(
                    lambda i: i.tache_id.tache_categ_id.id in planning_tache_categs.ids)
                rec.display_vt_interventions = True
            else:
                rec.vt_intervention_ids = False
                rec.display_vt_interventions = False

    @api.multi
    def _compute_app_order_line_ids(self):
        app_task_type = self.env.ref('of_followup.of_followup_task_type_app')
        product_categs = app_task_type.product_categ_ids
        for rec in self:
            if rec.type_id == app_task_type:
                rec.display_app_order_lines = True
                if rec.project_id.order_id.state == 'sale':
                    rec.app_order_line_ids = rec.project_id.order_id.order_line.filtered(
                        lambda l: l.product_id.categ_id.id in product_categs.ids and
                        (l.product_id.type == 'product' or l.of_is_kit) and l.product_uom_qty > 0)
                else:
                    rec.app_order_line_ids = False
            else:
                rec.app_order_line_ids = False
                rec.display_app_order_lines = False

    @api.multi
    def _compute_acc_order_line_ids(self):
        acc_task_type = self.env.ref('of_followup.of_followup_task_type_acc')
        app_task_type = self.env.ref('of_followup.of_followup_task_type_app')
        product_categs = app_task_type.product_categ_ids
        for rec in self:
            if rec.type_id == acc_task_type:
                rec.display_acc_order_lines = True
                if rec.project_id.order_id.state == 'sale':
                    rec.acc_order_line_ids = rec.project_id.order_id.order_line.filtered(
                        lambda l: l.product_id.categ_id.id not in product_categs.ids and
                        (l.product_id.type == 'product' or l.of_is_kit) and l.product_uom_qty > 0)
                else:
                    rec.acc_order_line_ids = False
            else:
                rec.acc_order_line_ids = False
                rec.display_acc_order_lines = False

    @api.multi
    def _compute_app_picking_line_ids(self):
        out_app_task_type = self.env.ref('of_followup.of_followup_task_type_out_app')
        product_categs = out_app_task_type.product_categ_ids
        for rec in self:
            if rec.type_id == out_app_task_type:
                rec.app_picking_line_ids = rec.project_id.order_id.picking_ids.mapped('move_lines').\
                    filtered(lambda l: not l.procurement_id and
                             l.product_id.categ_id.id in product_categs.ids and l.product_uom_qty > 0)
                rec.display_app_picking_lines = True
            else:
                rec.app_picking_line_ids = False
                rec.display_app_picking_lines = False

    @api.multi
    def _compute_acc_picking_line_ids(self):
        out_acc_task_type = self.env.ref('of_followup.of_followup_task_type_out_acc')
        out_app_task_type = self.env.ref('of_followup.of_followup_task_type_out_app')
        product_categs = out_app_task_type.product_categ_ids
        for rec in self:
            if rec.type_id == out_acc_task_type:
                rec.acc_picking_line_ids = rec.project_id.order_id.picking_ids.mapped('move_lines'). \
                    filtered(lambda l: not l.procurement_id and l.product_id.categ_id.id not in product_categs.ids and
                             l.product_uom_qty > 0)
                rec.display_acc_picking_lines = True
            else:
                rec.acc_picking_line_ids = False
                rec.display_acc_picking_lines = False

    @api.onchange('type_id')
    def _onchange_type_id(self):
        self.state_id = False
        if self.type_id and not self.predefined_task or self.force_state:
            state = self.env['of.followup.task.type.state'].search(
                [('task_type_id', '=', self.type_id.id), ('starting_state', '=', True)], limit=1)
            if state:
                self.state_id = state

    @api.multi
    def next_step(self):
        self.ensure_one()
        if self.predefined_task and not self.force_state:
            # On affiche une pop-up de confirmation
            return {
                'type': 'ir.actions.act_window',
                'name': "Avertissement !",
                'view_type': 'form',
                'view_mode': 'form',
                'res_model': 'of.followup.confirm.next.step',
                'target': 'new',
            }
        else:
            # On cherche l'étape suivante
            state = self.env['of.followup.task.type.state'].search(
                [('task_type_id', '=', self.type_id.id), ('sequence', '>', self.state_id.sequence)], limit=1)
            if state:
                self.state_id = state.id
        return True

    @api.model
    def create(self, vals):
        res = super(OFFollowupTask, self).create(vals)
        # Ajout d'un message dans le chatter du projet
        self.env['mail.message'].create({
            'author_id': self.env.user.partner_id.id,
            'model': 'of.followup.project',
            'res_id': res.project_id.id,
            'type': 'comment',
            'body': u"La tâche %s a été ajoutée au suivi." % res.name,
            'date': fields.Datetime.now(),
        })
        return res

    @api.multi
    def write(self, vals):
        res = super(OFFollowupTask, self).write(vals)
        if vals.get('state_id', False):
            for rec in self:
                # Ajout d'un message dans le chatter du projet
                self.env['mail.message'].create({
                    'author_id': self.env.user.partner_id.id,
                    'model': 'of.followup.project',
                    'res_id': rec.project_id.id,
                    'type': 'comment',
                    'body': u"La tâche %s a été passée à l'état %s." % (rec.name, rec.state_id.name),
                    'date': fields.Datetime.now(),
                })
        return res

    @api.multi
    def unlink(self):
        for rec in self:
            # Ajout d'un message dans le chatter du projet
            self.env['mail.message'].create({
                'author_id': self.env.user.partner_id.id,
                'model': 'of.followup.project',
                'res_id': rec.project_id.id,
                'type': 'comment',
                'body': u"La tâche %s a été supprimée du suivi." % rec.name,
                'date': fields.Datetime.now(),
            })
        return super(OFFollowupTask, self).unlink()

class OFFollowupTaskType(models.Model):
    _name = 'of.followup.task.type'
    _description = u"Type de tâches liées au suivi des projets"

    name = fields.Char(string=u"Nom", required=True)
    short_name = fields.Char(string=u"Nom court", required=True)
    active = fields.Boolean(string=u"Actif", default=True)
    predefined_task = fields.Boolean(string=u"Tâche pré-définie", readonly=True)
    state_ids = fields.One2many(
        comodel_name='of.followup.task.type.state', inverse_name='task_type_id', string=u"Etats")
    planning_tache_categ_ids = fields.Many2many(
        comodel_name='of.planning.tache.categ', string=u"Catégories de tâches planning")
    product_categ_ids = fields.Many2many(
        comodel_name='product.category', string=u"Catégories d'articles")

class OFFollowupTaskTypeState(models.Model):
    _name = 'of.followup.task.type.state'
    _description = u"Etat des types de tâches liées au suivi des projets"
    _order = 'sequence, id desc'

    task_type_id = fields.Many2one(comodel_name='of.followup.task.type', string=u"Type de tâche", ondelete='cascade')

    sequence = fields.Integer(string=u"Séquence")
    name = fields.Char(string=u"Nom", required=True)
    starting_state = fields.Boolean(string=u"Etat de départ")
    final_state = fields.Boolean(string=u"Etat final")
    stage_id = fields.Many2one(
        comodel_name='of.followup.project.stage', string=u"En retard à partir de la période",
        domain=[('code', 'not in', ['new', 'coming', 's+'])])

    @api.model
    def create(self, vals):
        # Gestion de la séquence lors de la création
        if vals.get('task_type_id'):
            other_states = self.search([('task_type_id', '=', vals.get('task_type_id'))])
            if other_states:
                sequence = max(other_states.mapped('sequence')) + 1
            else:
                sequence = 0
            vals.update({'sequence': sequence})
        return super(OFFollowupTaskTypeState, self).create(vals)

class OFFollowupProjectTemplate(models.Model):
    _name = 'of.followup.project.template'
    _description = "Modèle de suivi des projets"

    name = fields.Char(string=u"Nom", required=True)
    task_ids = fields.One2many(
        comodel_name='of.followup.project.tmpl.task', inverse_name='template_id', string=u"Tâches")
    default = fields.Boolean(string=u"Modèle par défaut")

class OFFollowupProjectTmplTask(models.Model):
    _name = 'of.followup.project.tmpl.task'
    _description = u"Type de tâches liées au modèle de suivi"
    _order = 'sequence'

    template_id = fields.Many2one(comodel_name='of.followup.project.template', string=u"Modèle de suivi")
    sequence = fields.Integer(string=u"Séquence")
    type_id = fields.Many2one(comodel_name='of.followup.task.type', string=u"Type de tâche", required=True)
    predefined_task = fields.Boolean(string=u"Tâche pré-définie", related='type_id.predefined_task', readonly=True)
    name = fields.Char(string=u"Nom", related='type_id.name', readonly=True)

class OFFollowupProjectTag(models.Model):
    _name = 'of.followup.project.tag'
    _description = u"Étiquette du suivi commande"

    name = fields.Char(string=u"Nom", required=True)
    color = fields.Integer(string=u"Index couleur")

    _sql_constraints = [
        ('name_uniq', 'unique (name)', u"Ce nom d'étiquette existe déjà !"),
    ]

class OFFollowupProjectAlert(models.Model):
    _name = 'of.followup.project.alert'
    _description = u"Alerte du suivi commande"

    name = fields.Char(string=u"Nom", required=True)
    color = fields.Integer(string=u"Index couleur", default=4)

class SaleOrder(models.Model):
    _inherit = 'sale.order'

    of_followup_project_id = fields.Many2one(comodel_name='of.followup.project', string=u"Suivi", copy=False)
    of_follow_count = fields.Integer(string=u"Nombre de suivi", compute='_compute_of_followup_count')

    @api.multi
    def _compute_of_followup_count(self):
        for rec in self:
            if rec.of_followup_project_id:
                rec.of_follow_count = 1
            else:
                rec.of_follow_count = 0

    @api.multi
    def action_followup_project(self):
        self.ensure_one()
        followup_project_obj = self.env['of.followup.project']
        ir_config_obj = self.env['ir.config_parameter']
        followup_project = followup_project_obj.search([('order_id', '=', self.id)])
        if not followup_project and not ir_config_obj.get_param('of.followup.migration', False):
            template = self.env['of.followup.project.template'].search([('default', '=', True)])
            values = {
                'order_id': self.id,
                'template_id': template and template[0].id or False
            }
            followup_project = followup_project_obj.create(values)

            if followup_project.template_id:
                new_tasks = []
                for task in followup_project.template_id.task_ids:
                    vals = {'sequence': task.sequence, 'type_id': task.type_id.id, 'name': task.name}
                    state = self.env['of.followup.task.type.state'].search(
                        [('task_type_id', '=', task.type_id.id), ('starting_state', '=', True)], limit=1)
                    if state:
                        if task.predefined_task:
                            vals.update({'predefined_state_id': state.id})
                        else:
                            vals.update({'state_id': state.id})
                    new_tasks += [(0, 0, vals)]
                followup_project.task_ids = new_tasks

            if self._context.get('auto_followup'):
                followup_project.user_id = self._context.get('followup_creator_id')
                return True
            else:
                return {
                    'type': 'ir.actions.act_window',
                    'view_mode': 'form',
                    'res_model': 'of.followup.project',
                    'res_id': followup_project.id,
                    'target': 'current',
                    'flags': {'initial_mode': 'edit', 'form': {'action_buttons': True, 'options': {'mode': 'edit'}}},
                }
        else:

            if self._context.get('auto_followup') or ir_config_obj.get_param('of.followup.migration', False):
                return True
            else:
                return {
                    'type': 'ir.actions.act_window',
                    'view_mode': 'form',
                    'res_model': 'of.followup.project',
                    'res_id': followup_project.id,
                    'target': 'current',
                }

    @api.multi
    def action_confirm(self):
        return super(SaleOrder, self).action_confirm()
        ir_config_obj = self.env['ir.config_parameter']
        if not self._context.get('order_cancellation', False) and \
                not ir_config_obj.get_param('of.followup.migration', False):
            for order in self:
                order.with_context(auto_followup=True, followup_creator_id=self.env.user.id).sudo().\
                    action_followup_project()
        return True

    @api.multi
    def action_view_followup(self):
        self.ensure_one()
        action = self.env.ref('of_followup.of_followup_project_action').read()[0]
        if self.of_followup_project_id:
            ctx = self._context.copy()
            ctx.update({'search_default_order_id': self.id})
            action['context'] = ctx
        else:
            action = {'type': 'ir.actions.act_window_close'}
        return action

class File(dms_base.DMSModel):
    _inherit = 'muk_dms.file'

    @api.model
    def of_get_object_partner_and_category(self, obj):
        if obj._name == 'of.followup.project':
            partner = obj.partner_id
            categ = self.env.ref('of_followup.of_followup_project_file_category')
        else:
            partner, categ = super(File, self).of_get_object_partner_and_category(obj)
        return partner, categ

    @api.multi
    def action_view_linked_record(self):
        result = super(File, self).action_view_linked_record()
        if self.of_file_type == 'related' and self.of_related_model == 'of.followup.project':
            result['view_id'] = self.env.ref('of_followup.of_followup_project_form_view').id
        return result