!5074 添加功能

Merge pull request !5074 from 李鹏宇/dev_zhou
This commit is contained in:
李鹏宇
2025-09-11 02:49:06 +00:00
committed by Gitee
27 changed files with 353 additions and 77 deletions
+80 -1
View File
@@ -4,6 +4,7 @@ from odoo import models, fields, api, _
from odoo.exceptions import ValidationError, UserError
import logging
from datetime import datetime, date, time, timedelta
import re
_logger = logging.getLogger(__name__)
@@ -52,7 +53,85 @@ class SoongWorkflowRecord(models.Model):
member_of_department = fields.Boolean("是否是本部门数据", compute='_compute_part_of_department', search='_search_part_of_department')
def admin_end(self):
pass
workflow_ids = self.env["soong.workflow.record"].browse(
self._context.get('active_ids', self._context.get('active_id', []))
)
current_datetime = fields.Datetime.now()
for workflow in workflow_ids:
record_id = self.env[workflow.res_model].search([('id', '=', workflow.res_id)])
if not record_id:
continue
log_records = None
if record_id.log_ids2:
log_records = record_id.log_ids2
elif record_id.log_ids:
log_records = record_id.log_ids
last_log = log_records[-1:]
if last_log:
last_log.write({
'approval_state': '3',
'lase_click_date': current_datetime,
})
end_node = self.env["soong.workflow.node"].search(
[('workflow_id', '=', workflow.workflow_id.id), ('is_end', '=', True)],
limit=1
)
end_btn = self.env["soong.workflow.node.button"].search(
[('node_id', '=', end_node.id),
('name', '=', '结束')],
limit=1
)
if end_btn:
self.execute_button_code(end_btn.id, workflow.res_model, workflow.res_id)
workflow.approve_state = 'end'
def execute_button_code(self, button_id, res_model, res_id):
"""
执行按钮code字段中的代码,动态解析并验证方法是否存在
:param button_id: soong.workflow.node.button的记录ID
:param res_model: 模型名称
"""
button = self.env["soong.workflow.node.button"].browse(button_id)
if not button or not button.code:
return
workflow = button.node_id.workflow_id if button.node_id else None
obj = self.env[res_model].browse(res_id)
if not obj:
raise UserError(f"找不到{res_model}模型中ID为{res_id}的记录")
# 从code中提取obj调用的方法名(匹配obj.xxx()格式)
method_match = re.search(r'obj\.(\w+)\(\)', button.code)
if not method_match:
raise UserError("该按钮没有配置结束的方法,请先配置!")
# 准备代码执行的上下文环境
local_context = {
'time': time,
'date': date,
'datetime': datetime,
'timedelta': timedelta,
'env': self.env,
'model': self.env[res_model],
'obj': obj,
'user': self.env.user,
'workflow': workflow,
'syslog': self._log_debug,
'warning': self._get_warning_exception,
'action': None
}
exec(button.code, globals(), local_context)
# 如果有返回的action,处理它
if local_context.get('action'):
return local_context['action']
def _log_debug(self, message):
"""用于syslog的实现"""
self.env.logger.debug(message)
def _get_warning_exception(self, message):
"""用于warning的实现"""
return Warning(message)
def update_self_users_ids(self):
record_ids = self.env["soong.workflow.record"].browse(
@@ -64,7 +64,7 @@
decoration-danger="approve_state == 'end'" delete="false"
create="false" default_order="write_date2 desc, id desc" expand="1">
<header>
<button name="admin_end" type="object" string="管理员强制结束" class="oe_highlight"/>
<button name="admin_end" type="object" string="管理员强制结束" class="oe_highlight" groups="soong_workflow.group_soong_workflow_manager"/>
<button name="delete_record" type="object" string="删除" class="oe_highlight" groups="soong_workflow.group_soong_workflow_manager"/>
</header>
<field name="code"/>
@@ -172,7 +172,7 @@
<field name="type">ir.actions.act_window</field>
<field name="res_model">soong.workflow.record</field>
<field name="view_mode">tree,kanban,search</field>
<field name="domain">[('reclaim_state', '=', False), '|', ('approvals_group_id.name', '=', '人力资源'), ('approvals_group_id.name', '=', '考勤')]</field>
<field name="domain">[('reclaim_state', '=', False), '|', ('approvals_group_id.name', '=', '人力资源'), ('approvals_group_id.name', '=', '考勤'), ('approve_state', '!=', 'start')]</field>
<field name="search_view_id" ref="soong_workflow_record_search"/>
</record>
<record id="soong_workflow_other_record_action" model="ir.actions.act_window">
@@ -24,7 +24,7 @@ class SoongWorkflowCommentWizard(models.TransientModel):
node_link_len = fields.Char(string="转交2")
is_supervising = fields.Boolean(string="是否督办")
comment_note_id = fields.Many2one('soong.note', string="常用意见", default='妥否,呈领导审批')
comment_note = fields.Char(string="意见")
comment_note = fields.Char(string="意见", default='同意')
comment = fields.Char(compute='_compute_comment', store=True)
state_list = fields.Char(string="状态列表")
record_id = fields.Integer()
@@ -492,13 +492,32 @@ class SoongWorkflowCommentWizard(models.TransientModel):
related_record.self_users_ids = [(6, 0, user_ids)] if user_ids else [(5,)]
return self.node_btn_id.with_context(cx).run()
if self.node_btn_name == '结束':
record_id.log_ids[-1].comment = self.comment
record_id.log_ids[-1].approval_state = "3"
record_id.log_ids[-1].lase_click_date = fields.Datetime.now()
record_id.work_end = True
self.env['soong.workflow.record'].search([('code', '=', record_id.code)]).write({'approve_state': 'end',
"employee_ids": False,
"node_id": False,})
# 获取当前登录用户
current_user = self.env.user
# 获取当前时间
current_datetime = fields.Datetime.now()
log_records = None
if record_id.log_ids2:
log_records = record_id.log_ids2
elif record_id.log_ids:
log_records = record_id.log_ids
if log_records:
# 查找当前用户的日志记录
user_log = log_records.filtered(lambda log: log.done_by == current_user)
if user_log:
user_log.write({
'comment': self.comment,
'approval_state': "3",
'lase_click_date': current_datetime
})
record_id.work_end = True
self.env['soong.workflow.record'].search([
('code', '=', record_id.code)
]).write({
'approve_state': 'end',
'employee_ids': False,
'node_id': False,
})
# 给申请人发消息
biz_record = self.env[self.res_model].browse(self.res_id)
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
import{_ as e}from"./index-CimcMLIq.js";const r={};function c(n,t){return null}const o=e(r,[["render",c]]);export{o as default};
@@ -0,0 +1 @@
import{B as _,E as i,C as d,D as v}from"./requests-DdoUQiv8.js";import{_ as p,r as h,o as m,u as y,h as f,i as l,g as a,t,k,l as g}from"./index-CimcMLIq.js";const w=r=>_("/yuthon/yuthon/will/task",{id:r}),x={class:"container"},B={class:"meeting-header"},C={class:"title"},q={class:"time"},D={class:"location"},E={__name:"Journey",setup(r){const u=y(),e=h({});return m(async()=>{try{const s=parseInt(u.query.id),n=await w(s);e.value=n.data}catch(s){i("获取任务详情失败"),console.error(s)}}),(s,n)=>{const o=v,c=d;return g(),f("div",x,[l("div",B,[l("div",C,t(e.value.name),1),l("div",q,t(e.value.start_date)+" "+t(e.value.start_date_week)+" "+t(e.value.request_hour_from)+" - "+t(e.value.request_hour_to),1),l("div",D,"地址: "+t(e.value.address),1)]),a(c,null,{default:k(()=>[a(o,{title:"组织人",value:e.value.user_id},null,8,["value"]),a(o,{title:"参与人",value:"共"+(e.value.employee_ids?.length||0)+"人"},null,8,["value"]),a(o,{title:"描述",value:e.value.remarks},null,8,["value"]),a(o,{title:"任务编号",value:e.value.code},null,8,["value"])]),_:1})])}}},N=p(E,[["__scopeId","data-v-0aa7162a"]]);export{N as default};
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
import{v as B,t as A,c as p,H as E,I as V,j as w,n as b,F as y,B as q,E as H,C as O,D as j}from"./requests-DdoUQiv8.js";import{d as x,g as a,T as G,F as I,s as K,_ as M,r as R,o as J,u as Q,h as S,i as f,t as k,k as P,l as C,p as U,q as W}from"./index-CimcMLIq.js";import{I as X}from"./index-BJ3ARmPk.js";const[Y,T]=p("tag"),Z={size:String,mark:Boolean,show:A,type:B("default"),color:String,plain:Boolean,round:Boolean,textColor:String,closeable:Boolean};var ee=x({name:Y,props:Z,emits:["close"],setup(t,{slots:e,emit:u}){const l=o=>{o.stopPropagation(),u("close",o)},r=()=>t.plain?{color:t.textColor||t.color,borderColor:t.color}:{color:t.textColor,background:t.color},d=()=>{var o;const{type:m,mark:i,plain:c,round:v,size:s,closeable:_}=t,g={mark:i,plain:c,round:v};s&&(g[s]=s);const h=_&&a(V,{name:"cross",class:[T("close"),E],onClick:l},null);return a("span",{style:r(),class:T([g,m])},[(o=e.default)==null?void 0:o.call(e),h])};return()=>a(G,{name:t.closeable?"van-fade":void 0},{default:()=>[t.show?d():null]})}});const te=w(ee),[ae,n]=p("card"),ne={tag:String,num:b,desc:String,thumb:String,title:String,price:b,centered:Boolean,lazyLoad:Boolean,currency:B("¥"),thumbLink:String,originPrice:b};var re=x({name:ae,props:ne,emits:["clickThumb"],setup(t,{slots:e,emit:u}){const l=()=>{if(e.title)return e.title();if(t.title)return a("div",{class:[n("title"),"van-multi-ellipsis--l2"]},[t.title])},r=()=>{if(e.tag||t.tag)return a("div",{class:n("tag")},[e.tag?e.tag():a(te,{mark:!0,type:"primary"},{default:()=>[t.tag]})])},d=()=>e.thumb?e.thumb():a(X,{src:t.thumb,fit:"cover",width:"100%",height:"100%",lazyLoad:t.lazyLoad},null),o=()=>{if(e.thumb||t.thumb)return a("a",{href:t.thumbLink,class:n("thumb"),onClick:c=>u("clickThumb",c)},[d(),r()])},m=()=>{if(e.desc)return e.desc();if(t.desc)return a("div",{class:[n("desc"),"van-ellipsis"]},[t.desc])},i=()=>{const c=t.price.toString().split(".");return a("div",null,[a("span",{class:n("price-currency")},[t.currency]),a("span",{class:n("price-integer")},[c[0]]),c.length>1&&a(I,null,[K("."),a("span",{class:n("price-decimal")},[c[1]])])])};return()=>{var c,v,s;const _=e.num||y(t.num),g=e.price||y(t.price),h=e["origin-price"]||y(t.originPrice),D=_||g||h||e.bottom,F=g&&a("div",{class:n("price")},[e.price?e.price():i()]),L=h&&a("div",{class:n("origin-price")},[e["origin-price"]?e["origin-price"]():`${t.currency} ${t.originPrice}`]),N=_&&a("div",{class:n("num")},[e.num?e.num():`x${t.num}`]),$=e.footer&&a("div",{class:n("footer")},[e.footer()]),z=D&&a("div",{class:n("bottom")},[(c=e["price-top"])==null?void 0:c.call(e),F,L,N,(v=e.bottom)==null?void 0:v.call(e)]);return a("div",{class:n()},[a("div",{class:n("header")},[o(),a("div",{class:n("content",{centered:t.centered})},[a("div",null,[l(),m(),(s=e.tags)==null?void 0:s.call(e)]),z])]),$])}}});const ce=w(re),oe=t=>q("/yuthon/yuthon/salary",{id:t}),ie={class:"container"},le={class:"header"},se={class:"salary-amount"},ue={class:"detail-section"},de={__name:"Salary",setup(t){const e=R({}),u=Q();return J(async()=>{const l=parseInt(u.query.id);try{const r=await oe(l);console.log(r),e.value=r.data}catch(r){H("获取工资条失败"),console.error(r)}}),(l,r)=>{const d=ce,o=j,m=O;return C(),S("div",ie,[f("div",le,k(e.value.date_from)+" - "+k(e.value.date_to)+" 工资条 ",1),a(d,{class:"salary-card",title:"实发工资",bordered:""},{desc:P(()=>[f("div",se,k(e.value.amount_total),1)]),_:1}),f("div",ue,[r[0]||(r[0]=f("div",{class:"section-title"},"明细",-1)),a(m,null,{default:P(()=>[(C(!0),S(I,null,U(e.value.lines_ids,i=>(C(),W(o,{key:i.name,title:i.name,value:i.total},null,8,["title","value"]))),128))]),_:1})])])}}},_e=M(de,[["__scopeId","data-v-4564b9eb"]]);export{_e as default};
@@ -0,0 +1 @@
import{v as P,t as S,n as d,c as B,G as u,F as $,J as j,I as D,j as _}from"./requests-DdoUQiv8.js";import{d as C,r as f,v as F,c as N,w as R,f as U,o as G,n as z,g as n,x as J,y as M,m as b}from"./index-CimcMLIq.js";const[O,i]=B("image"),T={src:String,alt:String,fit:String,position:String,round:Boolean,block:Boolean,width:d,height:d,radius:d,lazyLoad:Boolean,iconSize:d,showError:S,errorIcon:P("photo-fail"),iconPrefix:String,showLoading:S,loadingIcon:P("photo"),crossorigin:String,referrerpolicy:String,decoding:String};var V=C({name:O,props:T,emits:["load","error"],setup(r,{emit:g,slots:c}){const t=f(!1),a=f(!0),o=f(),{$Lazyload:l}=F().proxy,x=N(()=>{const e={width:u(r.width),height:u(r.height)};return $(r.radius)&&(e.overflow="hidden",e.borderRadius=u(r.radius)),e});R(()=>r.src,()=>{t.value=!1,a.value=!0});const v=e=>{a.value&&(a.value=!1,g("load",e))},m=()=>{const e=new Event("load");Object.defineProperty(e,"target",{value:o.value,enumerable:!0}),v(e)},h=e=>{t.value=!0,a.value=!1,g("error",e)},y=(e,s,I)=>I?I():n(D,{name:e,size:r.iconSize,class:s,classPrefix:r.iconPrefix},null),E=()=>{if(a.value&&r.showLoading)return n("div",{class:i("loading")},[y(r.loadingIcon,i("loading-icon"),c.loading)]);if(t.value&&r.showError)return n("div",{class:i("error")},[y(r.errorIcon,i("error-icon"),c.error)])},k=()=>{if(t.value||!r.src)return;const e={alt:r.alt,class:i("img"),decoding:r.decoding,style:{objectFit:r.fit,objectPosition:r.position},crossorigin:r.crossorigin,referrerpolicy:r.referrerpolicy};return r.lazyLoad?J(n("img",b({ref:o},e),null),[[M("lazy"),r.src]]):n("img",b({ref:o,src:r.src,onLoad:v,onError:h},e),null)},w=({el:e})=>{const s=()=>{e===o.value&&a.value&&m()};o.value?s():z(s)},L=({el:e})=>{e===o.value&&!t.value&&h()};return l&&j&&(l.$on("loaded",w),l.$on("error",L),U(()=>{l.$off("loaded",w),l.$off("error",L)})),G(()=>{z(()=>{var e;(e=o.value)!=null&&e.complete&&!r.lazyLoad&&m()})}),()=>{var e;return n("div",{class:i({round:r.round,block:r.block}),style:x.value},[k(),E(),(e=c.default)==null?void 0:e.call(c)])}}});const H=_(V);export{H as I};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+87 -12
View File
@@ -82,15 +82,45 @@ class GoApprovals(models.Model):
'approval_date': fields.Datetime.now(),
})
@api.depends('users_id', 'days', 'hours')
@api.depends('employee_ids', 'days', 'hours', 'statr_date', 'end_date', 'date_start', 'date_end', 'is_day', 'is_half',
'is_hour')
def _compute_name(self):
for rec in self:
if rec.is_hour == True:
rec.name = f"{rec.users_id.name}外出{rec.hours}小时"
elif rec.is_day == True:
rec.name = f"{rec.users_id.name}外出{rec.days}天"
employee_list = [employee.name for employee in rec.employee_ids]
# 将员工列表转换为逗号分隔的字符串
employees_str = ", ".join(employee_list)
base_name = f"{employees_str}外出"
date_info = ""
if rec.is_hour:
# 按小时
if rec.date_start and rec.date_end:
start_date = fields.Date.from_string(rec.date_start)
end_date = fields.Date.from_string(rec.date_end)
start_str = start_date.strftime('%Y-%m-%d')
end_str = end_date.strftime('%Y-%m-%d')
if start_date == end_date:
date_info = f"{start_str}共:{rec.hours}小时"
else:
date_info = f"{start_str}至{end_str}共:{rec.hours}小时"
elif rec.is_day or rec.is_half:
# 按天或半天
if rec.statr_date and rec.end_date:
start_str = rec.statr_date.strftime('%Y-%m-%d')
end_str = rec.end_date.strftime('%Y-%m-%d')
if rec.statr_date == rec.end_date:
date_part = start_str
else:
date_part = f"{start_str}至{end_str}"
if rec.is_day:
date_info = f"{date_part}共:{rec.days}天"
else:
date_info = f"{date_part}共:{rec.days}天"
if date_info:
rec.name = f"{base_name}{date_info}"
else:
rec.name = f"{rec.users_id.name}外出"
rec.name = base_name
@api.onchange('statr_date', 'end_date')
def _onchange_days(self):
@@ -138,23 +168,68 @@ class GoApprovals(models.Model):
def end_approval(self):
if self.meal:
# 创建日志
go_approval_list = []
first_day = self.statr_date.replace(day=1)
reports_id = self.env['yuthon.vacation.reports'].search([
('employee_id', '=', self.users_id.employee_id.id),
('vacation_date', '=', first_day)
])
workflow_record_id = self.env['soong.workflow.record'].search([
('code', '=', self.code)
])
go_approval_list.append((0, 0, {
meal_fee_value = (0, 0, {
'vacation_id': reports_id.id,
'update_type': 'add',
'workflow_record_id': workflow_record_id.id,
'update_date': fields.Datetime.now(),
'update_lines': 1 ,
'update_lines': 1,
'text': '审批'
}))
reports_id.write({'meal_fee_ids': [(0, 0, go_approval_list)]})
})
reports_id.write({'meal_fee_ids': [meal_fee_value]})
for employee in self.employee_ids:
if self.is_day or self.is_half:
start = self.statr_date
end = self.end_date
date_field = 'current_data'
elif self.is_hour:
start = self.date_start.date() if self.date_start else None
end = self.date_end.date() if self.date_end else None
date_field = 'current_data'
else:
return True
if not (start and end):
return True
attendance_records = self.env['hr.attendance'].search([
('employee_id', '=', employee.id),
(date_field, '>=', start),
(date_field, '<=', end)
])
if attendance_records:
attendance_records.write({
'is_clock_card': True,
'is_no_card': False,
'is_early_card': False,
'is_late_card': False,
'is_abnormal': False,
'absenteeism_card': False
})
else:
current_date = start
while current_date <= end:
self.env['hr.attendance'].create({
'employee_id': employee.id,
date_field: current_date,
'is_clock_card': True,
'check_in': False,
'check_out': False,
'is_no_card': False,
'is_early_card': False,
'is_late_card': False,
'is_abnormal': False,
'absenteeism_card': False
})
# 日期递增
current_date += timedelta(days=1)
+9 -2
View File
@@ -219,7 +219,7 @@ class HrAttendance(models.Model):
if record.absenteeism_card:
absenteeism_card += 1
if has_abnormal:
url = f"http://oa.thtzjt.com/#/clock_in_staticstis?user_id={employee.user_id.id}"
url = f"https://oa.thtzjt.com/webh5#/clock_in_staticstis?user_id={employee.user_id.id}"
self.sudo().env["wecom.apps"].sync_send_message_textcard(category='att', user_id=employee.wecom_userid, title="打卡异常提醒",
description="本月迟到: {}\n本月缺卡: {}\n本月早退: {}\n本月异常:{}\n 本月旷工: {}\n"
.format(is_late_card, is_no_card, is_early_card, is_abnormal, absenteeism_card),
@@ -264,7 +264,7 @@ class HrAttendance(models.Model):
# 工作日打卡情况判断
if is_workday:
if employee_record:
is_no_card = not employee_record.check_in or not employee_record.check_out
is_no_card = (not employee_record.check_in or not employee_record.check_out) and not employee_record.is_clock_card
else:
is_no_card = True
else:
@@ -1044,6 +1044,13 @@ class HrAttendance(models.Model):
self._update_vacation_report(record)
return result
def unlink(self):
for record in self:
self._update_vacation_report(record)
result = super(HrAttendance, self).unlink()
return result
def _update_vacation_report(self, record):
"""更新相关的假期报表日志"""
if record.current_data and record.employee_id:
@@ -110,7 +110,9 @@ class HrAttendanceDetail(models.Model):
for resource_id in role.resource_ids:
employee_list.append(resource_id.employee_id.id)
if not record.employee_id.id in employee_list:
days = self.env['yuthon.vacation.reports']._create_leave_record(slot_start_dt, slot_end_dt)
start_dt = slot_start_dt.date()
end_dt = slot_end_dt.date()
days = self.env['yuthon.vacation.reports']._create_leave_record(record.employee_id, start_dt, end_dt)
else:
monthly_slots = self.env['planning.slot'].search([
('employee_id', '=', record.employee_id.id),
@@ -13,6 +13,7 @@ class WifiMessage(models.Model):
mac = fields.Char(string='MAC地址')
name = fields.Char(string='详细地址')
bssid = fields.Char(string='BSSID')
create_time = fields.Date(string='创建日期', default=lambda self: fields.Date.today())
company_id = fields.Many2one("res.company", string="公司", default=lambda self: self.env.company)
@@ -22,13 +22,13 @@ class YuthonVacationReports(models.Model):
meal_fee_ids = fields.One2many('vacation.meal.line', 'vacation_id', string='误餐明细')
medical_fee_ids = fields.One2many('vacation.medical.line', 'vacation_id', string='体检费明细')
other_fee_ids = fields.One2many('vacation.other.line', 'vacation_id', string='其他明细')
vacation_date = fields.Date(string='日期', default=lambda self: fields.Date.today())
vacation_date = fields.Date(string='日期', default=lambda self: fields.Date.today().replace(day=1))
display_date = fields.Char(string='日期', compute='_compute_display_date')
done_leave = fields.Float(string='年假', compute='_compute_leave', store=True, digits=(16, 3), group_operator=None)
done_working_overtime = fields.Float(string='加班时', compute='_compute_working_overtime', digits=(16, 3),
store=True, group_operator=None)
leave = fields.Float(string='剩余年假', compute='_compute_leave', store=True, digits=(16, 3), group_operator=None)
working_overtime = fields.Float(string='剩余加班时', compute='_compute_working_overtime', store=True,
working_overtime = fields.Float(string='剩余补休', compute='_compute_working_overtime', store=True,
digits=(16, 3), group_operator=None)
meal_fee = fields.Integer(string="误餐次数", compute='_compute_meal_fee', store=True, group_operator=None)
medical_fee = fields.Float(string="体检费", compute='_compute_medical_fee', store=True, group_operator=None)
@@ -36,7 +36,7 @@ class YuthonVacationReports(models.Model):
leave_of_absence = fields.Float(string="事假", digits=(16, 3), compute='_compute_other_leave', store=True,
group_operator=None)
statutory_leave = fields.Integer(string="加班时(法定)", compute='_compute_other_leave', store=True, group_operator=None)
cop_leave = fields.Float(string='补休', digits=(16, 3), compute='_compute_other_leave', store=True,
cop_leave = fields.Float(string='补休', digits=(16, 3), store=True, compute='_compute_working_overtime',
group_operator=None)
duty_off_leave = fields.Integer(string='值班调休', compute='_compute_other_leave', store=True, group_operator=None)
marriage_leave = fields.Integer(string='婚假', compute='_compute_other_leave', store=True, group_operator=None)
@@ -73,6 +73,21 @@ class YuthonVacationReports(models.Model):
tracking=3,
default='unconfirmed')
@api.constrains('employee_id', 'vacation_date')
def _check_duplicate_monthly_record(self):
"""检查是否存在同一员工当月的重复记录"""
for record in self:
duplicate_records = self.search([
('employee_id', '=', record.employee_id.id),
('vacation_date', '=', record.vacation_date),
('id', '!=', record.id)
])
if duplicate_records:
raise ValidationError(
f"员工 {record.employee_id.name} 在 {record.vacation_date.strftime('%Y年%m月')} "
f"已存在假期额度记录,不能重复创建!"
)
def confirm_state(self):
for rec in self:
rec.vacation_state = 'confirmed'
@@ -131,8 +146,16 @@ class YuthonVacationReports(models.Model):
days_in_month = calendar.monthrange(year, month)[1]
month_start = date(year, month, 1)
month_end = date(year, month, days_in_month)
leave_days = rec._create_leave_record(month_start, month_end)
for day in range(1, days_in_month + 1):
leave_days = rec._create_leave_record(rec.employee_id, month_start, month_end)
entry_date = rec.employee_id.entry_date
if month_start > entry_date:
start_day = 1
else:
days_in_month = (date(year, month + 1, 1) - timedelta(days=1)).day
start_day = min(entry_date.day, days_in_month)
days_in_month = (date(year, month + 1, 1) - timedelta(days=1)).day
actual_days_range = range(start_day, days_in_month + 1)
for day in actual_days_range:
current_date = date(year, month, day)
attendances = self.env["hr.attendance"].sudo().search([
('employee_id', '=', rec.employee_id.id),
@@ -172,8 +195,8 @@ class YuthonVacationReports(models.Model):
rec._compute_attendance_fields1()
def update_data(self):
august_start = fields.Date.from_string('2025-08-01')
august_end = fields.Date.from_string('2025-08-31')
august_start = fields.Date.from_string('2025-09-01')
august_end = fields.Date.from_string('2025-9-30')
august_records = self.env["yuthon.vacation.reports"].search([
('vacation_date', '>=', august_start),
('vacation_date', '<=', august_end)
@@ -228,7 +251,10 @@ class YuthonVacationReports(models.Model):
for field in int_fields:
setattr(rec, field, getattr(rec, field) or 0)
def _create_leave_record(self, start_date, end_date):
def _create_leave_record(self, employee_id, start_date, end_date):
entry_date = employee_id.entry_date
# 调整日期范围:如果开始日期早于入职日期,则从入职日期开始计算
actual_start_date = max(start_date, entry_date) if entry_date else start_date
calendar_leaves = self.env['resource.calendar.leaves'].search([("calendar_id", "=", "行政考勤工作日")])
holidays_list = []
shift_days_list = []
@@ -243,7 +269,7 @@ class YuthonVacationReports(models.Model):
holidays_list = [self.remove_leading_zero(date) for date in holidays_list]
shift_days_list = [self.remove_leading_zero(date) for date in shift_days_list]
days = 0 # 初始化工作日计数
current_date = start_date
current_date = actual_start_date
while current_date <= end_date:
current_date_str = current_date.strftime('%m月%d日')
@@ -476,25 +502,33 @@ class YuthonVacationReports(models.Model):
'overtime_ids.legal_day',
'overtime_ids.public_holidays',
'overtime_ids.update_type',
'overtime_ids.is_void'
'overtime_ids.is_void',
'overtime_ids.workflow_record_id'
)
def _compute_working_overtime(self):
for record in self:
total_add1 = 0.0
total_add2 = 0.0
total_add = 0.0
total_reduce = 0.0
done_working_overtime = 0.0
for line in record.overtime_ids:
if not line.is_void:
line_total = (line.workday or 0) + (line.legal_day or 0) + (line.public_holidays or 0)
if line.update_type == 'add':
total_add += line_total
elif line.update_type == 'reduce':
total_reduce += line_total
total_add -= line_total
done_working_overtime += line_total
if line.is_void:
continue
line_total = (line.workday or 0) + (line.legal_day or 0) + (line.public_holidays or 0)
if line.update_type == 'add':
total_add += line_total
elif line.update_type == 'reduce':
total_add -= line_total
if line.workflow_record_id and line.update_type == 'add':
total_add1 += line_total
if line.workflow_record_id and line.update_type == 'reduce':
total_add2 += line_total
record.working_overtime = max(total_add / 8, 0)
record.done_working_overtime = done_working_overtime / 8
record.cop_leave = total_reduce / 8
record.done_working_overtime = total_add1 / 8
record.cop_leave = total_add2 / 8
@api.depends('meal_fee_ids.update_lines', 'meal_fee_ids.update_type', 'meal_fee_ids.is_void')
def _compute_meal_fee(self):
@@ -738,9 +772,9 @@ class VacationMealLine(models.Model):
delta = int(record.update_lines) * sign
if record.update_type == 'add':
employee.write({'meal_count': employee.meal_count + delta})
employee.write({'meal_number': employee.meal_number + delta})
elif record.update_type == 'reduce':
employee.write({'meal_count': employee.meal_count - delta})
employee.write({'meal_number': employee.meal_number - delta})
def unlink(self):
self._sync_to_employee(reverse=True)
@@ -12,7 +12,7 @@
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
</record>
<record id="module_he_attendance_sign" model="ir.module.category">
<field name="name">考勤记录详情组</field>
<field name="name">考勤记录详情/修正组</field>
</record>
<record id="group_he_attendance_sign_manager" model="res.groups">
<field name="name">管理员</field>
@@ -9,10 +9,16 @@
decoration-danger="is_workflow or is_approval"
editable="bottom">
<header>
<button name="compute_company_id" string="公司更新" type="object" class="btn btn-primary"/>
<!-- <button name="compute_company_id" string="公司更新" type="object" class="btn btn-primary"/>-->
<!-- <button name="button_all" string="班次更新" type="object" class="btn btn-primary"/>-->
<button name="compute_attendance_ids" string="重新计算" type="object" class="btn btn-primary"/>
<button name="state_fix" string="状态修正" type="object" class="btn btn-primary" groups="yuthon_hr_attendance.group_he_attendance_sign_manager"/>
<button name="unlink"
string="删除"
type="object"
icon="fa-trash"
groups="yuthon_hr_attendance.group_he_attendance_sign_manager"
confirm="确定要删除选中的记录吗?"/>
</header>
<field name="employee_id" class="list_current_data" options="{'no_create': True}"/>
<field name="current_data" optional="show" class="list_current_data"/>
@@ -39,8 +45,8 @@
<field name="is_workflow" optional="hidden"/>
<field name="workflow_id" optional="hidden"/>
<button name="button_controls_wizard" type="object" string="操作" class="oe_highlight" invisible="not (is_abnormal or is_early_card or is_late_card or absenteeism_card or is_no_card)"/>
<button name="button_correct_wizard" type="object" string="修正" class="btn btn-primary" invisible="not (is_abnormal or is_early_card or is_late_card or absenteeism_card or is_no_card)" groups="base.group_system"/>
<button name="revoke" type="object" string="撤销" class="btn btn-primary" invisible="not (is_check_out_red or is_check_in_red)" groups="base.group_system"/>
<button name="button_correct_wizard" type="object" string="修正" class="btn btn-primary" invisible="not (is_abnormal or is_early_card or is_late_card or absenteeism_card or is_no_card)" groups="yuthon_hr_attendance.group_he_attendance_sign_manager"/>
<button name="revoke" type="object" string="撤销" class="btn btn-primary" invisible="not (is_check_out_red or is_check_in_red)" groups="yuthon_hr_attendance.group_he_attendance_sign_manager"/>
<button name="button_workflow" type="object" string="流程" class="oe_highlight" invisible="not is_workflow"/>
<button name="button_source" type="object" string="详情" class="oe_highlight" groups="yuthon_hr_attendance.group_he_attendance_sign_manager"/>
</tree>
@@ -80,8 +86,8 @@
<field name="is_check_in_red" column_invisible="1"/>
<field name="is_check_out_red" column_invisible="1"/>
<button name="button_controls_wizard" type="object" string="操作" class="oe_highlight" invisible="not (is_abnormal or is_early_card or is_late_card or absenteeism_card or is_no_card)"/>
<button name="button_correct_wizard" type="object" string="修正" class="btn btn-primary" invisible="not (is_abnormal or is_early_card or is_late_card or absenteeism_card or is_no_card)" groups="base.group_system"/>
<button name="revoke" type="object" string="撤销" class="btn btn-primary" invisible="not (is_check_out_red or is_check_in_red)" groups="base.group_system"/>
<button name="button_correct_wizard" type="object" string="修正" class="btn btn-primary" invisible="not (is_abnormal or is_early_card or is_late_card or absenteeism_card or is_no_card)" groups="yuthon_hr_attendance.group_he_attendance_sign_manager"/>
<button name="revoke" type="object" string="撤销" class="btn btn-primary" invisible="not (is_check_out_red or is_check_in_red)" groups="yuthon_hr_attendance.group_he_attendance_sign_manager"/>
<button name="button_source" type="object" string="详情" class="oe_highlight" groups="yuthon_hr_attendance.group_he_attendance_sign_manager"/>
</tree>
</field>
@@ -157,14 +163,14 @@
<field name="binding_model_id" ref="yuthon_hr_attendance.model_hr_attendance"/>
<field name="state">code</field>
<field name="code">action = model.button_batch_correct()</field>
<field name="groups_id" eval="[(4, ref('base.group_system'))]"/>
<field name="groups_id" eval="[(4, ref('yuthon_hr_attendance.group_he_attendance_sign_manager'))]"/>
</record>
<record id="action_update_shift" model="ir.actions.server">
<field name="name">批量更新班次</field>
<field name="model_id" ref="yuthon_hr_attendance.model_hr_attendance"/>
<field name="binding_model_id" ref="yuthon_hr_attendance.model_hr_attendance"/>
<field name="state">code</field>
<field name="groups_id" eval="[(4, ref('base.group_system'))]"/>
<field name="groups_id" eval="[(4, ref('yuthon_hr_attendance.group_he_attendance_sign_manager'))]"/>
</record>
<record id="action_update_abnormal_shift" model="ir.actions.server">
<field name="name">批量更新异常</field>
@@ -172,6 +178,6 @@
<field name="binding_model_id" ref="yuthon_hr_attendance.model_hr_attendance"/>
<field name="state">code</field>
<field name="code">action = model.update_abnormal()</field>
<field name="groups_id" eval="[(4, ref('base.group_system'))]"/>
<field name="groups_id" eval="[(4, ref('yuthon_hr_attendance.group_he_attendance_sign_manager'))]"/>
</record>
</odoo>
@@ -10,6 +10,7 @@
<field name="name"/>
<field name="bssid" />
<field name="company_id"/>
<field name="create_time"/>
</tree>
</field>
</record>
@@ -99,7 +99,11 @@
<field name="arch" type="xml">
<form string="假期额度日志">
<sheet>
<field name="employee_id" invisible="1"/>
<div>
</div>
<field name="employee_id"/>
<field name="vacation_date"/>
<div class="col-12 text-center">
<h1>
<field name="display_date"/>
@@ -130,6 +130,11 @@ class YuthonMessageLogin(models.Model):
degree_id_ids = fields.One2many('education.details.line2', 'degree_id', string="学位证")
certificate_ids = fields.One2many('certificates.detail.line', 'certificate_id', string="专业证书")
@api.onchange('staff_name')
def _onchange_staff_name(self):
if self.staff_name:
self.name = '人员信息登记表'+ '-' + self.staff_name
@api.onchange('is_issues')
def _onchange_is_issues(self):
if self.is_issues:
@@ -12,6 +12,11 @@
parent="hr_process_work_job"
action="yuthon_recruitment_approval_action"
sequence="1"/>
<menuitem id="hr_menu_message_login"
name="个人信息登记表"
parent="hr_process_work_job"
action="yuthon_message_login_action"
sequence="2"/>
<menuitem id="hr_menu_interview_approval"
name="面试信息表"
parent="hr_process_work_job"
@@ -22,11 +27,6 @@
parent="hr_process_work_job"
action="yuthon_onboard_approval_action"
sequence="5"/>
<menuitem id="hr_menu_message_login"
name="个人信息登记表"
parent="hr_process_work_job"
action="yuthon_message_login_action"
sequence="7"/>
<menuitem id="hr_menu_prepare_approval"
name="编制调整申请表"
parent="hr_process_work_job"
Binary file not shown.
@@ -3,7 +3,7 @@
<record id="initialize_template_downloads_action" model="ir.actions.act_url">
<field name="name">初始化模板下载</field>
<field name="target">self</field>
<field name="url">.</field>
<field name="url">/yuthon_property/static/others/rent_temp.xlsx</field>
</record>
<record id="initialize_wizard_form" model="ir.ui.view">
<field name="name">审批意见弹窗</field>
@@ -216,11 +216,24 @@ class PropertyLeaseContract(models.Model):
responsibility_book_name = fields.Char(string="安全生产责任书(住宅)名称", compute='_compute_attachment_names',
store=True)
self_users_ids = fields.Many2many('res.users', string="当前经办人", default=lambda self: [(6, 0, [self.env.user.id])])
title_information = fields.Text(string="产权证信息")
title_information = fields.Text(string="产权证信息", compute='_compute_title_information')
contract_count = fields.Integer(string="联系人数量", compute='_compute_contract_count')
reconciliation_count = fields.Integer(string="对账数量", compute='_compute_reconciliation_count')
process_count = fields.Integer(string="流程数量", compute='_compute_process_count')
@api.depends('lease_ids.address_id.title_deed')
def _compute_title_information(self):
for record in self:
seen_titles = set()
unique_titles = []
for lease in record.lease_ids:
if lease.address_id and lease.address_id.title_deed:
title = lease.address_id.title_deed.strip()
if title and title not in seen_titles:
seen_titles.add(title)
unique_titles.append(title)
record.title_information = ', '.join(unique_titles)
def contract_signature(self):
return {
'type': 'ir.actions.act_window',
@@ -66,12 +66,6 @@ class ChoosePropertyWizard(models.Model):
name_val = lease.address_id.name
existing_names.append(name_val if name_val is not None else "无")
information_names = []
for lease in self.lease_contract_id.lease_ids:
if lease.address_id and lease.address_id.title_deed != '无产权':
deed_val = lease.address_id.title_deed
information_names.append(str(deed_val) if deed_val is not None else "无")
title_name = []
for lease in self.lease_contract_id.lease_ids:
if lease.address_id:
@@ -80,7 +74,6 @@ class ChoosePropertyWizard(models.Model):
# 过滤空字符串,避免拼接时出现空内容
all_names = [name for name in list(set(existing_names + lease_name)) if name]
title_information = [info for info in list(set(information_names + title_deed))]
title_address_combined = [addr for addr in list(set(title_address + title_name)) if addr]
existing_lease_ids = [(4, lease.id) for lease in self.lease_contract_id.lease_ids]
@@ -94,7 +87,6 @@ class ChoosePropertyWizard(models.Model):
if self.lease_contract_id.lease_ids else False,
'use_address': ','.join(all_names),
'address_for_delivery': ','.join(all_names),
'title_information': ','.join(title_information),
'total_area': sum([lease.property_area for lease in rec.lease_ids])
})
@@ -178,8 +178,8 @@ class PropertyRentalWizard(models.Model):
start_year_month = start_date_adj.strftime('%Y-%m')
end_year_month = rent_id.end_data.strftime('%Y-%m')
start_day = start_date_adj.day
# 通过period_month 来判断是否有免租期,则使用 period_rent,否则使用 monthly_amount
rent_amount = rent_id.period_rent if period_month else rent_id.monthly_amount
# 通过period_month 来判断是否有免租期,则使用 ,否则月租金使用 期间租金
rent_amount = rent_id.monthly_amount if period_month else rent_id.period_rent
if is_year:
if rent_id.month_number >12:
multiplier = rent_id.month_number - 12