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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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