|  | # Copyright 2016 上海开阖软件有限公司 (http://www.osbzr.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, api
from odoo.exceptions import UserError
from math import fabs
import copy
class TrialBalance(models.Model):
    """科目余额表"""
    _name = "trial.balance"
    _order = 'subject_code'
    _description = '科目余额表'
    @api.depends('cumulative_occurrence_debit',
                 'cumulative_occurrence_credit',
                 'ending_balance_debit',
                 'ending_balance_credit',
                 'subject_name_id')
    def _get_year_init(self):
        for tb in self:
            if tb.subject_name_id.costs_types in ('in', 'out'):
                tb.year_init_debit = tb.year_init_credit = 0
                continue
            if tb.subject_name_id.balance_directions == 'in':
                # 年初借 = 期末借 - 期末贷 - 本年借 + 本年贷
                tb.year_init_debit = (
                    tb.ending_balance_debit
                    - tb.ending_balance_credit
                    - tb.cumulative_occurrence_debit
                    + tb.cumulative_occurrence_credit)
                tb.year_init_credit = 0
            else:
                # 年初贷 = 期末贷 - 期末借 - 本年贷 + 本年借
                tb.year_init_credit = (
                    tb.ending_balance_credit
                    - tb.ending_balance_debit
                    - tb.cumulative_occurrence_credit
                    + tb.cumulative_occurrence_debit)
                tb.year_init_debit = 0
    period_id = fields.Many2one('finance.period', string='会计期间')
    subject_code = fields.Char('科目编码')
    subject_name = fields.Char('科目名称')
    subject_name_id = fields.Many2one(
        'finance.account', string='科目', ondelete='cascade')
    account_type = fields.Selection(
        string='科目类型', related='subject_name_id.account_type')
    level = fields.Integer(
        string='科目级次', related='subject_name_id.level', store=True)
    year_init_debit = fields.Float(
        '年初余额(借方)', digits='Amount', default=0, compute=_get_year_init)
    year_init_credit = fields.Float(
        '年初余额(贷方)', digits='Amount', default=0, compute=_get_year_init)
    initial_balance_debit = fields.Float(
        '期初余额(借方)', digits='Amount', default=0)
    initial_balance_credit = fields.Float(
        '期初余额(贷方)', digits='Amount', default=0)
    current_occurrence_debit = fields.Float(
        '本期发生额(借方)', digits='Amount', default=0)
    current_occurrence_credit = fields.Float(
        '本期发生额(贷方)', digits='Amount', default=0)
    ending_balance_debit = fields.Float(
        '期末余额(借方)', digits='Amount', default=0)
    ending_balance_credit = fields.Float(
        '期末余额(贷方)', digits='Amount', default=0)
    cumulative_occurrence_debit = fields.Float(
        '本年累计发生额(借方)', digits='Amount', default=0)
    cumulative_occurrence_credit = fields.Float(
        '本年累计发生额(贷方)', digits='Amount', default=0)
    def button_change_number(self):
        self.ensure_one()
        view = self.env.ref('finance.change_cumulative_occurrence_wizard_form')
        return {
            'name': '调整累计数',
            'type': 'ir.actions.act_window',
            '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('期间:%s 借贷不平\n\n差异金额:%s' %
                                (period_id.name, diff_year_init))
            if diff_cumulative_occurrence != 0:
                raise UserError('期间:%s 借贷不平\n\n差异金额:%s' %
                                (period_id.name, diff_cumulative_occurrence))
            if diff_ending_balance != 0:
                raise UserError('期间:%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('期间:%s 借贷不平\n\n差异金额:%s' %
                                (period_id.name, diff_initial_balance))
            if diff_current_occurrence != 0:
                raise UserError('期间:%s 借贷不平\n\n差异金额:%s' %
                                (period_id.name, diff_current_occurrence))
            if diff_ending_balance != 0:
                raise UserError('期间:%s 借贷不平\n\n差异金额:%s' %
                                (period_id.name, diff_ending_balance))
        return True
