osbzr/gooderp_addons

View on GitHub
finance/models/trial_balance.py

Summary

Maintainability
F
4 days
Test Coverage
# -*- coding: utf-8 -*-
from odoo import models, fields, api
from odoo.exceptions import UserError
from math import fabs
import copy
import odoo.addons.decimal_precision as dp


class TrialBalance(models.Model):
    """科目余额表"""
    _name = "trial.balance"
    _order = 'subject_code'
    _description = u'科目余额表'

    @api.one
    @api.depends('cumulative_occurrence_debit', 'cumulative_occurrence_credit',
                 'ending_balance_debit', 'ending_balance_credit', 'subject_name_id')
    def _get_year_init(self):
        if self.subject_name_id.costs_types in ('in', 'out'):
            self.year_init_debit = self.year_init_credit = 0
            return True
        if self.subject_name_id.balance_directions == 'in':
            # 年初借 = 期末借 - 期末贷 - 本年借 + 本年贷
            self.year_init_debit = self.ending_balance_debit - self.ending_balance_credit - \
                self.cumulative_occurrence_debit + self.cumulative_occurrence_credit
            self.year_init_credit = 0
        else:
            # 年初贷 = 期末贷 - 期末借 - 本年贷 + 本年借
            self.year_init_credit = self.ending_balance_credit - self.ending_balance_debit - \
                self.cumulative_occurrence_credit + self.cumulative_occurrence_debit
            self.year_init_debit = 0

    period_id = fields.Many2one('finance.period', string=u'会计期间')
    subject_code = fields.Char(u'科目编码')
    subject_name = fields.Char(u'科目')
    subject_name_id = fields.Many2one('finance.account', string=u'科目', ondelete='cascade')
    account_type = fields.Selection(string=u'科目类型', selection=[('view', 'View'), ('normal', 'Normal')], related='subject_name_id.account_type')
    level = fields.Integer(string=u'科目级次', related='subject_name_id.level', store=True )
    year_init_debit = fields.Float(u'年初余额(借方)', digits=dp.get_precision(
        'Amount'), default=0, compute=_get_year_init)
    year_init_credit = fields.Float(u'年初余额(贷方)', digits=dp.get_precision(
        'Amount'), default=0, compute=_get_year_init)
    initial_balance_debit = fields.Float(
        u'期初余额(借方)', digits=dp.get_precision('Amount'), default=0)
    initial_balance_credit = fields.Float(
        u'期初余额(贷方)', digits=dp.get_precision('Amount'), default=0)
    current_occurrence_debit = fields.Float(
        u'本期发生额(借方)', digits=dp.get_precision('Amount'), default=0)
    current_occurrence_credit = fields.Float(
        u'本期发生额(贷方)', digits=dp.get_precision('Amount'), default=0)
    ending_balance_debit = fields.Float(
        u'期末余额(借方)', digits=dp.get_precision('Amount'), default=0)
    ending_balance_credit = fields.Float(
        u'期末余额(贷方)', digits=dp.get_precision('Amount'), default=0)
    cumulative_occurrence_debit = fields.Float(
        u'本年累计发生额(借方)', digits=dp.get_precision('Amount'), default=0)
    cumulative_occurrence_credit = fields.Float(
        u'本年累计发生额(贷方)', digits=dp.get_precision('Amount'), default=0)

    def button_change_number(self):
        self.ensure_one()

        view = self.env.ref('finance.change_cumulative_occurrence_wizard_form')

        return {
            'name': u'调整累计数',
            'type': 'ir.actions.act_window',
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'change.cumulative.occurrence.wizard',
            'views': [(view.id, 'form')],
            'view_id': view.id,
            'target': 'new',
            'context': dict(
                self.env.context,
                active_id=self.id,
                active_ids=[self.id]
            ),
        }

    @api.model
    def check_trial_balance(self, period_id):
        res = {}
        trial_balance_items = self.env['trial.balance'].search([('period_id', '=', period_id.id), ('account_type', '=', 'normal')])

        field_list = [
            "total_year_init_debit", "total_year_init_credit", "total_initial_balance_debit",
            "total_initial_balance_credit", "total_current_occurrence_debit", "total_current_occurrence_credit",
            "total_ending_balance_debit", "total_ending_balance_credit", "total_cumulative_occurrence_debit",
            "total_cumulative_occurrence_credit"
        ]
        for field in field_list:
            res.update({field: sum(trial_balance_items.mapped(field[6:]))})

        if period_id == period_id.get_init_period():
            diff_year_init = res.get('total_year_init_debit', 0) - res.get('total_year_init_credit', 0)
            diff_cumulative_occurrence = res.get('total_cumulative_occurrence_debit', 0
                                                 ) - res.get('total_cumulative_occurrence_credit', 0)
            diff_ending_balance = res.get('total_ending_balance_debit', 0) - res.get('total_ending_balance_credit', 0)

            if diff_year_init != 0:
                raise UserError(u'期间:%s 借贷不平\n\n差异金额:%s' % (period_id.name, diff_year_init))
            if diff_cumulative_occurrence != 0:
                raise UserError(u'期间:%s 借贷不平\n\n差异金额:%s' % (period_id.name, diff_cumulative_occurrence))
            if diff_ending_balance != 0:
                raise UserError(u'期间:%s 借贷不平\n\n差异金额:%s' % (period_id.name, diff_ending_balance))
        else:
            diff_initial_balance = res.get('total_initial_balance_debit', 0) - res.get('total_initial_balance_credit', 0)
            diff_current_occurrence = res.get('total_current_occurrence_debit', 0
                                              ) - res.get('total_current_occurrence_credit', 0)
            diff_ending_balance = res.get('total_ending_balance_debit', 0) - res.get('total_ending_balance_credit', 0)

            if diff_initial_balance != 0:
                raise UserError(u'期间:%s 借贷不平\n\n差异金额:%s' % (period_id.name, diff_initial_balance))
            if diff_current_occurrence != 0:
                raise UserError(u'期间:%s 借贷不平\n\n差异金额:%s' % (period_id.name, diff_current_occurrence))
            if diff_ending_balance != 0:
                raise UserError(u'期间:%s 借贷不平\n\n差异金额:%s' % (period_id.name, diff_ending_balance))

        return True


