# -*- coding: utf-8 -*-
# Copyright 2018 JDG <www.yunside.com>
# Create by Carmen <carmen.ling@yunside.com> at 2018/5/17

from odoo import models, fields, api, SUPERUSER_ID
from odoo.exceptions import ValidationError
from odoo.osv import expression
from odoo.http import request
from odoo import tools
import datetime
from odoo.tools import config

BASE_STATE = [('draft', u'新建'),
              ('confirm', u'确认'),
              ('audit', u'审核'),
              ('unaudit', u'反审核'),
              ('cancel', u'作废')]


class AbstractBaseBiz(models.AbstractModel):
    """
    业务单据基类
    """

    _name = 'jdg.abstract.base.biz'
    _inherit = 'jdg.abstract.base'
    _description = u"业务单据基类"
    _order = 'number desc'
    _seq_prefix = None

    number = fields.Char(string=u'编号', required=True, index=True, copy=False, default='/', readonly=True,
                         help=u'编号规则：1位系统前缀+2位单据前缀+4位组织ID+6位年月日+4位流水号，如：FPO00012001010001')
    state = fields.Selection(BASE_STATE, string=u'状态', default='draft', help=u'控制数据是否有效')
    date = fields.Date(string=u'业务日期', default=fields.Date.context_today, index=True)

    show_cancel_to_draft_button = fields.Boolean(string=u'显示回到新建按钮', help=u'控制业务单据能否由作废状态回到新建状态',
                                                 compute='get_show_cancel_to_draft_button',
                                                 default=False,
                                                 store=False)

    time_confirm = fields.Datetime(string=u'确认时间', copy=False)
    time_audit = fields.Datetime(string=u'审核时间', copy=False)
    time_unaudit = fields.Datetime(string=u'反审时间', copy=False)
    time_cancel = fields.Datetime(string=u'作废时间', copy=False)

    uid_confirm = fields.Many2one('res.users', string=u'确认者', readonly=True, help=u'由确定动作自动反写', copy=False)
    uid_audit = fields.Many2one('res.users', string=u'审核者', readonly=True, help=u'由审核动作自动反写', copy=False)
    uid_unaudit = fields.Many2one('res.users', string=u'反审者', readonly=True, help=u'由反审动作自动反写', copy=False)
    uid_cancel = fields.Many2one('res.users', string=u'作废者', readonly=True, help=u'由作废动作自动反写', copy=False)
    is_import = fields.Boolean(string=u'是否单据导入', default=False, readonly=True, copy=False)
    source_type = fields.Integer(string=u'来源类型', default=0, readonly=True, help=u'0：PC录入，1：终端录入', copy=False)

    # _sql_constraints = [
    #     ('number_uniq', 'unique (number,company_id)', u'编号重复！')
    # ]

    @api.model
    def name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
        """
        名称模糊搜索:去掉name模糊条件；
        Updated By YGJ @2018/5/23
        :return:
        """
        args = args or []
        domain = []
        if name:
            domain = [('number', '=ilike', '%' + name + '%')]
            if operator in expression.NEGATIVE_TERM_OPERATORS:
                domain = ['&', '!'] + domain[1:]
        records = self.search(domain + args, limit=limit)
        return records.name_get()

    @api.model
    def create_default_sequence(self):
        """
        创建ir_sequence记录, code组合为(self._name).company_id
        在单据创建记录前检查是否有该模型该公司的ir_sequence，如果没有则创建
        注意：当context中指定了force_company时，取sequence的逻辑以force_company而不是当前用户的company_id
        TODO：每次单据创建记录时都会查询可能存在性能消耗，优化后消耗较小，后期如有必要可将方案修改为每次创建company后就创建相关的ir_sequence
        :return: {object: ir_sequence}
        """
        force_company = self._context.get('force_company')
        if not force_company:
            force_company = self.env.user.company_id.id
        seq_code = str(self._name) + ('.%s' % force_company)
        seq_rec = self.env['ir.sequence'].sudo().search(
            ['&', ('code', '=', seq_code), ('company_id', '=', force_company)], limit=1)
        if not seq_rec:
            prefix = config.get('system_prefix', '').strip()
            company_str = ("000" + str(force_company))[-4:]
            if self._seq_prefix:
                prefix += str(self._seq_prefix) + company_str + '%(y)s%(month)s%(day)s'
            else:
                prefix += company_str + '%(y)s%(month)s%(day)s'
            seq_val = {
                'name': seq_code,
                'code': seq_code,
                'prefix': prefix,
                'padding': 4,
                'company_id': force_company
            }
            seq_rec = self.env['ir.sequence'].sudo().create(seq_val)
        return seq_rec

    @api.model
    def create(self, values):
        """
        获取自动单号
        :param values:
        :return:
        """
        company_id = values.get('company_id')
        date = values.get('date')
        if company_id and date:
            is_lock = self.is_lock(date, company_id)
            if is_lock:
                company = self.env['res.company'].sudo().browse(company_id)
                raise ValidationError(u'组织【%s】已锁定业务日期【%s】的业务单据【%s】，不允许进行创建' %
                                      (company.name, date, self._description))
        if values.get('number', '/') == '/':
            seq_rec = self.create_default_sequence()
            values.update({
                'number': seq_rec._next()
            })

        source_type = 0
        if request:
            source_type = request.httprequest.headers.get('source_type', 0)
            values.update({
                'source_type': source_type
            })
        ctx = self.env.context.copy()
        ctx['source_type'] = int(source_type)
        return super(AbstractBaseBiz, self.with_context(ctx)).create(values)

    @api.multi
    def write(self, values):
        for item in self:
            company_id = values.get('company_id', item.company_id.id)
            date = values.get('date', item.date)
            if company_id and date:
                is_lock = self.is_lock(date, company_id)
                if is_lock:
                    company = self.env['res.company'].sudo().browse(company_id)
                    raise ValidationError(u'组织【%s】已锁定业务日期【%s】的业务单据【%s】，不允许进行修改' %
                                          (company.name, date, self._description))
        source_type = 0
        if request:
            source_type = request.httprequest.headers.get('source_type', 0)
        ctx = self.env.context.copy()
        ctx['source_type'] = int(source_type)
        return super(AbstractBaseBiz, self.with_context(ctx)).write(values)

    @api.model
    def is_lock(self, date, company_id):
        """
        判断指定单据在指定日期指定组织是否已锁单
        :param model:
        :param date:
        :param company_id:
        :return:
        """
        if date and company_id:
            is_lock = False
            force_user_with_admin = self.env.context.get('force_user_with_admin')
            if not force_user_with_admin and self.env.uid != SUPERUSER_ID:
                cr = self.env.cr
                sql = '''
                SELECT JD_GET_IS_LOCK(%(company_id)s, %(model)s, %(date)s) AS is_lock
                '''
                params = {
                    'date': date,
                    'model': self._name,
                    'company_id': company_id
                }
                cr.execute(sql, params)
                rec = cr.dictfetchone()
                if rec:
                    is_lock = rec.get('is_lock')
            return is_lock

    @api.multi
    def unlink(self):
        """
        只有新建状态下的单据允许删除，子类覆盖该方法时注意该逻辑
        :return:
        """
        for record in self:
            if record.state != 'draft':
                raise ValidationError('只有新建状态下的单据才能删除：%s' % self._description + '-' + record.number)

        source_type = 0
        if request:
            source_type = request.httprequest.headers.get('source_type', 0)
        ctx = self.env.context.copy()
        ctx['source_type'] = int(source_type)
        return super(AbstractBaseBiz, self.with_context(ctx)).unlink()

    @api.multi
    def do_confirm(self):
        """
        确认动作，改变单据状态，子类覆盖该方法时注意逻辑
        :return:
        """
        for record in self:
            if record.state == 'draft':
                # self.check_access_rights_button('confirm')
                return record.with_context(force_company=record.company_id.id).jdg_sudo().write({
                    'state': 'confirm',
                    'uid_confirm': self.env.user.id,
                    'time_confirm': fields.Datetime.now()
                })
            else:
                raise ValidationError('只有新建状态下的单据才能确认：%s' % self._description + '-' + record.number)

    @api.multi
    @api.depends('state')
    def get_hide_edit_button(self):
        """
        编辑按钮可见性：根据状态控制编辑按钮可见性。子类中可以覆盖该方法添加更详细的控制条件。
        :return:
        """
        for each in self:
            each.hide_edit_button = True if each.state in ('audit', 'cancel', 'closed') else False

    @api.multi
    @api.depends('state')
    def get_show_cancel_to_draft_button(self):
        """
        作废转新建按钮可见性。
        :return:
        """
        for each in self:
            if each.state == 'cancel':
                param = self.env['ir.config_parameter'].sudo().get_param(
                    "jadedragon.biz.ctrl.allow_cancel_to_draft", True)  # Todo：使用自定义的参数和参数值模型进行配置。
                each.show_cancel_to_draft_button = True if param == "True" else False
            else:
                each.show_cancel_to_draft_button = False

    @api.multi
    def do_audit(self):
        """
        增加审核条件:确认、反审核状态单据才可审核。
        Updated By YGJ @2018/5/23
        :return:
        """
        for record in self:
            self.check_access_rights('audit')
            if record.state in ('confirm', 'unaudit'):
                record.with_context(force_company=record.company_id.id).jdg_sudo().write({
                    'state': 'audit',
                    'uid_audit': self.env.user.id,
                    'time_audit': fields.Datetime.now()
                })
            else:
                raise ValidationError('只有确认和反审核状态下的单据才能审核：%s' % self._description + '-' + record.number)

            # 电子签名
            if record.need_digital_signature():
                record.make_signature_signed()
            
            # do_logging
            self.env['jd.system.log.biz'].jdg_sudo().do_logging(record.company_id.id or None, self.env.user.id, 'audit', self._name, record.id, record.number)

    @api.multi
    def do_confirm_audit(self):
        """
        确认并审核
        :return:
        """
        for item in self:
            item.do_confirm()
            item.do_audit()

    @api.multi
    def do_unaudit(self):
        """
        反审核动作，改变单据状态，子类覆盖该方法时注意逻辑
        :return:
        """
        for record in self:
            self.check_access_rights('unaudit')
            if record.state == 'audit':
                record.with_context(force_company=record.company_id.id).jdg_sudo().write({
                    'state': 'unaudit',
                    'uid_unaudit': self.env.user.id,
                    'time_unaudit': fields.Datetime.now()
                })
            else:
                raise ValidationError('只有审核状态下的单据才能反审核：%s' % self._description + '-' + record.number)

            # 电子签名
            if record.need_digital_signature():
                record.discard_signature_signed()
            
            # do_logging
            self.env['jd.system.log.biz'].jdg_sudo().do_logging(record.company_id.id or None, self.env.user.id, 'unaudit', self._name, record.id, record.number)

    @api.multi
    def do_cancel(self):
        """
        作废动作，改变单据状态，子类覆盖该方法时注意逻辑
        :return:
        """
        for record in self:
            if record.state in ('draft', 'confirm', 'unaudit'):
                # self.check_access_rights_button('cancel')
                
                # do_logging
                self.env['jd.system.log.biz'].jdg_sudo().do_logging(record.company_id.id or None, self.env.user.id, 'cancel', self._name, record.id, record.number)

                return record.with_context(force_company=record.company_id.id).jdg_sudo().write({
                    'state': 'cancel',
                    'uid_cancel': self.env.user.id,
                    'time_cancel': fields.Datetime.now()
                })
            else:
                raise ValidationError('只有新建、确认和反审核状态下的单据才能作废：%s' % self._description + '-' + record.number)

    @api.multi
    def do_cancel_to_draft(self):
        """
        由作废回到新建状态，各单据按需添加按钮调用该函数。
        :return:
        """
        for record in self:
            if record.state == 'cancel':
                param = self.env['ir.config_parameter'].sudo().get_param(
                    "jadedragon.biz.ctrl.allow_cancel_to_draft", True)  # Todo：使用自定义的参数和参数值模型进行配置。
                if param:
                    return record.with_context(force_company=record.company_id.id).jdg_sudo().write({
                        'state': 'draft'
                    })
                else:
                    raise ValidationError('系统未开放此权限！')
            else:
                raise ValidationError('只有作废状态下的单据才能回到新建：%s' % self._description + '-' + record.number)

    @api.multi
    def do_action_esign_qrcode(self):
        """
        电子签名按钮动作
        弹出签名wizard，组装签名链接，显示签名二维码，显示部分关键内容
        id="action_partner_merge"
            res_model="base.partner.merge.automatic.wizard"
            src_model="res.partner"
            target="new"
            multi="True"
            key2="client_action_multi"
            view_mode="form"
            name="Merge Selected Contacts"
        :return:
        """
        for item in self:
            return {
                'type': 'ir.actions.act_window',
                'res_model': 'jd.base.wizard.esign.qrcode',
                'view_type': 'form',
                'view_mode': 'form',
                'views': [(self.env.ref('jd_base.base_esign_qrcode_wizard_form').id, 'form')],
                'target': 'new',
                'res_id': None,
                'context': dict(self._context),
            }

    ##############################################################
    ##########################电子签名相关##########################
    ##############################################################
    # TODO:将电子签名相关逻辑移动到电子签名模块继承实现
    @api.multi
    def get_signature_range(self):
        """
        默认单据范围是：集团内。
        可在具体单据重写
        :return: 
        """
        return 'inner'

    @api.multi
    def need_digital_signature(self):
        """
        单据是否需要电子签名
        :return: 
        """
        self.ensure_one()

        # 模块未安装则返回False
        rec = self.env['ir.module.module'].sudo().search([('name', '=', 'jd_digital_signature')], limit=1)
        if not rec or rec.state != 'installed':
            return False

        range_order = self.get_signature_range()
        config = self.env['jd.digital.signature.config'].sudo().search(
            [('model_id.model', '=', self._name), ('range', '=', range_order), ('state', '=', 'enable')])
        return config

    @api.multi
    def get_signature_order_info(self):
        """
        获得电子签名相关信息。如签名图片base64格式、签名人身份信息等
        可在具体单据重写
        :return: 
        """
        for item in self:
            # 获得该单据的所有签名
            result = {}
            result.update({
                'img_b64_info': self.env['jd.digital.signature.status'].get_signature_img_b64_info(item._name, item.id),
            })
            return result

    @api.multi
    def make_signature_signed(self):
        """
        审核检验 是否合成电子签名
        :return:
        """
        for item in self:
            res_model = item._name
            res_id = item.id
            # 判断当前单据签名图片是否上传完成
            is_finished, missing_info = \
                self.env['jd.digital.signature.status'].is_signature_upload_finish(res_model, res_id)
            if is_finished:
                self.env['jd.digital.signature.util'].create_signed_pdf(res_model, res_id)
            else:
                raise ValidationError(missing_info)

    @api.multi
    def discard_signature_signed(self):
        """
        反审核 删除签名图片 作废已完成签名的pdf
        :return:
        """
        for item in self:
            res_model = item._name
            res_id = item.id
            # 删除签名图片
            self.env['jd.digital.signature.status'].delete_signature(res_model, res_id)
            util = self.env['jd.digital.signature.util']
            signed_attachment = util.get_signed_attachment(res_model, res_id)
            if signed_attachment and signed_attachment.datas:
                discard_b64pdf = util.discard_pdf(signed_attachment.datas)

                # 覆盖签名单据
                signed_attachment.datas = discard_b64pdf
                filename = signed_attachment.name

                # 查看以往废弃数量
                recs = self.env['ir.attachment'].sudo().search(
                    [('res_model', '=', res_model), ('res_id', '=', res_id), ('name', 'ilike', '%_discard%pdf')])
                signed_attachment.name = filename.replace('_signed', '_discard%s' % len(recs) if recs else '_discard')
