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.
298 lines
11 KiB
Python
298 lines
11 KiB
Python
# -*- coding: utf-8 -*-
|
|
import datetime
|
|
|
|
from odoo import models, fields, api
|
|
from odoo.exceptions import ValidationError, UserError, MissingError
|
|
|
|
PERIOD_SELECTION = [(str(i+1), '第%s期' % str(i+1)) for i in range(12)]
|
|
|
|
|
|
def get_date_end_year(date_start):
|
|
"""根据开始日期计算一整年时间段的结束日期
|
|
|
|
:param date_start: 日期对象
|
|
:return: 结束日期,日期对象
|
|
"""
|
|
year_end = date_start.year + 1
|
|
month_end = date_start.month
|
|
if date_start.day == 29:
|
|
day_end = date_start.day - 1
|
|
else:
|
|
day_end = date_start.day
|
|
|
|
date_end = datetime.date(year_end, month_end, day_end) - datetime.timedelta(days=1)
|
|
return date_end
|
|
|
|
|
|
class AccountFisclyear(models.Model):
|
|
"""会计年度"""
|
|
_name = 'fr.account.fiscalyear'
|
|
_description = '会计年度'
|
|
_order = 'name desc'
|
|
|
|
_sql_constraints = [('unique_year_company', 'UNIQUE (name, company_id)', '会计年度已存在!')]
|
|
|
|
def _default_get_name(self):
|
|
"""私有方法:获取默认会计年度"""
|
|
last_fiscalyear = self.search([('company_id', '=', self.env.user.company_id.id)], limit=1)
|
|
if not last_fiscalyear:
|
|
name = int(datetime.date.today().year)
|
|
else:
|
|
name = int(last_fiscalyear.name) + 1
|
|
return name
|
|
|
|
def _default_get_date_start(self):
|
|
"""私有方法:获取会计年度默认开始日期"""
|
|
last_fiscalyear = self.search([('company_id', '=', self.env.user.company_id.id)], limit=1)
|
|
if not last_fiscalyear:
|
|
date_start = datetime.date(datetime.date.today().year, 1, 1)
|
|
else:
|
|
date_start = last_fiscalyear.date_end + datetime.timedelta(days=1)
|
|
return date_start
|
|
|
|
# 基础字段
|
|
name = fields.Char(string='会计年度', default=_default_get_name, required=True, index=True)
|
|
date_start = fields.Date(string='开始日期', required=True, default=_default_get_date_start)
|
|
init_period = fields.Selection(selection=PERIOD_SELECTION, string='初始会计期间', default='1', required=True)
|
|
|
|
# 关系字段
|
|
company_id = fields.Many2one('res.company', string='公司', required=True,
|
|
default=lambda self: self.env.company)
|
|
period_ids = fields.One2many('fr.account.period', 'fiscalyear_id', string='会计期间', readonly=True)
|
|
|
|
# 计算字段
|
|
date_end = fields.Date(string='结束日期', compute='_compute_date_end', store=True)
|
|
|
|
# 状态
|
|
state = fields.Selection([
|
|
('draft', '草稿'),
|
|
('activated', '激活'),
|
|
('close', '关闭'),
|
|
], string='状态', readonly=True, copy=False, index=True, default='draft')
|
|
|
|
# ===================
|
|
# 公用方法
|
|
# ===================
|
|
|
|
def activate_fiscalyear(self):
|
|
"""单实例方法:激活会计期间并添加新的总账条目"""
|
|
if not self:
|
|
return
|
|
|
|
# 验证状态
|
|
if set(self.mapped('state')) != {'draft'}:
|
|
raise ValidationError('非草稿状态的会计年度无法激活!')
|
|
|
|
for rec in self:
|
|
# 验证连续
|
|
last_year = rec.get_prev_fiscalyear()
|
|
if last_year and last_year.state == 'draft':
|
|
raise ValidationError('请先激活上一会计年度!')
|
|
|
|
# 限制激活以前的年度
|
|
# activated_year = self.search([('state', '=', 'activated'), ('company_id', '=', rec.company_id.id)], limit=1)
|
|
# if int(activated_year.name) > int(rec.name):
|
|
# raise ValidationError('无法激活以前的会计年度!')
|
|
|
|
rec.write({'state': 'activated'})
|
|
|
|
|
|
def close_fiscalyear(self):
|
|
"""多实例方法:关闭会计年度"""
|
|
if not self:
|
|
return
|
|
|
|
# 操作限制
|
|
if set(self.mapped('state')) != {'activated'}:
|
|
raise ValidationError('非激活状态的会计年度无法关闭!')
|
|
|
|
if not set(self.mapped('period_ids').mapped('state')).issubset({'unuse', 'close'}):
|
|
raise ValidationError('当期会计年度下存在未关闭的会计期间!')
|
|
|
|
self.write({'state': 'close'})
|
|
|
|
|
|
def reopen_fiscalyear(self):
|
|
"""多实例方法:重启会计年度"""
|
|
if not self:
|
|
return
|
|
|
|
# 操作限制
|
|
if set(self.mapped('state')) != {'close'}:
|
|
raise ValidationError('非关闭状态的会计年度无法重启!')
|
|
|
|
self.write({'state': 'activated'})
|
|
|
|
|
|
def get_prev_fiscalyear(self):
|
|
"""单实例方法:获取该会计年度的上一会计年度
|
|
|
|
:return: 会计年度对象,单实例或空对象
|
|
"""
|
|
self.ensure_one()
|
|
|
|
prev_name = str(int(self.name) - 1)
|
|
prev_year = self.search([('name', '=', prev_name), ('company_id', '=', self.company_id.id)])
|
|
|
|
if len(prev_year) > 1:
|
|
raise ValueError('错误!该公司上一会计年度不唯一。')
|
|
|
|
return prev_year
|
|
|
|
|
|
def get_next_fiscalyear(self):
|
|
"""单实例方法:获取该会计年度的下一会计年度
|
|
|
|
:return: 会计年度对象,单实例或空对象
|
|
"""
|
|
self.ensure_one()
|
|
|
|
next_name = str(int(self.name) + 1)
|
|
next_year = self.search([('name', '=', next_name), ('company_id', '=', self.company_id.id)])
|
|
|
|
if len(next_year) > 1:
|
|
raise ValueError('错误!该公司下一会计年度不唯一。')
|
|
|
|
return next_year
|
|
|
|
|
|
def get_period_by_num(self, num):
|
|
"""单实例方法:获取会计年度的指定期间号的会计期间
|
|
|
|
:return: 会计期间对象
|
|
"""
|
|
self.ensure_one()
|
|
|
|
period = self.mapped('period_ids').filtered(lambda x: x.num == num)
|
|
return period
|
|
|
|
# ===================
|
|
# 继承方法
|
|
# ===================
|
|
|
|
def unlink(self):
|
|
if set(self.mapped('state')) != {'draft'}:
|
|
raise ValidationError('无法删除非草稿状态的会计年度!')
|
|
|
|
return super(AccountFisclyear, self).unlink()
|
|
|
|
# ===================
|
|
# 计算方法
|
|
# ===================
|
|
@api.depends('date_start')
|
|
def _compute_date_end(self):
|
|
"""多实例方法:计算会计年度结束时间"""
|
|
for rec in self:
|
|
if rec.date_start:
|
|
rec.date_end = get_date_end_year(rec.date_start)
|
|
|
|
# ===================
|
|
# onchange方法
|
|
# ===================
|
|
@api.onchange('date_start', 'date_end', 'name')
|
|
def _generate_periods(self):
|
|
"""根据开始结束日期自动生成期数"""
|
|
# 验证名称
|
|
self._check_name()
|
|
|
|
# 计算会计期间
|
|
if self.date_start and self.date_end:
|
|
|
|
self.period_ids = None
|
|
months_count = 12
|
|
|
|
# 生成会计期间
|
|
for i in range(months_count):
|
|
# 生成会计期间名称
|
|
num = i + 1
|
|
name = '%s年%s期' % (self.name, num)
|
|
|
|
# 计算会计期间开始日期
|
|
year_start = self.date_start.year
|
|
month_start = self.date_start.month + i
|
|
if month_start >= 12: # 修正年月
|
|
year_start += (month_start - 1) // 12
|
|
month_start = (month_start - 1) % 12 + 1
|
|
date_start = datetime.date(year_start, month_start, 1)
|
|
|
|
# 计算会计期间结束日期
|
|
year_end = year_start
|
|
month_end = month_start + 1
|
|
if month_end > 12: # 修正年月
|
|
year_end += 1
|
|
month_end = 1
|
|
date_end = datetime.date(year_end, month_end, 1) - datetime.timedelta(days=1)
|
|
|
|
# 计算状态
|
|
if date_start <= datetime.date.today() <= date_end:
|
|
state = 'ongoing'
|
|
else:
|
|
state = 'open'
|
|
|
|
# 创建期间
|
|
self.env['fr.account.period'].new({
|
|
'name': name,
|
|
'num': num,
|
|
'year': int(self.name),
|
|
'date_start': date_start,
|
|
'date_end': date_end,
|
|
'state': state,
|
|
'fiscalyear_id': self.id,
|
|
'company_id': self.company_id.id
|
|
})
|
|
|
|
@api.onchange('init_period', 'period_ids')
|
|
def _change_periods_state(self):
|
|
"""多实例方法:根据初始期间更新会计期间状态"""
|
|
current_date = datetime.date.today()
|
|
# 更新无效期间状态
|
|
periods_unuse = self.period_ids.filtered(lambda period: period.num < int(self.init_period))
|
|
periods_unuse.update({'state': 'unuse'})
|
|
|
|
# 更新有效期间状态
|
|
periods_use = self.period_ids - periods_unuse
|
|
periods_use.update({'state': 'open'})
|
|
|
|
# 更新当前期间状态
|
|
period_current = periods_use.filtered(lambda period: period.date_start < current_date < period.date_end)
|
|
period_current.update({'state': 'ongoing'})
|
|
|
|
# ===================
|
|
# 约束方法
|
|
# ===================
|
|
@api.constrains('name')
|
|
def _constrains_fiscalyear_name(self):
|
|
self._check_name()
|
|
|
|
@api.constrains('date_start')
|
|
def _constrains_date_start(self):
|
|
for rec in self:
|
|
if rec.date_start.year != int(rec.name):
|
|
raise ValidationError('年份与日期不匹配!')
|
|
|
|
previous_fiscalyear = rec.get_prev_fiscalyear()
|
|
if previous_fiscalyear and rec.date_start != previous_fiscalyear.date_end + datetime.timedelta(days=1):
|
|
raise ValidationError('当前会计年度开始日期与上一会计年度结束日期不连续!')
|
|
|
|
@api.constrains('init_period')
|
|
def _constrains_init_period(self):
|
|
for rec in self:
|
|
previous_fiscalyear = rec.get_prev_fiscalyear()
|
|
if previous_fiscalyear and rec.init_period != '1':
|
|
raise ValidationError('当前会计年度的会计期间与上一会计年度会计期间状态不连续!')
|
|
|
|
# ===================
|
|
# 私有方法
|
|
# ===================
|
|
|
|
def _check_name(self):
|
|
for rec in self:
|
|
try:
|
|
year = int(rec.name)
|
|
except ValueError:
|
|
raise ValidationError('请输入正确的年份!')
|
|
|
|
current_year = datetime.date.today().year
|
|
if year < current_year - 50 or year > current_year + 50:
|
|
raise ValidationError('请输入正确的年份!')
|