class CheckTrialBalanceWizard(models.TransientModel):
    """ 检查试算平衡

    """

    _name = 'check.trial.balance.wizard'
    _description = u'检查试算平衡'

    @api.model
    def default_get(self, fields):
        res = super(CheckTrialBalanceWizard, self).default_get(fields)
        active_id = self.env.context.get('active_id', False)

        trial_balance_item = self.env['trial.balance'].browse(active_id)
        period_id = trial_balance_item.period_id
        is_init_period = False
        if period_id == period_id.get_init_period():
            is_init_period = True
        trial_balance_items = self.env['trial.balance'].search([('period_id', '=', period_id.id), ('account_type', '=', 'normal')])

        field_list = [
            "total_year_init_debit", "total_year_init_credit", "total_initial_balance_debit",
            "total_initial_balance_credit", "total_current_occurrence_debit", "total_current_occurrence_credit",
            "total_ending_balance_debit", "total_ending_balance_credit", "total_cumulative_occurrence_debit",
            "total_cumulative_occurrence_credit"
        ]
        for field in field_list:
            res.update({field: sum(trial_balance_items.mapped(field[6:]))})

        res.update({'period_id': period_id.id, 'is_init_period': is_init_period})
        if is_init_period:
            diff_year_init = res.get('total_year_init_debit', 0) - res.get('total_year_init_credit', 0)
            diff_cumulative_occurrence = res.get('total_cumulative_occurrence_debit', 0
                                                 ) - res.get('total_cumulative_occurrence_credit', 0)
            diff_ending_balance = res.get('total_ending_balance_debit', 0) - res.get('total_ending_balance_credit', 0)
            if diff_year_init != 0:
                res.update({'is_balance': False, 'result': '2', 'diff': diff_year_init})
            if diff_cumulative_occurrence != 0:
                res.update({'is_balance': False, 'result': '2', 'diff': diff_cumulative_occurrence})
            if diff_ending_balance != 0:
                res.update({'is_balance': False, 'result': '2', 'diff': diff_ending_balance})
            if not (diff_year_init or diff_cumulative_occurrence or diff_ending_balance):
                res.update({'is_balance': True, 'result': '1'})
        else:
            diff_initial_balance = res.get('total_initial_balance_debit',0) - res.get('total_initial_balance_credit',0)
            diff_current_occurrence = res.get('total_current_occurrence_debit',0) - res.get('total_current_occurrence_credit',0)
            diff_ending_balance = res.get('total_ending_balance_debit',0) - res.get('total_ending_balance_credit',0)
            if diff_initial_balance != 0:
                res.update({'is_balance': False, 'result': '2', 'diff': diff_initial_balance})
            if diff_current_occurrence != 0:
                res.update({'is_balance': False, 'result': '2', 'diff': diff_current_occurrence})
            if diff_ending_balance != 0:
                res.update({'is_balance': False, 'result': '2', 'diff': diff_ending_balance})
            if not (diff_initial_balance or diff_current_occurrence or diff_ending_balance):
                res.update({'is_balance': True, 'result': '1'})

        return res

    period_id = fields.Many2one(
        'finance.period', string=u'会计期间', help=u'检查试算平衡的期间')

    is_init_period = fields.Boolean(
        string=u'Is Init Period',
    )

    total_year_init_debit = fields.Float(u'年初余额(借方)', digits=dp.get_precision(
        'Amount'), default=0)
    total_year_init_credit = fields.Float(u'年初余额(贷方)', digits=dp.get_precision(
        'Amount'), default=0)
    total_initial_balance_debit = fields.Float(
        u'期初余额(借方)', digits=dp.get_precision('Amount'), default=0)
    total_initial_balance_credit = fields.Float(
        u'期初余额(贷方)', digits=dp.get_precision('Amount'), default=0)
    total_current_occurrence_debit = fields.Float(
        u'本期发生额(借方)', digits=dp.get_precision('Amount'), default=0)
    total_current_occurrence_credit = fields.Float(
        u'本期发生额(贷方)', digits=dp.get_precision('Amount'), default=0)
    total_ending_balance_debit = fields.Float(
        u'期末余额(借方)', digits=dp.get_precision('Amount'), default=0)
    total_ending_balance_credit = fields.Float(
        u'期末余额(贷方)', digits=dp.get_precision('Amount'), default=0)
    total_cumulative_occurrence_debit = fields.Float(
        u'本年累计发生额(借方)', digits=dp.get_precision('Amount'), default=0)
    total_cumulative_occurrence_credit = fields.Float(
        u'本年累计发生额(贷方)', digits=dp.get_precision('Amount'), default=0)
    result = fields.Selection(
            string=u'借贷平衡情况',
            selection=[('1', u'借贷平衡'), ('2', u'借贷不平')]
        )
    is_balance = fields.Boolean(
            string=u'Is Balance',
        )
    diff = fields.Float(
            string=u'差异金额',
        )    
    company_id = fields.Many2one(
        'res.company',
        string=u'公司',
        change_default=True,
        default=lambda self: self.env['res.company']._company_default_get())

