# -*- coding: utf-8 -*- import datetime from collections import defaultdict from odoo import api, models, fields from odoo.exceptions import UserError, MissingError BALANCE_DIRECTION = [('debit', '借'), ('credit', '贷'), ('balance', '平')] ACCOUNTING_ELEMENTS = [ ('asset', '资产'), ('liability', '负债'), ('equity', '所有者权益'), ('income', '收入'), ('expense', '费用'), ('profit', '利润'), ] ACCOUNT_LEVEL = [ ('1', '总分类科目'), ('2', '二级明细科目'), ('3', '三级明细科目'), ('4', '四级明细科目'), ('5', '五级明细科目'), ('6', '六级明细科目'), ('7', '七级明细科目'), ('8', '八级明细科目'), ('9', '九级明细科目'), ('10', '十级明细科目'), ] def balance_direction_to_debit_credit(balance, direction): if direction == 'debit' and balance > 0: debit = balance credit = 0 elif direction == 'debit' and balance < 0: debit = 0 credit = - balance elif direction == 'credit' and balance > 0: debit = 0 credit = balance elif direction == 'credit' and balance < 0: debit = - balance credit = 0 elif direction == 'balance' or balance == 0: debit = credit = 0 else: raise ValueError('参数direction的值不正确!') return debit, credit def balance_to_balance_direction(balance): if balance > 0: balance = balance direction = 'debit' elif balance < 0: balance = - balance direction = 'credit' else: balance = 0 direction = 'balance' return balance, direction class InquiryWizardMixin(models.AbstractModel): _name = 'inquiry.wizard.mixin' _description = '查询向导基类' fiscalyear_from = fields.Many2one('fr.account.fiscalyear', string='开始年度', required=True, ondelete='cascade') fiscalyear_to = fields.Many2one('fr.account.fiscalyear', string='结束年度', required=True, ondelete='cascade') period_from = fields.Many2one('fr.account.period', string='开始期间', required=True, ondelete='cascade') period_to = fields.Many2one('fr.account.period', string='结束期间', required=True, ondelete='cascade') account_ids = fields.Many2many('account.account', string='科目') account_from = fields.Many2one('account.account', string='起始科目') account_to = fields.Many2one('account.account', string='结束科目') level_from = fields.Selection(ACCOUNT_LEVEL, string='起始等级') level_to = fields.Selection(ACCOUNT_LEVEL, string='结束等级') level_is = fields.Boolean(string='等级非连续查询') level_from_one = fields.Selection(ACCOUNT_LEVEL, string='总分类科目', default='1') level_to_all = fields.Selection(ACCOUNT_LEVEL, string='其他等级') internal_group = fields.Selection(ACCOUNTING_ELEMENTS, string='会计要素') user_type_id = fields.Many2one('account.account.type', string='科目类型') include_unpost = fields.Boolean(string='包含未过账凭证') def get_accounts(self): self.ensure_one() accounts = self.account_ids if not self.account_ids: accounts = self.env['account.account'].search( [('company_id', '=', self.period_from.company_id.id), ('state', '=', 'on_use')]) # 筛选科目会计要素 if self.internal_group: accounts = accounts.filtered(lambda x: x.internal_group == self.internal_group) # 筛选科目类型 if self.user_type_id: accounts = accounts.filtered(lambda x: x.user_type_id == self.user_type_id) # 筛选科目代码 if self.account_from and self.account_to: accounts = accounts.filtered(lambda x: x.code >= self.account_from.code and x.code <= self.account_to.code) elif self.account_from: accounts = accounts.filtered(lambda x: x.code >= self.account_from.code) elif self.account_to: accounts = accounts.filtered(lambda x: x.code <= self.account_to.code) # 筛选科目等级 if self.level_from and self.level_to: accounts = accounts.filtered(lambda x: x.fr_account_level >= self.level_from and x.fr_account_level <= self.level_to) elif self.level_from: accounts = accounts.filtered(lambda x: x.fr_account_level >= self.level_from) elif self.level_to: accounts = accounts.filtered(lambda x: x.fr_account_level <= self.level_to) # 筛选科目等级 if self.level_is: if self.level_from_one and self.level_to_all: accounts = accounts.filtered(lambda x: x.fr_account_level == self.level_from_one or x.fr_account_level == self.level_to_all) return accounts @api.model def default_get(self, fields_list): defaults = super(InquiryWizardMixin, self).default_get(fields_list) fiscalyear = self.env['fr.account.fiscalyear'].search([], limit=1) defaults.update({ 'fiscalyear_from': fiscalyear.id, 'fiscalyear_to': fiscalyear.id, }) return defaults @api.onchange('fiscalyear_from') def _change_period_from(self): """自动获取开始期间""" if self.fiscalyear_from: period = self.env['fr.account.period'].search( [('fiscalyear_id', '=', self.fiscalyear_from.id), ('state', '!=', 'unuse')], limit=1) self.period_from = period @api.onchange('fiscalyear_to') def _change_period_to(self): """自动获取结束期间""" if self.fiscalyear_to: period = self.env['fr.account.period'].search( [('fiscalyear_id', '=', self.fiscalyear_to.id), ('state', '!=', 'unuse')], limit=1) self.period_to = period class BalanceInquiryWizard(models.TransientModel): """科目余额表查询向导""" _name = 'balance.inquiry.wizard' _inherit = 'inquiry.wizard.mixin' _description = '科目余额表查询向导' only_top_accounts = fields.Boolean(string='仅显示一级科目') hidden_no_occur = fields.Boolean(string='隐藏无发生额的科目') hidden_no_balance = fields.Boolean(string='隐藏余额为零的科目') company_id = fields.Many2one('res.company', string='公司', default=lambda self: self.env.company.id) def confirm(self): self.ensure_one() date_start = self.period_from.date_start date_end = self.period_to.date_end # 获取科目 account_ids = self.get_accounts() occur_data = self._fetch_occur_data(self.period_from.company_id.id, date_start, date_end, self.include_unpost) balance_data = self._fetch_balance_data(self.period_from.company_id.id, date_end, self.include_unpost) # 生成科目余额表汇总值 TODO 添加资产负债等小计 vals_list = [] for account in account_ids: # 获取借方和贷方发生 descendant_ids = account.get_descendant_ids(leaf=True) debit_occur = sum([occur_data[descendant_id][0] for descendant_id in descendant_ids]) credit_occur = sum([occur_data[descendant_id][1] for descendant_id in descendant_ids]) if self.hidden_no_occur and debit_occur == 0 and credit_occur == 0: continue # 获取期末借方和贷方余额 balance_end = sum([balance_data[descendant_id] for descendant_id in descendant_ids]) debit_end, credit_end = balance_direction_to_debit_credit(balance_end, 'debit') if all([self.hidden_no_balance, debit_end == 0, credit_end == 0, debit_occur == 0, credit_occur == 0]): continue # 获取期初借方和贷方余额 balance_start = balance_end - debit_occur + credit_occur debit_start, credit_start = balance_direction_to_debit_credit(balance_start, 'debit') # 添加结果, 此处添加结果时,如果是小数位数,会按照多位数进行存储。 # 多位小数位数 vals_list.append({ 'account_id': account.id, 'debit_start': debit_start, 'credit_start': credit_start, 'debit_occur': debit_occur, 'credit_occur': credit_occur, 'debit_end': debit_end, 'credit_end': credit_end, 'date_start': date_start, 'date_end': date_end, 'include_unpost': self.include_unpost, }) # 创建记录 balance_summaries = self.env['account.balance.temp'].create(vals_list) # 获取总账窗口动作 act_window = self.env['ir.actions.act_window']._for_xml_id('account_ledger.AccountBalanceTempViewActionList') act_window['domain'] = [('id', 'in', balance_summaries.ids)] act_window['display_name'] = f'科目余额表:{self.period_from.name} - {self.period_to.name}' return act_window def _fetch_occur_data(self, company_id, date_start, date_end, include_unpost=False): if include_unpost is False: state_condition = "and fr_state = 'posted'" else: state_condition = "" # 借贷发生预读取 self.env.cr.execute(f""" SELECT account_id, sum( debit ) AS debit, sum( credit ) AS credit FROM account_move_line WHERE company_id = {company_id} and date <= '{date_end}' and date >= '{date_start}' {state_condition} GROUP BY account_id """) occur_data = defaultdict(lambda: (0, 0)) for res in self.env.cr.fetchall(): occur_data[res[0]] = (res[1], res[2]) return occur_data def _fetch_balance_data(self, company_id, date_end, include_unpost=False): if include_unpost is False: state_condition = "and fr_state = 'posted'" else: state_condition = "" # 期末余额预读取 self.env.cr.execute(f""" SELECT account_id, sum( balance ) AS balance FROM account_move_line WHERE company_id = {company_id} and date <= '{date_end}' {state_condition} GROUP BY account_id """) balance_data = defaultdict(float) for res in self.env.cr.fetchall(): balance_data[res[0]] = res[1] return balance_data class AccountBalanceTemp(models.TransientModel): _name = 'account.balance.temp' _description = '科目余额表查询结果临时表' _order = 'account_id' # 基础字段 date_start = fields.Date(string='开始日期') date_end = fields.Date(string='结束日期') debit_start = fields.Monetary(string='期初借方') credit_start = fields.Monetary(string='期初贷方') debit_occur = fields.Monetary(string='借方发生') credit_occur = fields.Monetary(string='贷方发生') debit_end = fields.Monetary(string='期末借方') credit_end = fields.Monetary(string='期末贷方') include_unpost = fields.Boolean(string='包含未过账凭证') # 关系字段 account_id = fields.Many2one('account.account', string='会计科目', required=True, ondelete='cascade') # account_line_id = fields.Many2one('account.move.line', string='会计科目明细', required=True,ondelete='cascade') # 关联字段 # internal_group = fields.Selection(related='account_id.user_type_id.internal_group', string='会计要素', store=True) internal_group = fields.Selection(related='account_id.account_type', string='会计要素', store=True) account_level = fields.Selection(related='account_id.fr_account_level', string='科目等级') company_id = fields.Many2one(related='account_id.company_id', string='公司') currency_id = fields.Many2one(related='account_id.currency_id', string='币种') # partner_id = fields.Many2one(related='account_line_id.partner_id',string="业务伙伴") # analytic_account_id = fields.Many2one(related='account_line_id.analytic_account_id',string="分析账户") # analytic_tag_ids = fields.Many2many(related='account_line_id.analytic_tag_ids',string="分析标签") def action_move_line_list(self): account_ids = self.account_id.get_descendant_ids() domain = [('account_id', 'in', account_ids), ('date', '>=', self.date_start), ('date', '<=', self.date_end)] if self.include_unpost is False: domain.append(('fr_state', '=', 'posted'), ) return { 'type': 'ir.actions.act_window', 'res_model': 'account.move.line', 'name': self.account_id.name_get()[0][1], 'views': [[False, 'list'], [False, 'form']], 'domain': domain, 'context': {'create': False} } # 重复联系人明细行合并 def patch_account(self, jsd): a = {} for js in jsd: key = f'{js["account_id"]}_{js["partner_id"]}' if key not in a.keys(): a[key] = {} a[key]['debit'] = js['debit'] a[key]['credit'] = js['credit'] a[key]['account_balance_id'] = js['account_balance_id'] a[key]['account_balance_date'] = js['account_balance_date'] a[key]['date_start'] = js['date_start'] a[key]['date_end'] = js['date_end'] else: a[key]['debit'] += js['debit'] a[key]['credit'] += js['credit'] ret = [{'account_id': int(key.split("_")[0]), 'partner_id': int(key.split("_")[1]), 'debit': value['debit'], 'credit': value['credit'], 'account_balance_id': value['account_balance_id'], 'account_balance_date': value['account_balance_date'], 'date_start': value['date_start'], 'date_end': value['date_end'] } for key, value in a.items()] return ret def action_move_line_list_yeb(self): account_balance_date = fields.Datetime.now() account_ids = self.account_id.get_descendant_ids() domain = [('account_id', 'in', account_ids), ('date', '>=', self.date_start), ('date', '<=', self.date_end)] domain_all = [('account_id', 'in', account_ids)] # 判断余额表查询,是否添加包含未过账凭证 if self.include_unpost is False: domain_all.append(('fr_state', '=', 'posted'), ) data = [] for account in self.env['account.move.line'].search(domain_all): if account.partner_id: if (account.date >= self.date_start) and (account.date <= self.date_end): if not account.account_balance_id: account.account_balance_id = self.id res = { 'account_id': account.account_id.id, 'partner_id': account.partner_id.id, 'debit': account.debit, 'credit': account.credit, 'account_balance_id': self.id, 'account_balance_date': account_balance_date, 'date_start': self.date_start, 'date_end': self.date_end, } data.append(res) if account.date < self.date_start: if not account.account_balance_id: account.account_balance_id = self.id res = { 'account_id': account.account_id.id, 'partner_id': account.partner_id.id, 'debit': 0, 'credit': 0, 'account_balance_id': self.id, 'account_balance_date': account_balance_date, 'date_start': account.date, 'date_end': self.date_end, } data.append(res) # 6月9号修改。期初取值问题,取时间之前的值,业务伙伴 new_data = self.patch_account(data) end_data = [] for partner_line in new_data: qc = self.env['account.move.line'].search([ ('account_id', '=', partner_line.get('account_id')), ('date', '<', self.date_start), ('partner_id', '=', partner_line.get('partner_id'))]) # 期初余额 时间点下所有科目,借方 - 贷方 beginning = sum(qc.mapped('debit')) - sum(qc.mapped('credit')) # 期末余额 = 期初 + 借方 - 贷方 balance = beginning + partner_line.get('debit') - partner_line.get('credit') partner_line.update({ 'beginning': beginning, 'balance': balance, }) end_data.append(partner_line) self.env['fr.partner.balance'].create(end_data) # if self.include_unpost is True: # domain.append(('fr_state', '=', 'posted'), ) balance_domain = [('account_balance_id', '=', self.id), ('account_balance_date', '=', account_balance_date)] # if (self.account_id.user_type_id.type == 'receivable') or (self.account_id.user_type_id.type == 'payable'): if (self.account_id.account_type == 'receivable') or (self.account_id.account_type == 'payable'): view_id = self.env.ref('account_ledger.PartnerBalanceTree').id return { 'type': 'ir.actions.act_window', 'res_model': 'fr.partner.balance', 'search_view_id': self.env.ref('account_ledger.PartnerBalanceViewSearch').id, 'name': self.account_id.name_get()[0][1], 'views': [(view_id, 'list')], 'domain': balance_domain, 'context': {'create': 0, 'search_default_partner_id_group': 1} } else: return def action_move_line_list_yeb_prompt(self): raise UserError('只可进一步查看类型为:“应收”或“应付” 类科目') def print_quotation(self, displayName): """余额表打印功能""" vals_list = [] res_balance = { 'date': datetime.date.today(), 'company_id': self.env.user.company_id.id, 'displayName': displayName, } report_recs = self.env['report.account.balance'].create(res_balance) for balance in self: res = { 'detail_id': report_recs.id, 'account_id': balance.account_id.id, 'debit_start': balance.debit_start, 'credit_start': balance.credit_start, 'debit_occur': balance.debit_occur, 'credit_occur': balance.credit_occur, 'debit_end': balance.debit_end, 'credit_end': balance.credit_end, 'company_id': balance.company_id.id, 'currency_id': balance.currency_id.id, } vals_list.append(res) self.env['report.account.balance.line'].create(vals_list) context = {'lang': 'zh_CN', 'tz': 'Asia/Shanghai', 'uid': self.env.user.id, 'active_ids': [report_recs.id]} return { 'context': context, 'type': 'ir.actions.report', 'report_name': 'account_ledger.account_balance_temp', 'report_type': 'qweb-html', 'report_file': 'account_ledger.report_balance.temp', 'name': displayName, } class PartnerBalance(models.Model): _name = 'fr.partner.balance' _description = '客户余额表' account_id = fields.Many2one('account.account', string='会计科目', required=True, ondelete='cascade') partner_id = fields.Many2one('res.partner', string='业务伙伴') beginning = fields.Monetary(string='期初') debit = fields.Monetary(string='借方') credit = fields.Monetary(string='贷方') balance = fields.Monetary(string='余额') company_id = fields.Many2one(related='account_id.company_id', string='公司') currency_id = fields.Many2one(related='account_id.currency_id', string='币种') account_balance_id = fields.Char(string="余额表id") account_balance_date = fields.Datetime(string="查询时间") date_start = fields.Date(string='查询开始期间') date_end = fields.Date(string='查询结束期间')