From 7a5c7b42a22882289c6954a96e7dd7a5f3cbe92a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Fri, 5 Dec 2025 18:18:41 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_catering/__init__.py | 1 + yuthon_catering/__manifest__.py | 13 +-- yuthon_catering/data/catering_time.xml | 18 ++++ yuthon_catering/models/__init__.py | 1 + yuthon_catering/models/yuthon_catering.py | 66 +++++++++++++++ .../models/yuthon_catering_setting.py | 31 +++++++ yuthon_catering/security/ir.model.access.csv | 3 +- .../static/description/catering.png | Bin 0 -> 4057 bytes .../static/src/js/catering_create_button.js | 28 +++++++ .../src/xml/catering_create_button_views.xml | 10 +++ yuthon_catering/views/root_menu.xml | 4 +- yuthon_catering/views/yuthon_catering.xml | 3 +- .../views/yuthon_catering_conf.xml | 2 +- .../views/yuthon_catering_setting_views.xml | 48 +++++++++++ yuthon_catering/wizard/__init__.py | 1 + .../wizard/catering_reinstated_wizard.py | 78 ++++++++++++++++++ .../catering_reinstated_wizard_views.xml | 20 +++++ 17 files changed, 318 insertions(+), 9 deletions(-) create mode 100644 yuthon_catering/data/catering_time.xml create mode 100644 yuthon_catering/models/yuthon_catering_setting.py create mode 100644 yuthon_catering/static/description/catering.png create mode 100644 yuthon_catering/static/src/js/catering_create_button.js create mode 100644 yuthon_catering/static/src/xml/catering_create_button_views.xml create mode 100644 yuthon_catering/views/yuthon_catering_setting_views.xml create mode 100644 yuthon_catering/wizard/__init__.py create mode 100644 yuthon_catering/wizard/catering_reinstated_wizard.py create mode 100644 yuthon_catering/wizard/catering_reinstated_wizard_views.xml diff --git a/yuthon_catering/__init__.py b/yuthon_catering/__init__.py index 9e5827f90..cec04a5b0 100644 --- a/yuthon_catering/__init__.py +++ b/yuthon_catering/__init__.py @@ -1,3 +1,4 @@ # -*- coding: utf-8 -*- from . import controllers from . import models +from . import wizard diff --git a/yuthon_catering/__manifest__.py b/yuthon_catering/__manifest__.py index 7b4de9300..dced2d36e 100644 --- a/yuthon_catering/__manifest__.py +++ b/yuthon_catering/__manifest__.py @@ -13,12 +13,15 @@ 'views/root_menu.xml', 'views/yuthon_catering.xml', 'views/yuthon_catering_conf.xml', + 'views/yuthon_catering_setting_views.xml', + 'wizard/catering_reinstated_wizard_views.xml', 'templates/templates.xml', ], - # 'assets': { - # 'web.assets_backend': [ - # 'base_dynamic_columns/static/src/**/*', - # ], - # }, + 'assets': { + 'web.assets_backend': [ + 'yuthon_catering/static/src/js/catering_create_button.js', + 'yuthon_catering/static/src/xml/catering_create_button_views.xml', + ], + }, } diff --git a/yuthon_catering/data/catering_time.xml b/yuthon_catering/data/catering_time.xml new file mode 100644 index 000000000..69c1d6ecd --- /dev/null +++ b/yuthon_catering/data/catering_time.xml @@ -0,0 +1,18 @@ + + + + + 定时任务:餐饮收集任务 + + code + model.catering_task() + + + 1 + weeks + + -1 + + + + \ No newline at end of file diff --git a/yuthon_catering/models/__init__.py b/yuthon_catering/models/__init__.py index 5b6692f79..4bfbd4d62 100644 --- a/yuthon_catering/models/__init__.py +++ b/yuthon_catering/models/__init__.py @@ -1,3 +1,4 @@ # -*- coding: utf-8 -*- from . import yuthon_catering from . import yuthon_catering_conf +from . import yuthon_catering_setting diff --git a/yuthon_catering/models/yuthon_catering.py b/yuthon_catering/models/yuthon_catering.py index 3da727d34..499a6259f 100644 --- a/yuthon_catering/models/yuthon_catering.py +++ b/yuthon_catering/models/yuthon_catering.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from odoo import api, fields, models +from datetime import datetime, timedelta class YuthonCatering(models.Model): @@ -9,9 +10,74 @@ class YuthonCatering(models.Model): employee_id = fields.Many2one(string='员工', comodel_name='hr.employee', required=True) breakfast = fields.Boolean(string='早餐', default=False) lunch = fields.Boolean(string='午餐', default=False) + menu_day_id = fields.Many2one('yuthon.menu.setting', string='菜单') amount = fields.Float(string='结算费用') note = fields.Text(string='备注') + def catering_task(self): + conf_records = self.env['yuthon.catering.conf'].search([]) + employee_ids = conf_records.mapped('employee_ids') + + today = fields.Date.today() + date_list = [today + timedelta(days=i) for i in range(7)] + + create_vals = [] + for employee in employee_ids: + for target_date in date_list: + if not self._process_shift_days(target_date): + continue + existing = self.search([ + ('employee_id', '=', employee.id), + ('date', '=', target_date) + ], limit=1) + + menu_record = self.env['yuthon.menu.setting'].search([ + ('menu_date', '=', target_date) + ], limit=1) + + if not existing: + create_vals.append({ + 'menu_record': menu_record.id if menu_record else False, + 'date': target_date, + 'employee_id': employee.id, + }) + + if create_vals: + self.create(create_vals) + + def _process_shift_days(self, current_data): + """ + 判断是否是工作日 调休日 双休 + """ + calendar_leaves = self.env['resource.calendar.leaves'].search([("calendar_id", "=", "行政考勤工作日")]) + holidays_list = [] + shift_days_list = [] + # 提取节假日和调休日 + for leave in calendar_leaves: + if leave.holidays: + holidays_list.extend(leave.holidays.split(',')) + if leave.alternate_rest_day: + holidays_list.extend(leave.alternate_rest_day.split(',')) + if leave.shift_replacement_day: + shift_days_list.extend(leave.shift_replacement_day.split(',')) + + 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] + result = f"{current_data.month}月{current_data.day}日" + if result in shift_days_list: + return True + elif (result in holidays_list) or (current_data.weekday() in [5, 6]): + return False + else: + return True + + def remove_leading_zero(self, date_str): + month, day = date_str.split('月') + month = month.lstrip('0') + day = day.rstrip('日') + day = day.lstrip('0') + return f'{month}月{day}日' + def cron_send_message(self): url = f'/catering/portal/' diff --git a/yuthon_catering/models/yuthon_catering_setting.py b/yuthon_catering/models/yuthon_catering_setting.py new file mode 100644 index 000000000..600d8fa90 --- /dev/null +++ b/yuthon_catering/models/yuthon_catering_setting.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +from odoo import api, fields, models + +class YuthonMenuSetting(models.Model): + _name = 'yuthon.menu.setting' + _description = '菜单配置' + + breakfast_ids = fields.Many2many( + 'yuthon.dishes.setting', + 'yuthon_menu_breakfast_rel', + 'menu_id', + 'dish_id', + string='早餐' + ) + lunch_ids = fields.Many2many( + 'yuthon.dishes.setting', + 'yuthon_menu_lunch_rel', + 'menu_id', + 'dish_id', + string='午餐' + ) + menu_date = fields.Date(string='日期') + + +class YuthonDishesSetting(models.Model): + _name = 'yuthon.dishes.setting' + _description = '菜品配置' + + name = fields.Char(string='菜品名称') + dishes_type = fields.Selection([('breakfast', '早餐'), ('lunch', '午餐')], string='菜品类型') + diff --git a/yuthon_catering/security/ir.model.access.csv b/yuthon_catering/security/ir.model.access.csv index 70698e012..9f46bc0f4 100644 --- a/yuthon_catering/security/ir.model.access.csv +++ b/yuthon_catering/security/ir.model.access.csv @@ -1,6 +1,7 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_yuthon_catering,yuthon_catering,model_yuthon_catering,,1,1,1,1 access_yuthon_catering_conf,yuthon_catering_conf,model_yuthon_catering_conf,,1,1,1,1 - +access_yuthon_menu_setting,yuthon_menu_setting,model_yuthon_menu_setting,,1,1,1,1 +access_yuthon_dishes_setting,yuthon_dishes_setting,model_yuthon_dishes_setting,,1,1,1,1 diff --git a/yuthon_catering/static/description/catering.png b/yuthon_catering/static/description/catering.png new file mode 100644 index 0000000000000000000000000000000000000000..df91e0b7a590afd6b826966d34e33676e2b5d71c GIT binary patch literal 4057 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 + + + + + + + diff --git a/yuthon_catering/views/root_menu.xml b/yuthon_catering/views/root_menu.xml index f8e1397ae..90058bb5d 100644 --- a/yuthon_catering/views/root_menu.xml +++ b/yuthon_catering/views/root_menu.xml @@ -1,7 +1,9 @@ - + diff --git a/yuthon_catering/views/yuthon_catering.xml b/yuthon_catering/views/yuthon_catering.xml index 81414145c..9a008e1ee 100644 --- a/yuthon_catering/views/yuthon_catering.xml +++ b/yuthon_catering/views/yuthon_catering.xml @@ -6,12 +6,13 @@ yuthon.catering.tree yuthon.catering - + + diff --git a/yuthon_catering/views/yuthon_catering_conf.xml b/yuthon_catering/views/yuthon_catering_conf.xml index 51718b2e2..57fdcbe36 100644 --- a/yuthon_catering/views/yuthon_catering_conf.xml +++ b/yuthon_catering/views/yuthon_catering_conf.xml @@ -8,7 +8,7 @@ - + diff --git a/yuthon_catering/views/yuthon_catering_setting_views.xml b/yuthon_catering/views/yuthon_catering_setting_views.xml new file mode 100644 index 000000000..913a424e6 --- /dev/null +++ b/yuthon_catering/views/yuthon_catering_setting_views.xml @@ -0,0 +1,48 @@ + + + + yuthon_menu_setting_tree + yuthon.menu.setting + + + + + + + + + + 菜单配置 + yuthon.menu.setting + tree + + + yuthon_dishes_setting_tree + yuthon.dishes.setting + + + + + + + + + 菜品配置 + yuthon.dishes.setting + tree + + + + + + diff --git a/yuthon_catering/wizard/__init__.py b/yuthon_catering/wizard/__init__.py new file mode 100644 index 000000000..26d2cb010 --- /dev/null +++ b/yuthon_catering/wizard/__init__.py @@ -0,0 +1 @@ +from . import catering_reinstated_wizard \ No newline at end of file diff --git a/yuthon_catering/wizard/catering_reinstated_wizard.py b/yuthon_catering/wizard/catering_reinstated_wizard.py new file mode 100644 index 000000000..e5a4efe18 --- /dev/null +++ b/yuthon_catering/wizard/catering_reinstated_wizard.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +from odoo import api, fields, models +from datetime import datetime, timedelta, time + + +class CateringReinstatedWizard(models.TransientModel): + _name = 'catering.reinstated.wizard' + _description = '复用弹窗' + + def confirm(self): + today = fields.Date.today() + date_list = [] + for i in range(7): + target_date = today + timedelta(days=i) + if self._process_shift_days(target_date): + date_list.append(target_date) + + for current_date in date_list: + # 上周对应日期 + last_week_date = current_date - timedelta(days=7) + + # 查找上周该日期的所有餐饮记录 + last_week_records = self.env['yuthon.catering'].search([ + ('date', '=', last_week_date) + ]) + if not last_week_records: + continue + + for last_record in last_week_records: + existing = self.env['yuthon.catering'].search([ + ('employee_id', '=', last_record.employee_id.id), + ('date', '=', current_date) + ], limit=1) + if existing: + continue + + self.env['yuthon.catering'].create({ + 'date': current_date, + 'employee_id': last_record.employee_id.id, + 'breakfast': last_record.breakfast, + 'lunch': last_record.lunch, + 'menu_day_id': last_record.menu_day_id.id, + 'amount': last_record.amount, + 'note': last_record.note, + }) + + def _process_shift_days(self, current_data): + """ + 判断是否是工作日 调休日 双休 + """ + calendar_leaves = self.env['resource.calendar.leaves'].search([("calendar_id", "=", "行政考勤工作日")]) + holidays_list = [] + shift_days_list = [] + # 提取节假日和调休日 + for leave in calendar_leaves: + if leave.holidays: + holidays_list.extend(leave.holidays.split(',')) + if leave.alternate_rest_day: + holidays_list.extend(leave.alternate_rest_day.split(',')) + if leave.shift_replacement_day: + shift_days_list.extend(leave.shift_replacement_day.split(',')) + + 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] + result = f"{current_data.month}月{current_data.day}日" + if result in shift_days_list: + return True + elif (result in holidays_list) or (current_data.weekday() in [5, 6]): + return False + else: + return True + + def remove_leading_zero(self, date_str): + month, day = date_str.split('月') + month = month.lstrip('0') + day = day.rstrip('日') + day = day.lstrip('0') + return f'{month}月{day}日' \ No newline at end of file diff --git a/yuthon_catering/wizard/catering_reinstated_wizard_views.xml b/yuthon_catering/wizard/catering_reinstated_wizard_views.xml new file mode 100644 index 000000000..335a0ce58 --- /dev/null +++ b/yuthon_catering/wizard/catering_reinstated_wizard_views.xml @@ -0,0 +1,20 @@ + + + + catering.reinstated.wizard.form + catering.reinstated.wizard + +
+ +
+ 确定要执行复用逻辑吗? +
+
+ +
+
+
+
\ No newline at end of file