添加功能
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
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
|
||||
|
@@ -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",
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<templates>
|
||||
<t t-name="customer_confirm_module.ListView.Buttons" t-inherit="web.ListView.Buttons">
|
||||
<xpath expr="//div[hasclass('o_list_buttons')]" position="after">
|
||||
<button type="button" class="btn btn-primary" style="margin-left: 10px;" t-on-click="openCustomerWizard">
|
||||
生成
|
||||
</button>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -158,11 +158,6 @@
|
||||
<!-- parent="menu_hr_attendance_customer_board"-->
|
||||
<!-- action="action_planning_slot_form7"-->
|
||||
<!-- sequence="10"/>-->
|
||||
<!-- <menuitem id="menu_hr_attendance_customer_flow_board"-->
|
||||
<!-- name="排班规则"-->
|
||||
<!-- parent="menu_hr_attendance_customer_board"-->
|
||||
<!-- action="yuthon_customer_planning_view_action"-->
|
||||
<!-- sequence="12"/>-->
|
||||
<menuitem id="menu_hr_attendance_duty_summary_menu_board"
|
||||
name="粮库计划"
|
||||
parent="yuthon_attendance_planning_board"
|
||||
@@ -177,6 +172,11 @@
|
||||
parent="menu_hr_attendance_duty_summary_menu_board"
|
||||
action="view_yuthon_duty_plan_action"
|
||||
sequence="5"/>
|
||||
<menuitem id="menu_hr_attendance_customer_flow_board"
|
||||
name="客服值班"
|
||||
parent="yuthon_attendance_planning_board"
|
||||
action="yuthon_customer_planning_view_action"
|
||||
sequence="15"/>
|
||||
<!--流程-->
|
||||
<menuitem id="menu_yuthon_customer_process_board"
|
||||
name="流程"
|
||||
|
||||
@@ -4,59 +4,14 @@
|
||||
<field name="name">yuthon.customer.planning.tree</field>
|
||||
<field name="model">yuthon.customer.planning</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="排版规则">
|
||||
<field name="name"/>
|
||||
<field name="start_index_time"/>
|
||||
<field name="end_index_time"/>
|
||||
<field name="is_enable"/>
|
||||
<tree string="客服值班" js_class="custom_customer_confirm_button" editable="bottom">
|
||||
<field name="employee_ids" widget="many2many_tags"/>
|
||||
<field name="date"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_yuthon_customer_planning_form" model="ir.ui.view">
|
||||
<field name="name">yuthon.customer.planning.form</field>
|
||||
<field name="model">yuthon.customer.planning</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="排版规则" create="0">
|
||||
<header>
|
||||
<button name="auto_customer_plan" type="object" string="安排值班" class="oe_highlight"/>
|
||||
<button name="customer_next_plan" type="object" string="安排下个月值班" class="oe_highlight"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<label for="name" placeholder="请输入标题"/>
|
||||
<h1>
|
||||
<field name="name" required="1"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="notice"/>
|
||||
<field name="select_time" required="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="start_index_time"/>
|
||||
<field name="end_index_time"/>
|
||||
<field name="is_enable"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="值班人员">
|
||||
<field name="plan_line_ids">
|
||||
<tree editable="bottom">
|
||||
<field name="customer_plan_id" column_invisible="1"/>
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="employee_id" required="1"
|
||||
domain="[('department_id.name', '=', '客户部')]" widget="ztree_select"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="yuthon_customer_planning_view_action" model="ir.actions.act_window">
|
||||
<field name="name">排版规则</field>
|
||||
<field name="name">客服值班</field>
|
||||
<field name="res_model">yuthon.customer.planning</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
@@ -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
|
||||
from . import vacation_overtime_active_wizard
|
||||
from . import customer_create_wizard
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_customer_create_wizard" model="ir.ui.view">
|
||||
<field name="name">生成弹窗</field>
|
||||
<field name="model">customer.create.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<group>
|
||||
<field name="select_year"/>
|
||||
<field name="select_month"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="create_customer" string="确认" type="object" class="oe_highlight"/>
|
||||
<button string="返回" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user