模块迁移
This commit is contained in:
parent
0d14744c15
commit
cfe31fc044
1
yuthon_attendance/__init__.py
Normal file
1
yuthon_attendance/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import models
|
||||
0
yuthon_attendance/__manifest__.py
Normal file
0
yuthon_attendance/__manifest__.py
Normal file
0
yuthon_attendance/data/demo.xml
Normal file
0
yuthon_attendance/data/demo.xml
Normal file
1
yuthon_attendance/models/__init__.py
Normal file
1
yuthon_attendance/models/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import attendance
|
||||
114
yuthon_attendance/models/attendance.py
Normal file
114
yuthon_attendance/models/attendance.py
Normal file
@ -0,0 +1,114 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import fields, models, api
|
||||
from datetime import timedelta
|
||||
|
||||
# 教学用:规定上班时间为 09:00。
|
||||
# 注意:Odoo 数据库里的 Datetime 以 UTC 存储,这里直接用字段的“小时”做演示比较;
|
||||
# 生产环境应先用 fields.Datetime.context_timestamp() 把时间转换到用户时区再判断。
|
||||
WORK_START_HOUR = 9
|
||||
|
||||
|
||||
class Attendance(models.Model):
|
||||
"""教职工出勤记录。"""
|
||||
_name = 'yuthon.attendance'
|
||||
_description = '出勤记录'
|
||||
_order = 'date desc, employee_id'
|
||||
|
||||
employee_id = fields.Many2one(
|
||||
'yuthon.employee',
|
||||
string='教职工',
|
||||
required=True,
|
||||
ondelete='cascade',
|
||||
)
|
||||
date = fields.Date(
|
||||
string='日期',
|
||||
required=True,
|
||||
default=fields.Date.today,
|
||||
)
|
||||
check_in = fields.Datetime(string='签到时间')
|
||||
check_out = fields.Datetime(string='签退时间')
|
||||
|
||||
# ---- 计算字段 ----
|
||||
working_hours = fields.Float(
|
||||
string='工时(小时)',
|
||||
compute='_compute_working_hours',
|
||||
store=True,
|
||||
)
|
||||
status = fields.Selection(
|
||||
[
|
||||
('normal', '正常'),
|
||||
('late', '迟到'),
|
||||
('absent', '缺勤'),
|
||||
],
|
||||
string='状态',
|
||||
compute='_compute_status',
|
||||
store=True,
|
||||
)
|
||||
late_minutes = fields.Integer(
|
||||
string='迟到分钟',
|
||||
compute='_compute_status',
|
||||
store=True,
|
||||
)
|
||||
|
||||
# ---- 约束 ----
|
||||
_sql_constraints = [
|
||||
('uniq_employee_date', 'unique(employee_id, date)',
|
||||
'同一教职工一天只能有一条出勤记录!'),
|
||||
]
|
||||
|
||||
@api.constrains('check_in', 'check_out')
|
||||
def _check_time_order(self):
|
||||
for rec in self:
|
||||
if rec.check_in and rec.check_out and rec.check_out < rec.check_in:
|
||||
raise models.ValidationError('签退时间不能早于签到时间。')
|
||||
|
||||
@api.depends('check_in', 'check_out')
|
||||
def _compute_working_hours(self):
|
||||
for rec in self:
|
||||
if rec.check_in and rec.check_out and rec.check_out >= rec.check_in:
|
||||
delta = rec.check_out - rec.check_in
|
||||
rec.working_hours = round(delta.total_seconds() / 3600.0, 2)
|
||||
else:
|
||||
rec.working_hours = 0.0
|
||||
|
||||
@api.depends('check_in')
|
||||
def _compute_status(self):
|
||||
for rec in self:
|
||||
if not rec.check_in:
|
||||
rec.status = 'absent'
|
||||
rec.late_minutes = 0
|
||||
continue
|
||||
check_in_hour = rec.check_in.hour + rec.check_in.minute / 60.0
|
||||
if check_in_hour > WORK_START_HOUR:
|
||||
rec.status = 'late'
|
||||
threshold = rec.check_in.replace(
|
||||
hour=WORK_START_HOUR, minute=0, second=0, microsecond=0)
|
||||
rec.late_minutes = max(0, int(
|
||||
(rec.check_in - threshold).total_seconds() // 60))
|
||||
else:
|
||||
rec.status = 'normal'
|
||||
rec.late_minutes = 0
|
||||
|
||||
|
||||
class Employee(models.Model):
|
||||
"""通过 _inherit 反向扩展 yuthon.employee,把出勤记录挂上去。
|
||||
|
||||
这是「模块组合」的关键示范:yuthon_attendance 不修改 yuthon_employee 的源码,
|
||||
而是继承它追加 One2many 反向关联,让两个模块保持解耦。
|
||||
"""
|
||||
_inherit = 'yuthon.employee'
|
||||
|
||||
attendance_ids = fields.One2many(
|
||||
'yuthon.attendance',
|
||||
'employee_id',
|
||||
string='出勤记录',
|
||||
)
|
||||
attendance_count = fields.Integer(
|
||||
string='出勤记录数',
|
||||
compute='_compute_attendance_count',
|
||||
)
|
||||
|
||||
@api.depends('attendance_ids')
|
||||
def _compute_attendance_count(self):
|
||||
for rec in self:
|
||||
rec.attendance_count = len(rec.attendance_ids)
|
||||
0
yuthon_attendance/security/ir.model.access.csv
Normal file
0
yuthon_attendance/security/ir.model.access.csv
Normal file
|
|
114
yuthon_attendance/views/attendance_views.xml
Normal file
114
yuthon_attendance/views/attendance_views.xml
Normal file
@ -0,0 +1,114 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- ============ 列表视图 ============ -->
|
||||
<record id="view_yuthon_attendance_list" model="ir.ui.view">
|
||||
<field name="name">yuthon.attendance.list</field>
|
||||
<field name="model">yuthon.attendance</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="出勤记录" decoration-danger="status == 'absent'"
|
||||
decoration-warning="status == 'late'">
|
||||
<field name="employee_id"/>
|
||||
<field name="date"/>
|
||||
<field name="check_in"/>
|
||||
<field name="check_out"/>
|
||||
<field name="working_hours"/>
|
||||
<field name="status"/>
|
||||
<field name="late_minutes"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ============ 表单视图 ============ -->
|
||||
<record id="view_yuthon_attendance_form" model="ir.ui.view">
|
||||
<field name="name">yuthon.attendance.form</field>
|
||||
<field name="model">yuthon.attendance</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="出勤记录">
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="employee_id"/>
|
||||
<field name="date"/>
|
||||
<field name="check_in"/>
|
||||
<field name="check_out"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="working_hours"/>
|
||||
<field name="status"/>
|
||||
<field name="late_minutes"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ============ 搜索视图 ============ -->
|
||||
<record id="view_yuthon_attendance_search" model="ir.ui.view">
|
||||
<field name="name">yuthon.attendance搜索</field>
|
||||
<field name="model">yuthon.attendance</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="搜索出勤">
|
||||
<field name="employee_id"/>
|
||||
<field name="date"/>
|
||||
<separator/>
|
||||
<filter name="filter_late" string="迟到"
|
||||
domain="[('status', '=', 'late')]"/>
|
||||
<filter name="filter_absent" string="缺勤"
|
||||
domain="[('status', '=', 'absent')]"/>
|
||||
<separator/>
|
||||
<group expand="0" string="分组">
|
||||
<filter name="groupby_employee" string="按教职工"
|
||||
context="{'group_by': 'employee_id'}"/>
|
||||
<filter name="groupby_status" string="按状态"
|
||||
context="{'group_by': 'status'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ============ 图形(迟到分钟汇总) ============ -->
|
||||
<record id="view_yuthon_attendance_graph" model="ir.ui.view">
|
||||
<field name="name">yuthon.attendance.graph</field>
|
||||
<field name="model">yuthon.attendance</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="各教职工迟到分钟数" type="bar">
|
||||
<field name="employee_id" type="row"/>
|
||||
<field name="late_minutes" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ============ 透视表 ============ -->
|
||||
<record id="view_yuthon_attendance_pivot" model="ir.ui.view">
|
||||
<field name="name">yuthon.attendance.pivot</field>
|
||||
<field name="model">yuthon.attendance</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="出勤透视">
|
||||
<field name="employee_id" type="row"/>
|
||||
<field name="status" type="col"/>
|
||||
<field name="late_minutes" type="measure"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ============ 动作 ============ -->
|
||||
<record id="action_yuthon_attendance" model="ir.actions.act_window">
|
||||
<field name="name">出勤记录</field>
|
||||
<field name="res_model">yuthon.attendance</field>
|
||||
<field name="view_mode">list,form,graph,pivot</field>
|
||||
<field name="search_view_id" ref="view_yuthon_attendance_search"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
登记第一条出勤记录
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ============ 子菜单(挂载到 yuthon_employee 的根菜单) ============ -->
|
||||
<menuitem id="menu_yuthon_attendance" name="出勤"
|
||||
parent="yuthon_employee.menu_yuthon_hr_root"
|
||||
action="action_yuthon_attendance" sequence="20"/>
|
||||
|
||||
</odoo>
|
||||
1
yuthon_employee/__init__.py
Normal file
1
yuthon_employee/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import models
|
||||
33
yuthon_employee/__manifest__.py
Normal file
33
yuthon_employee/__manifest__.py
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
'name': 'Yuthon 员工管理',
|
||||
'version': '18.0.1.0.0',
|
||||
'summary': '大学人事基础:教职工档案(工号/部门/岗位/基本工资)',
|
||||
'description': """
|
||||
教学模块①(基础层):yuthon_employee
|
||||
======================================
|
||||
定义教职工(员工)主数据模型 yuthon.employee,是整个「出勤-薪资」体系的
|
||||
数据根(root)。后续 yuthon_attendance、yuthon_salary 都依赖本模块。
|
||||
|
||||
教学要点:
|
||||
- 模型定义:_name / _description
|
||||
- 字段类型:Char / Selection / Float / Date / Boolean
|
||||
- 唯一约束:_sql_constraints(工号不可重复)
|
||||
- 模块组合:被其它模块通过 _inherit 追加反向关联(attendance_ids / payslip_ids)
|
||||
- ACL:ir.model.access.csv 授权
|
||||
""",
|
||||
'author': 'Yuthon',
|
||||
'website': '',
|
||||
'category': 'Education',
|
||||
'depends': ['base', 'web'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'views/employee_views.xml',
|
||||
],
|
||||
'demo': [
|
||||
'data/demo.xml',
|
||||
],
|
||||
'license': 'LGPL-3',
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
0
yuthon_employee/data/demo.xml
Normal file
0
yuthon_employee/data/demo.xml
Normal file
1
yuthon_employee/models/__init__.py
Normal file
1
yuthon_employee/models/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import employee
|
||||
74
yuthon_employee/models/employee.py
Normal file
74
yuthon_employee/models/employee.py
Normal file
@ -0,0 +1,74 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import fields, models, api
|
||||
|
||||
|
||||
class Employee(models.Model):
|
||||
"""教职工(员工)主数据模型。
|
||||
|
||||
这是「出勤-薪资」体系的**数据根**:出勤记录挂在员工下,
|
||||
工资条在月底把「员工基本工资 + 当月出勤聚合结果」join 在一起算出来。
|
||||
"""
|
||||
_name = 'yuthon.employee'
|
||||
_description = '教职工(员工)'
|
||||
_order = 'employee_no'
|
||||
|
||||
# ---- 基础信息 ----
|
||||
employee_no = fields.Char(
|
||||
string='工号',
|
||||
required=True,
|
||||
index=True,
|
||||
help='唯一工号,如 T2024001(教师)/ A2024002(行政)',
|
||||
)
|
||||
name = fields.Char(
|
||||
string='姓名',
|
||||
required=True,
|
||||
)
|
||||
gender = fields.Selection(
|
||||
[('male', '男'), ('female', '女')],
|
||||
string='性别',
|
||||
)
|
||||
department = fields.Selection(
|
||||
[
|
||||
('teaching', '教学部'),
|
||||
('admin', '行政部'),
|
||||
('logistics', '后勤部'),
|
||||
('support', '教辅部'),
|
||||
],
|
||||
string='部门',
|
||||
required=True,
|
||||
default='teaching',
|
||||
)
|
||||
job_title = fields.Char(
|
||||
string='岗位 / 职称',
|
||||
help='如:讲师、副教授、教务员',
|
||||
)
|
||||
phone = fields.Char(string='联系电话')
|
||||
email = fields.Char(string='邮箱')
|
||||
|
||||
# ---- 薪资相关(被 yuthon_salary 读取)----
|
||||
base_salary = fields.Float(
|
||||
string='基本工资',
|
||||
default=0.0,
|
||||
help='教学演示用 Float;生产环境建议改用 Monetary + currency_id',
|
||||
)
|
||||
hire_date = fields.Date(string='入职日期')
|
||||
|
||||
active = fields.Boolean(
|
||||
string='在职',
|
||||
default=True,
|
||||
)
|
||||
|
||||
# ---- 反向关联(由 yuthon_attendance / yuthon_salary 通过 _inherit 追加)----
|
||||
# attendance_ids / payslip_ids 不在此定义,避免基础模块反向依赖业务模块。
|
||||
# 这是「模块解耦」的核心:根模块不知道子模块的存在。
|
||||
|
||||
# ---- 约束 ----
|
||||
_sql_constraints = [
|
||||
('uniq_employee_no', 'unique(employee_no)', '工号已存在,不可重复!'),
|
||||
]
|
||||
|
||||
@api.constrains('base_salary')
|
||||
def _check_base_salary(self):
|
||||
for rec in self:
|
||||
if rec.base_salary < 0:
|
||||
raise models.ValidationError('基本工资不能为负数。')
|
||||
0
yuthon_employee/security/ir.model.access.csv
Normal file
0
yuthon_employee/security/ir.model.access.csv
Normal file
|
|
0
yuthon_employee/views/employee_views.xml
Normal file
0
yuthon_employee/views/employee_views.xml
Normal file
2
yuthon_salary/__init__.py
Normal file
2
yuthon_salary/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from . import models
|
||||
from . import wizards
|
||||
35
yuthon_salary/__manifest__.py
Normal file
35
yuthon_salary/__manifest__.py
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
'name': 'Yuthon 薪资管理',
|
||||
'version': '18.0.1.0.0',
|
||||
'summary': '工资条:聚合出勤迟到/缺勤,自动计算扣款与实发',
|
||||
'description': """
|
||||
教学模块③(汇总层):yuthon_salary
|
||||
======================================
|
||||
依赖 yuthon_employee + yuthon_attendance。定义工资条模型 yuthon.payslip,
|
||||
在月底把「员工基本工资 + 当月出勤聚合结果」join 在一起算出实发工资;
|
||||
并提供「生成月度工资条」向导做批量处理。
|
||||
|
||||
教学要点:
|
||||
- 跨模型聚合:env['yuthon.attendance'].search_count(...) 统计迟到/缺勤
|
||||
- computed field 多级联动:统计 -> 扣款 -> 实发
|
||||
- related 字段:basic_wage 取自员工基本工资
|
||||
- TransientModel 向导:批量生成工资条(讲解 wizard 与批量处理)
|
||||
- 状态机:draft -> done
|
||||
""",
|
||||
'author': 'Yuthon',
|
||||
'website': '',
|
||||
'category': 'Education',
|
||||
'depends': ['yuthon_employee', 'yuthon_attendance', 'web'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'views/payslip_views.xml',
|
||||
'views/payslip_generate_views.xml',
|
||||
],
|
||||
'demo': [
|
||||
'data/demo.xml',
|
||||
],
|
||||
'license': 'LGPL-3',
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
26
yuthon_salary/data/demo.xml
Normal file
26
yuthon_salary/data/demo.xml
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- 工资条聚合“2026-07”整月的出勤记录(来自 yuthon_attendance 的 demo) -->
|
||||
<record id="payslip_zhang_2026_07" model="yuthon.payslip">
|
||||
<field name="employee_id" ref="yuthon_employee.employee_zhang"/>
|
||||
<field name="period_start">2026-07-01</field>
|
||||
<field name="period_end">2026-07-31</field>
|
||||
<field name="allowance">500.0</field>
|
||||
</record>
|
||||
|
||||
<record id="payslip_li_2026_07" model="yuthon.payslip">
|
||||
<field name="employee_id" ref="yuthon_employee.employee_li"/>
|
||||
<field name="period_start">2026-07-01</field>
|
||||
<field name="period_end">2026-07-31</field>
|
||||
<field name="allowance">300.0</field>
|
||||
</record>
|
||||
|
||||
<record id="payslip_wang_2026_07" model="yuthon.payslip">
|
||||
<field name="employee_id" ref="yuthon_employee.employee_wang"/>
|
||||
<field name="period_start">2026-07-01</field>
|
||||
<field name="period_end">2026-07-31</field>
|
||||
<field name="allowance">800.0</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
1
yuthon_salary/models/__init__.py
Normal file
1
yuthon_salary/models/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import payslip
|
||||
106
yuthon_salary/models/payslip.py
Normal file
106
yuthon_salary/models/payslip.py
Normal file
@ -0,0 +1,106 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import fields, models, api
|
||||
|
||||
# 教学用扣款规则(可在 production 改为可配置的 salary.rule 模型)
|
||||
LATE_PENALTY = 50.0 # 迟到一次扣 50
|
||||
DAYS_PER_MONTH = 21.75 # 月计薪天数,用于把“缺勤天数”折算成金额
|
||||
|
||||
|
||||
class Payslip(models.Model):
|
||||
"""工资条:聚合员工当月出勤,自动计算扣款与实发。"""
|
||||
_name = 'yuthon.payslip'
|
||||
_description = '工资条'
|
||||
_order = 'period_end desc, employee_id'
|
||||
|
||||
employee_id = fields.Many2one(
|
||||
'yuthon.employee',
|
||||
string='教职工',
|
||||
required=True,
|
||||
ondelete='cascade',
|
||||
)
|
||||
period_start = fields.Date(string='起始日期', required=True)
|
||||
period_end = fields.Date(string='结束日期', required=True)
|
||||
|
||||
# ---- 金额 ----
|
||||
basic_wage = fields.Float(
|
||||
string='基本工资',
|
||||
related='employee_id.base_salary',
|
||||
store=True,
|
||||
readonly=True,
|
||||
)
|
||||
allowance = fields.Float(string='津贴/补贴', default=0.0)
|
||||
|
||||
# ---- 出勤聚合(自动统计)----
|
||||
late_count = fields.Integer(
|
||||
string='迟到次数', compute='_compute_attendance_stats', store=True)
|
||||
absent_days = fields.Integer(
|
||||
string='缺勤天数', compute='_compute_attendance_stats', store=True)
|
||||
|
||||
# ---- 扣款(自动计算)----
|
||||
late_deduction = fields.Float(
|
||||
string='迟到扣款', compute='_compute_deductions', store=True)
|
||||
absent_deduction = fields.Float(
|
||||
string='缺勤扣款', compute='_compute_deductions', store=True)
|
||||
attendance_deduction = fields.Float(
|
||||
string='出勤扣款合计', compute='_compute_deductions', store=True)
|
||||
|
||||
# ---- 实发 ----
|
||||
net_salary = fields.Float(
|
||||
string='实发工资', compute='_compute_net', store=True)
|
||||
|
||||
state = fields.Selection(
|
||||
[('draft', '草稿'), ('done', '已确认')],
|
||||
string='状态',
|
||||
default='draft',
|
||||
)
|
||||
|
||||
# ---- 计算逻辑 ----
|
||||
@api.depends('employee_id', 'period_start', 'period_end')
|
||||
def _compute_attendance_stats(self):
|
||||
Attendance = self.env['yuthon.attendance']
|
||||
for rec in self:
|
||||
domain = [('employee_id', '=', rec.employee_id.id)]
|
||||
if rec.period_start:
|
||||
domain.append(('date', '>=', rec.period_start))
|
||||
if rec.period_end:
|
||||
domain.append(('date', '<=', rec.period_end))
|
||||
# sudo() 确保在后台重算时不受当前用户 ACL 影响
|
||||
rec.late_count = Attendance.sudo().search_count(
|
||||
domain + [('status', '=', 'late')])
|
||||
rec.absent_days = Attendance.sudo().search_count(
|
||||
domain + [('status', '=', 'absent')])
|
||||
|
||||
@api.depends('late_count', 'absent_days', 'basic_wage')
|
||||
def _compute_deductions(self):
|
||||
for rec in self:
|
||||
rec.late_deduction = rec.late_count * LATE_PENALTY
|
||||
daily = (rec.basic_wage / DAYS_PER_MONTH) if rec.basic_wage else 0.0
|
||||
rec.absent_deduction = rec.absent_days * daily
|
||||
rec.attendance_deduction = rec.late_deduction + rec.absent_deduction
|
||||
|
||||
@api.depends('basic_wage', 'allowance', 'attendance_deduction')
|
||||
def _compute_net(self):
|
||||
for rec in self:
|
||||
rec.net_salary = rec.basic_wage + rec.allowance - rec.attendance_deduction
|
||||
|
||||
# ---- 业务动作 ----
|
||||
def action_confirm(self):
|
||||
self.write({'state': 'done'})
|
||||
|
||||
def action_draft(self):
|
||||
self.write({'state': 'draft'})
|
||||
|
||||
|
||||
class Employee(models.Model):
|
||||
"""_inherit 反向扩展:把工资条挂到员工上。"""
|
||||
_inherit = 'yuthon.employee'
|
||||
|
||||
payslip_ids = fields.One2many(
|
||||
'yuthon.payslip', 'employee_id', string='工资条')
|
||||
payslip_count = fields.Integer(
|
||||
string='工资条数', compute='_compute_payslip_count')
|
||||
|
||||
@api.depends('payslip_ids')
|
||||
def _compute_payslip_count(self):
|
||||
for rec in self:
|
||||
rec.payslip_count = len(rec.payslip_ids)
|
||||
0
yuthon_salary/security/ir.model.access.csv
Normal file
0
yuthon_salary/security/ir.model.access.csv
Normal file
|
|
40
yuthon_salary/views/payslip_generate_views.xml
Normal file
40
yuthon_salary/views/payslip_generate_views.xml
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- ============ 向导表单 ============ -->
|
||||
<record id="view_yuthon_payslip_generate_form" model="ir.ui.view">
|
||||
<field name="name">yuthon.payslip.generate.form</field>
|
||||
<field name="model">yuthon.payslip.generate.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="生成月度工资条">
|
||||
<group>
|
||||
<field name="period_start"/>
|
||||
<field name="period_end"/>
|
||||
<field name="allowance"/>
|
||||
<field name="employee_ids" widget="many2many_tags"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="action_generate" type="object"
|
||||
string="生成" class="btn-primary"/>
|
||||
<button string="取消" class="btn-secondary"
|
||||
special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ============ 向导动作 ============ -->
|
||||
<record id="action_yuthon_payslip_generate" model="ir.actions.act_window">
|
||||
<field name="name">生成月度工资条</field>
|
||||
<field name="res_model">yuthon.payslip.generate.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<!-- ============ 子菜单:放在“薪资”下,方便一键批量生成 ============ -->
|
||||
<menuitem id="menu_yuthon_payslip_generate"
|
||||
name="生成月度工资条"
|
||||
parent="yuthon_employee.menu_yuthon_hr_root"
|
||||
action="action_yuthon_payslip_generate" sequence="31"/>
|
||||
|
||||
</odoo>
|
||||
108
yuthon_salary/views/payslip_views.xml
Normal file
108
yuthon_salary/views/payslip_views.xml
Normal file
@ -0,0 +1,108 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- ============ 列表视图 ============ -->
|
||||
<record id="view_yuthon_payslip_list" model="ir.ui.view">
|
||||
<field name="name">yuthon.payslip.list</field>
|
||||
<field name="model">yuthon.payslip</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="工资条" decoration-info="state == 'draft'">
|
||||
<field name="employee_id"/>
|
||||
<field name="period_start"/>
|
||||
<field name="period_end"/>
|
||||
<field name="basic_wage"/>
|
||||
<field name="allowance"/>
|
||||
<field name="late_count"/>
|
||||
<field name="absent_days"/>
|
||||
<field name="attendance_deduction"/>
|
||||
<field name="net_salary" decoration-bf="1"/>
|
||||
<field name="state"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ============ 表单视图 ============ -->
|
||||
<record id="view_yuthon_payslip_form" model="ir.ui.view">
|
||||
<field name="name">yuthon.payslip.form</field>
|
||||
<field name="model">yuthon.payslip</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="工资条">
|
||||
<header>
|
||||
<button name="action_confirm" type="object"
|
||||
string="确认" class="btn-primary" invisible="state != 'draft'"/>
|
||||
<button name="action_draft" type="object"
|
||||
string="转草稿" invisible="state != 'done'"/>
|
||||
<field name="state" widget="statusbar"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="employee_id"/>
|
||||
<field name="period_start"/>
|
||||
<field name="period_end"/>
|
||||
<field name="state" invisible="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="basic_wage"/>
|
||||
<field name="allowance"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group string="出勤聚合">
|
||||
<field name="late_count"/>
|
||||
<field name="absent_days"/>
|
||||
</group>
|
||||
<group string="扣款与实发">
|
||||
<field name="late_deduction"/>
|
||||
<field name="absent_deduction"/>
|
||||
<field name="attendance_deduction"/>
|
||||
<field name="net_salary" decoration-bf="1"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ============ 搜索视图 ============ -->
|
||||
<record id="view_yuthon_payslip_search" model="ir.ui.view">
|
||||
<field name="name">yuthon.payslip搜索</field>
|
||||
<field name="model">yuthon.payslip</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="搜索工资条">
|
||||
<field name="employee_id"/>
|
||||
<field name="period_start"/>
|
||||
<field name="period_end"/>
|
||||
<separator/>
|
||||
<filter name="filter_draft" string="草稿"
|
||||
domain="[('state', '=', 'draft')]"/>
|
||||
<filter name="filter_done" string="已确认"
|
||||
domain="[('state', '=', 'done')]"/>
|
||||
<separator/>
|
||||
<group expand="0" string="分组">
|
||||
<filter name="groupby_employee" string="按教职工"
|
||||
context="{'group_by': 'employee_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ============ 动作 ============ -->
|
||||
<record id="action_yuthon_payslip" model="ir.actions.act_window">
|
||||
<field name="name">工资条</field>
|
||||
<field name="res_model">yuthon.payslip</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="search_view_id" ref="view_yuthon_payslip_search"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
先用「生成月度工资条」创建第一张工资条
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ============ 子菜单(挂载到员工模块的根菜单) ============ -->
|
||||
<menuitem id="menu_yuthon_payslip" name="薪资"
|
||||
parent="yuthon_employee.menu_yuthon_hr_root"
|
||||
action="action_yuthon_payslip" sequence="30"/>
|
||||
|
||||
</odoo>
|
||||
0
yuthon_salary/wizards/__init__.py
Normal file
0
yuthon_salary/wizards/__init__.py
Normal file
0
yuthon_salary/wizards/payslip_generate.py
Normal file
0
yuthon_salary/wizards/payslip_generate.py
Normal file
Loading…
Reference in New Issue
Block a user