You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

493 lines
21 KiB
Python

8 months ago
# -*- 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='查询结束期间')