class ChangeCumulativeOccurrenceWizard(models.TransientModel):
    """ The summary line for a class docstring should fit on one line.

    """
    _name = 'change.cumulative.occurrence.wizard'
    _description = u'Change Cumulative Occurrence Wizard'

    old_cumulative_occurrence_debit = fields.Float(
        u'本年累计发生额(借方)', digits=dp.get_precision('Amount'))
    cumulative_occurrence_debit = fields.Float(
        u'本年累计发生额(借方)', digits=dp.get_precision('Amount'))
    old_cumulative_occurrence_credit = fields.Float(
        u'本年累计发生额(贷方)', digits=dp.get_precision('Amount'))
    cumulative_occurrence_credit = fields.Float(
        u'本年累计发生额(贷方)', digits=dp.get_precision('Amount'))

    cumulative_occurrence = fields.Float(u'本年累计实际发生额', digits=dp.get_precision('Amount'))

    account_id = fields.Many2one(
        string=u'科目',
        comodel_name='finance.account',
        ondelete='set null',
    )

    costs_types = fields.Selection(
        [
            ('assets', u'资产'),
            ('debt', u'负债'),
            ('equity', u'所有者权益'),
            ('in', u'收入类'),
            ('out', u'费用类'),
            ('cost', u'成本类'),
        ],
        u'科目类型',
        related='account_id.user_type.costs_types',
    )

    trial_balance_id = fields.Many2one(
        string=u'Trial Balance',
        comodel_name='trial.balance',
        ondelete='set null',
    )

    @api.model
    def default_get(self, fields):
        if len(self.env.context.get('active_ids', False)) > 1:
            raise UserError( u'一次只能调整一行')
        res = super(ChangeCumulativeOccurrenceWizard, self).default_get(fields)
        active_id = self.env.context.get('active_id', False)
        if active_id:
            trial_balance_item = self.env['trial.balance'].browse( active_id)

            if trial_balance_item.subject_name_id.account_type == 'view':
                raise UserError( u'只能调整末级科目相关的行')
            
            res.update( {
                        'cumulative_occurrence_debit': trial_balance_item.cumulative_occurrence_debit,
                        'cumulative_occurrence_credit': trial_balance_item.cumulative_occurrence_credit,
                        'old_cumulative_occurrence_debit': trial_balance_item.cumulative_occurrence_debit,
                        'old_cumulative_occurrence_credit': trial_balance_item.cumulative_occurrence_credit,
                        'trial_balance_id': active_id,
                        'account_id': trial_balance_item.subject_name_id.id
                })
        return res

    @api.multi
    def update_cumulative_occurrence(self):
        parent_accounts =[]
        account = self.trial_balance_id.subject_name_id
        while account:
            parent_accounts.append(account)
            account = account.parent_id

        diff_cumulative_occurrence_debit = 0
        diff_cumulative_occurrence_credit = 0

        if self.costs_types in ('in', 'out'):
            diff_cumulative_occurrence_debit = self.cumulative_occurrence - self.old_cumulative_occurrence_debit
            diff_cumulative_occurrence_credit = self.cumulative_occurrence - self.old_cumulative_occurrence_credit
        else:
            diff_cumulative_occurrence_debit = self.cumulative_occurrence_debit - self.old_cumulative_occurrence_debit
            diff_cumulative_occurrence_credit = self.cumulative_occurrence_credit - self.old_cumulative_occurrence_credit

        for account in parent_accounts:
            trial_balance_ids = self.env['trial.balance'].search( [ ('subject_name_id', '=', account.id), ('period_id', '=', self.trial_balance_id.period_id.id)])
            for trial_balance_id in trial_balance_ids:
                trial_balance_id.write({'cumulative_occurrence_debit' : trial_balance_id.cumulative_occurrence_debit + diff_cumulative_occurrence_debit})
                trial_balance_id.write({'cumulative_occurrence_credit':  trial_balance_id.cumulative_occurrence_credit + diff_cumulative_occurrence_credit})

        view = self.env.ref('finance.init_balance_tree')
        return {
            'type': 'ir.actions.act_window',
            'name': u'科目余额表:' + self.trial_balance_id.period_id.name,
            'view_type': 'form',
            'view_mode': 'tree',
            'res_model': 'trial.balance',
            'target': 'current',
            'view_id': False,
            'views': [(view.id, 'tree')],
            'domain': [('period_id', '=', self.trial_balance_id.period_id.id)]
        }

