diff --git a/yuthon_hr_attendance/__manifest__.py b/yuthon_hr_attendance/__manifest__.py index 67f7d648a..ac2b082be 100644 --- a/yuthon_hr_attendance/__manifest__.py +++ b/yuthon_hr_attendance/__manifest__.py @@ -81,6 +81,7 @@ 'wizard/yuthon_delete_planning_wizard_views.xml', 'wizard/attendance_controls_wizard.xml', 'wizard/attendance_correct_wizard.xml', + 'wizard/customer_create_wizard_views.xml', 'wizard/vacation_put_wizard_views.xml', 'wizard/vacation_reports_wizard.xml', 'wizard/vacation_confirm_wizard_views.xml', @@ -104,6 +105,8 @@ 'yuthon_hr_attendance/static/src/xml/button_attendance_month.xml', 'yuthon_hr_attendance/static/src/js/button_detail_wechat.js', 'yuthon_hr_attendance/static/src/xml/button_detail_wechat.xml', + 'yuthon_hr_attendance/static/src/js/button_customer_confirm.js', + 'yuthon_hr_attendance/static/src/xml/button_customer_confirm.xml', 'yuthon_hr_attendance/static/src/components/*/*.xml', 'yuthon_hr_attendance/static/src/css/*.css', 'yuthon_hr_attendance/static/src/js/button_meal_confirm.js', diff --git a/yuthon_hr_attendance/models/yuthon_customer_planning.py b/yuthon_hr_attendance/models/yuthon_customer_planning.py index c006ca9f7..c6db1be24 100644 --- a/yuthon_hr_attendance/models/yuthon_customer_planning.py +++ b/yuthon_hr_attendance/models/yuthon_customer_planning.py @@ -5,88 +5,11 @@ from odoo.exceptions import UserError class YuthonCustomerPlanning(models.Model): _name = 'yuthon.customer.planning' - _description = '客户排班计划' + _description = '客服值班计划' - name = fields.Char(string='计划名称', required=True) - start_index_time = fields.Date(string='生效时间', default=fields.Date.today()) - end_index_time = fields.Date(string='失效时间') - is_enable = fields.Boolean(string="启用", default=True) - plan_line_ids = fields.One2many('customer.plan.line', 'customer_plan_id', string='关联计划明细') - notice = fields.Text(string='计划说明') - select_time = fields.Selection([('1', '一月'), ('2', '二月'), ('3', '三月'), ('4', '四月'), - ('5', '五月'), ('6', '六月'), ('7', '七月'), ('8', '八月'), - ('9', '九月'), ('10', '十月'), ('11', '十一月'), - ('12', '十二月')], string='选择月份', default=lambda self: self._get_default_month()) + employee_ids = fields.Many2many('hr.employee', string="员工") + date = fields.Date(string='日期') - def auto_customer_plan(self, next_date=False): - selected_month = int(self.select_time) - year = datetime.now().year - if not next_date: - start_date = datetime(year, selected_month, 1) - # 计算结束日期(下个月的第一天) - next_month = (selected_month % 12) + 1 - next_year = year + (1 if next_month == 1 else 0) # 如果是12月,年份加1 - end_date = datetime(next_year, next_month, 1) - existing_slots = self.env['planning.slot'].search([ - ('role_id.name', '=', '客服中心'), - ('start_datetime', '>=', start_date), - ('start_datetime', '<=', end_date), - ]) - if len(existing_slots) > 0: - raise UserError(f"该月已经有排程,请选择其他时间。") - else: - next_month = (selected_month % 12) + 1 - next_year = year + (1 if next_month == 1 else 0) - start_date = datetime(next_year, next_month, 1) - next_next_month = (next_month % 12) + 1 - next_next_year = next_year + (1 if next_next_month == 1 else 0) - end_date = datetime(next_next_year, next_next_month, 1) - existing_slots = self.env['planning.slot'].search([ - ('role_id.name', '=', '客服中心'), - ('start_datetime', '>=', start_date), - ('start_datetime', '<=', end_date), - ]) - if len(existing_slots) > 0: - raise UserError(f"该月已经有排程,请选择其他时间。") - - role_a = self.env['planning.role'].search([('name', '=', '客服中心')], limit=1) - role_template_a = self.env['planning.slot.template'].search([('planning_name', '=', '白班'), ('role_id', '=', role_a.id)]) - role_template_b = self.env['planning.slot.template'].search([('planning_name', '=', '值班'), ('role_id', '=', role_a.id)]) - current_date = start_date - while current_date < end_date: - day_of_week = current_date.weekday() - if day_of_week < 6: - if day_of_week < 5: - for employee in self.plan_line_ids: - self.create_planning_slot(employee, role_a, role_template_a, current_date) - elif day_of_week == 5: - current_employee = self.plan_line_ids[current_date.day % len(self.plan_line_ids)] - self.create_planning_slot(current_employee, role_a, role_template_b, current_date) - current_date += timedelta(days=1) - - def customer_next_plan(self): - self.auto_customer_plan(next_date=True) - - def create_planning_slot(self, record, role, role_template, current_date): - start_hour = int(role_template.start_time) - end_hour = int(role_template.end_time) - current_datetime = datetime.combine(current_date, time(0, 0, 0)) - start_time = current_datetime.replace(hour=start_hour) + timedelta(hours=16) - timedelta(days=1) - if role_template.end_time < role_template.start_time: - end_time = current_datetime.replace(hour=end_hour) + timedelta(days=1, hours=16) - timedelta(days=1) - else: - end_time = current_datetime.replace(hour=end_hour) + timedelta(hours=16) - timedelta(days=1) - self.env['planning.slot'].create({ - 'resource_id': record.employee_id.resource_id.id, - 'template_id': role_template.id, - 'role_id': role.id, - 'start_datetime': start_time, - 'end_datetime': end_time, - 'allocated_hours': 8, - }) - def _get_default_month(self): - current_month = datetime.now().month - return str(current_month) # 返回字符串类型的月份 class CustomerPlanLine(models.Model): _name = 'customer.plan.line' diff --git a/yuthon_hr_attendance/models/yuthon_vacation_reports.py b/yuthon_hr_attendance/models/yuthon_vacation_reports.py index 98cd7c1e5..7d4cb2a5f 100644 --- a/yuthon_hr_attendance/models/yuthon_vacation_reports.py +++ b/yuthon_hr_attendance/models/yuthon_vacation_reports.py @@ -145,6 +145,9 @@ class YuthonVacationReports(models.Model): duty_schedule_env = self.env['duty.adjustment.schedule'] planning_slot_env = self.env['planning.slot'] + # 新增:客服值班计划模型 + customer_planning_env = self.env['yuthon.customer.planning'] + for record in self: if not record.employee_id or not record.vacation_date: record.duty_ids = False @@ -157,29 +160,32 @@ class YuthonVacationReports(models.Model): last_day = calendar.monthrange(year, month)[1] month_end = date(year, month, last_day) + # ====================== 原有逻辑 ====================== + # 1. 删除非调休的 duty 记录 all_duty_records = duty_schedule_env.search([ ('vacation_id', '=', record.id), ('duty_date', '>=', month_start), ('duty_date', '<=', month_end) ]) - # 过滤出:非已调休的记录 → 批量删除 non_adjusted_duties = all_duty_records.filtered(lambda d: not d.is_adjustment) if non_adjusted_duties: non_adjusted_duties.unlink() + # 2. 从 planning.slot 白值生成值班记录 white_duty_slots = planning_slot_env.search([ ('employee_id', '=', emp_id), ('planning_name', '=', '白值'), ('start_datetime', '>=', datetime.combine(month_start, datetime.min.time())), ('start_datetime', '<=', datetime.combine(month_end, datetime.max.time())) ]) - existing_duty_dates = { - d.duty_date for d in duty_schedule_env.search([ - ('vacation_id', '=', record.id), - ('duty_date', '>=', month_start), - ('duty_date', '<=', month_end) - ]) - } + + # 已存在的日期 + existing_duty_dates = {d.duty_date for d in duty_schedule_env.search([ + ('vacation_id', '=', record.id), + ('duty_date', '>=', month_start), + ('duty_date', '<=', month_end) + ])} + create_vals = [] for slot in white_duty_slots: if not slot.start_datetime: @@ -193,8 +199,27 @@ class YuthonVacationReports(models.Model): 'duty_date': duty_date, 'is_adjustment': False }) + + # 从客服值班计划取日期 + customer_planning_records = customer_planning_env.search([ + ('date', '>=', month_start), + ('date', '<=', month_end), + ('employee_ids', 'in', emp_id), + ]) + for plan in customer_planning_records: + duty_date = plan.date + if duty_date in existing_duty_dates: + continue + create_vals.append({ + 'employee_id': emp_id, + 'vacation_id': record.id, + 'duty_date': duty_date, + 'is_adjustment': False + }) + if create_vals: duty_schedule_env.create(create_vals) + record.duty_ids = duty_schedule_env.search([ ('vacation_id', '=', record.id), ('duty_date', '>=', month_start), diff --git a/yuthon_hr_attendance/security/ir.model.access.csv b/yuthon_hr_attendance/security/ir.model.access.csv index b179dbd83..3259b634b 100644 --- a/yuthon_hr_attendance/security/ir.model.access.csv +++ b/yuthon_hr_attendance/security/ir.model.access.csv @@ -102,4 +102,5 @@ access_overtime_carryover_line,overtime.carryover.line,model_overtime_carryover_ access_yuthon_meal_line,yuthon.meal.line,model_yuthon_meal_line,base.group_user,1,1,1,1 access_vacation_meal_active_wizard,vacation.meal.active.wizard,model_vacation_meal_active_wizard,base.group_user,1,1,1,1 -access_vacation_overtime_active_wizard,vacation.overtime.active.wizard,model_vacation_overtime_active_wizard,base.group_user,1,1,1,1 \ No newline at end of file +access_vacation_overtime_active_wizard,vacation.overtime.active.wizard,model_vacation_overtime_active_wizard,base.group_user,1,1,1,1 +access_customer_create_wizard,customer.create.wizard,model_customer_create_wizard,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/yuthon_hr_attendance/static/src/js/button_customer_confirm.js b/yuthon_hr_attendance/static/src/js/button_customer_confirm.js new file mode 100644 index 000000000..44e037c0b --- /dev/null +++ b/yuthon_hr_attendance/static/src/js/button_customer_confirm.js @@ -0,0 +1,29 @@ +/** @odoo-module */ +import { ListController } from "@web/views/list/list_controller"; +import { registry } from '@web/core/registry'; +import { listView } from '@web/views/list/list_view'; + +export class CustomAttendanceCustomerConfirmController extends ListController { + setup() { + super.setup(); + } + + openCustomerWizard() { + this.actionService.doAction({ + type: 'ir.actions.act_window', + res_model: 'customer.create.wizard', + name: '客服排班生成', + view_mode: 'form', + view_type: 'form', + views: [[false, 'form']], + target: 'new', // 以弹窗形式打开 + context: {}, + }); + } +} + +registry.category("views").add("custom_customer_confirm_button", { + ...listView, + Controller: CustomAttendanceCustomerConfirmController, + buttonTemplate: "customer_confirm_module.ListView.Buttons", +}); \ No newline at end of file diff --git a/yuthon_hr_attendance/static/src/xml/button_customer_confirm.xml b/yuthon_hr_attendance/static/src/xml/button_customer_confirm.xml new file mode 100644 index 000000000..a7d9d8e6a --- /dev/null +++ b/yuthon_hr_attendance/static/src/xml/button_customer_confirm.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/yuthon_hr_attendance/views/all_attendance_menu.xml b/yuthon_hr_attendance/views/all_attendance_menu.xml index 620d0b59c..285b45122 100644 --- a/yuthon_hr_attendance/views/all_attendance_menu.xml +++ b/yuthon_hr_attendance/views/all_attendance_menu.xml @@ -158,11 +158,6 @@ - - - - - + yuthon.customer.planning.tree yuthon.customer.planning - - - - - + + + - - yuthon.customer.planning.form - yuthon.customer.planning - -
-
-
- -
-
- - - - - - - - - - - - - - - - - - - - - - -
-
-
-
- 排版规则 + 客服值班 yuthon.customer.planning tree,form diff --git a/yuthon_hr_attendance/wizard/__init__.py b/yuthon_hr_attendance/wizard/__init__.py index 15a002755..3091dcb00 100644 --- a/yuthon_hr_attendance/wizard/__init__.py +++ b/yuthon_hr_attendance/wizard/__init__.py @@ -14,4 +14,5 @@ from . import export_date_wizard from . import yuthon_vacation_import_wizard from . import overtime_change_wizard from . import vacation_meal_active_wizard -from . import vacation_overtime_active_wizard \ No newline at end of file +from . import vacation_overtime_active_wizard +from . import customer_create_wizard \ No newline at end of file diff --git a/yuthon_hr_attendance/wizard/customer_create_wizard.py b/yuthon_hr_attendance/wizard/customer_create_wizard.py new file mode 100644 index 000000000..8e6260df5 --- /dev/null +++ b/yuthon_hr_attendance/wizard/customer_create_wizard.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +import calendar +from odoo import models, api, fields, _ +from odoo.tools.float_utils import float_round +from odoo.exceptions import ValidationError, UserError +from datetime import date +from datetime import timedelta +from datetime import datetime, time + + +class CustomerCreateWizard(models.TransientModel): + _name = 'customer.create.wizard' + _description = '客服排班生成弹窗' + + select_month = fields.Selection([('1', '一月'), ('2', '二月'), ('3', '三月'), ('4', '四月'), + ('5', '五月'), ('6', '六月'), ('7', '七月'), ('8', '八月'), + ('9', '九月'), ('10', '十月'), ('11', '十一月'), + ('12', '十二月')], string='月份', + default=lambda self: str(datetime.now().month)) + select_year = fields.Selection(selection=[(str(i), str(i)) for i in range(2026, 2060)], string='年份', + default=lambda self: str(datetime.now().year)) + + def create_customer(self): + year = int(self.select_year) + month = int(self.select_month) + saturdays = [] + current_date = datetime(year, month, 1) + if month == 12: + next_month = datetime(year + 1, 1, 1) + else: + next_month = datetime(year, month + 1, 1) + last_day = next_month - timedelta(days=1) + + while current_date <= last_day: + if current_date.weekday() == 5: + saturdays.append(current_date.strftime('%Y-%m-%d')) + current_date += timedelta(days=1) + + # 3. 批量创建值班计划 + planning_vals_list = [] + for date in saturdays: + planning_vals_list.append({ + 'date': date, + }) + + if planning_vals_list: + self.env['yuthon.customer.planning'].create(planning_vals_list) + diff --git a/yuthon_hr_attendance/wizard/customer_create_wizard_views.xml b/yuthon_hr_attendance/wizard/customer_create_wizard_views.xml new file mode 100644 index 000000000..e68827272 --- /dev/null +++ b/yuthon_hr_attendance/wizard/customer_create_wizard_views.xml @@ -0,0 +1,21 @@ + + + + 生成弹窗 + customer.create.wizard + +
+ + + + + +
+
+
+
+ +