class CheckTrialBalanceWizard(models.TransientModel):
    """ 检查试算平衡
    """
    _name = 'check.trial.balance.wizard'
    _description = '检查试算平衡'
    @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='会计期间', help='检查试算平衡的期间')
    is_init_period = fields.Boolean(
        string='Is Init Period',
    )
    total_year_init_debit = fields.Float(
        '年初余额(借方)', digits='Amount', default=0)
    total_year_init_credit = fields.Float(
        '年初余额(贷方)', digits='Amount', default=0)
    total_initial_balance_debit = fields.Float(
        '期初余额(借方)', digits='Amount', default=0)
    total_initial_balance_credit = fields.Float(
        '期初余额(贷方)', digits='Amount', default=0)
    total_current_occurrence_debit = fields.Float(
        '本期发生额(借方)', digits='Amount', default=0)
    total_current_occurrence_credit = fields.Float(
        '本期发生额(贷方)', digits='Amount', default=0)
    total_ending_balance_debit = fields.Float(
        '期末余额(借方)', digits='Amount', default=0)
    total_ending_balance_credit = fields.Float(
        '期末余额(贷方)', digits='Amount', default=0)
    total_cumulative_occurrence_debit = fields.Float(
        '本年累计发生额(借方)', digits='Amount', default=0)
    total_cumulative_occurrence_credit = fields.Float(
        '本年累计发生额(贷方)', digits='Amount', default=0)
    result = fields.Selection(
        string='借贷平衡情况',
        selection=[('1', '借贷平衡'), ('2', '借贷不平')]
    )
    is_balance = fields.Boolean(
        string='Is Balance',
    )
    diff = fields.Float(
        string='差异金额',
    )
    company_id = fields.Many2one(
        'res.company',
        string='公司',
        change_default=True,
        default=lambda self: self.env.company)
class ChangeCumulativeOccurrenceWizard(models.TransientModel):
    """ The summary line for a class docstring should fit on one line.
    """
    _name = 'change.cumulative.occurrence.wizard'
    _description = 'Change Cumulative Occurrence Wizard'
    old_cumulative_occurrence_debit = fields.Float(
        '原本年累计发生额(借方)', digits='Amount')
    cumulative_occurrence_debit = fields.Float(
        '本年累计发生额(借方)', digits='Amount')
    old_cumulative_occurrence_credit = fields.Float(
        '原本年累计发生额(贷方)', digits='Amount')
    cumulative_occurrence_credit = fields.Float(
        '本年累计发生额(贷方)', digits='Amount')
    cumulative_occurrence = fields.Float('本年累计实际发生额', digits='Amount')
    account_id = fields.Many2one(
        string='科目',
        comodel_name='finance.account',
        ondelete='set null',
    )
    costs_types = fields.Selection(
        string='科目类型',
        related='account_id.user_type.costs_types',
    )
    trial_balance_id = fields.Many2one(
        string='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('一次只能调整一行')
        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('只能调整末级科目相关的行')
            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
    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_list')
        return {
            'type': 'ir.actions.act_window',
            'name': '科目余额表:' + self.trial_balance_id.period_id.name,
            'view_mode': 'list',
            'res_model': 'trial.balance',
            'target': 'main',
            'view_id': False,
            'views': [(view.id, 'list')],
            'domain': [('period_id', '=', self.trial_balance_id.period_id.id)]
        }
class CreateTrialBalanceWizard(models.TransientModel):
    """根据输入的期间 生成科目余额表的 向导 """
    _name = "create.trial.balance.wizard"
    _description = '科目余额表的创建向导'
    @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='会计期间',
        help='限定生成期间的范围')
    has_transaction = fields.Boolean(
        string='有发生额',
    )
    has_balance = fields.Boolean(
        string='有余额',
    )
    company_id = fields.Many2one(
        'res.company',
        string='公司',
        change_default=True,
        default=lambda self: self.env.company)
    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))])
    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))])
    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
    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('期间%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 list(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 list(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_list').id
        if self.period_id == self.period_id.get_init_period():
            view_id = self.env.ref('finance.init_balance_list').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': '科目余额表:' + self.period_id.name,
            'view_mode': 'list',
            'res_model': 'trial.balance',
            'target': 'main',
            'view_id': False,
            'context': context,
            'views': [(view_id, 'list')],
            '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 = '明细账或总账创建向导'
    @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='开始期间',
        default=_default_begin_period_id,
        help='默认是本年第一个期间')
    period_end_id = fields.Many2one(
        'finance.period',
        string='结束期间',
        default=_default_end_period_id,
        help='默认是当前期间')
    subject_name_id = fields.Many2one(
        'finance.account',
        string='会计科目 从',
        default=_default_subject_name_id,
        help='默认是所有科目的最小code')
    subject_name_end_id = fields.Many2one(
        'finance.account',
        string='到',
        default=_default_subject_name_end_id,
        help='默认是所有科目的最大code')
    no_occurred = fields.Boolean(
        '有发生额', default=True, help='无发生额的科目不显示明细账,默认为不显示')
    no_balance = fields.Boolean(
        '有余额', default=True, help='无余额的科目不显示明细账,默认为不显示')
    company_id = fields.Many2one(
        'res.company',
        string='公司',
        change_default=True,
        default=lambda self: self.env.company)
    @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': '错误',
                'message':
                    '结束期间必须大于等于开始期间!\n开始期间为:%s 结束期间为:%s' %
                    (self.period_begin_id.name, self.period_end_id.name),
            }}
    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 + ":" + '期初余额'})
        return vals_dict
    def judgment_lending(self, balance, balance_credit, balance_debit):
        """根据明细账的借贷 金额 判断出本条记录的余额 及方向,balance 为上一条记录余额
            传入参数 余额 ,贷方,借方
            :return:返回一个tuple (借贷平借贷方向 ,余额)
        """
        balance += balance_debit - balance_credit
        if balance > 0:
            direction = '借'
        elif balance < 0:
            direction = '贷'
        else:
            direction = '平'
        return (direction, balance)
    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_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 + ":" + '本期合计'}
        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 + ":" + '本年累计'})
        return [period_vals, vals_dict]
    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 range(len(sql_results)):
            if i == 0:
                balance = initial_balance['balance']
                if initial_balance['direction'] == '贷':
                    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
    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'] == '借'
            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 + ":" + '本期合计'
        })
        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 + ":" + '本年累计'
        })
        return [current_occurrence, initial_balance_new]
    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('期间%s未结账,无法取到%s期初余额' %
                                (last_period.name, self.period_begin_id.name))
        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_list').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': '明细账 : %s' % title,
            'view_mode': 'list',
            'res_model': 'vouchers.summary',
            'target': 'main',
            'view_id': False,
            'views': [(view_id, 'list')],
            'domain': [('id', 'in', vouchers_summary_ids)],
            'limit': 65535,
        }
    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(
                '期间%s未结账,无法取到%s期初余额' %
                (last_period.name, self.period_begin_id.name))
        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_list').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': '总账 %s' % title,
            'view_mode': 'list',
            'res_model': 'general.ledger.account',
            'target': 'main',
            'view_id': False,
            'views': [(view_id, 'list')],
            'domain': [('id', 'in', vouchers_summary_ids)],
            'limit': 65535,
        }
