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

# -*- 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('请输入正确的年份!')