|  | # Copyright 2016 上海开阖软件有限公司 (http://www.osbzr.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.exceptions import UserError
from odoo import fields, models, api
from odoo.tools import float_compare, float_is_zero
class ReconcileOrder(models.Model):
    _name = 'reconcile.order'
    _description = '核销单'
    _inherit = ['mail.thread']
    TYPE_SELECTION = [
        ('adv_pay_to_get', '预收冲应收'),
        ('adv_get_to_pay', '预付冲应付'),
        ('get_to_pay', '应收冲应付'),
        ('get_to_get', '应收转应收'),
        ('pay_to_pay', '应付转应付'),
    ]
    state = fields.Selection([
        ('draft', '草稿'),
        ('done', '已确认'),
        ('cancel', '已作废'),
    ], string='状态', readonly=True,
        default='draft', copy=False, index=True,
        tracking=True,
        help='核销单状态标识,新建时状态为草稿;确认后状态为已确认')
    partner_id = fields.Many2one('partner', string='往来单位', required=True,
                                 ondelete='restrict',
                                 help='该单据对应的业务伙伴,与业务类型一起带出待核销的明细行')
    to_partner_id = fields.Many2one('partner', string='转入往来单位',
                                    ondelete='restrict',
                                    help='应收转应收、应付转应付时对应的转入业务伙伴,'
                                    '订单确认时会影响该业务伙伴的应收/应付')
    advance_payment_ids = fields.One2many(
        'advance.payment', 'pay_reconcile_id',
        string='预收/付款单行',
        help='业务伙伴有预收/付款单,自动带出,用来与应收/应付款单核销')
    receivable_source_ids = fields.One2many(
        'source.order.line', 'receivable_reconcile_id',
        string='应收结算单行',
        help='业务伙伴有应收结算单,自动带出,待与预收款单核销')
    payable_source_ids = fields.One2many(
        'source.order.line', 'payable_reconcile_id',
        string='应付结算单行',
        help='业务伙伴有应付结算单,自动带出,待与预付款单核销')
    business_type = fields.Selection(TYPE_SELECTION, string='业务类型',
                                     help='类型:预收冲应收,预付冲应付,应收冲应付,应收转应收,应付转应付'
                                     )
    name = fields.Char(string='单据编号', copy=False, readonly=True,
                       help='单据编号,创建时会自动生成')
    date = fields.Date(string='单据日期',
                       default=lambda self: fields.Date.context_today(self),
                       help='单据创建日期')
    note = fields.Text(string='备注',
                       help='可以为该单据添加一些需要的标识信息')
    company_id = fields.Many2one(
        'res.company',
        string='公司',
        change_default=True,
        default=lambda self: self.env.company)
    @api.model_create_multi
    def create(self, vals_list):
        # 创建时查找该业务伙伴是否存在 未审核 状态下的核销单
        for values in vals_list:
            if values.get('partner_id'):
                orders = self.env['reconcile.order'].search([
                    ('partner_id', '=', values.get('partner_id')),
                    ('state', '=', 'draft'),
                    ('id', '!=', self.id),
                    ('business_type', '=', values.get('business_type'))])
                if orders:
                    raise UserError(
                        '业务伙伴(%s)、业务类型(%s)存在未审核的核销单,请先审核' % (
                                    orders.partner_id.name,
                                    dict(self.fields_get(
                                        allfields=['business_type']
                                        )['business_type']['selection']
                                    )[orders.business_type]))
        return super(ReconcileOrder, self).create(vals_list)
    def write(self, values):
        # 写入时查找该业务伙伴是否存在 未审核 状态下的核销单
        orders = self.env['reconcile.order'].search([
            ('partner_id', '=',
                (values.get('partner_id') or self.partner_id.id)),
            ('state', '=', 'draft'),
            ('id', '!=', self.id),
            ('business_type', '=',
                (values.get('business_type') or self.business_type))])
        if orders:
            raise UserError(
                '业务伙伴(%s)、业务类型(%s)存在未审核的核销单,请先审核' % (
                    orders.partner_id.name,
                    dict(self.fields_get(
                        allfields=['business_type'])['business_type']
                        ['selection'])[orders.business_type]))
        return super(ReconcileOrder, self).write(values)
    def _get_money_order(self, way='get'):
        """
        搜索到满足条件的预收/付款单,为one2many字段赋值构造列表
        :param way: 收/付款单的type
        :return: list
        """
        money_orders = self.env['money.order'].search(
            [('partner_id', '=', self.partner_id.id),
             ('type', '=', way),
             ('state', '=', 'done'),
             ('to_reconcile', '!=', 0)])
        result = []
        for order in money_orders:
            result.append((0, 0, {
                'name': order.id,
                'amount': order.amount,
                'date': order.date,
                'reconciled': order.reconciled,
                'to_reconcile': order.to_reconcile,
                'this_reconcile': order.to_reconcile,
            }))
        return result
    def _get_money_invoice(self, way='income'):
        """
        搜索到满足条件的money.invoice记录并且取出invoice对象 构造出one2many的
        :param way: money.invoice 中的category_id 的type
        :return:
        """
        MoneyInvoice = self.env['money.invoice'].search([
            ('category_id.type', '=', way),
            ('partner_id', '=', self.partner_id.id),
            ('state', '=', 'done'),
            ('to_reconcile', '!=', 0)])
        result = []
        for invoice in MoneyInvoice:
            result.append((0, 0, {
                'name': invoice.id,
                'category_id': invoice.category_id.id,
                'amount': invoice.amount,
                'date': invoice.date,
                'reconciled': invoice.reconciled,
                'to_reconcile': invoice.to_reconcile,
                'date_due': invoice.date_due,
                'this_reconcile': invoice.to_reconcile,
            }))
        return result
    @api.onchange('partner_id', 'to_partner_id', 'business_type')
    def onchange_partner_id(self):
        """
        onchange 类型字段 当改变 客户或者转入往来单位  业务类型 自动生成 对应的
        核销单各种明细。
        :return:
        """
        if not self.partner_id or not self.business_type:
            return {}
        # 先清空之前填充的数据
        self.advance_payment_ids = None
        self.receivable_source_ids = None
        self.payable_source_ids = None
        if self.business_type == 'adv_pay_to_get':  # 预收冲应收
            self.advance_payment_ids = self._get_money_order('get')
            self.receivable_source_ids = self._get_money_invoice('income')
        if self.business_type == 'adv_get_to_pay':  # 预付冲应付
            self.advance_payment_ids = self._get_money_order('pay')
            self.payable_source_ids = self._get_money_invoice('expense')
        if self.business_type == 'get_to_pay':  # 应收冲应付
            self.receivable_source_ids = self._get_money_invoice('income')
            self.payable_source_ids = self._get_money_invoice('expense')
        if self.business_type == 'get_to_get':  # 应收转应收
            self.receivable_source_ids = self._get_money_invoice('income')
            return {'domain':
                    {'to_partner_id': [('c_category_id', '!=', False)]}}
        if self.business_type == 'pay_to_pay':  # 应付转应付
            self.payable_source_ids = self._get_money_invoice('expense')
            return {'domain':
                    {'to_partner_id': [('s_category_id', '!=', False)]}}
    def _get_or_pay(self, line, business_type,
                    partner_id, to_partner_id, name):
        """
        核销单 核销时 对具体核销单行进行的操作
        :param line:
        :param business_type:
        :param partner_id:
        :param to_partner_id:
        :param name:
        :return:
        """
        decimal_amount = self.env.ref('core.decimal_amount')
        if float_compare(
                line.this_reconcile,
                line.to_reconcile,
                precision_digits=decimal_amount.digits) == 1:
            raise UserError('核销金额不能大于未核销金额。\n核销金额:%s 未核销金额:%s' %
                            (line.this_reconcile, line.to_reconcile))
        # 更新每一行的已核销余额、未核销余额
        line.name.to_reconcile -= line.this_reconcile
        line.name.reconciled += line.this_reconcile
        # 应收转应收、应付转应付
        if business_type in ['get_to_get', 'pay_to_pay']:
            if not float_is_zero(line.this_reconcile, 2):
                # 转入业务伙伴往来增加
                self.env['money.invoice'].create({
                    'name': name,
                    'category_id': line.category_id.id,
                    'amount': line.this_reconcile,
                    'date': self.date,
                    'reconciled': 0,  # 已核销金额
                    'to_reconcile': line.this_reconcile,  # 未核销金额
                    'date_due': line.date_due,
                    'partner_id': to_partner_id.id,
                })
                # 转出业务伙伴往来减少
                to_invoice_id = self.env['money.invoice'].create({
                    'name': name,
                    'category_id': line.category_id.id,
                    'amount': -line.this_reconcile,
                    'date': self.date,
                    'date_due': line.date_due,
                    'partner_id': partner_id.id,
                })
                # 核销 转出业务伙伴 的转出金额
                to_invoice_id.to_reconcile = 0
                to_invoice_id.reconciled = -line.this_reconcile
        # 应收冲应付,应收行、应付行分别生成负的结算单,并且核销
        if business_type in ['get_to_pay']:
            if not float_is_zero(line.this_reconcile, 2):
                invoice_id = self.env['money.invoice'].create({
                    'name': name,
                    'category_id': line.category_id.id,
                    'amount': -line.this_reconcile,
                    'date': self.date,
                    'date_due': line.date_due,
                    'partner_id': partner_id.id,
                })
                # 核销 业务伙伴 的本次核销金额
                invoice_id.to_reconcile = 0
                invoice_id.reconciled = -line.this_reconcile
        return True
    def reconcile_order_done(self):
        '''核销单的审核按钮'''
        # 核销金额不能大于未核销金额
        for order in self:
            if order.state == 'done':
                raise UserError('核销单%s已确认,不能再次确认。' % order.name)
            order_reconcile, invoice_reconcile = 0, 0
            if order.business_type in ['get_to_get', 'pay_to_pay'] \
                    and order.partner_id == order.to_partner_id:
                raise UserError(
                    '业务伙伴和转入往来单位不能相同。\n业务伙伴:%s 转入往来单位:%s'
                    % (order.partner_id.name, order.to_partner_id.name))
            # 核销预收预付
            for line in order.advance_payment_ids:
                order_reconcile += line.this_reconcile
                decimal_amount = self.env.ref('core.decimal_amount')
                if float_compare(
                        line.this_reconcile,
                        line.to_reconcile,
                        precision_digits=decimal_amount.digits) == 1:
                    raise UserError('核销金额不能大于未核销金额。\n核销金额:%s 未核销金额:%s' % (
                        line.this_reconcile, line.to_reconcile))
                # 更新每一行的已核销余额、未核销余额
                line.name.to_reconcile -= line.this_reconcile
                line.name.reconciled += line.this_reconcile
            for line in order.receivable_source_ids:
                invoice_reconcile += line.this_reconcile
                self._get_or_pay(line, order.business_type,
                                 order.partner_id,
                                 order.to_partner_id, order.name)
            for line in order.payable_source_ids:
                if self.business_type == 'adv_get_to_pay':
                    invoice_reconcile += line.this_reconcile
                else:
                    order_reconcile += line.this_reconcile
                self._get_or_pay(line, order.business_type,
                                 order.partner_id,
                                 order.to_partner_id, order.name)
            # 核销金额必须相同
            if order.business_type in ['adv_pay_to_get',
                                       'adv_get_to_pay', 'get_to_pay']:
                decimal_amount = self.env.ref('core.decimal_amount')
                if float_compare(
                        order_reconcile,
                        invoice_reconcile,
                        precision_digits=decimal_amount.digits) != 0:
                    raise UserError('核销金额必须相同, %s 不等于 %s'
                                    % (order_reconcile, invoice_reconcile))
            order.state = 'done'
        return True
    def _get_or_pay_cancel(self, line, business_type, name):
        """
        反核销时 对具体核销单行进行的操作
        """
        # 每一行的已核销金额减少、未核销金额增加
        line.name.to_reconcile += line.this_reconcile
        line.name.reconciled -= line.this_reconcile
        # 应收转应收、应付转应付、应收冲应付,找到生成的结算单反审核并删除
        if business_type in ['get_to_get', 'pay_to_pay', 'get_to_pay']:
            invoices = self.env['money.invoice'].search([('name', '=', name)])
            for inv in invoices:
                if inv.state == 'done':
                    inv.reconciled = 0.0
                    inv.money_invoice_draft()
                inv.unlink()
        return True
    def reconcile_order_draft(self):
        ''' 核销单的反审核按钮 '''
        for order in self:
            if order.state == 'draft':
                raise UserError('核销单%s已撤销,不能再次撤销。' % order.name)
            order_reconcile, invoice_reconcile = 0, 0
            if order.business_type in ['get_to_get', 'pay_to_pay'] \
                    and order.partner_id == order.to_partner_id:
                raise UserError(
                    '业务伙伴和转入往来单位不能相同。\n业务伙伴:%s 转入往来单位:%s'
                    % (order.partner_id.name, order.to_partner_id.name))
            # 反核销预收预付
            for line in order.advance_payment_ids:
                order_reconcile += line.this_reconcile
                # 每一行的已核销余额减少、未核销余额增加
                line.name.to_reconcile += line.this_reconcile
                line.name.reconciled -= line.this_reconcile
            # 反核销应收行
            for line in order.receivable_source_ids:
                invoice_reconcile += line.this_reconcile
                self._get_or_pay_cancel(line, order.business_type, order.name)
            # 反核销应付行
            for line in order.payable_source_ids:
                if order.business_type == 'adv_get_to_pay':
                    invoice_reconcile += line.this_reconcile
                else:
                    order_reconcile += line.this_reconcile
                self._get_or_pay_cancel(line, order.business_type, order.name)
            # 反核销时,金额必须相同
            if self.business_type in [
                    'adv_pay_to_get', 'adv_get_to_pay', 'get_to_pay']:
                decimal_amount = self.env.ref('core.decimal_amount')
                if float_compare(
                        order_reconcile,
                        invoice_reconcile,
                        precision_digits=decimal_amount.digits) != 0:
                    raise UserError('反核销时,金额必须相同, %s 不等于 %s'
                                    % (order_reconcile, invoice_reconcile))
            order.state = 'draft'
        return True
