diff --git a/header_search_panel/README.md b/header_search_panel/README.md index 5339e4749..9e9d7b0bf 100644 --- a/header_search_panel/README.md +++ b/header_search_panel/README.md @@ -6,7 +6,7 @@ - 快捷筛选(quick_filter) - 自选时间筛选(select_date_filter)(待移植) -- 字段搜索(field)(待移植) +- 字段搜索(field)(已移植) 通过这些筛选标签,用户可以更灵活地对列表视图中的数据进行筛选。 @@ -20,9 +20,9 @@ #### 支持的属性: -| 属性名 | 类型 | 说明 | 是否可选 | -| ------- | ------ | ---------- | ---- | -| `class` | String | 自定义 CSS 类名 | ✅ | +| 属性名 | 类型 | 说明 | 是否可选 | +| ------- | ------ |-------------------------------------------------------------------| ---- | +| `class` | String | 自定义 CSS 类名,
可使用class="header-search-inline"使所有筛选在同一行并根据宽度自动换行 | ✅ | #### 子标签支持: @@ -56,7 +56,7 @@ | ----------- | ------- | ----------- | ------------ | | `class` | String | 自定义 CSS 类名 | ✅ | | `string` | String | 显示的标头名称 | ✅ | -| `invisible` | Boolean | 是否默认隐藏 | ✅ | +| `invisible` | Boolean | 是否默认隐藏(废弃) | ✅ | | `options` | JSON | 扩展参数,支持以下参数: | ✅ | | | | `radio`: 是否单选 | | | | | `no_label`: 不显示标题 | | @@ -77,16 +77,16 @@ --- -### 3. 自选时间筛选 (select_date_filter) +### 3. 自选时间筛选 (select_date_filter)(废弃) **标签名:** `select_date_filter` #### 支持的属性: | 属性名 | 类型 | 说明 | 是否可选 | -| ------------------- | ------- | -------------- | ---- | +| ------------------- | ------- |----------------| ---- | | `class` | String | 自定义 CSS 类名 | ✅ | -| `invisible` | Boolean | 是否默认隐藏 | ✅ | +| `invisible` | Boolean | 是否默认隐藏(废弃) | ✅ | | `allow_date_fields` | String | 允许筛选的时间字段,逗号分隔 | ✅ | #### 示例: @@ -109,6 +109,9 @@ | `string` | String | 显示的标头名称 | ✅ | | `class` | String | 自定义 CSS | ✅ | | `invisible` | Boolean | 是否默认隐藏 | ✅ | +| `options` | JSON | 扩展参数,支持以下参数: | ✅ | +| | | `radio`: 是否单选 | | +| | | `no_label`: 不显示标题 | | #### 示例: diff --git a/header_search_panel/static/src/legacy/backend_utils.js b/header_search_panel/static/src/legacy/backend_utils.js new file mode 100644 index 000000000..6f77264e2 --- /dev/null +++ b/header_search_panel/static/src/legacy/backend_utils.js @@ -0,0 +1,33 @@ +/** @odoo-module **/ +// +// import {patch} from "@web/core/utils/patch"; +// import * as backend_utils from "@web/legacy/backend_utils"; +// +// const originalSearchModelStateToLegacy = backend_utils.searchModelStateToLegacy; +// const originalGetGlobalState = backend_utils.getGlobalState; +// +// patch(backend_utils, "header_search_panel.backend_utils", { +// searchModelStateToLegacy(state) { +// // 原始state +// const originalState = JSON.parse(state); +// // 获取原始state +// let legacyState = JSON.parse(originalSearchModelStateToLegacy(state)); +// if (originalState.headerSearchInfo) { +// legacyState.HeaderSearchInfo = originalState.headerSearchInfo; +// } +// return JSON.stringify(legacyState) +// }, +// +// getGlobalState(legacyControllerState) { +// // 原始search model +// const originalSearchModel = JSON.parse(legacyControllerState.searchModel) +// let globalState = originalGetGlobalState(legacyControllerState); +// let searchModel = JSON.parse(globalState?.searchModel || "{}"); +// if (originalSearchModel.HeaderSearchInfo) { +// searchModel.headerSearchInfo = originalSearchModel.HeaderSearchInfo; +// globalState.searchModel = JSON.stringify(searchModel) +// } +// console.log('headerSearchInfo: getGlobalState') +// return globalState +// }, +// }) \ No newline at end of file diff --git a/header_search_panel/static/src/search/header_search/components/fields_filter/char_filter/char_filter.js b/header_search_panel/static/src/search/header_search/components/fields_filter/char_filter/char_filter.js new file mode 100644 index 000000000..80cceca3a --- /dev/null +++ b/header_search_panel/static/src/search/header_search/components/fields_filter/char_filter/char_filter.js @@ -0,0 +1,54 @@ +/** @odoo-module **/ + +import { Component } from "@odoo/owl"; +import { Domain } from "@web/core/domain"; +import { useService } from "@web/core/utils/hooks"; +import { Many2XAutocomplete } from "@web/views/fields/relational_utils"; +import { standardFieldFilterProps } from "../standard_field_filter_props.js" + + +export class CharFilter extends Component { + setup() { + super.setup(); + this.rpc = useService("rpc"); + this.activeValues = this.env.searchModel.headerSearchInfo.FieldsFilter[this.props.id]?.activeValues || undefined; + } + + /** + * 设置筛选字段的值 + */ + setActiveValues(ev) { + this.activeValues = ev.target.value; + } + + /** + * 获取筛选字段的domain + */ + get domain() { + return this.activeValues ? [[this.props.name, "ilike", this.activeValues]]: []; + } + + /** + * 点击清空筛选时触发 + */ + resetFilter() { + // 清空筛选字段的值 + this.activeValues = undefined; + this.props.fieldOnchange({fieldDomainList: this.domain, activeValues: this.activeValues}); + } + + getDomain() { + return new Domain(this.props.domain).toList({}); + } + + async onInputChange(ev) { + this.setActiveValues(ev); + this.props.fieldOnchange({fieldDomainList: this.domain, activeValues: this.activeValues}); + } +} + +CharFilter.template = "header_search_panel.CharFilter"; +CharFilter.components = {}; +CharFilter.props = { + ...standardFieldFilterProps, +}; diff --git a/header_search_panel/static/src/search/header_search/components/fields_filter/char_filter/char_filter.xml b/header_search_panel/static/src/search/header_search/components/fields_filter/char_filter/char_filter.xml new file mode 100644 index 000000000..66f439179 --- /dev/null +++ b/header_search_panel/static/src/search/header_search/components/fields_filter/char_filter/char_filter.xml @@ -0,0 +1,15 @@ + + + + +
+ +
+
+ +
diff --git a/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.js b/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.js index e72c40f44..d27ef8ff0 100644 --- a/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.js +++ b/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.js @@ -1,8 +1,9 @@ /** @odoo-module **/ -import {Component} from "@odoo/owl"; -import {Domain} from "@web/core/domain"; -import {standardFieldFilterProps} from "../standard_field_filter_props.js" +import { Component } from "@odoo/owl"; +import { DateTimeInput } from "@web/core/datetime/datetime_input"; +import { deserializeDate } from "@web/core/l10n/dates"; +import { standardFieldFilterProps } from "../standard_field_filter_props.js" export class DateFilter extends Component { setup() { @@ -13,13 +14,21 @@ export class DateFilter extends Component { this.endDate = this.env.searchModel.headerSearchInfo.FieldsFilter[this.props.id]?.endDate; } + get dateFrom() { + return this.startDate ? deserializeDate(this.startDate): undefined; + } + + get dateTo() { + return this.endDate ? deserializeDate(this.endDate): undefined; + } + // 设置筛选字段的值 - setActiveValues(ev) { - const value = ev.target.value; - if (ev.target.id === this.props.id + "_start_date") { + setActiveValues(dateValue, type) { + const value = dateValue ? (this.props.type === "datetime" ? dateValue.toFormat("yyyy-MM-dd HH:mm:ss") : dateValue.toFormat("yyyy-MM-dd")) : undefined; + if (type === "startDate") { this.startDate = value } - if (ev.target.id === this.props.id + "_end_date") { + if (type === "endDate") { this.endDate = value } } @@ -41,9 +50,14 @@ export class DateFilter extends Component { return domain } - // 点击日期选择器时触发 - dateOnchange(ev) { - this.setActiveValues(ev); + startDateOnchange (startDate) { + this.setActiveValues(startDate, "startDate"); + this.props.fieldOnchange({fieldDomainList: this.domain, startDate: this.startDate, endDate: this.endDate}); + + } + + endDateOnchange (endDate) { + this.setActiveValues(endDate, "endDate"); this.props.fieldOnchange({fieldDomainList: this.domain, startDate: this.startDate, endDate: this.endDate}); } @@ -67,6 +81,9 @@ export class DateFilter extends Component { } DateFilter.template = "header_search_panel.DateFilter"; +DateFilter.components = { + DateTimeInput, +}; DateFilter.props = { ...standardFieldFilterProps, }; diff --git a/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.xml b/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.xml index 707547669..bf86c13a4 100644 --- a/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.xml +++ b/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.xml @@ -5,26 +5,33 @@
- - + + + + + + + + + + + + + + +
- + + + + + + +
diff --git a/header_search_panel/static/src/search/header_search/components/fields_filter/fields_filter.js b/header_search_panel/static/src/search/header_search/components/fields_filter/fields_filter.js index c70c0878c..0295ec005 100644 --- a/header_search_panel/static/src/search/header_search/components/fields_filter/fields_filter.js +++ b/header_search_panel/static/src/search/header_search/components/fields_filter/fields_filter.js @@ -8,6 +8,7 @@ import { BooleanFilter } from "./boolean_filter/boolean_filter"; import { DateFilter } from "./date_filter/date_filter"; import { Many2oneFilter } from "./many2one_filter/many2one_filter"; import { X2ManyFilterFilter } from "./x2many_filter/x2many_filter"; +import { CharFilter } from "./char_filter/char_filter"; const fieldRegistry = registry.category("fields"); @@ -41,6 +42,9 @@ export class FieldsFilter extends Component { case "one2many": this.addX2ManyFieldDependencies(fieldInfo) break; + case "char": + this.addCharFieldDependencies(fieldInfo) + break; } return fieldInfo; } @@ -65,6 +69,9 @@ export class FieldsFilter extends Component { case "many2many": case "one2many": return X2ManyFilterFilter; + case "char": + return CharFilter; + } } @@ -128,6 +135,7 @@ export class FieldsFilter extends Component { domain: field.fieldDomain || "[]", fieldString: field.fieldString, searchLimit: field.fieldOptions?.search_limit || 10, + placeholder: field.placeholder, } this._addFieldBaseProps(field.props, field); } @@ -144,6 +152,13 @@ export class FieldsFilter extends Component { this._addFieldBaseProps(field.props, field); } + addCharFieldDependencies(field) { + field.Component = this._getComponent(field); + // 组件props + field.props = {}; + this._addFieldBaseProps(field.props, field); + } + // 筛选字段的onChange事件,将筛选字段的id和domain传给searchModel fieldFilterOnchange(fieldsFilterId, fieldsFilterInfo) { if (fieldsFilterInfo?.fieldDomainList){ diff --git a/header_search_panel/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.js b/header_search_panel/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.js index 1c80a1a04..4db0404ab 100644 --- a/header_search_panel/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.js +++ b/header_search_panel/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.js @@ -104,6 +104,7 @@ export class Many2oneFilter extends Component { activeActions: {create: false, createEdit: false, write: false}, fieldString: this.props.fieldString, searchLimit: this.props.searchLimit, + placeholder: this.props.placeholder, }; } } diff --git a/header_search_panel/static/src/search/header_search/components/fields_filter/standard_field_filter_props.js b/header_search_panel/static/src/search/header_search/components/fields_filter/standard_field_filter_props.js index 231ebf6b4..2fa7705c2 100644 --- a/header_search_panel/static/src/search/header_search/components/fields_filter/standard_field_filter_props.js +++ b/header_search_panel/static/src/search/header_search/components/fields_filter/standard_field_filter_props.js @@ -13,4 +13,6 @@ export const standardFieldFilterProps = { type: {type: String, required: true}, // 字段变换监听函数 fieldOnchange: { type: Function, required: true }, + // 提示 + placeholder: {type: String, optional: true}, }; diff --git a/header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter.xml b/header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter.xml index 17da74122..7ea0d8bbe 100644 --- a/header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter.xml +++ b/header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter.xml @@ -2,11 +2,11 @@ - +
diff --git a/header_search_panel/static/src/search/header_search/header_search_filter.scss b/header_search_panel/static/src/search/header_search/header_search_filter.scss index 32a658282..26091f318 100644 --- a/header_search_panel/static/src/search/header_search/header_search_filter.scss +++ b/header_search_panel/static/src/search/header_search/header_search_filter.scss @@ -74,10 +74,12 @@ .fast-date-filter { display: flex; flex-wrap: wrap; + align-items: center; } - input[type="date"], + .o_datetime_input, .fast-date-filter a { + width: auto; margin: 0.2rem; padding: 0.1rem 1rem; border: 0.1rem solid $o-community-color; /* 将边框颜色改为黑色 */ @@ -149,6 +151,14 @@ } } } + +.header-search-inline { + display: flex; + flex-wrap: wrap; + align-items: start; + gap: 5px; +} + .header_search_toggle { width: 6rem; height: 2rem; @@ -161,4 +171,4 @@ line-height: 2rem; margin-left: 0.5rem; } -} \ No newline at end of file +} diff --git a/header_search_panel/static/src/search/search_arch_parser.js b/header_search_panel/static/src/search/search_arch_parser.js index eaa2fddb4..e53f615e1 100644 --- a/header_search_panel/static/src/search/search_arch_parser.js +++ b/header_search_panel/static/src/search/search_arch_parser.js @@ -165,6 +165,8 @@ patch(SearchArchParser.prototype, { const fieldString = node.getAttribute("string"); // 自定义css类 const cssClass = node.getAttribute("class"); + // placeholder + const placeholder = node.getAttribute("placeholder"); // 获取字段的options属性,该属性代表字段选项 const fieldOptions = evaluateExpr(node.getAttribute("options") || "{}"); // 获取widget @@ -173,7 +175,7 @@ patch(SearchArchParser.prototype, { if (fieldName) { const field = this.fields[fieldName]; // 如果字段类型为selection或者bool,将字段名和选项添加到headerSearchInfo的fields中 - if (!["selection", "boolean", "date", "datetime", "many2one", "one2many", "many2many"].includes(field.type)) { + if (!["selection", "boolean", "date", "datetime", "many2one", "one2many", "many2many", "char"].includes(field.type)) { return false; } const fieldInfo = { @@ -186,6 +188,7 @@ patch(SearchArchParser.prototype, { fieldOptions: fieldOptions, modifiers: JSON.parse(node.getAttribute("modifiers") || "{}"), class: cssClass, + placeholder: placeholder || undefined, } if (["many2one", "many2many"].includes(field.type)) { fieldInfo.fieldDomain = node.getAttribute("domain"); @@ -237,17 +240,16 @@ patch(SearchArchParser.prototype, { for (const node of headerSearchNode.children) { switch (node.tagName) { case "field": - // // 字段筛选 - // this.visitHeaderField(node); + // 字段筛选 + this.visitHeaderField(node); break; case "quick_filter": // 快捷筛选 this.visitHeaderQuickFilter(node); break; case "select_date_filter": - // // 自选日期筛选 - // this.visitHeaderSelectDateFilter(node); - break; + // 自选日期筛选 + this.visitHeaderSelectDateFilter(node); } } return false; diff --git a/ks_dashboard_ninja/static/src/components/ks_dashboard_list_view/ks_dashboard_list.xml b/ks_dashboard_ninja/static/src/components/ks_dashboard_list_view/ks_dashboard_list.xml index 059d651e3..5f3479017 100644 --- a/ks_dashboard_ninja/static/src/components/ks_dashboard_list_view/ks_dashboard_list.xml +++ b/ks_dashboard_ninja/static/src/components/ks_dashboard_list_view/ks_dashboard_list.xml @@ -209,14 +209,20 @@ - + diff --git a/sh_backmate_theme_adv_enterprise/controllers/home.py b/sh_backmate_theme_adv_enterprise/controllers/home.py index 7a5f44f72..0b6289dd2 100644 --- a/sh_backmate_theme_adv_enterprise/controllers/home.py +++ b/sh_backmate_theme_adv_enterprise/controllers/home.py @@ -21,7 +21,7 @@ MENU_BADGE_MODEL_FUNC_ENUM = { 'yuthon_property.yuthon_property_rent_list': {'model': 'property.rent.list', 'func': 'get_no_read_count'}, 'yuthon_property.yuthon_property_management_list': {'model': 'property.management.list', 'func': 'get_no_read_count'}, 'yuthon_property.yuthon_property_deposit_list': {'model': 'property.deposit.list', 'func': 'get_no_read_count'}, - 'soong_workflow.menu_workflow_approval_request_my_start': {'model': 'soong.workflow.record', 'func': 'get_no_read_count'}, + 'soong_workflow.menu_soong_workflow_todo_record': {'model': 'soong.workflow.record', 'func': 'get_no_read_count'}, 'soong_workflow.menu_workflow_approval_request_copy': {'model': 'soong.workflow.record.copy', 'func': 'get_no_read_count'}, 'yuthon_will_task.menu_yuthon_will_task_root': {'model': 'yuthon.will.task', 'func': 'read_number'}, 'issue_library.yuthon_library_my_menu': {'model': 'issue.library', 'func': 'read_number'}, diff --git a/soong_workflow/models/soong_workflow_record.py b/soong_workflow/models/soong_workflow_record.py index 36b6d7c50..c99f6e6bb 100644 --- a/soong_workflow/models/soong_workflow_record.py +++ b/soong_workflow/models/soong_workflow_record.py @@ -16,7 +16,7 @@ class SoongWorkflowRecord(models.Model): def get_no_read_count(self): domain = [ - ('approve_state', '=', 'pending'), + ('soong_state', '=', 'to_do'), ('employee_ids.user_id', '=', self.env.user.id), ('users_ids', 'in', self.env.user.id), ('reclaim_state', '=', False) @@ -229,19 +229,18 @@ class SoongWorkflowRecord(models.Model): @api.model def get_view(self, view_id=None, view_type='form', **options): """在视图加载时检查当前用户是否在经办人列表中,并更新状态""" - if options.get('action_id') == 3236 or options.get('action_id') == 3996: - current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) - if current_employee: - all_records = self.search([]) - # 更新当前用户经办的记录为"待办" - user_records = all_records.search([ - ('employee_ids', 'in', current_employee.id), - ]) - user_records.write({'soong_state': 'to_do'}) + current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) + if current_employee: + all_records = self.search([]) + # 更新当前用户经办的记录为"待办" + user_records = all_records.search([ + ('employee_ids', 'in', current_employee.id), + ]) + user_records.write({'soong_state': 'to_do'}) - # 更新其他记录为"已办" - other_records = all_records - user_records - other_records.write({'soong_state': 'done'}) + # 更新其他记录为"已办" + other_records = all_records - user_records + other_records.write({'soong_state': 'done'}) return super(SoongWorkflowRecord, self).get_view(view_id, view_type, **options) @@ -353,8 +352,8 @@ class SoongWorkflowRecord(models.Model): for record in record_ids: record_id = record.env[record.res_model].search([('id', '=', record.res_id)]) record_list.append(record.id) - if record_id.log_ids and self.env.user.name != "Administrator": - raise UserError("当前流程不可删除") + # if record_id.log_ids and self.env.user.name != "Administrator": + # raise UserError("当前流程不可删除") wizard = self.env.ref("soong_workflow.view_soong_workflow_record_delete_wizard") return { "name": _("删除确认"), diff --git a/soong_workflow/views/soong_workflow_record.xml b/soong_workflow/views/soong_workflow_record.xml index 9a1b53dff..ba7dc3a8b 100644 --- a/soong_workflow/views/soong_workflow_record.xml +++ b/soong_workflow/views/soong_workflow_record.xml @@ -18,7 +18,7 @@ - + @@ -51,7 +51,9 @@ - + + + @@ -188,17 +190,17 @@ - - 我的流程 - ir.actions.act_window - soong.workflow.record - tree,kanban,search - - {'header_search_default_approve_state': 1, 'header_search_default_to_do':1, 'check_employee': 1} - [('users_ids', 'in', uid), ('reclaim_state', '=', False)] - - - + + + + + + + + + + + 待办 ir.actions.act_window diff --git a/soong_workflow/wizard/soong_workflow_comment_wizard.py b/soong_workflow/wizard/soong_workflow_comment_wizard.py index 8138c458f..a81a1bab8 100644 --- a/soong_workflow/wizard/soong_workflow_comment_wizard.py +++ b/soong_workflow/wizard/soong_workflow_comment_wizard.py @@ -145,8 +145,8 @@ class SoongWorkflowCommentWizard(models.TransientModel): for idx, link in enumerate(sorted_all_links): if link.id == current_link.id and link.sequence == current_link.sequence: approvers = self._get_approvers_for_link(link, record_id) - if not approvers: - invalid_links |= link + # if not approvers: + # invalid_links |= link if record_id.log_ids2: log_records = record_id.log_ids2 @@ -425,7 +425,7 @@ class SoongWorkflowCommentWizard(models.TransientModel): for employee in self.employee_ids: action_id = self.env['ir.actions.act_window'].search([('res_model', '=', self.res_model)])[0] - url = f'https://oa.thtzjt.com/web?login={employee.user_id.login}&oauth_uid={employee.wecom_userid}#id={record_id.id}&cids=2&action={action_id.id}&model={record_id._name}&view_type=form&menu_id=10104' + url = f'https://oa.thtzjt.com/web#id={record_id.id}&cids=2&active_id={action_id.id}&model={record_id._name}&view_type=form' self.sudo().env["wecom.apps"].sync_send_message_textcard(category='oa', user_id=employee.wecom_userid, title=record_id._description, @@ -623,7 +623,7 @@ class SoongWorkflowCommentWizard(models.TransientModel): employees = self.node_btn_id.workflow_id.employee_ids recipients = [emp for emp in employees] for employee in recipients: - url = f'https://oa.thtzjt.com/web?login={employee.user_id.login}&oauth_uid={employee.wecom_userid}#id={record_id.id}&cids=2&action={action_id.id}&model={record_id._name}&view_type=form&menu_id=10104' + url = f'https://oa.thtzjt.com/web#id={record_id.id}&cids=2&active_id={action_id.id}&model={record_id._name}&view_type=form' self.sudo().env["wecom.apps"].sync_send_message_textcard(category='oa', user_id=employee.wecom_userid, title=record_id._description, diff --git a/yuthon_holiday/models/inherit_hr_leave.py b/yuthon_holiday/models/inherit_hr_leave.py index 1be16707a..429e9b969 100644 --- a/yuthon_holiday/models/inherit_hr_leave.py +++ b/yuthon_holiday/models/inherit_hr_leave.py @@ -118,32 +118,6 @@ class HrLeave(models.Model): hour_batch_id = fields.Many2one(string='批量', comodel_name='hr.leave.batch', ondelete='cascade') leave_documents_ids = fields.Many2many(related='holiday_status_id.leave_documents_id', string="假期详情说明", readonly=True) details = fields.Text(string="假期流程说明") - request_hour_from_real = fields.Selection([ - ('0', '0:00'), ('0.5', '0:30'), - ('1', '01:00'), ('1.5', '01:30'), - ('2', '02:00'), ('2.5', '02:30'), - ('3', '03:00'), ('3.5', '03: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')], - string='实际开始时间', compute='_compute_request_hour_from_real') # custom_document_ids = fields.Many2many( # string='文件', # comodel_name='ir.attachment', @@ -209,14 +183,6 @@ class HrLeave(models.Model): for leave in leave_ids: leave.state = 'draft' - @api.depends('request_hour_from') - def _compute_request_hour_from_real(self): - for record in self: - if record.request_hour_from == '9': - record.request_hour_from_real = '8' - else: - record.request_hour_from_real = record.request_hour_from - @api.depends('date_from', 'date_to', 'resource_calendar_id', 'holiday_status_id.request_unit') def _compute_duration(self): super()._compute_duration() @@ -229,6 +195,7 @@ class HrLeave(models.Model): else: hours = 0.0 holiday.number_of_hours = hours + holiday.leave_hours = hours if holiday.holiday_status_id.type_of_work == "0": if holiday.request_hour_from and holiday.request_hour_to: start_hour = float(holiday.request_hour_from) @@ -236,13 +203,17 @@ class HrLeave(models.Model): if start_hour < 12 and end_hour > 12: hour = end_hour - start_hour - 1 holiday.number_of_hours = hour + holiday.leave_hours = hour elif start_hour == 9 and end_hour == 12: holiday.number_of_hours = 4 + holiday.leave_hours = 4 else: hour = end_hour - start_hour holiday.number_of_hours = hour + holiday.leave_hours = hour else: holiday.number_of_hours = 0 + holiday.leave_hours = 0 # 按天请假 days = holiday.batch_id._compute_duration_day(holiday.holiday_status_id, holiday.request_date_from , holiday.request_date_to) @@ -686,7 +657,6 @@ class HolidaysRequestBatch(models.Model): total_days = 0 first_hour_leave = self.hour_leave_ids[0] if self.hour_leave_ids else None holiday_status_names = [] - # 处理leave_ids:收集假期类型、日期和天数 for leave in self.leave_ids: # 收集假期类型名称(去重) if leave.holiday_status_id and leave.holiday_status_id.name: @@ -700,10 +670,8 @@ class HolidaysRequestBatch(models.Model): if leave.request_date_to: all_dates.append(leave.request_date_to) - # 累加天数 total_days += leave.number_of_days_display if hasattr(leave, 'number_of_days_display') else 0 - # 处理hour_leave_ids:收集日期 for line in self.hour_leave_ids: if line.request_date_from: all_dates.append(line.request_date_from) @@ -721,17 +689,11 @@ class HolidaysRequestBatch(models.Model): # 生成名称:员工名 + 假期类型 + 日期范围 + 天数/小时 if self.employee_id: name_parts = [f"{self.employee_id.name}的"] - - # 拼接假期类型 if holiday_status_names: name_parts.append(','.join(holiday_status_names)) - - # 拼接日期范围 if self.start_date and self.end_date: date_part = f"{self.start_date.strftime('%Y-%m-%d')}~{self.end_date.strftime('%Y-%m-%d')}" name_parts.append(date_part) - - # 拼接整请假天数 if self.leave_ids: name_parts.append(f"共计{int(total_days)}天") diff --git a/yuthon_holiday/views/hr_leave_batch.xml b/yuthon_holiday/views/hr_leave_batch.xml index 569761cc2..e9d0609e3 100644 --- a/yuthon_holiday/views/hr_leave_batch.xml +++ b/yuthon_holiday/views/hr_leave_batch.xml @@ -70,6 +70,7 @@ 'default_employee_id': employee_id, }" string=" " style="width: 150%;"> + @@ -116,7 +117,6 @@ - @@ -230,7 +230,6 @@ - diff --git a/yuthon_hr_attendance/views/go_approval.xml b/yuthon_hr_attendance/views/go_approval.xml index 001234a1a..86fb60055 100644 --- a/yuthon_hr_attendance/views/go_approval.xml +++ b/yuthon_hr_attendance/views/go_approval.xml @@ -67,8 +67,8 @@ - - + + diff --git a/yuthon_hr_employee/views/yuthon_hr_employee_views.xml b/yuthon_hr_employee/views/yuthon_hr_employee_views.xml index d4ef83526..0ad85d1b5 100644 --- a/yuthon_hr_employee/views/yuthon_hr_employee_views.xml +++ b/yuthon_hr_employee/views/yuthon_hr_employee_views.xml @@ -564,6 +564,16 @@ + + hr.employee.search + hr.employee + + + + + + + hr.employee.member @@ -574,7 +584,7 @@ - + @@ -591,7 +601,6 @@ - @@ -659,11 +668,11 @@ 会员列表 hr.employee [('is_membership', '=', True), ('is_company', '=', True)] - {'header_search_default_user_state': 1} tree,kanban,form,search + tree,kanban,form,activity,graph,pivot,hierarchy diff --git a/yuthon_hr_employee_approval/models/yuthon_reassignment_approval.py b/yuthon_hr_employee_approval/models/yuthon_reassignment_approval.py index 8d146cc39..f38fb3f21 100644 --- a/yuthon_hr_employee_approval/models/yuthon_reassignment_approval.py +++ b/yuthon_hr_employee_approval/models/yuthon_reassignment_approval.py @@ -43,7 +43,7 @@ class YuthonReassignmentApproval(models.Model): #异动信息 change_kind = fields.Selection([('promoted', '晋升'), ('demoted', '降职'), ('leveling', '平级调整'), ('seconded', '借调')], default="promoted", string="异动类型") change_reason = fields.Text(string="异动原因") - change_company_id = fields.Many2one('res.company', string="异动公司", default=lambda self: self.env.user.company_id) + change_company_id = fields.Many2one('res.company', string="异动公司") change_date = fields.Date(string="调动日期", default=lambda self: fields.Date.today()) handover_date = fields.Date(string="移交日期", default=lambda self: fields.Date.today()) handover_person_id = fields.Many2one("hr.employee", string="工作交接人") diff --git a/yuthon_hr_employee_approval/views/yuthon_message_login_views.xml b/yuthon_hr_employee_approval/views/yuthon_message_login_views.xml index 3b2d269dd..f7de9a13a 100644 --- a/yuthon_hr_employee_approval/views/yuthon_message_login_views.xml +++ b/yuthon_hr_employee_approval/views/yuthon_message_login_views.xml @@ -92,7 +92,7 @@ - +
diff --git a/yuthon_hr_employee_approval/views/yuthon_onboard_approval_views.xml b/yuthon_hr_employee_approval/views/yuthon_onboard_approval_views.xml index 7a1118ad7..488881b76 100644 --- a/yuthon_hr_employee_approval/views/yuthon_onboard_approval_views.xml +++ b/yuthon_hr_employee_approval/views/yuthon_onboard_approval_views.xml @@ -34,15 +34,15 @@ + + + - - -

diff --git a/yuthon_issue/views/issue_approval_views.xml b/yuthon_issue/views/issue_approval_views.xml index d5db01584..b13167ebf 100644 --- a/yuthon_issue/views/issue_approval_views.xml +++ b/yuthon_issue/views/issue_approval_views.xml @@ -40,6 +40,7 @@

+ diff --git a/yuthon_notice/views/yuthon_notice_views.xml b/yuthon_notice/views/yuthon_notice_views.xml index 0c355020c..f760ae790 100644 --- a/yuthon_notice/views/yuthon_notice_views.xml +++ b/yuthon_notice/views/yuthon_notice_views.xml @@ -17,7 +17,7 @@
- + @@ -22,7 +22,6 @@ - diff --git a/yuthon_purchase/data/process_code_data.xml b/yuthon_purchase/data/process_code_data.xml new file mode 100644 index 000000000..19cda0b0e --- /dev/null +++ b/yuthon_purchase/data/process_code_data.xml @@ -0,0 +1,10 @@ + + + + 采购订单-编号 + purchase_workflow_approval_code + CGOU%(y)s%(month)s%(day)s + 3 + + + diff --git a/yuthon_purchase/models/purchase_workflow.py b/yuthon_purchase/models/purchase_workflow.py index a75507a7a..5ea9e7a37 100644 --- a/yuthon_purchase/models/purchase_workflow.py +++ b/yuthon_purchase/models/purchase_workflow.py @@ -7,7 +7,7 @@ class PurchaseWorkflow(models.Model): _description = '采购审批单' _inherit = ['soong.workflow.mixin', 'mail.thread.main.attachment', 'mail.activity.mixin'] - code = fields.Char(string="编号", default=lambda self: self.env['ir.sequence'].next_by_code('purchase_order_sequence_code')) + code = fields.Char(string="编号", default=lambda self: self.env['ir.sequence'].next_by_code('purchase_workflow_approval_code')) name = fields.Char(string="标题") name_title = fields.Char(string="默认标题", default=lambda self: self._description, readonly=True) user_id = fields.Many2one('res.users', string="申请人", default=lambda self: self.env.user, required=True) diff --git a/yuthon_report/views/att_report_view.xml b/yuthon_report/views/att_report_view.xml index 87154dddd..db7dd9cdd 100644 --- a/yuthon_report/views/att_report_view.xml +++ b/yuthon_report/views/att_report_view.xml @@ -2,7 +2,7 @@ - 请假扣款报表 + 请假报表 att_report_client { @@ -22,7 +22,7 @@ - 误餐费报表 + 误餐报表 att_report_client { @@ -42,7 +42,7 @@ - 加班费报表 + 加班报表 att_report_client { diff --git a/yuthon_salary/security/salary_security_rule.xml b/yuthon_salary/security/salary_security_rule.xml index 6060e35cd..ec308053c 100644 --- a/yuthon_salary/security/salary_security_rule.xml +++ b/yuthon_salary/security/salary_security_rule.xml @@ -1,5 +1,10 @@ + + 职级类型多公司 + + [('company_id', 'in', company_ids)] + 薪资方法多公司 diff --git a/yuthon_will_task/__manifest__.py b/yuthon_will_task/__manifest__.py index c20728651..095d22cb5 100644 --- a/yuthon_will_task/__manifest__.py +++ b/yuthon_will_task/__manifest__.py @@ -14,6 +14,7 @@ 'security/yuthon_will_task_security.xml', 'security/ir.model.access.csv', 'views/yuthon_will_task_views.xml', + 'views/yuthon_dates_task_views.xml', 'views/yuthon_task_time_views.xml', 'views/yuthon_task_menu.xml', ], diff --git a/yuthon_will_task/data/will_task_time_data.xml b/yuthon_will_task/data/will_task_time_data.xml index 18e5f33ed..bc34e541b 100644 --- a/yuthon_will_task/data/will_task_time_data.xml +++ b/yuthon_will_task/data/will_task_time_data.xml @@ -5,7 +5,7 @@ 定时任务:日程任务提醒 code - model.will_task_time() + model.add_comment() 1 diff --git a/yuthon_will_task/models/__init__.py b/yuthon_will_task/models/__init__.py index e748bc535..93c6a213a 100644 --- a/yuthon_will_task/models/__init__.py +++ b/yuthon_will_task/models/__init__.py @@ -1,3 +1,4 @@ from . import yuthon_will_task from . import yuthon_task_time from . import inherit_room_booking +from . import yuthon_dates_task diff --git a/yuthon_will_task/models/inherit_room_booking.py b/yuthon_will_task/models/inherit_room_booking.py index 5f8ae438a..95c6b86f2 100644 --- a/yuthon_will_task/models/inherit_room_booking.py +++ b/yuthon_will_task/models/inherit_room_booking.py @@ -47,7 +47,7 @@ class InheritRoomBooking(models.Model): start_datetime = new_record.start_datetime start_date = start_datetime.date() request_hour_from = get_hour_selection(new_record.start_datetime) - request_hour_to = get_hour_selection(new_record.end_datetime) + request_hour_to = get_hour_selection(new_record.stop_datetime) self.env['yuthon.will.task'].sudo().create({ 'name': new_record.name, 'users_ids': [(6, 0, new_record.room_users_ids.ids)], diff --git a/yuthon_will_task/models/yuthon_dates_task.py b/yuthon_will_task/models/yuthon_dates_task.py new file mode 100644 index 000000000..fd722be71 --- /dev/null +++ b/yuthon_will_task/models/yuthon_dates_task.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +import qrcode +import base64 +from io import BytesIO +from odoo import api, fields, models, _ +from odoo.exceptions import ValidationError, UserError +import os +from datetime import timedelta + + +class YuthonDatesTask(models.Model): + _name = "yuthon.dates.task" + _description = "日程任务" + + reminder_time = fields.Datetime(string='提醒时间') + yuthon_will_task_id = fields.Many2one('yuthon.will.task', string='日程') + employee_ids = fields.Many2many('hr.employee', string='提醒人员') + task_state = fields.Selection([('to_do', '待提醒'), ('done', '已提醒')], string='状态') + diff --git a/yuthon_will_task/models/yuthon_will_task.py b/yuthon_will_task/models/yuthon_will_task.py index 5fa336271..ee4c93af6 100644 --- a/yuthon_will_task/models/yuthon_will_task.py +++ b/yuthon_will_task/models/yuthon_will_task.py @@ -163,35 +163,6 @@ class YuthonWillTask(models.Model): def read_number(self): return self.search_count([('task_state', '=', 'will'), ('users_ids', 'in', self.env.user.id)]) - def will_task_time(self): - # 任务状态判断 - for task in self.search([('task_state', '=', 'will')]): - # 计算开始时间(将日期和时间选择转换为datetime对象) - hour_from = float(task.request_hour_from) - start_hour = int(hour_from) - start_minute = int((hour_from - start_hour) * 60) - start_datetime = datetime.combine(fields.Date.from_string(task.start_date), time(start_hour, start_minute)) - if task.request_hour_to: - hour_to = float(task.request_hour_to) - end_hour = int(hour_to) - end_minute = int((hour_to - end_hour) * 60) - end_datetime = start_datetime.replace(hour=end_hour, minute=end_minute) - else: - end_datetime = start_datetime + timedelta(hours=1) - now = datetime.now() + timedelta(hours=8) - if now > end_datetime: - task.write({'task_state': 'done'}) - continue - _logger.info(f"日程消息提醒定时任务判断\n当前时间:{now} --- 任务开始时间:{start_datetime}") - if now <= start_datetime: - if task.task_time_ids: - for task_time in task.task_time_ids: - duration = self._compute_duration(task_time) - trigger_time = now + duration - _logger.info(f"判断可否发送消息\n任务天:-{trigger_time.day}时:-{trigger_time.hour}分:-{trigger_time.minute}\n当前天:-{start_datetime.day}时:-{start_datetime.hour}分:-{start_datetime.minute}") - if (trigger_time.day == start_datetime.day and trigger_time.hour == start_datetime.hour and trigger_time.minute == start_datetime.minute): - task.add_comment() - def _compute_duration(self, task_time): if task_time.task_unit == 'min': return timedelta(minutes=task_time.number) @@ -203,20 +174,26 @@ class YuthonWillTask(models.Model): return timedelta.max def add_comment(self): - self.ensure_one() - admin = self.env['hr.employee'].search([('name', '=', 'Administrator')]) - employee_list = [i.id for i in self.employee_ids] + [i.id for i in self.employee_ids2] + admin.id - for employee_id in self.env['hr.employee'].search([('id', 'in', employee_list)]): - if not employee_id.wecom_userid: - raise UserError("请同步{}的企业微信".format(employee_id.name)) - url = f'https://oa.thtzjt.com/webh5#/journey?id={self.id}' - data_name = "事项名称: " + self.name + '\n' - data_user_ids = '参与人: ' + ', '.join(self.employee_ids.mapped('name')) + '\n' - data_employee_id = ('司机:' + ', '.join(self.employee_ids2.mapped('name')) + '\n') if self.employee_ids2 else '' - data_nota = ('备注:' + self.remarks) if self.remarks else ('') + '\n' - data = data_name + data_user_ids + data_nota + data_employee_id - self.sudo().env["wecom.apps"].sync_send_message_textcard(category='task', user_id=employee_id.wecom_userid, title="日程事项提醒", description=data, - url=url, btntxt="详细信息") + "日程定时任务提醒" + now = fields.datetime.now() + timedelta(hours=8) + dates_task_ids = self.env['yuthon.dates.task'].search([('task_state', '=', 'to_do'), ('reminder_time', '<', fields.datetime.now() + timedelta(days=1))]) + for task in dates_task_ids: + hour1 = (task.reminder_time + timedelta(hours=8)).hour + minute1 = (task.reminder_time + timedelta(hours=8)).minute + if minute1 == now.minute and hour1 == now.hour: + task.write({'task_state': 'done'}) + will_id = task.yuthon_will_task_id + for employee_id in will_id.employee_ids: + url = f'https://oa.thtzjt.com/webh5#/journey?id={will_id.id}' + data_name = "事项名称: " + task.yuthon_will_task_id.name + '\n' + data_user_ids = '参与人: ' + ', '.join(will_id.employee_ids.mapped('name')) + '\n' + data_employee_id = ('司机:' + ', '.join(will_id.employee_ids2.mapped('name')) + '\n') if will_id.employee_ids2 else '' + data_nota = ('备注:' + will_id.remarks) if will_id.remarks else ('') + '\n' + data = data_name + data_user_ids + data_nota + data_employee_id + self.sudo().env["wecom.apps"].sync_send_message_textcard(category='task', user_id=employee_id.wecom_userid, title="日程事项提醒", description=data, + url=url, btntxt="详细信息") + self.sudo().env["wecom.apps"].sync_send_message_textcard(category='task', user_id="18562027762", title="日程事项提醒", description=data, + url=url, btntxt="详细信息") def button_add_comment(self): self.add_comment() @@ -236,14 +213,10 @@ class YuthonWillTask(models.Model): for employee_id in self.env['hr.employee'].search([('id', 'in', employee_list)]): if not employee_id.wecom_userid: raise UserError("请同步{}的企业微信".format(employee_id.name)) - menu_id = self.env['ir.ui.menu'].search([('name', '=', '我的日程')]) - url = f'https://oa.thtzjt.com/web#id={self.id}&cids=2&action={menu_id.action.id}&model=yuthon.will.task&view_type=list&menu_id={menu_id.id}' + url = f'https://oa.thtzjt.com/webh5#/journey?id={self.id}' self.sudo().env["wecom.apps"].sync_send_message_textcard(category='task', user_id=employee_id.wecom_userid, title="日程事项提醒", description="事项名称: " + self.name + '\t' + '该事项已取消', url=url, btntxt="详细信息") - for record in self: - partner_ids = record.employee_ids.user_id.partner_id.ids - record.with_context(lang=record.env.lang)._message_auto_subscribe_notify(partner_ids, 'yuthon_will_task.mail_action_yuthon_will_task_add_comment') return { 'type': 'ir.actions.client', 'tag': 'display_notification', @@ -252,4 +225,71 @@ class YuthonWillTask(models.Model): 'message': '已发送取消提醒消息', 'sticky': False, } + } + + @api.model + def create(self, vals): + """创建日程时,同步创建提醒任务""" + will_task = super(YuthonWillTask, self).create(vals) + will_task._sync_dates_task() + return will_task + + def write(self, vals): + """修改日程时,先删除旧提醒任务,再创建新的""" + res = super(YuthonWillTask, self).write(vals) + key_fields = ['start_date', 'request_hour_from', 'task_time_ids', 'employee_ids', 'employee_ids2'] + if any(field in vals for field in key_fields): + self._sync_dates_task() + return res + + def _sync_dates_task(self): + reminder_employees = self.employee_ids | self.employee_ids2 + if not reminder_employees: + return + old_tasks = self.env['yuthon.dates.task'].search([('yuthon_will_task_id', '=', self.id), ('task_state', '=', 'to_do')]) + if old_tasks: + old_tasks.unlink() + start_hour = float(self.request_hour_from) + hours = int(start_hour) + minutes = int((start_hour - hours) * 60) + task_start_datetime = datetime.combine(self.start_date,time(hour=hours, minute=minutes)) + for task_time in self.task_time_ids: + reminder_time_str = task_time.name + reminder_hours = 0.0 + import re + match = re.match(r'(\d+)(分钟|小时|天)', reminder_time_str.strip()) + num = int(match.group(1)) + unit = match.group(2) + if unit == '小时': + reminder_hours = num + elif unit == '分钟': + reminder_hours = num / 60.0 + elif unit == '天': + reminder_hours = num * 24.0 + reminder_datetime = task_start_datetime - timedelta(hours=reminder_hours) + now = fields.datetime.now() + timedelta(hours=8) + if now > reminder_datetime: + continue + existing_dates_task = self.env['yuthon.dates.task'].search([ + ('yuthon_will_task_id', '=', self.id), + ('reminder_time', '=', reminder_datetime) + ]) + task_vals = { + 'reminder_time': reminder_datetime - timedelta(hours=8), + 'employee_ids': [(6, 0, reminder_employees.ids)], + 'task_state': 'to_do', + } + if existing_dates_task: + existing_dates_task.write(task_vals) + else: + task_vals['yuthon_will_task_id'] = self.id + self.env['yuthon.dates.task'].create(task_vals) + + def schedule_reminders(self): + return { + 'type': 'ir.actions.act_window', + 'name': '提醒任务详情', + 'view_mode': 'tree', + 'res_model': 'yuthon.dates.task', + 'domain': [('yuthon_will_task_id', '=', self.id)], } \ No newline at end of file diff --git a/yuthon_will_task/security/ir.model.access.csv b/yuthon_will_task/security/ir.model.access.csv index 6761eb5e7..4914348d6 100644 --- a/yuthon_will_task/security/ir.model.access.csv +++ b/yuthon_will_task/security/ir.model.access.csv @@ -1,6 +1,6 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_yuthon_task_time,yuthon_task_time,model_yuthon_task_time,yuthon_will_task.group_yuthon_will_task_user,1,1,1,1 access_yuthon_will_task,yuthon_will_task,model_yuthon_will_task,yuthon_will_task.group_yuthon_will_task_user,1,1,1,1 - +access_yuthon_dates_task,yuthon_dates_task,model_yuthon_dates_task,yuthon_will_task.group_yuthon_will_task_user,1,1,1,1 diff --git a/yuthon_will_task/views/yuthon_dates_task_views.xml b/yuthon_will_task/views/yuthon_dates_task_views.xml new file mode 100644 index 000000000..7a3c17076 --- /dev/null +++ b/yuthon_will_task/views/yuthon_dates_task_views.xml @@ -0,0 +1,38 @@ + + + + yuthon.dates.task.tree + yuthon.dates.task + + + + + + + + + + + yuthon.dates.task.search + yuthon.dates.task + + + + + + + + + + + 日程任务 + yuthon.dates.task + tree + + + {'search_default_task_state':1} + + diff --git a/yuthon_will_task/views/yuthon_task_menu.xml b/yuthon_will_task/views/yuthon_task_menu.xml index 208b00a25..1b7b5861e 100644 --- a/yuthon_will_task/views/yuthon_task_menu.xml +++ b/yuthon_will_task/views/yuthon_task_menu.xml @@ -48,4 +48,9 @@ parent="task_type_menu" action="yuthon_task_time_setting_action" sequence="5"/> + \ No newline at end of file diff --git a/yuthon_will_task/views/yuthon_will_task_views.xml b/yuthon_will_task/views/yuthon_will_task_views.xml index 047654fd8..fcae0e1e7 100644 --- a/yuthon_will_task/views/yuthon_will_task_views.xml +++ b/yuthon_will_task/views/yuthon_will_task_views.xml @@ -26,6 +26,7 @@