From 1ae2b2340b6522c49902f570eec8e3a31d55b408 Mon Sep 17 00:00:00 2001 From: sunjie Date: Tue, 23 Dec 2025 19:18:20 +0800 Subject: [PATCH 1/2] =?UTF-8?q?1.=20=E6=97=A5=E6=9C=9F=E5=8F=98=E5=8C=96?= =?UTF-8?q?=202.=20=E6=90=9C=E7=B4=A2=E6=9B=B4=E5=A4=9A=203.=20=E8=8F=9C?= =?UTF-8?q?=E5=8D=95=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sun_badge_menu/models/ir_ui_menu.py | 93 ++++++++++++++++++- .../yuthon_report/receivable_report.js | 50 +++++++--- .../yuthon_report/receivable_report.xml | 10 +- 3 files changed, 136 insertions(+), 17 deletions(-) diff --git a/sun_badge_menu/models/ir_ui_menu.py b/sun_badge_menu/models/ir_ui_menu.py index 0325632ec..49975e359 100644 --- a/sun_badge_menu/models/ir_ui_menu.py +++ b/sun_badge_menu/models/ir_ui_menu.py @@ -3,7 +3,8 @@ import re -from odoo import models, fields +from odoo import models, fields, api, tools +from odoo.osv import expression def compute_badge_count_recursive(web_menus, menu_id): @@ -31,6 +32,93 @@ class IrUiMenu(models.Model): _inherit = "ir.ui.menu" show_badge_count = fields.Boolean(string="显示菜单角标",default=True) + is_phone = fields.Boolean(string="手机菜单",default=False) + is_pc = fields.Boolean(string="PC菜单",default=True) + + @api.model + @tools.ormcache_context('self._uid', 'debug', keys=('lang',)) + def load_menus(self, debug): + """ Loads all menu items (all applications and their sub-menus). + + :return: the menu root + :rtype: dict('children': menu_nodes) + """ + fields = ['name', 'sequence', 'parent_id', 'action', 'web_icon','show_badge_count'] + menu_roots = self.get_user_roots() + menu_roots_data = menu_roots.read(fields) if menu_roots else [] + menu_root = { + 'id': False, + 'name': 'root', + 'parent_id': [-1, ''], + 'children': [menu['id'] for menu in menu_roots_data], + } + + all_menus = {'root': menu_root} + + if not menu_roots_data: + return all_menus + + # menus are loaded fully unlike a regular tree view, cause there are a + # limited number of items (752 when all 6.1 addons are installed) + menus_domain = [('id', 'child_of', menu_roots.ids)] + blacklisted_menu_ids = self._load_menus_blacklist() + if blacklisted_menu_ids: + menus_domain = expression.AND([menus_domain, [('id', 'not in', blacklisted_menu_ids)]]) + menus = self.search(menus_domain) + menu_items = menus.read(fields) + xmlids = (menu_roots + menus)._get_menuitems_xmlids() + + # add roots at the end of the sequence, so that they will overwrite + # equivalent menu items from full menu read when put into id:item + # mapping, resulting in children being correctly set on the roots. + menu_items.extend(menu_roots_data) + + mi_attachments = self.env['ir.attachment'].sudo().search_read( + domain=[('res_model', '=', 'ir.ui.menu'), + ('res_id', 'in', [menu_item['id'] for menu_item in menu_items if menu_item['id']]), + ('res_field', '=', 'web_icon_data')], + fields=['res_id', 'datas', 'mimetype']) + + mi_attachment_by_res_id = {attachment['res_id']: attachment for attachment in mi_attachments} + + # set children ids and xmlids + menu_items_map = {menu_item["id"]: menu_item for menu_item in menu_items} + for menu_item in menu_items: + menu_item.setdefault('children', []) + parent = menu_item['parent_id'] and menu_item['parent_id'][0] + menu_item['xmlid'] = xmlids.get(menu_item['id'], "") + if parent in menu_items_map: + menu_items_map[parent].setdefault( + 'children', []).append(menu_item['id']) + attachment = mi_attachment_by_res_id.get(menu_item['id']) + if attachment: + menu_item['web_icon_data'] = attachment['datas'] + menu_item['web_icon_data_mimetype'] = attachment['mimetype'] + else: + menu_item['web_icon_data'] = False + menu_item['web_icon_data_mimetype'] = False + all_menus.update(menu_items_map) + + # sort by sequence + for menu_id in all_menus: + all_menus[menu_id]['children'].sort(key=lambda id: all_menus[id]['sequence']) + + # recursively set app ids to related children + def _set_app_id(app_id, menu): + menu['app_id'] = app_id + for child_id in menu['children']: + _set_app_id(app_id, all_menus[child_id]) + + for app in menu_roots_data: + app_id = app['id'] + _set_app_id(app_id, all_menus[app_id]) + + # filter out menus not related to an app (+ keep root menu) + all_menus = {menu['id']: menu for menu in all_menus.values() if menu.get('app_id')} + all_menus['root'] = menu_root + + return all_menus + def load_web_menus(self, debug): """ Loads all menu items (all applications and their sub-menus) and @@ -75,7 +163,8 @@ class IrUiMenu(models.Model): action_id = int(action_id) if action_id else False badge_count = 0 res_model = False - if action_model and action_id: + # 增加 show_badge_count 限制是否显示"角标". + if action_model and action_id and menu['show_badge_count']: action_record = self.env[action_model].browse(action_id).sudo() if action_model == 'ir.actions.act_window' and action_record.view_mode != 'form': diff --git a/yuthon_report/static/src/components/yuthon_report/receivable_report.js b/yuthon_report/static/src/components/yuthon_report/receivable_report.js index dba883bbe..0172dd14f 100644 --- a/yuthon_report/static/src/components/yuthon_report/receivable_report.js +++ b/yuthon_report/static/src/components/yuthon_report/receivable_report.js @@ -33,11 +33,11 @@ class ReceivableReport extends Component { selectManagementType: {}, condition: false, conditions: [ - { 'name': '', 'value': '' }, - { 'name': '自有', 'value': 'self' }, - { 'name': '代管', 'value': 'agency' } + {'name': '', 'value': ''}, + {'name': '自有', 'value': 'self'}, + {'name': '代管', 'value': 'agency'} ], - domainUsers:[] + domainUsers: [] }); this.config = this.props.action.context.report_config || {}; @@ -90,14 +90,14 @@ class ReceivableReport extends Component { } getDomain() { - console.log(this.state) - const isCompanyEmpty = Object.keys(this.state.selectCompany).length === 0; - if (isCompanyEmpty) { - return []; - } else { - return [['id','in', this.state.domainUsers]]; + console.log(this.state) + const isCompanyEmpty = Object.keys(this.state.selectCompany).length === 0; + if (isCompanyEmpty) { + return []; + } else { + return [['id', 'in', this.state.domainUsers]]; + } } -} async onUpdatePartner(v) { console.log(v) @@ -105,7 +105,7 @@ class ReceivableReport extends Component { } async searchUserDomain() { - this.state.selectPartner = {} + this.state.selectPartner = {} let startDate = formatDate(this.state.startDate, {format: "yyyy-MM-dd"}) let endDate = formatDate(this.state.endDate, {format: "yyyy-MM-dd"}) @@ -123,13 +123,35 @@ class ReceivableReport extends Component { this.state.domainUsers = results } + async onUpdateCompany(v) { - this.state.selectCompany = v[0] || {} + console.log(v) + // 执行一次 查询 + // 通过 read 重新查询一次, 更多只返回了 id + let values = await this.serviceOrm.call( + 'yuthon.property.company', + 'read', + [[v[0].id]], + { + fields: ['display_name'], + } + ) + console.log(values) + this.state.selectCompany = values[0] || {} // 查询这个过程中的 user_id 组合成列表.返回. // 重置 partner 筛选结果 // 修改过滤条件 await this.searchUserDomain() } + onClickPreMonth(){ + // 开始的月份的前一个月 + this.state.startDate = this.state.startDate.minus({months: 1}); + this.state.endDate = this.state.startDate.endOf("month"); + } + onClickNextMonth() { + this.state.startDate = this.state.startDate.plus({months: 1}); + this.state.endDate = this.state.startDate.endOf("month"); + } async onSearch() { console.log(this.state) @@ -170,7 +192,7 @@ class ReceivableReport extends Component { } } - async onUpdateManagementType(v) { + async onUpdateManagementType(v) { this.state.selectManagementType = v[0] || {}; } diff --git a/yuthon_report/static/src/components/yuthon_report/receivable_report.xml b/yuthon_report/static/src/components/yuthon_report/receivable_report.xml index ee72b6b06..f570e3741 100644 --- a/yuthon_report/static/src/components/yuthon_report/receivable_report.xml +++ b/yuthon_report/static/src/components/yuthon_report/receivable_report.xml @@ -10,9 +10,13 @@
-
+
+ + + +
From 718e24936aeea65a0024ae73a8a658f8b6df4fe6 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: Tue, 23 Dec 2025 19:46:28 +0800 Subject: [PATCH 2/2] =?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 --- .../models/__init__.py | 3 ++- .../models/yuthon_employee_setting.py | 9 +++++++ .../models/yuthon_service_approval.py | 27 ++++++++++--------- .../security/ir.model.access.csv | 1 + .../views/yuthon_service_approval_views.xml | 8 +++--- 5 files changed, 31 insertions(+), 17 deletions(-) create mode 100644 yuthon_hr_employee_approval/models/yuthon_employee_setting.py diff --git a/yuthon_hr_employee_approval/models/__init__.py b/yuthon_hr_employee_approval/models/__init__.py index de509b22a..900eecc9f 100644 --- a/yuthon_hr_employee_approval/models/__init__.py +++ b/yuthon_hr_employee_approval/models/__init__.py @@ -27,4 +27,5 @@ from . import yuthon_department_change from . import yuthon_renewal_approval from . import contract_review_approval from . import yuthon_employee_add -from . import yuthon_pre_examination \ No newline at end of file +from . import yuthon_pre_examination +from . import yuthon_employee_setting \ No newline at end of file diff --git a/yuthon_hr_employee_approval/models/yuthon_employee_setting.py b/yuthon_hr_employee_approval/models/yuthon_employee_setting.py new file mode 100644 index 000000000..591a681c8 --- /dev/null +++ b/yuthon_hr_employee_approval/models/yuthon_employee_setting.py @@ -0,0 +1,9 @@ +from odoo import models, fields, api +from dateutil.relativedelta import relativedelta + + +class YuthonServiceType(models.Model): + _name = 'yuthon.service.type' + _description = '服务类型' + + name = fields.Char(string="名称") diff --git a/yuthon_hr_employee_approval/models/yuthon_service_approval.py b/yuthon_hr_employee_approval/models/yuthon_service_approval.py index 5efac9fc1..6ee4a26bb 100644 --- a/yuthon_hr_employee_approval/models/yuthon_service_approval.py +++ b/yuthon_hr_employee_approval/models/yuthon_service_approval.py @@ -10,20 +10,22 @@ class YuthonServiceApproval(models.Model): name = fields.Char(string='标题') code = fields.Char(string="编号", default=lambda self: self.env['ir.sequence'].next_by_code('yuthon_service_approval_code')) - service_type = fields.Selection([('done', '在职证明'), ('leaving', '离职证明'), ('income', '收入证明')], string="服务类型") + service_type_id = fields.Many2one('yuthon.service.type', string="服务类型") users_id = fields.Many2one('res.users', string="申请人", default=lambda self: self.env.user.id) documents_ids = fields.Many2many('ir.attachment', string="附件") department_id = fields.Many2one(related='users_id.employee_id.department_id', string="部门", store=True) - employee_id = fields.Many2one('hr.employee', string="员工姓名") + employee_id = fields.Many2one('hr.employee', string="员工姓名", default=lambda self: self.env.user.employee_id.id) company_id = fields.Many2one(related="users_id.employee_id.company_id", string="公司", store=True) tenant_user_card = fields.Many2one('ir.attachment', string="身份证") - onboarding_time = fields.Date(related='employee_id.open_start_time', string="入职时间") + onboarding_time = fields.Date(related='employee_id.open_start_time', string="入职日期") job_id = fields.Many2one(related='employee_id.job_id', string="岗位") + is_turnover = fields.Boolean() reason_issuance = fields.Char(string="开证原因") is_issues = fields.Boolean(string='是否上会') issues_result = fields.Html(string="会议结果") related_issues_id = fields.Many2one('issue.library', string="关联议题") + separation_date = fields.Date(string="离职日期") urgency_level = fields.Selection([('normal', '普件'), ('urgent', '急件'), ('emergency', '特急件')], string='紧急度', default='normal') active = fields.Boolean('Active', default=True, tracking=True) @@ -50,14 +52,13 @@ class YuthonServiceApproval(models.Model): 'approval_date': fields.Datetime.now(), }) - @api.onchange('service_type') + @api.onchange('service_type_id') def _onchange_service_type(self): - if self.service_type and self.users_id.name: - # 创建一个映射表,将技术值映射到显示文本 - service_type_map = { - 'done': '在职证明', - 'leaving': '离职证明', - 'income': '收入证明' - } - service_display = service_type_map.get(self.service_type, self.service_type) - self.name = f"{self.users_id.name}的{service_display}" \ No newline at end of file + if self.service_type_id and self.users_id.name: + self.name = f"{self.users_id.name}的{self.service_type_id.name}" + + if self.service_type_id.name == '离职证明': + self.is_turnover = True + else: + self.is_turnover = False + diff --git a/yuthon_hr_employee_approval/security/ir.model.access.csv b/yuthon_hr_employee_approval/security/ir.model.access.csv index 9bea8f0e7..c22a515ef 100644 --- a/yuthon_hr_employee_approval/security/ir.model.access.csv +++ b/yuthon_hr_employee_approval/security/ir.model.access.csv @@ -56,3 +56,4 @@ access_contract_review_approval,contract_review_approval,model_contract_review_a access_awards_line, awards_line,model_awards_line,base.group_user,1,1,1,1 access_yuthon_employee_add,yuthon_employee_add,model_yuthon_employee_add,base.group_user,1,1,1,1 access_yuthon_pre_examination,yuthon_pre_examination,model_yuthon_pre_examination,base.group_user,1,1,1,1 +access_yuthon_service_type,yuthon_service_type,model_yuthon_service_type,base.group_user,1,1,1,1 diff --git a/yuthon_hr_employee_approval/views/yuthon_service_approval_views.xml b/yuthon_hr_employee_approval/views/yuthon_service_approval_views.xml index 10aa4fb62..a63fd49b8 100644 --- a/yuthon_hr_employee_approval/views/yuthon_service_approval_views.xml +++ b/yuthon_hr_employee_approval/views/yuthon_service_approval_views.xml @@ -52,11 +52,13 @@ - - + + + - + +