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.

805 lines
35 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 -*-
from collections import defaultdict
from odoo import api, fields, models
from odoo.exceptions import ValidationError, UserError, MissingError
from odoo.tools import float_compare
from odoo.osv import expression
import logging
_logger = logging.getLogger(__name__)
BALANCE_DIRECTION = [('debit', ''), ('credit', ''), ('balance', '')]
ACCOUNT_LEVEL = [
('1', '总分类科目'),
('2', '二级明细科目'),
('3', '三级明细科目'),
('4', '四级明细科目'),
('5', '五级明细科目'),
('6', '六级明细科目'),
('7', '七级明细科目'),
('8', '八级明细科目'),
('9', '九级明细科目'),
('10', '十级明细科目'),
]
def balance_debit_to_balance_direction(balance_debit):
if balance_debit > 0:
balance = balance_debit
direction = 'debit'
elif balance_debit < 0:
balance = - balance_debit
direction = 'credit'
else:
balance = 0
direction = 'balance'
return balance, direction
class AccountAccountExtension(models.Model):
"""会计科目:原生会计科目扩展"""
_inherit = 'account.account'
_description = '会计科目'
user_type_id = fields.Many2one('account.account.type', string='Type',
help="Account Type is used for information purpose, to generate country-specific legal reports, and set the rules to close a fiscal year and generate opening entries.")
internal_type = fields.Selection(related='user_type_id.type', string="Internal Type", store=True, readonly=True)
internal_group = fields.Selection(related='user_type_id.internal_group', string="Internal Group", store=True,
readonly=True)
# 基础字段
fr_short_code = fields.Char(string='助记码', copy=False, index=True)
fr_initialized = fields.Boolean(string='是否初始化', default=False, readonly=True, copy=False)
fr_init_balance = fields.Monetary(string='初始科目余额')
fr_init_direction = fields.Selection(BALANCE_DIRECTION, string='初始余额方向')
state = fields.Selection([
('uninitialized', '未初始化'),
('on_use', '使用中'),
('deprecated', '已封存'),
], string='状态', default='uninitialized', copy=False, readonly=True, index=True)
fr_default_rec = fields.Boolean(string='客户默认应收', default=False)
fr_default_pay = fields.Boolean(string='客户默认应付', default=False)
# 关系字段
fr_parent_account_id = fields.Many2one('account.account', string='上级科目', ondelete='cascade',store=True,)
fr_child_ids = fields.One2many('account.account', 'fr_parent_account_id', string='下级科目')
fr_move_line_ids = fields.One2many('account.move.line', 'account_id', string='日记账项目', readonly=True)
fr_category_ids = fields.Many2many('account.analytic.category', string='分析类别')
# 关联字段
currency_id = fields.Many2one(related='company_id.currency_id', string='币种')
# 计算字段
# fr_account_level = fields.Selection(ACCOUNT_LEVEL, string='科目等级', compute='_compute_fr_account_level', store=True, readonly=True)
fr_account_level = fields.Selection(ACCOUNT_LEVEL, string='科目等级', compute='_compute_fr_account_level', store=True, readonly=True, recursive=True)
fr_path_name = fields.Char(string="科目路径名称", compute='_compute_fr_path_full_name', store=True, readonly=True)
# fr_full_name = fields.Char(string="科目完整名称", compute='_compute_fr_path_full_name', store=True, readonly=True)
fr_full_name = fields.Char(string="科目完整名称", compute='_compute_fr_path_full_name', store=True, readonly=True, recursive=True)
fr_as_leaf = fields.Boolean(string='末级科目', compute='_compute_tree_info', store=True, readonly=True)
fr_child_count = fields.Integer(stirng='下级科目数量', compute='_compute_tree_info', store=True)
fr_move_lines_count = fields.Integer(string='日记账项目数量', compute='_compute_fr_move_lines_count')
fr_current_balance = fields.Monetary(string='当前科目余额', compute='_compute_current_balance_direction')
fr_current_direction = fields.Selection(BALANCE_DIRECTION, string='当前余额方向', compute='_compute_current_balance_direction')
partner_bool = fields.Boolean(string="业务伙伴")
cash_bool = fields.Boolean(string="现金流量")
analysis_bool = fields.Boolean(string="分析账户")
label_bool = fields.Boolean(string="分析标签")
def update_all_default_pay_rec(self):
"""
科目表默认应收应付修改后,点击该按钮来更新所有联系人的会计分录。
"""
# 查找该公司下会计分录的默认值
pay_account = self.search(
# [("user_type_id.type", "=", "payable"), ("fr_default_pay", "=", True),
[("account_type", "=", "payable"), ("fr_default_pay", "=", True),
('company_id', '=', self.env.company.id)], limit=1
)
recv_account = self.search(
# [("user_type_id.type", "=", "receivable"), ("fr_default_rec", "=", True),
[("account_type", "=", "receivable"), ("fr_default_rec", "=", True),
('company_id', '=', self.env.company.id)], limit=1
)
# 查询该公司下的所有联系人
partners = self.env['res.partner'].search([])
# 查出字段id
payable_fields_id = self.env['ir.model.fields'].sudo().search(
[('name', '=', 'property_account_payable_id'), ('model', '=', 'res.partner')])
receivable_fields_id = self.env['ir.model.fields'].sudo().search(
[('name', '=', 'property_account_receivable_id'), ('model', '=', 'res.partner')])
for partner in partners:
# 如果该联系人为公司并且有上级公司则跳过
if partner.is_company and partner.parent_id is True:
continue
# 查找该公司下该联系人是否已有会计分录的记录,如果有则修改,没有则新建
property_payable_id = self.env['ir.property'].sudo().search(
[('company_id', '=', self.env.company.id), ('name', '=', 'property_account_payable_id'),
('res_id', '=', 'res.partner,' + str(partner.id))])
property_receivable_id = self.env['ir.property'].sudo().search(
[('company_id', '=', self.env.company.id), ('name', '=', 'property_account_receivable_id'),
('res_id', '=', 'res.partner,' + str(partner.id))])
if property_payable_id:
property_payable_id.write({
'value_reference': 'account.account,' + str(pay_account.id)
})
else:
self.env['ir.property'].sudo().create({
'name': 'property_account_payable_id',
'res_id': 'res.partner,' + str(partner.id),
'company_id': self.env.company.id,
'fields_id': payable_fields_id.id,
'value_reference': 'account.account,' + str(pay_account.id),
'type': 'many2one',
})
if property_receivable_id:
property_receivable_id.write({
'value_reference': 'account.account,' + str(recv_account.id)
})
else:
self.env['ir.property'].sudo().create({
'name': 'property_account_receivable_id',
'res_id': 'res.partner,' + str(partner.id),
'company_id': self.env.company.id,
'fields_id': receivable_fields_id.id,
'value_reference': 'account.account,' + str(recv_account.id),
'type': 'many2one',
})
# 21-5-26 新增字段。勾选现金流量,选择现金流量进行绑定
#fr_cash_flow_id = fields.Many2one('account.cash.flow', string='现金流量项目')
# 选择现金流量,如果勾选掉现金流量,那么将绑定的现金流量清空
# @api.onchange('cash_bool')
# def onchange_cash_bool(self):
# if not self.cash_bool:
# self.fr_cash_flow_id = None
# 科目分级
def count_account_level(self):
"""计算科目是否作为末级科目及下级科目数量"""
all_record = self.env['account.account'].search([])
if all_record:
for i in all_record:
if i.code and i.name:
code_length=len(i.code)
if(code_length==6):
record=self.env['account.account'].search([('code','=',i.code[0:4]),('company_id','=',i.company_id.id)], limit=1)
i.fr_parent_account_id=record.id
i.fr_account_level='2'
elif (code_length == 7):
record = self.env['account.account'].search(
[('code', '=', i.code[0:4]), ('company_id', '=', i.company_id.id)], limit=1)
i.fr_parent_account_id = record.id
i.fr_account_level = '2'
new_code = i.code.replace('.', '')
new_record = self.env['account.account'].search(
[('code', '=', new_code), ('company_id', '=', i.company_id.id)], limit=1)
['1002.02','1002.01']
if i.code in ['1002.02','1002.01']:
i.unlink()
else:
i.code = i.code.replace('.', '')
elif(code_length==8):
record=self.env['account.account'].search([('code','=',i.code[0:6]),('company_id','=',i.company_id.id)], limit=1)
i.fr_parent_account_id=record.id
i.fr_account_level='3'
elif(code_length==10):
record=self.env['account.account'].search([('code','=',i.code[0:8]),('company_id','=',i.company_id.id)], limit=1)
i.fr_parent_account_id=record.id
i.fr_account_level='4'
elif(code_length==12):
record=self.env['account.account'].search([('code','=',i.code[0:10]),('company_id','=',i.company_id.id)], limit=1)
i.fr_parent_account_id=record.id
i.fr_account_level='5'
elif(code_length==14):
record=self.env['account.account'].search([('code','=',i.code[0:12]),('company_id','=',i.company_id.id)], limit=1)
i.fr_parent_account_id=record.id
i.fr_account_level='6'
elif(code_length==16):
record=self.env['account.account'].search([('code','=',i.code[0:14]),('company_id','=',i.company_id.id)], limit=1)
i.fr_parent_account_id=record.id
i.fr_account_level='7'
elif(code_length==18):
record=self.env['account.account'].search([('code','=',i.code[0:16]),('company_id','=',i.company_id.id)], limit=1)
i.fr_parent_account_id=record.id
i.fr_account_level='8'
elif(code_length==20):
record=self.env['account.account'].search([('code','=',i.code[0:18]),('company_id','=',i.company_id.id)], limit=1)
i.fr_parent_account_id=record.id
i.fr_account_level='9'
elif(code_length==22):
record=self.env['account.account'].search([('code','=',i.code[0:20]),('company_id','=',i.company_id.id)], limit=1)
i.fr_parent_account_id=record.id
i.fr_account_level='10'
for line in all_record:
if line.fr_child_count == 0:
line.fr_as_leaf = True
else:
line.fr_as_leaf = False
# 科目重置
def remove_account_chart(self):
to_removes = [
# 清除财务科目,用于重设
'res.partner.bank',
'res.bank',
'account.move.line',
# 'account.invoice',
'account.payment',
'account.bank.statement',
# 'account.tax.account.tag',
'account.tax',
'account.account.account.tag',
# 'wizard_multi_charts_accounts',
'account.journal',
'account.account',
]
# todo: 要做 remove_hr因为工资表会用到 account
# 更新account关联很多是多公司字段故只存在 ir_property故在原模型只能用update
try:
# reset default tax不管多公司
field1 = self.env['ir.model.fields']._get('product.template', "taxes_id").id
field2 = self.env['ir.model.fields']._get('product.template', "supplier_taxes_id").id
sql = ("delete from ir_default where field_id = %s or field_id = %s") % (field1, field2)
sql2 = ("update account_journal set bank_account_id=NULL;")
self._cr.execute(sql)
self._cr.execute(sql2)
self._cr.commit()
except Exception as e:
pass
# try:
# # 增加对 pos的处理
# sql = ("update pos_config set journal_id=NULL;")
# self._cr.execute(sql)
# self._cr.commit()
# except Exception as e:
# pass
try:
rec = self.env['res.partner'].search([])
for r in rec:
r.write({
'property_account_receivable_id': None,
'property_account_payable_id': None,
})
except Exception as e:
_logger.error('remove data error: %s,%s', 'account_chart', e)
try:
rec = self.env['product.category'].search([])
for r in rec:
r.write({
'property_account_income_categ_id': None,
'property_account_expense_categ_id': None,
'property_account_creditor_price_difference_categ': None,
'property_stock_account_input_categ_id': None,
'property_stock_account_output_categ_id': None,
'property_stock_valuation_account_id': None,
})
except Exception as e:
pass
try:
rec = self.env['stock.location'].search([])
for r in rec:
r.write({
'valuation_in_account_id': None,
'valuation_out_account_id': None,
})
except Exception as e:
pass
seqs = []
return self.remove_app_data(to_removes, seqs)
# 清数据o=对象, s=序列
def remove_app_data(self, o, s=[]):
for line in o:
obj_name = line
obj = self.pool.get(obj_name)
if not obj:
# 有时安装出错数据乱,没有 obj 但有 table
t_name = obj_name.replace('.', '_')
else:
t_name = obj._table
sql = "delete from %s" % t_name
try:
self._cr.execute(sql)
self._cr.commit()
except Exception as e:
_logger.error('remove data error: %s,%s', line, e)
# 更新序号
for line in s:
domain = [('code', '=ilike', line + '%')]
try:
seqs = self.env['ir.sequence'].sudo().search(domain)
if seqs.exists():
seqs.write({
'number_next': 1,
})
except Exception as e:
_logger.error('reset sequence data error: %s,%s', line, e)
return True
# ===================
# 公用方法
# ===================
def initialize_account(self):
"""多实例方法:初始化科目"""
if not self:
return
# 验证公司
company_id = self.mapped('company_id')
if len(company_id) > 1:
raise ValidationError('初始化的科目不属于同一公司!')
# 验证状态
if set(self.mapped('state')) != {'uninitialized'}:
raise ValidationError('仅能对未初始化的科目进行初始化!')
# 验证会计年度
fiscalyears = self.env['fr.account.fiscalyear'].search(
[('state', '=', 'activated'), ('company_id', '=', company_id.id)])
if not fiscalyears:
raise MissingError('无使用的会计年度,请先至少创建并激活一个会计年度!')
accounts_leaf = self.filtered(lambda acc: acc.fr_as_leaf is True)
accounts_leaf_debit = accounts_leaf.filtered(lambda acc: acc.fr_init_direction == 'debit')
accounts_leaf_credit = accounts_leaf.filtered(lambda acc: acc.fr_init_direction == 'credit')
# 验证借贷平衡
balance_debit = sum(accounts_leaf_debit.mapped('fr_init_balance'))
balance_credit = sum(accounts_leaf_credit.mapped('fr_init_balance'))
if float_compare(balance_debit, balance_credit, 2) != 0:
raise ValidationError('科目的初始借方余额和贷方余额不相等!')
# 先对父级科目执行初始化操作
parent_accounts = self.mapped('fr_parent_account_id').filtered(
lambda account: account.state == 'uninitialized')
parent_accounts.initialize_account()
# 筛选掉已初始化的科目
accounts = self.filtered(lambda account: account.state == 'uninitialized')
# 设置默认币种
accounts_no_currency = accounts.filtered(lambda account: not account.currency_id)
accounts_no_currency.write({'currency_id': self.env.user.company_id.currency_id.id})
# 修正默认初始余额方向
accounts_no_balance = accounts.filtered(lambda account: account.fr_init_balance == 0.0)
accounts_no_balance.write({'fr_init_direction': 'balance'})
# 更新状态
self.write({'fr_initialized': True, 'state': 'on_use'})
# 生成期初分录
self._generate_init_move(accounts_leaf_debit, accounts_leaf_credit, fiscalyears)
def initialize_account_cancel(self):
"""多实例方法:取消初始化科目"""
if not self:
return
# 验证公司
company_id = self.mapped('company_id')
if len(company_id) > 1:
raise ValidationError('初始化的科目不属于同一公司!')
# 验证状态
if set(self.mapped('state')) != {'on_use'}:
raise ValidationError('仅能对已经初始化的科目取消初始化!')
# 验证是否已记账
if self.env['account.move.line'].search_count([('account_id', 'in', self.ids), ('fr_state', '=', 'posted')]) > 0:
raise ValidationError('包含已过账凭证的科目无法取消初始化!')
# 更新状态
self.write({'fr_initialized': False, 'state': 'uninitialized'})
def deprecated_account(self):
"""多实例方法:封存科目"""
if not self:
return
# 验证状态
if set(self.mapped('deprecated')) != {False} or set(self.mapped('state')) != {'on_use'}:
raise ValidationError('仅能对正在使用中的科目进行封存!')
if set(self.mapped('fr_as_leaf')) != {True}:
raise ValidationError('仅能对末级科目进行封存!')
# 验证当前科目余额和方向
accounts = self.filtered(lambda account: float_compare(account.fr_current_balance, 0, 2))
if accounts:
account_str = [f"{account.name}\n" for account in accounts]
message = "存在以下科目的余额不为零,无法对科目进行封存:\n" + ''.join(account_str) + "请先手动结转科目余额。"
raise ValidationError(message)
self.write({'deprecated': True, 'state': 'deprecated'})
def deprecated_account_cancel(self):
"""多实例方法:取消封存科目"""
if not self:
return
# 验证状态
if set(self.mapped('deprecated')) != {True} or set(self.mapped('state')) != {'deprecated'}:
raise ValidationError('仅能对已封存的科目取消封存!')
self.write({'deprecated': False, 'state': 'on_use'})
def migrate_move_lines(self, account):
"""单实例方法:迁移日记账项目
迁移一个会计科目的日记账项目至下级科目
:param account: 迁移至的会计科目
:return: True
"""
self.ensure_one()
account.ensure_one()
if account.fr_parent_account_id != self:
raise ValidationError('仅能迁移日记账项目至下级会计科目!')
if account.state != 'on_use':
raise ValidationError('仅能迁移日记账项目至使用中的会计科目!')
self.fr_move_line_ids.with_context(migrate=True).write({'account_id': account.id})
return True
@api.model
def get_import_templates(self):
"""模型方法:获取导入模板"""
return [{
'label': '会计科目导入模板',
'template': '/account_ledger/static/xlsx/会计科目导入模板.xlsx'
}]
def get_categories(self):
result = [{
'id': category.id,
'name': category.name,
} for category in self.fr_category_ids]
return list(result)
def get_descendant_ids(self, leaf=False, no_self=False):
"""单实例方法获取科目所有后代科目ID
:param leaf: 仅获取末级科目默认False
:param no_self: 不包含本身默认False
:return: list, 后代科目IDs
"""
self.ensure_one()
descendants = temp_accounts = self
while temp_accounts:
temp_accounts = temp_accounts.mapped('fr_child_ids')
descendants += temp_accounts
# 获取末级科目
if leaf is True:
descendants = descendants.filtered(lambda acc: acc.fr_as_leaf is True)
if no_self is True:
descendants -= self
return descendants.ids
# ===================
# 视图方法
# ===================
def create_child_account(self):
"""单实例方法:创建下级科目
:return : 科目创建表单视图
"""
self.ensure_one()
return {
"type": "ir.actions.act_window",
"name": "创建科目",
"res_model": "account.account",
"views": [[False, "form"]],
'context': {
# 'default_user_type_id': self.user_type_id.id,
'default_user_type_id': self.id,
'default_fr_parent_account_id': self.id,
'default_code': self.code + str(self.fr_child_count + 101)[-2:],
},
"target": "new",
}
def wizard_migrate_move_lines(self):
"""单实例方法:迁移日记账项目
:return : 迁移日记账项目向导视图
"""
self.ensure_one()
# 操作限制
return {
"type": "ir.actions.act_window",
"name": "迁移日记账项目",
"res_model": "wizard.migrate.move.lines",
"views": [[False, "form"]],
'context': {
'default_source_account_id': self.id,
},
"target": "new",
}
# ===================
# 继承方法
# ===================
@api.model_create_multi
def create(self, vals_list):
recs = super(AccountAccountExtension, self).create(vals_list)
# 根据科目编码自动绑定层级关系
for rec in recs.filtered(lambda x: not x.fr_parent_account_id):
parent_account = self.search(
[('code', '=', rec.code[:-2]), ('company_id', '=', rec.company_id.id)])
if parent_account:
rec.write({
'fr_parent_account_id': parent_account.id,
'fr_account_level': str(int(parent_account.fr_account_level) + 1),
# 'user_type_id': parent_account.user_type_id.id,
'account_type': parent_account.account_type,
})
# 更新父级科目的初始科目余额和方向
self._update_parent_init_balance_direction(recs)
return recs
def write(self, vals):
res = super(AccountAccountExtension, self).write(vals)
# 更改初始余额和方向后更新父级科目初始余额和方向
if 'fr_init_balance' in vals.keys() or 'fr_init_direction' in vals.keys():
self._update_parent_init_balance_direction(self)
return res
def unlink(self):
all_states = set(self.mapped('state'))
if 'on_use' in all_states:
raise ValidationError('无法删除使用中的科目!')
if 'deprecated' in all_states:
raise ValidationError('无法删除已封存的科目!')
return super(AccountAccountExtension, self).unlink()
def name_get(self):
result = []
for account in self:
if isinstance(account['id'],int):
name = account.code + ' ' + account.fr_full_name
result.append((account.id, name))
else:
result.append(account['id'])
return result
@api.model
def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
args = args or []
domain = []
if name:
domain = ['|', ('code', '=ilike', name.split(' ')[0] + '%'), ('fr_full_name', operator, name)]
if operator in expression.NEGATIVE_TERM_OPERATORS:
domain = ['&', '!'] + domain[1:]
account_ids = self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid)
return self.browse(account_ids).name_get()
# ===================
# 计算方法
# ===================
@api.depends('fr_parent_account_id', 'fr_parent_account_id.fr_account_level')
def _compute_fr_account_level(self):
"""计算科目等级"""
for account in self:
if account.fr_parent_account_id:
if account.fr_parent_account_id.fr_account_level == '10':
raise ValidationError('科目等级达到上限,无法再创建下级科目!')
account.fr_account_level = str(int(account.fr_parent_account_id.fr_account_level) + 1 )
else:
account.fr_account_level = '1'
@api.depends('name', 'fr_parent_account_id.fr_full_name')
def _compute_fr_path_full_name(self):
"""计算科目完整名称和路径名称"""
for account in self:
if account.fr_parent_account_id.fr_full_name and account.name:
account.fr_path_name = account.fr_parent_account_id.fr_full_name + '/'
account.fr_full_name = account.fr_path_name + account.name
else:
account.fr_path_name = ''
account.fr_full_name = account.name
@api.depends('fr_child_ids')
def _compute_tree_info(self):
"""计算科目是否作为末级科目及下级科目数量"""
for account in self:
account.fr_child_count = len(account.fr_child_ids)
if account.fr_child_count == 0:
account.fr_as_leaf = True
else:
account.fr_as_leaf = False
# 2022.4.20JIIE添加使客户默认应收应付隐藏时将其设置为False。
account.fr_default_rec = False
account.fr_default_pay = False
@api.depends('fr_move_line_ids')
def _compute_fr_move_lines_count(self):
"""计算日记账项目数量"""
for account in self:
account.fr_move_lines_count = len(account.fr_move_line_ids)
def _compute_current_balance_direction(self):
"""计算科目当前科目余额和方向"""
balance_data = self._fetch_balance_data()
for account in self:
descendant_ids = account.get_descendant_ids(leaf=True)
balance_tmp = sum([balance_data[descendant_id] for descendant_id in descendant_ids])
balance, direction = balance_debit_to_balance_direction(balance_tmp)
account.update({
'fr_current_balance': balance,
'fr_current_direction': direction,
})
# ===================
# 约束方法
# ===================
@api.constrains('fr_init_direction', 'fr_init_balance')
def _constrains_fr_init_balance_direction(self):
for rec in self:
if rec.fr_init_balance < 0:
raise ValidationError('初始余额不能为负!')
elif rec.fr_init_balance != 0.0 and rec.fr_init_direction == 'balance':
raise ValidationError('存在有初始余额的科目余额方向为平!')
elif rec.fr_init_balance != 0.0 and not rec.fr_init_direction:
raise ValidationError('存在有初始余额的科目未设置余额方向!')
@api.constrains('fr_default_rec')
def _constrains_fr_default_rec(self):
for rec in self:
if rec.fr_default_rec is True:
if rec.fr_as_leaf is False:
raise ValidationError('非末级科目无法作为客户默认应收科目!')
if rec.internal_type != 'receivable':
raise ValidationError('非应收类科目无法作为客户默认应收科目!')
if len(self.search([('fr_default_rec', '=', True), ('company_id', '=', rec.company_id.id), ('state', '=', 'on_use')])) > 1:
raise ValidationError('每个公司仅能设置一个客户默认应收科目!')
@api.constrains('fr_default_pay')
def _constrains_fr_default_pay(self):
for rec in self:
if rec.fr_default_pay is True:
if rec.fr_as_leaf is False:
raise ValidationError('非末级科目无法作为客户默认应付科目!')
if rec.internal_type != 'payable':
raise ValidationError('非应付类科目无法作为客户默认应付科目!')
if len(self.search([('fr_default_pay', '=', True), ('company_id', '=', rec.company_id.id), ('state', '=', 'on_use')])) > 1:
raise ValidationError('每个公司仅能设置一个客户默认应付科目!')
# ===================
# 私有方法
# ===================
@api.model
def _update_parent_init_balance_direction(self, accounts):
"""模型方法:更新记录的父级初始余额和方向"""
for account in accounts:
if account.fr_parent_account_id:
parent_account = account.fr_parent_account_id
if parent_account.fr_child_ids and parent_account.fr_as_leaf is False:
# 计算贷方余额合计
sub_accounts_credit = parent_account.fr_child_ids.filtered(
lambda sub_account: sub_account.fr_init_direction == 'credit')
balance_credit = sum(sub_accounts_credit.mapped('fr_init_balance'))
# 计算借方余额合计
sub_accounts_debit = parent_account.fr_child_ids.filtered(
lambda sub_account: sub_account.fr_init_direction == 'debit')
balance_debit = sum(sub_accounts_debit.mapped('fr_init_balance'))
# 计算余额(贷方)
balance = balance_credit - balance_debit
# 计算余额方向
if balance > 0:
direction = 'credit'
elif balance < 0:
balance = - balance
direction = 'debit'
else:
direction = 'balance'
# 更新余额和方向
parent_account.write({
'fr_init_balance': balance,
'fr_init_direction': direction,
})
def _generate_init_move(self, accounts_leaf_debit, accounts_leaf_credit, fiscalyears):
"""模型方法:生成期初分录"""
move_lines = []
# 初始余额方向为借
for account_leaf in accounts_leaf_debit:
move_lines.append((0, 0, {
'name': '期初分录',
'account_id': account_leaf.id,
'debit': account_leaf.fr_init_balance,
'credit': 0,
}))
# 初始余额方向为贷
for account_leaf in accounts_leaf_credit:
move_lines.append((0, 0, {
'name': '期初分录',
'account_id': account_leaf.id,
'debit': 0,
'credit': account_leaf.fr_init_balance,
}))
# 期初日记账获取
journal_id = self.env['account.journal'].search([('name', '=', '期初')], limit=1)
if not journal_id:
journal_id = self.env['account.journal'].create({
'name': '期初',
'code': '',
'type': 'general',
'company_id':self.env.company.id,
})
# 创建凭证
if len(move_lines) > 0:
return self.env['account.move'].create({
'journal_id': journal_id.id,
'num': '/',
'date': fiscalyears[-1].period_ids.filtered(lambda x: x.state in ['ongoing', 'open'])[0].date_start,
'ref': '期初分录',
'line_ids': move_lines,
'state': 'draft',
})
def _fetch_balance_data(self):
# 期末余额预读取
self.env.cr.execute(f"""
SELECT
account_id,
sum( balance ) AS balance
FROM
account_move_line
WHERE
fr_state = 'posted'
GROUP BY
account_id
""")
balance_data = defaultdict(float)
for res in self.env.cr.fetchall():
balance_data[res[0]] = res[1]
return balance_data