|
|
# -*- 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
|