class CreateTrialBalanceWizard(models.TransientModel):
    """根据输入的期间 生成科目余额表的 向导 """
    _name = "create.trial.balance.wizard"
    _description = u'科目余额表的创建向导'

    @api.model
    def _default_period_id(self):
        return self._default_period_id_impl()

    @api.model
    def _default_period_id_impl(self):
        """
                        默认是当前会计期间
        :return: 当前会计期间的对象
        """
        return self.env['finance.period'].get_date_now_period_id()

    period_id = fields.Many2one(
        'finance.period', default=_default_period_id, string=u'会计期间', help=u'限定生成期间的范围')
    has_transaction = fields.Boolean(
        string=u'有发生额',
    )
    has_balance = fields.Boolean(
        string=u'有余额',
    )
    company_id = fields.Many2one(
        'res.company',
        string=u'公司',
        change_default=True,
        default=lambda self: self.env['res.company']._company_default_get())

    @api.multi
    def compute_last_period_id(self, period_id):
        """取得参数区间的上个期间"""
        if int(period_id.month) == 1:
            year = int(period_id.year) - 1
            month = 12
        else:
            year = period_id.year
            month = int(period_id.month) - 1
        return self.env['finance.period'].search([('year', '=', str(year)), ('month', '=', str(month))])

    @api.multi
    def compute_next_period_id(self, period_id):
        """取得输入期间的下一个期间"""
        if int(period_id.month) == 12:
            year = int(period_id.year) + 1
            month = 1
        else:
            year = period_id.year
            month = int(period_id.month) + 1
        return self.env['finance.period'].search([('year', '=', str(year)), ('month', '=', str(month))])

    @api.multi
    def get_period_balance(self, period_id):
        """取出本期发生额
            返回结果是 科目 借 贷
         """
        # sql = '''select vol.account_id as account_id,
        #             sum(vol.debit) as debit,
        #             sum(vol.credit) as credit
        #          from voucher as vo
        #          left join voucher_line as vol
        #          on vo.id = vol.voucher_id
        #          where vo.period_id=%s
        #          group by vol.account_id'''
        # self.env.cr.execute(sql, (period_id,))
        # return self.env.cr.dictfetchall()
        self.ensure_one()
        data = []
        for account in self.env['finance.account'].search([]):
            account_balance = account.get_balance(period_id)
            data.append( {
                'account_id':account.id,
                'debit': account_balance.get('debit'),
                'credit': account_balance.get('credit'),
                'balance': account_balance.get('balance')
                })

        return data

    @api.multi
    def create_trial_balance(self):
        """ \
            生成科目余额表 \
            1.如果所选区间已经关闭则直接调出已有的科目余额表记录
            2.判断如果所选的区间的 前一个期间没有关闭则报错
            3.如果上一个区间不存在则报错
        """
        trial_balance_objs = self.env['trial.balance'].search(
            [('period_id', '=', self.period_id.id)])
        trial_balance_ids = [
            trial_balance_row.id for trial_balance_row in trial_balance_objs]
        if not self.period_id.is_closed:
            trial_balance_objs.unlink()
            last_period = self.compute_last_period_id(self.period_id)
            if last_period:
                if not last_period.is_closed:
                    raise UserError(u'期间%s未结账,无法取到%s期期初余额' % (last_period.name, self.period_id.name))
            period_id = self.period_id.id
            current_occurrence_dic_list = self.get_period_balance(period_id)
            trial_balance_dict = {}
            """把本期发生额的数量填写到  准备好的dict 中 """
            for current_occurrence in current_occurrence_dic_list:
                account = self.env['finance.account'].browse(
                    current_occurrence.get('account_id'))
                ending_balance_debit = ending_balance_credit = 0
                this_debit = current_occurrence.get('debit', 0) or 0
                this_credit = current_occurrence.get('credit', 0) or 0
                if account.balance_directions == 'in':
                    ending_balance_debit = this_debit - this_credit
                else:
                    ending_balance_credit = this_credit - this_debit
                account_dict = {'period_id': period_id,
                                'current_occurrence_debit': this_debit,
                                'current_occurrence_credit': this_credit,
                                'subject_code': account.code,
                                'initial_balance_credit': 0,
                                'initial_balance_debit': 0,
                                'ending_balance_debit': ending_balance_debit,
                                'ending_balance_credit': ending_balance_credit,
                                'cumulative_occurrence_debit': this_debit,
                                'cumulative_occurrence_credit': this_credit,
                                'subject_name_id': account.id}
                trial_balance_dict[account.id] = account_dict
            trial_balance_dict.update(self.construct_trial_balance_dict(
                trial_balance_dict, last_period))
            trial_balance_ids = [self.env['trial.balance'].create(vals).id for (key, vals) in
                                 trial_balance_dict.items()]

        else:
            # 更新 科目余额表, 将新出现的 科目加入到 科目余额表
            trial_balance_dict = {}
            period_id = self.period_id.id
            last_period = self.compute_last_period_id(self.period_id)
            current_occurrence_dic_list = self.get_period_balance(period_id)
            exist_trial_balanace_accounts = self.env['trial.balance'].search([('period_id', '=',
                                                                               period_id)]).mapped('subject_name_id')
            for current_occurrence in current_occurrence_dic_list:
                account = self.env['finance.account'].browse(current_occurrence.get('account_id'))
                if account not in exist_trial_balanace_accounts:
                    trial_balance_dict[account.id] = self._prepare_account_dict(current_occurrence, period_id)

            trial_balance_dict.update(self.construct_trial_balance_dict(trial_balance_dict, last_period))

            for (key, vals) in trial_balance_dict.items():
                if key not in exist_trial_balanace_accounts.ids:
                    trial_balance_ids.extend(self.env['trial.balance'].create(vals).ids)

        # 对 科目余额表 上下级 数据重新计算
        for trial_item in self.env['trial.balance'].search([('account_type', '=', 'view'),('period_id','=',self.period_id.id)], order='level desc'):
            parent_account_id = trial_item.subject_name_id
            child_account_ids = self.env['finance.account'].search( [('id', 'child_of', parent_account_id.id),('account_type','=', 'normal')])
            child_trial_items = self.env['trial.balance'].search([('subject_name_id', 'in', child_account_ids.ids),('period_id','=',self.period_id.id)])
            trial_item.write(
                {
                    "year_init_debit": sum(child_trial_items.mapped("year_init_debit")),
                    "year_init_credit": sum(child_trial_items.mapped("year_init_credit")),
                    "initial_balance_debit": sum(child_trial_items.mapped("initial_balance_debit")),
                    "initial_balance_credit": sum(child_trial_items.mapped("initial_balance_credit")),
                    "current_occurrence_debit": sum(child_trial_items.mapped("current_occurrence_debit")),
                    "current_occurrence_credit": sum(child_trial_items.mapped("current_occurrence_credit")),
                    "ending_balance_debit": sum(child_trial_items.mapped("ending_balance_debit")),
                    "ending_balance_credit": sum(child_trial_items.mapped("ending_balance_credit")),
                    "cumulative_occurrence_debit": sum(child_trial_items.mapped("cumulative_occurrence_debit")),
                    "cumulative_occurrence_credit": sum(child_trial_items.mapped("cumulative_occurrence_credit")),
                }
            )

        view_id = self.env.ref('finance.trial_balance_tree').id
        if self.period_id == self.period_id.get_init_period():
            view_id = self.env.ref('finance.init_balance_tree').id

        context = {}
        if self.has_balance and not self.has_transaction:
            context.update({'search_default_has_balance': 1 })
        elif not self.has_balance and self.has_transaction:
            context.update({'search_default_has_transaction': 1})
        elif self.has_balance and self.has_transaction:
            context.update({'search_default_has_balance': 1, 'search_default_has_transaction': 1 })

        return {
            'type': 'ir.actions.act_window',
            'name': u'科目余额表:' + self.period_id.name,
            'view_type': 'form',
            'view_mode': 'tree',
            'res_model': 'trial.balance',
            'target': 'current',
            'view_id': False,
            'context': context,
            'views': [(view_id, 'tree')],
            'domain': [('id', 'in', trial_balance_ids)]
        }

    def _prepare_account_dict(self, current_occurrence, period_id):
        account = self.env['finance.account'].browse(current_occurrence.get('account_id'))
        ending_balance_debit = ending_balance_credit = 0
        this_debit = current_occurrence.get('debit', 0) or 0
        this_credit = current_occurrence.get('credit', 0) or 0
        if account.balance_directions == 'in':
            ending_balance_debit = this_debit - this_credit
        else:
            ending_balance_credit = this_credit - this_debit
        account_dict = {
            'period_id': period_id,
            'current_occurrence_debit': this_debit,
            'current_occurrence_credit': this_credit,
            'subject_code': account.code,
            'initial_balance_credit': 0,
            'initial_balance_debit': 0,
            'ending_balance_debit': ending_balance_debit,
            'ending_balance_credit': ending_balance_credit,
            'cumulative_occurrence_debit': this_debit,
            'cumulative_occurrence_credit': this_credit,
            'subject_name_id': account.id
        }
        return account_dict


    def compute_trial_balance_data(self, trial_balance, last_period, subject_name_id, trial_balance_dict):
        ''' 获得 科目余额表 数据 '''
        initial_balance_credit = trial_balance.ending_balance_credit or 0
        initial_balance_debit = trial_balance.ending_balance_debit or 0
        this_debit = this_credit = ending_balance_credit = 0

        if subject_name_id in trial_balance_dict:  # 本月有发生额
            this_debit = trial_balance_dict[subject_name_id].get(
                'current_occurrence_debit', 0) or 0
            this_credit = trial_balance_dict[subject_name_id].get(
                'current_occurrence_credit', 0) or 0
            ending_balance_debit = initial_balance_debit + \
                this_debit - initial_balance_credit - this_credit
            if ending_balance_debit < 0:
                ending_balance_credit -= ending_balance_debit
                ending_balance_debit = 0
        else:
            ending_balance_credit = initial_balance_credit
            ending_balance_debit = initial_balance_debit
        # 本年累计发生额
        if self.period_id.year == last_period.year:
            cumulative_occurrence_credit = this_credit + \
                trial_balance.cumulative_occurrence_credit
            cumulative_occurrence_debit = this_debit + \
                trial_balance.cumulative_occurrence_debit
        else:
            cumulative_occurrence_credit = this_credit
            cumulative_occurrence_debit = this_debit

        return_vals = [initial_balance_credit, initial_balance_debit, ending_balance_credit,
                       ending_balance_debit, this_debit, this_credit, cumulative_occurrence_credit,
                       cumulative_occurrence_debit]
        return return_vals

    def construct_trial_balance_dict(self, trial_balance_dict, last_period):
        """ 结合上一期间的 数据 填写  trial_balance_dict(余额表 记录生成dict)   """
        currency_dict = copy.deepcopy(trial_balance_dict)
        for trial_balance in self.env['trial.balance'].search([('period_id', '=', last_period.id)]):
            subject_name_id = trial_balance.subject_name_id.id

            [initial_balance_credit, initial_balance_debit, ending_balance_credit, ending_balance_debit, this_debit,
             this_credit, cumulative_occurrence_credit, cumulative_occurrence_debit] = \
                self.compute_trial_balance_data(
                    trial_balance, last_period, subject_name_id, currency_dict)

            subject_code = trial_balance.subject_code
            currency_dict[trial_balance.subject_name_id.id] = {
                'initial_balance_credit': initial_balance_credit,
                'initial_balance_debit': initial_balance_debit,
                'ending_balance_credit': ending_balance_credit,
                'ending_balance_debit': ending_balance_debit,
                'current_occurrence_debit': this_debit,
                'current_occurrence_credit': this_credit,
                'cumulative_occurrence_credit': cumulative_occurrence_credit,
                'cumulative_occurrence_debit': cumulative_occurrence_debit,
                'subject_code': subject_code,
                'period_id': self.period_id.id,
                'subject_name_id': subject_name_id
            }
        return currency_dict