class VouchersSummary(models.TransientModel):
    """明细帐"""
    _name = 'vouchers.summary'
    _description = '明细账'
    date = fields.Date('日期', help='日期')
    period_id = fields.Many2one('finance.period', string='会计期间', help='会计期间')
    voucher_id = fields.Many2one('voucher', '凭证字号', help='凭证字号')
    summary = fields.Char('摘要', help='从凭证中获取到对应的摘要')
    direction = fields.Char('方向', help='会计术语,主要方向借、贷、平, 当借方金额大于贷方金额 方向为借\n\
     ,当贷方金额大于借方金额 方向为贷\n  借贷相等时 方向为平')
    debit = fields.Float('借方金额', digits='Amount', help='借方金额')
    credit = fields.Float('贷方金额', digits='Amount', help='贷方金额')
    balance = fields.Float('余额', digits='Amount', help='一般显示为正数,计算方式:当方向为借时 \
                                   余额= 借方金额-贷方金额, 当方向为贷时 余额= 贷方金额-借方金额')
    def view_detail_voucher(self):
        '''查看凭证明细按钮'''
        view = self.env.ref('finance.voucher_form')
        return {
            'name': '会计凭证明细',
            '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 = '总账'
    period_id = fields.Many2one(
        'finance.period', string='会计期间',  help='记录本条记录的期间!')
    summary = fields.Char('摘要', help='摘要')
    direction = fields.Char('方向', help='会计术语,主要方向借、贷、平, 当借方金额大于贷方金额 方向为借\n\
     ,当贷方金额大于借方金额 方向为贷\n  借贷相等时 方向为平')
    debit = fields.Float('借方金额', digits='Amount', help='借方金额')
    credit = fields.Float('贷方金额', digits='Amount', help='贷方金额')
    balance = fields.Float('余额', digits='Amount', help='一般显示为正数,计算方式:当方向为借时\
                                   余额= 借方金额-贷方金额, 当方向为贷时 余额= 贷方金额-借方金额')
 |