diff --git a/preview_easy/controllers/preview.py b/preview_easy/controllers/preview.py index 34b6021a9..42b276c08 100644 --- a/preview_easy/controllers/preview.py +++ b/preview_easy/controllers/preview.py @@ -67,3 +67,8 @@ class preview(http.Controller): # http://view.officeapps.live.com/op/view.aspx?src=https%3A%2F%2Fj2762p6576.vicp.fun%2Fpreview_easy%2Fstatic%2Fdescription%2F123.xlsx # http://view.officeapps.live.com/op/view.aspx?src=https%3A%2F%2Fj2762p6576.vicp.fun%2Fweb%2Fcontent%2Foffice_preview%2F863 return werkzeug.wrappers.Response(attach.raw, status=200, headers=headers) + + @http.route('/web/document_module/installed', type='json', method=['POST'], auth='none') + def document_module_installed(self): + document_module = request.env['ir.module.module'].sudo().search([('name', '=', 'documents')]) + return {'installed': document_module and document_module.state == 'installed'} diff --git a/preview_easy/static/src/js/qweb.js b/preview_easy/static/src/js/qweb.js index a6a6ea89e..cfee1c391 100644 --- a/preview_easy/static/src/js/qweb.js +++ b/preview_easy/static/src/js/qweb.js @@ -36,6 +36,9 @@ export class Many2ManyBinaryField extends Many2ManyTagsField { nameCreateField: { type: String, optional: true }, string: { type: String, optional: true }, noSearchMore: { type: Boolean, optional: true }, + showSelectBtn: { type: Boolean, optional: true }, + previewType: { type: String, optional: true }, + previewString: { type: String, optional: true }, }; setup() { @@ -106,16 +109,10 @@ export class Many2ManyBinaryField extends Many2ManyTagsField { } preview_Obj(id, name) { - - - var winStr = 'height=600,width=1000,top=' + (screen.height - 600) / 2 + ',left=' + (screen.width - 1000) / 2 + ',toolbar=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=no,location=no'; - var newWin = window.open('/web/content/preview/'+ id , name, winStr); - newWin.focus(); - - - - - } + var winStr = 'height=600,width=1000,top=' + (screen.height - 600) / 2 + ',left=' + (screen.width - 1000) / 2 + ',toolbar=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=no,location=no'; + var newWin = window.open('/web/content/preview/'+ id , name, winStr); + newWin.focus(); + } getExtension(file) { return file.name.replace(/^.*\./, ""); @@ -167,6 +164,21 @@ export class Many2ManyBinaryField extends Many2ManyTagsField { title, }); } + get documentsInstalled() { + try { + const result = $.ajax({ + url: "/web/document_module/installed", + type: "POST", + async: false, // 设置为 false 表示同步请求 + contentType: "application/json", + data: "{}" + }); + return result?.responseJSON?.result?.installed; + } catch (error) { + console.error('Error fetching document installed status:', error); + return false; // 或者其他默认值 + } + } } export const many2ManyBinaryField = { @@ -186,6 +198,9 @@ export const many2ManyBinaryField = { ], extractProps: ({ attrs, options }) => ({ acceptedFileExtensions: options.accepted_file_extensions, + showSelectBtn: options.show_select_btn === undefined ? true : options.show_select_btn, + previewType: options.preview_type, + previewString: options.preview_string, className: attrs.class, }), }; diff --git a/preview_easy/static/src/xml/qweb.xml b/preview_easy/static/src/xml/qweb.xml index f53f3a7ab..20a3e9133 100644 --- a/preview_easy/static/src/xml/qweb.xml +++ b/preview_easy/static/src/xml/qweb.xml @@ -6,8 +6,9 @@ - + + +
+
+ + +
+
-
-
-
×
+
+ +
+
+ +
+
Uploading
+
+
+ +
+
×
+
- +
diff --git a/yuthon_holiday/__manifest__.py b/yuthon_holiday/__manifest__.py index fca9bb0af..b9cc677ae 100644 --- a/yuthon_holiday/__manifest__.py +++ b/yuthon_holiday/__manifest__.py @@ -10,8 +10,8 @@ 'views/inherit_hr_type_views.xml', 'views/inherit_hr_leave_allocation_views.xml', 'views/inherit_hr_leave_accrual_plan_views.xml', - 'views/hr_leave_accrual_views.xml', 'views/hr_leave_batch.xml', + 'views/hr_leave_accrual_views.xml', ], 'demo': [], 'depends': ['hr_holidays'], diff --git a/yuthon_holiday/models/inherit_hr_leave.py b/yuthon_holiday/models/inherit_hr_leave.py index cb18ecf76..7a34c666d 100644 --- a/yuthon_holiday/models/inherit_hr_leave.py +++ b/yuthon_holiday/models/inherit_hr_leave.py @@ -1,12 +1,42 @@ # -*- coding: utf-8 -*- +import time +from datetime import datetime, timedelta + from odoo import models, fields, api, _ from odoo.exceptions import ValidationError -class HrLeave(models.Model): - _inherit = 'hr.leave' +# class HrLeave(models.Model): +# _inherit = 'hr.leave' +# +# batch_id = fields.Many2one(string='批量', comodel_name='hr.leave.batch', required=True, ondelete='cascade') - batch_id = fields.Many2one(string='批量', comodel_name='hr.leave.batch', required=True, ondelete='cascade') +HOUR_SELECTION = [ + ('0', '00:00'), ('0.5', '00:30'), + ('1', '01:00'), ('1.5', '01:30'), + ('2', '01:00'), ('2.5', '01:30'), + ('3', '03:00'), ('3.5', '01:30'), + ('4', '04:00'), ('4.5', '04:30'), + ('5', '05:00'), ('5.5', '05:30'), + ('6', '06:00'), ('6.5', '06:30'), + ('7', '07:00'), ('7.5', '07:30'), + ('8', '08:00'), ('8.5', '08:30'), + ('9', '09:00'), ('9.5', '09:30'), + ('10', '10:00'), ('10.5', '10:30'), + ('11', '11:00'), ('11.5', '11:30'), + ('12', '12:00'), ('12.5', '12:30'), + ('13', '13:00'), ('13.5', '13:30'), + ('14', '14:00'), ('14.5', '14:30'), + ('15', '15:00'), ('15.5', '15:30'), + ('16', '16:00'), ('16.5', '16:30'), + ('17', '17:00'), ('17.5', '17:30'), + ('18', '18:00'), ('18.5', '18:30'), + ('19', '19:00'), ('19.5', '19:30'), + ('20', '20:00'), ('20.5', '20:30'), + ('21', '21:00'), ('21.5', '21:30'), + ('22', '22:00'), ('22.5', '22:30'), + ('23', '23:00'), ('23.5', '23:30') +] class HolidaysRequestBatch(models.Model): @@ -21,12 +51,14 @@ class HolidaysRequestBatch(models.Model): return emp name = fields.Char(string='标题') - employee_id = fields.Many2one(string='员工', comodel_name='hr.employee', required=True, default=_get_default_emp, - index=True) - leave_ids = fields.One2many(string='详情', comodel_name='hr.leave', inverse_name='batch_id') - state = fields.Selection(string='状态', selection=[('draft', '撤回'), ('confirm', '转交'), - ('refuse', '拒绝'), ('validate', '审批') - ], default='draft', tracking=True, copy=False) + employee_id = fields.Many2one(string='员工', comodel_name='hr.employee', required=True, default=_get_default_emp, index=True) + # leave_ids = fields.One2many(string='详情', comodel_name='hr.leave', inverse_name='batch_id') + state = fields.Selection(string='状态', selection=[ + ('draft', '撤回'), + ('confirm', '转交'), + ('refuse', '拒绝'), + ('validate', '审批'), + ], default='draft', tracking=True, copy=False) leave_id = fields.Many2one('res.users', string="请假人", default=lambda self: self.env.user.id) department_id = fields.Many2one(related='leave_id.employee_id.department_id', string="部门") days = fields.Float(string='天数') @@ -34,6 +66,21 @@ class HolidaysRequestBatch(models.Model): text = fields.Html(string='申请事由') leave_line_ids = fields.One2many('hr.leave.line', 'leave_batch_id', string="关联请假详情明细行") is_issues = fields.Boolean(string='是否上会') + leave_by_hour = fields.Boolean(string='按小时请假', default=False) + leave_unit = fields.Selection(string='时长单位', selection=[('day', '天'), ('hour', '小时')]) + + @api.constrains('leave_line_ids') + def _check_leave_line_ids(self): + for rec in self: + if rec.leave_line_ids.filtered(lambda line: line.leave_duration <= 0): + raise ValidationError('请假天数不能小于0') + if rec.leave_line_ids.filtered(lambda line: line.residual < 0): + raise ValidationError('假期剩余天数不能小于0') + + @api.onchange('leave_by_hour') + def _onchange_leave_by_hour(self): + self.leave_unit = 'hour' if self.leave_by_hour else 'day' + self.leave_line_ids = None # 提交 def action_submit(self): @@ -70,27 +117,111 @@ class HrLeaveLine(models.Model): _name = 'hr.leave.line' leave_batch_id = fields.Many2one('hr.leave.batch', string="关联请假审批") - leave_type_id = fields.Many2one('hr.leave.type', string="请假类型") - available_days = fields.Integer(string="可用天数") - applicants = fields.Selection(related='leave_type_id.request_unit', string="申请单位") + employee_id = fields.Many2one(string='员工', comodel_name='hr.employee', related='leave_batch_id.employee_id') + leave_type_id = fields.Many2one( + string="请假类型", + comodel_name='hr.leave.type', + domain=""" + ('company_id', 'in', [employee_company_id, False]), + '|', + ('requires_allocation', '=', 'no'), + ('has_valid_allocation', '=', True), + ]""") + available_days = fields.Float(string="可用天数", digits=(16, 2), compute="_compute_available_days") leave_documents_ids = fields.Many2many(related='leave_type_id.leave_documents_id', string="假期详情说明") - start_time = fields.Date(string="开始时间") - end_time = fields.Date(string="结束时间") - days = fields.Float(string="天数", compute='_compute_days') - residual = fields.Float(string="剩余天数",compute='_compute_remaining_days') + start_time = fields.Date(string="开始日期", required=True) + start_hour_from = fields.Selection(string="开始时间", selection=HOUR_SELECTION) + end_time = fields.Date(string="结束日期", required=True) + end_hour_from = fields.Selection(string="结束时间", selection=HOUR_SELECTION) + leave_duration = fields.Float(string="时长", compute='_compute_leave_duration') + leave_unit = fields.Selection(string="单位", related='leave_batch_id.leave_unit') + residual = fields.Float(string="剩余时长", compute='_compute_remaining_days') + custom_document_ids = fields.Many2many( + string='文件', + comodel_name='ir.attachment', + relation='leave_custom_doc_rel', + column1='leave_id', + column2='attachment_id' + ) + @api.constrains('start_time', 'end_time') + def _constraints_leave_date(self): + """ + 按小时请假时不能跨天 + """ + for rec in self: + if rec.leave_batch_id.leave_by_hour: + if rec.start_time != rec.end_time: + raise ValidationError('按小时请假禁止跨天') - @api.depends('start_time', 'end_time') - def _compute_days(self): + @api.depends('leave_type_id') + def _compute_available_days(self): + for rec in self: + rec.available_days = rec.leave_type_id.virtual_remaining_leaves + + @api.depends('start_time', 'start_hour_from', 'end_time', 'end_hour_from') + def _compute_leave_duration(self): + """ + 根据不同单位计算不同的时长,去除国家法定节假日与双休 + 天: 使用start_time - end_time + 小时: 使用start_time + start_hour_from - end_time + end_hour_from + """ + leave_duration = 0 for record in self: if record.start_time and record.end_time: - delta = record.end_time - record.start_time - record.days = delta.days - else: - record.days = 0 + # 按小时 + if record.leave_batch_id.leave_by_hour: + if record.start_hour_from and record.end_hour_from: + hour_selection = dict(HOUR_SELECTION) + start_time_obj = datetime.strptime(hour_selection[record.start_hour_from], "%H:%M").time() + end_time_obj = datetime.strptime(hour_selection[record.end_hour_from], "%H:%M").time() + start_date = datetime.combine(record.start_time, start_time_obj) + end_time = datetime.combine(record.end_time, end_time_obj) + leave_duration = (end_time - start_date).total_seconds() / 3600 + else: + start_date = datetime.combine(record.start_time, datetime.min.time()) + end_time = datetime.combine(record.end_time, datetime.min.time()) + leave_duration = record._get_leave_duration(start_date, end_time) + record.leave_duration = leave_duration - @api.depends('available_days', 'days') + @api.depends('available_days', 'leave_duration') def _compute_remaining_days(self): for record in self: - record.residual = record.available_days - record.days + # 如果假期类型需要分配 + if record.leave_type_id.requires_allocation == 'yes': + record.residual = record.available_days - record.residual + else: + record.residual = 0 + @api.onchange('start_time', 'start_hour_from', 'end_time', 'end_hour_from') + def _onchange_leave_date(self): + if self.leave_batch_id.leave_by_hour and self.start_time and self.end_time and self.start_time != self.end_time: + raise ValidationError('按小时请假禁止跨天') + + # 计算开始时间与结束时间之间的时长 + def _get_leave_duration(self, start_time: datetime, end_time: datetime): + """ + 公共假期计算 + 查询所有公共假期(resource.calendar.leaves) + 通过开始时间与结束时间,去除公共假期,再去除双休,计算时长, + """ + self.ensure_one() + all_resource_calendar_leaves = self.env['resource.calendar.leaves'].search([('resource_id', '=', False)]) + # 创建一个集合存储假期的每一天 + holidays = [] + for holiday in all_resource_calendar_leaves: + # 根据公共假期的开始时间与结束时间计算时间段 + current = holiday.date_from + while current <= holiday.date_to: + # 将当天0点的时间戳放入假期集合 + holidays.append(int(int(current.timestamp()) + 8 * 3600) // 86400 * 86400 - 8 * 3600) + current += timedelta(days=1) + effective_days = 0 + current = start_time + while current <= end_time: + current_timestamp = int(int(current.timestamp()) + 8 * 3600) // 86400 * 86400 - 8 * 3600 + if current.weekday() < 5 and current_timestamp not in holidays: # 0-4为工作日 + effective_days += 1 + current += timedelta(days=1) + + return effective_days diff --git a/yuthon_holiday/views/hr_leave_batch.xml b/yuthon_holiday/views/hr_leave_batch.xml index 5a87ff96e..0a2b44623 100644 --- a/yuthon_holiday/views/hr_leave_batch.xml +++ b/yuthon_holiday/views/hr_leave_batch.xml @@ -31,8 +31,7 @@
@@ -53,43 +52,50 @@ + + + - - + + - - - - - - - - - - - - - - + + - + - + + + - + +