class CreateVouchersSummaryWizard(models.TransientModel):
    """创建 明细账或者总账的向导 """
    _name = "create.vouchers.summary.wizard"
    _description = u'明细账或总账创建向导'

    @api.model
    def _default_end_period_id(self):
        """
        默认是当前会计期间
        :return: 当前会计期间的对象
        """
        return self.env['finance.period'].get_date_now_period_id()

    @api.model
    def _default_begin_period_id(self):
        """
            默认是当前会计期间
            :return: 当前会计期间的对象
            """
        return self.env['finance.period'].get_year_fist_period_id()

    @api.model
    def _default_subject_name_id(self):
        return self.env['finance.account'].get_smallest_code_account()

    @api.model
    def _default_subject_name_end_id(self):
        return self.env['finance.account'].get_max_code_account()

    period_begin_id = fields.Many2one('finance.period', string=u'开始期间', default=_default_begin_period_id,
                                      help=u'默认是本年第一个期间')
    period_end_id = fields.Many2one(
        'finance.period', string=u'结束期间', default=_default_end_period_id, help=u'默认是当前期间')
    subject_name_id = fields.Many2one('finance.account', string=u'会计科目 从', default=_default_subject_name_id,
                                      help=u'默认是所有科目的最小code')
    subject_name_end_id = fields.Many2one('finance.account', string=u'到', default=_default_subject_name_end_id,
                                          help=u'默认是所有科目的最大code')
    no_occurred = fields.Boolean(
        u'有发生额', default=True, help=u'无发生额的科目不显示明细账,默认为不显示')
    no_balance = fields.Boolean(
        u'有余额', default=True, help=u'无余额的科目不显示明细账,默认为不显示')
    company_id = fields.Many2one(
        'res.company',
        string=u'公司',
        change_default=True,
        default=lambda self: self.env['res.company']._company_default_get())

    @api.onchange('period_begin_id', 'period_end_id')
    def onchange_period(self):
        '''结束期间大于起始期间报错'''

        if self.env['finance.period'].period_compare(self.period_end_id, self.period_begin_id) < 0:
            self.period_end_id = self.period_begin_id
            return {'warning': {
                'title': u'错误',
                'message': u'结束期间必须大于等于开始期间!\n开始期间为:%s 结束期间为:%s' %
                           (self.period_begin_id.name, self.period_end_id.name),
            }}

    @api.multi
    def get_initial_balance(self, period, account_row):
        """取得期初余额"""
        vals_dict = {}
        if period:
            period_id = period.id
        else:
            period_id = False
        initial_balance_credit = 0
        initial_balance_debit = 0
        trial_balance_obj = self.env['trial.balance'].search(
            [('period_id', '=', period_id), ('subject_name_id', '=', account_row.id)])
        if trial_balance_obj:
            initial_balance_credit = trial_balance_obj.ending_balance_credit
            initial_balance_debit = trial_balance_obj.ending_balance_debit
        direction_tuple = self.judgment_lending(
            0, initial_balance_credit, initial_balance_debit)
        vals_dict.update({
            'date': False,
            'direction': direction_tuple[0],
            'balance': fabs(direction_tuple[1]),
            'summary': account_row.code + ' ' + account_row.name + u":" + u'期初余额'})
        return vals_dict

    @api.multi
    def judgment_lending(self, balance, balance_credit, balance_debit):
        """根据明细账的借贷 金额 判断出本条记录的余额 及方向,balance 为上一条记录余额
            传入参数 余额 ,贷方,借方
            :return:返回一个tuple (借贷平借贷方向 ,余额)
        """
        balance += balance_debit - balance_credit
        if balance > 0:
            direction = u'借'
        elif balance < 0:
            direction = u'贷'
        else:
            direction = u'平'
        return (direction, balance)

    @api.multi
    def get_year_balance(self, period, subject_name):
        """根据期间和科目名称 计算出本期合计 和本年累计 (已经关闭的期间)
        :param period 期间 subject_name 科目object
        return: [本期合计dict,本年合计dict ]
        """
        vals_dict = {}
        trial_balance_obj = self.env['trial.balance'].search(
            [('period_id', '=', period.id), ('subject_name_id', '=', subject_name.id)])
        if trial_balance_obj:
            cumulative_occurrence_credit = trial_balance_obj.cumulative_occurrence_credit
            cumulative_occurrence_debit = trial_balance_obj.cumulative_occurrence_debit
            current_occurrence_credit = trial_balance_obj.current_occurrence_credit
            current_occurrence_debit = trial_balance_obj.current_occurrence_debit
            ending_balance_debit = trial_balance_obj.ending_balance_debit
            ending_balance_credit = trial_balance_obj.ending_balance_credit
        else:
            cumulative_occurrence_credit = 0
            cumulative_occurrence_debit = 0
            current_occurrence_credit = 0
            current_occurrence_debit = 0
            ending_balance_debit = 0
            ending_balance_credit = 0
        # direction_tuple = self.judgment_lending(0, cumulative_occurrence_credit, cumulative_occurrence_debit)
        direction_tuple_period = self.judgment_lending(
            0, ending_balance_credit, ending_balance_debit)
        period_vals = {
            'date': False,
            'direction': direction_tuple_period[0],
            'period_id': period.id,
            'credit': current_occurrence_credit,
            'debit': current_occurrence_debit,
            'balance': fabs(direction_tuple_period[1]),
            'summary': subject_name.code + ' ' + subject_name.name + u":" + u'本期合计'}
        vals_dict.update({
            'date': False,
            'direction': direction_tuple_period[0],
            'balance': fabs(direction_tuple_period[1]),
            'period_id': False,
            'debit': cumulative_occurrence_debit,
            'credit': cumulative_occurrence_credit,
            'summary': subject_name.code + ' ' + subject_name.name + u":" + u'本年累计'})
        return [period_vals, vals_dict]

    @api.multi
    def get_current_occurrence_amount(self, period, subject_name):
        """计算出 本期的科目的 voucher_line的明细记录 """
        child_ids = self.env['finance.account'].search([('id','child_of',subject_name.id)])
        account_ids = tuple(child_ids.ids)
        sql = ''' select vo.date as date, vo.id as voucher_id,COALESCE(vol.debit,0) as debit,vol.name
                  as summary,COALESCE(vol.credit,0) as credit
                  from voucher as vo left join voucher_line as vol
                  on vo.id = vol.voucher_id where vo.state='done' and vo.period_id=%s and  vol.account_id = %s
                  order by vo.name
                 '''
        self.env.cr.execute(sql, (period.id, subject_name.id))
        sql_results = self.env.cr.dictfetchall()
        last_period = self.env['create.trial.balance.wizard'].compute_last_period_id(
            period)
        local_last_period = last_period
        initial_balance = self.get_initial_balance(
            local_last_period, subject_name)
        balance = 0  # 上一条记录余额
        for i in xrange(len(sql_results)):
            if i == 0:
                balance = initial_balance['balance']
                if initial_balance['direction'] == u'贷':
                    balance = -balance
            else:
                balance += sql_results[i - 1]['debit'] - \
                    sql_results[i - 1]['credit']
            direction_tuple = self.judgment_lending(
                balance, sql_results[i]['credit'], sql_results[i]['debit'])
            sql_results[i].update({'direction': direction_tuple[0],
                                   'balance': fabs(direction_tuple[1]),
                                   'period_id': period.id}
                                  )
        return sql_results

    @api.multi
    def get_unclose_year_balance(self, initial_balance_new, period, subject_name):
        """取得没有关闭的期间的 本期合计和 本年累计"""
        current_occurrence = {}
        child_ids = self.env['finance.account'].search([('id','child_of',subject_name.id)])
        account_ids = tuple(child_ids.ids)
        sql = ''' select  sum(COALESCE(vol.debit,0)) as debit,sum(COALESCE(vol.credit,0)) as credit
         from voucher as vo left join voucher_line as vol
            on vo.id = vol.voucher_id where vo.state='done' and vo.period_id=%s and  vol.account_id in %s
                 group by vol.account_id'''
        self.env.cr.execute(sql, (period.id, account_ids))
        sql_results = self.env.cr.dictfetchall()
        current_credit = 0
        current_debit = 0
        if sql_results:
            current_credit = sum(row.get('credit', 0) for row in sql_results)
            current_debit = sum(row.get('debit', 0) for row in sql_results)
        # 本年累计
        # 查找累计区间,作本年累计
        year_balance_debit = year_balance_credit = 0
        compute_periods = self.env['finance.period'].search([('year', '=', str(period.year)),
                                                             ('month', '<=', str(period.month))])
        init_period_id =False
        for line_period in compute_periods:
            if line_period == line_period.get_init_period():
                init_period_id = line_period

            sql = ''' select  sum(COALESCE(vol.debit,0)) as debit,sum(COALESCE(vol.credit,0)) as credit
             from voucher as vo left join voucher_line as vol
                on vo.id = vol.voucher_id where vo.state='done' and  vo.period_id=%s and  vol.account_id in %s
                     group by vol.account_id'''
            self.env.cr.execute(sql, (line_period.id, account_ids))
            sql_results = self.env.cr.dictfetchall()
            if sql_results:
                year_balance_debit = year_balance_debit + \
                    sum(row.get('debit', 0) for row in sql_results)
                year_balance_credit = year_balance_credit + \
                    sum(row.get('credit', 0) for row in sql_results)

        if init_period_id:
            trial_balance_init_period = self.env['trial.balance'].search([('subject_name_id','=', subject_name.id),('period_id','=',init_period_id.id)])
            year_balance_debit -= sum(trial_balance_init_period.mapped('year_init_debit'))
            year_balance_credit -= sum(trial_balance_init_period.mapped('year_init_credit'))

        direction_tuple_current = self.judgment_lending(initial_balance_new.get('balance', 0) if
                                                        initial_balance_new['direction'] == u'借' else -initial_balance_new.get(
            'balance', 0), current_credit, current_debit)
        current_occurrence.update({
            'date': False,
            'direction': direction_tuple_current[0],
            'balance': fabs(direction_tuple_current[1]),
            'debit': current_debit,
            'credit': current_credit,
            'period_id': period.id,
            'summary': subject_name.code + ' ' + subject_name.name + u":" + u'本期合计'
        })
        initial_balance_new.update({
            'date': False,
            'direction': direction_tuple_current[0],
            'balance': abs(direction_tuple_current[1]),
            'debit': year_balance_debit,
            'credit': year_balance_credit,
            'period_id': False,
            'summary': subject_name.code + ' ' + subject_name.name + u":" + u'本年累计'
        })
        return [current_occurrence, initial_balance_new]

    @api.multi
    def create_vouchers_summary(self):
        """创建出根据所选期间范围内的 明细帐记录"""
        last_period = self.env['create.trial.balance.wizard'].compute_last_period_id(
            self.period_begin_id)
        if last_period:
            if not last_period.is_closed:
                raise UserError(u'期间%s未结账,无法取到%s期初余额' %
                                (last_period.name, self.period_begin_id.name))
        # period_end = self.env['create.trial.balance.wizard'].compute_next_period_id(self.period_end_id)
        vouchers_summary_ids = []
        subject_ids = self.env['finance.account'].search([('code', '>=', self.subject_name_id.code),
                                                          ('code', '<=', self.subject_name_end_id.code)])
        account_ids = []
        for subject_id in subject_ids:
            child_subject_ids = self.env['finance.account'].search([('id', 'child_of', subject_id.id)])
            account_ids.extend(child_subject_ids)

        new_account_ids =[]

        for account in account_ids:
            if account not in new_account_ids:
                new_account_ids.append(account)

        for account_line in new_account_ids:
            local_last_period = last_period
            local_currcy_period = self.period_begin_id
            break_flag = True
            init = 1
            while break_flag:
                create_vals = []
                initial_balance = self.get_initial_balance(
                    local_last_period, account_line)  # 取上期间期初余额
                if init:
                    create_vals.append(initial_balance)  # 期初
                    init = 0
                occurrence_amount = self.get_current_occurrence_amount(
                    local_currcy_period, account_line)  # 本期明细
                create_vals += occurrence_amount
                if local_currcy_period.is_closed:
                    cumulative_year_occurrence = self.get_year_balance(
                        local_currcy_period, account_line)  # 本期合计 本年累计
                else:
                    cumulative_year_occurrence = self.get_unclose_year_balance(copy.deepcopy(initial_balance),
                                                                               local_currcy_period, account_line)
                create_vals += cumulative_year_occurrence
                if local_currcy_period.id == self.period_end_id.id:
                    break_flag = False
                local_last_period = local_currcy_period
                local_currcy_period = self.env['create.trial.balance.wizard'].compute_next_period_id(
                    local_currcy_period)
                if not local_currcy_period:  # 无下一期间,退出循环。
                    break_flag = False
                # # 无发生额不显示
                # if self.no_occurred and len(occurrence_amount) == 0:
                #     continue
                # 无余额不显示
                if cumulative_year_occurrence[0].get('credit') == 0 \
                        and cumulative_year_occurrence[0].get('debit') == 0 \
                        and cumulative_year_occurrence[0].get('balance') == 0 :
                    continue
                for vals in create_vals:  # create_vals 值顺序为:期初余额  本期明细  本期本年累计
                    vouchers_summary_ids.append(
                        (self.env['vouchers.summary'].create(vals)).id)
        view_id = self.env.ref('finance.vouchers_summary_tree').id

        title = self.period_begin_id.name
        if self.period_end_id != self.period_begin_id:
            title += '-'
            title += self.period_end_id.name
        title += '_'
        title += self.subject_name_id.name
        if self.subject_name_end_id != self.subject_name_id:
            title += '-'
            title += self.subject_name_end_id.name

        return {
            'type': 'ir.actions.act_window',
            'name': u'明细账 : %s' % title,
            'view_type': 'form',
            'view_mode': 'tree',
            'res_model': 'vouchers.summary',
            'target': 'current',
            'view_id': False,
            'views': [(view_id, 'tree')],
            'domain': [('id', 'in', vouchers_summary_ids)],
            'limit': 65535,
        }

    @api.multi
    def create_general_ledger_account(self):
        """创建总账"""
        last_period = self.env['create.trial.balance.wizard'].compute_last_period_id(
            self.period_begin_id)
        if last_period and not last_period.is_closed:
            raise UserError(u'期间%s未结账,无法取到%s期初余额' %
                            (last_period.name, self.period_begin_id.name))
        # period_end = self.env['create.trial.balance.wizard'].compute_next_period_id(self.period_end_id)
        vouchers_summary_ids = []
        subject_ids = self.env['finance.account'].search([('code', '>=', self.subject_name_id.code),
                                                         ('code', '<=', self.subject_name_end_id.code)])
        account_ids = []
        for subject_id in subject_ids:
            child_subject_ids = self.env['finance.account'].search([('id', 'child_of', subject_id.id)])
            account_ids.extend(child_subject_ids)

        new_account_ids =[]

        for account in account_ids:
            if account not in new_account_ids:
                new_account_ids.append(account)

        for account_line in new_account_ids:
            local_last_period = last_period
            local_currcy_period = self.period_begin_id
            break_flag = True
            while break_flag:
                create_vals = []
                initial_balance = self.get_initial_balance(
                    local_last_period, account_line)
                create_vals.append(initial_balance)
                if local_currcy_period.is_closed:
                    cumulative_year_occurrence = self.get_year_balance(
                        local_currcy_period, account_line)
                else:
                    cumulative_year_occurrence = self.get_unclose_year_balance(copy.deepcopy(initial_balance),
                                                                               local_currcy_period, account_line)
                create_vals += cumulative_year_occurrence
                if local_currcy_period.id == self.period_end_id.id:
                    break_flag = False
                local_last_period = local_currcy_period
                local_currcy_period = self.env['create.trial.balance.wizard'].compute_next_period_id(
                    local_currcy_period)
                if not local_currcy_period:  # 无下一期间,退出循环。
                    break_flag = False
                # 无余额不显示
                if self.no_balance \
                        and cumulative_year_occurrence[0].get('credit') == 0 \
                        and cumulative_year_occurrence[0].get('debit') == 0 \
                        and cumulative_year_occurrence[1].get('credit') == 0 \
                        and cumulative_year_occurrence[1].get('debit') == 0:
                    continue
                for vals in create_vals:
                    del vals['date']
                    vouchers_summary_ids.append(
                        (self.env['general.ledger.account'].create(vals)).id)

        view_id = self.env.ref('finance.general_ledger_account_tree').id

        title = self.period_begin_id.name
        if self.period_end_id != self.period_begin_id:
            title += '-'
            title += self.period_end_id.name
        title += '_'
        title += self.subject_name_id.name
        if self.subject_name_end_id != self.subject_name_id:
            title += '-'
            title += self.subject_name_end_id.name

        return {
            'type': 'ir.actions.act_window',
            'name': u'总账 %s' % title,
            'view_type': 'form',
            'view_mode': 'tree',
            'res_model': 'general.ledger.account',
            'target': 'current',
            'view_id': False,
            'views': [(view_id, 'tree')],
            'domain': [('id', 'in', vouchers_summary_ids)],
            'limit': 65535,
        }