class SourceOrderLine(models.Model):
    _name = 'source.order.line'
    _description = '待核销行'
    money_id = fields.Many2one('money.order', string='收付款单',
                               ondelete='cascade',
                               help='待核销行对应的收付款单')  # 收付款单上的待核销行
    receivable_reconcile_id = fields.Many2one(
        'reconcile.order',
        string='应收核销单', ondelete='cascade',
        help='核销单上的应收结算单明细')  # 核销单上的应收结算单明细
    payable_reconcile_id = fields.Many2one('reconcile.order',
                                           string='应付核销单', ondelete='cascade',
                                           help='核销单上的应付结算单明细')  # 核销单上的应付结算单明细
    name = fields.Many2one('money.invoice', string='发票号',
                           copy=False, required=True,
                           ondelete='cascade',
                           help='待核销行对应的结算单')
    category_id = fields.Many2one('core.category', string='类别',
                                  required=True, ondelete='restrict',
                                  help='待核销行类别:采购 或者 销售等')
    date = fields.Date(string='单据日期',
                       help='单据创建日期')
    amount = fields.Float(string='单据金额',
                          digits='Amount',
                          help='待核销行对应金额')
    reconciled = fields.Float(string='已核销金额',
                              digits='Amount',
                              help='待核销行已核销掉的金额')
    to_reconcile = fields.Float(string='未核销金额',
                                digits='Amount',
                                help='待核销行未核销掉的金额')
    this_reconcile = fields.Float(string='本次核销金额',
                                  digits='Amount',
                                  help='本次要核销掉的金额')
    invoice_date = fields.Date(string='开票日期',
                               help='待核销行开票日期',
                               related='name.invoice_date')
    date_due = fields.Date(string='到期日',
                           help='待核销行的到期日')
    company_id = fields.Many2one(
        'res.company',
        string='公司',
        change_default=True,
        default=lambda self: self.env.company)
class AdvancePayment(models.Model):
    _name = 'advance.payment'
    _description = '核销单预收付款行'
    pay_reconcile_id = fields.Many2one('reconcile.order',
                                       string='核销单', ondelete='cascade',
                                       help='核销单预收付款行对应的核销单')
    name = fields.Many2one('money.order', string='预收/付款单',
                           copy=False, required=True, ondelete='cascade',
                           help='核销单预收/付款行对应的预收/付款单')
    note = fields.Text('备注', related='name.note')
    date = fields.Date(string='单据日期',
                       help='单据创建日期')
    amount = fields.Float(string='单据金额',
                          digits='Amount',
                          help='预收/付款单的预收/付金额')
    reconciled = fields.Float(string='已核销金额',
                              digits='Amount',
                              help='已核销的预收/预付款金额')
    to_reconcile = fields.Float(string='未核销金额',
                                digits='Amount',
                                help='未核销的预收/预付款金额')
    this_reconcile = fields.Float(string='本次核销金额',
                                  digits='Amount',
                                  help='本次核销的预收/预付款金额')
    company_id = fields.Many2one(
        'res.company',
        string='公司',
        change_default=True,
        default=lambda self: self.env.company)
 |