class VouchersSummary(models.TransientModel):
    """明细帐"""
    _name = 'vouchers.summary'
    _description = u'明细账'

    date = fields.Date(u'日期', help=u'日期')
    period_id = fields.Many2one('finance.period', string=u'会计期间', help=u'会计期间')
    voucher_id = fields.Many2one('voucher', u'凭证字号', help=u'凭证字号')
    summary = fields.Char(u'摘要', help=u'从凭证中获取到对应的摘要')
    direction = fields.Char(u'方向', help=u'会计术语,主要方向借、贷、平, 当借方金额大于贷方金额 方向为借\n\
     ,当贷方金额大于借方金额 方向为贷\n  借贷相等时 方向为平')
    debit = fields.Float(u'借方金额', digits=dp.get_precision('Amount'), help=u'借方金额')
    credit = fields.Float(u'贷方金额', digits=dp.get_precision('Amount'), help=u'贷方金额')
    balance = fields.Float(u'余额', digits=dp.get_precision('Amount'), help=u'一般显示为正数,计算方式:当方向为借时 \
                                   余额= 借方金额-贷方金额, 当方向为贷时 余额= 贷方金额-借方金额')

    @api.multi
    def view_detail_voucher(self):
        '''查看凭证明细按钮'''
        view = self.env.ref('finance.voucher_form')
        return {
            'name': u'会计凭证明细',
            'view_type': 'form',
            'view_mode': 'form',
            'view_id': False,
            'views': [(view.id, 'form')],
            'res_model': 'voucher',
            'type': 'ir.actions.act_window',
            'res_id': self.voucher_id.id,
        }


class GeneralLedgerAccount(models.TransientModel):
    """总账"""
    _name = 'general.ledger.account'
    _description = u'总账'

    period_id = fields.Many2one(
        'finance.period', string=u'会计期间',  help=u'记录本条记录的期间!')
    summary = fields.Char(u'摘要', help=u'摘要')
    direction = fields.Char(u'方向', help=u'会计术语,主要方向借、贷、平, 当借方金额大于贷方金额 方向为借\n\
     ,当贷方金额大于借方金额 方向为贷\n  借贷相等时 方向为平')
    debit = fields.Float(u'借方金额', digits=dp.get_precision('Amount'), help=u'借方金额')
    credit = fields.Float(u'贷方金额', digits=dp.get_precision('Amount'), help=u'贷方金额')
    balance = fields.Float(u'余额', digits=dp.get_precision('Amount'), help=u'一般显示为正数,计算方式:当方向为借时\
                                   余额= 借方金额-贷方金额, 当方向为贷时 余额= 贷方金额-借方金额')