From 2c6e0ee70debd448829924e8002d4bfe8f1683a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= Date: Fri, 14 Feb 2025 09:40:12 +0800 Subject: [PATCH] 1 --- tx_planning_resource/__init__.py | 6 + tx_planning_resource/__manifest__.py | 28 ++++ tx_planning_resource/i18n/zh_CN.po | 41 +++++ tx_planning_resource/models/__init__.py | 7 + tx_planning_resource/models/planning_slot.py | 28 ++++ .../models/resource_resource.py | 14 ++ .../planning_gantt/planning_gantt_model.js | 143 ++++++++++++++++++ .../views/resource_resource_views.xml | 38 +++++ 8 files changed, 305 insertions(+) create mode 100644 tx_planning_resource/__init__.py create mode 100644 tx_planning_resource/__manifest__.py create mode 100644 tx_planning_resource/i18n/zh_CN.po create mode 100644 tx_planning_resource/models/__init__.py create mode 100644 tx_planning_resource/models/planning_slot.py create mode 100644 tx_planning_resource/models/resource_resource.py create mode 100644 tx_planning_resource/static/src/views/planning_gantt/planning_gantt_model.js create mode 100644 tx_planning_resource/views/resource_resource_views.xml diff --git a/tx_planning_resource/__init__.py b/tx_planning_resource/__init__.py new file mode 100644 index 000000000..476b11a1c --- /dev/null +++ b/tx_planning_resource/__init__.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python +# -*- coding:utf-8 -*- +# @FileName :__init__.py.py +# @Time :2025/1/12 下午2:29 +# @Author :zzw +from . import models diff --git a/tx_planning_resource/__manifest__.py b/tx_planning_resource/__manifest__.py new file mode 100644 index 000000000..4b53e7525 --- /dev/null +++ b/tx_planning_resource/__manifest__.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +{ + 'name': "tx_planning_resource", + + 'summary': """ + tx_planning_resource + """, + 'description': """ + tx_planning_resource + """, + + 'author': "Tongxin Intelligent Technology Co., Ltd", + 'website': "https://www.txodoo.cn/", + 'maintainer': 'zzw', + 'category': 'Tx', + 'version': '17.0.0.0', + 'depends': ['resource', 'planning'], + 'data': [ + # 'security/ir.model.access.csv', + 'views/resource_resource_views.xml', + ], + 'assets': { + 'web.assets_backend': [ + 'tx_planning_resource/static/src/views/planning_gantt/*', + ] + }, + 'application': True +} diff --git a/tx_planning_resource/i18n/zh_CN.po b/tx_planning_resource/i18n/zh_CN.po new file mode 100644 index 000000000..292d10606 --- /dev/null +++ b/tx_planning_resource/i18n/zh_CN.po @@ -0,0 +1,41 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * tx_planning_resource +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0+e-20240415\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-01-12 15:24+0000\n" +"PO-Revision-Date: 2025-01-12 15:24+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: tx_planning_resource +#: model:ir.model,name:tx_planning_resource.model_planning_slot +msgid "Planning Shift" +msgstr "计划转变" + +#. module: tx_planning_resource +#: model:ir.actions.act_window,name:tx_planning_resource.act_resource_resource_only_tree +#: model:ir.model,name:tx_planning_resource.model_resource_resource +#: model:ir.ui.menu,name:tx_planning_resource.menu_resource_resource_only_tree +#: model_terms:ir.ui.view,arch_db:tx_planning_resource.tx_resource_resource_tree +msgid "Resources" +msgstr "资源" + +#. module: tx_planning_resource +#: model_terms:ir.actions.act_window,help:tx_planning_resource.act_resource_resource_only_tree +msgid "" +"Resources allow you to create and manage resources that should be involved in a specific\n" +" project phase. You can also set their efficiency level and workload based on their weekly working hours." +msgstr "资源允许您创建和管理资源参与的特定的计划阶段。您还能在其每周工作时间基础上设置其效率和工作量。" + +#. module: tx_planning_resource +#: model:ir.model.fields,field_description:tx_planning_resource.field_resource_resource__sequence +msgid "Sequence" +msgstr "序号" diff --git a/tx_planning_resource/models/__init__.py b/tx_planning_resource/models/__init__.py new file mode 100644 index 000000000..4997efcdf --- /dev/null +++ b/tx_planning_resource/models/__init__.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +# -*- coding:utf-8 -*- +# @FileName :__init__.py.py +# @Time :2025/1/12 下午2:30 +# @Author :zzw +from . import planning_slot +from . import resource_resource diff --git a/tx_planning_resource/models/planning_slot.py b/tx_planning_resource/models/planning_slot.py new file mode 100644 index 000000000..b89e21f82 --- /dev/null +++ b/tx_planning_resource/models/planning_slot.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +# -*- coding:utf-8 -*- +# @FileName :planning_slot.py +# @Time :2025/1/12 下午3:18 +# @Author :zzw +from odoo import models, fields, api, _ +from typing import Dict, List + + +class TxPlanningSlot(models.Model): + _inherit = "planning.slot" + _order = 'resource_id' + + @api.model + @api.returns('self') + def search_fetch(self, domain, field_names, offset=0, limit=None, order=None): + if self._context.get('show_job_title', False): + if not order: + order = 'resource_id' + return super().search_fetch(domain, field_names, offset, limit, order) + + def web_read(self, specification: Dict[str, Dict]) -> List[Dict]: + res = super().web_read(specification) + if self._context.get('show_job_title', False): + if res and res[0].get('resource_id', {}).get('id'): + data_list = [r.get('resource_id', {}).get('id') for r in res if r.get('resource_id', {})] + self.env['resource.resource'].search([('id', 'in', data_list)], order="sequence,name") + return res diff --git a/tx_planning_resource/models/resource_resource.py b/tx_planning_resource/models/resource_resource.py new file mode 100644 index 000000000..3359fb397 --- /dev/null +++ b/tx_planning_resource/models/resource_resource.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +# -*- coding:utf-8 -*- +# @FileName :resource_resource.py +# @Time :2025/1/12 下午2:43 +# @Author :zzw +from odoo import models, fields, api, _ +from odoo.exceptions import ValidationError + + +class TxResourceResource(models.Model): + _inherit = "resource.resource" + _order = "sequence,name" + + sequence = fields.Integer(string='Sequence', default=10) diff --git a/tx_planning_resource/static/src/views/planning_gantt/planning_gantt_model.js b/tx_planning_resource/static/src/views/planning_gantt/planning_gantt_model.js new file mode 100644 index 000000000..5ba85d40b --- /dev/null +++ b/tx_planning_resource/static/src/views/planning_gantt/planning_gantt_model.js @@ -0,0 +1,143 @@ +/** @odoo-module **/ + +import {PlanningGanttModel} from "@planning/views/planning_gantt/planning_gantt_model"; +import {patch} from "@web/core/utils/patch"; +import { groupBy, unique } from "@web/core/utils/arrays"; + + +patch(PlanningGanttModel.prototype, { + _generateRows(_, params) { + const {groupedBy, groups, parentGroup} = params; + if (!this.hideOpenShift) { + if (parentGroup.length === 0) { + // _generateRows is a recursive function. + // Here, we are generating top level rows. + if (this._allowCreateEmptyGroups(groupedBy)) { + // The group with false values for every groupby can be absent from + // groups (= groups returned by read_group basically). + // Here we add the fake group {} in groups in any case (this simulates the group + // with false values mentionned above). + // This will force the creation of some rows with resId = false + // (e.g. 'Open Shifts') from top level to bottom level. + groups.push({}); + } + if (this._allowedEmptyGroups(groupedBy)) { + params.addOpenShifts = true; + } + } + if (params.addOpenShifts && groupedBy.length === 1) { + // Here we are generating some rows on last level under a common + // "parent" (if any: first level can be last level). + // We make sure that a row with resId = false for + // the unique groupby in groupedBy and same "parent" will be + // added by adding a suitable fake group to the groups (a subset + // of the groups returned by read_group). + const fakeGroup = Object.assign({}, ...parentGroup); + groups.push(fakeGroup); + } + } + debugger + const rows = this._generateRows_copy(...arguments); + debugger + // keep empty row to the head and sort the other rows alphabetically + // if (rows.length > 1 && _.resModel !== 'planning.slot') { + // rows.sort((a, b) => { + // if (a.resId && !b.resId) { + // return 1; + // } else if (!a.resId && b.resId) { + // return -1; + // } else { + // return a.name.localeCompare(b.name); + // } + // }); + // } + return rows; + }, + + _generateRows_copy(metaData, params) { + const groupedBy = params.groupedBy; + const groups = params.groups; + const groupLevel = metaData.groupedBy.length - groupedBy.length; + const parentGroup = params.parentGroup; + debugger + + if (!groupedBy.length || !groups.length) { + const recordIds = []; + for (const g of groups) { + recordIds.push(...(g.__record_ids || [])); + } + return [ + { + groupLevel, + id: JSON.stringify([...parentGroup, {}]), + isGroup: false, + name: "", + recordIds: unique(recordIds), + }, + ]; + } + + /** @type {Row[]} */ + const rows = []; + + // Some groups might be empty (thanks to expand_groups), so we can't + // simply group the data, we need to keep all returned groups + const groupedByField = groupedBy[0]; + const currentLevelGroups = groupBy(groups, (g) => { + if (g[groupedByField] === undefined) { + // we want to group the groups with undefined values for groupedByField with the ones + // with false value for the same field. + // we also want to be sure that stringification keeps groupedByField: + // JSON.stringify({ key: undefined }) === "{}" + // see construction of id below. + g[groupedByField] = false; + } + return g[groupedByField]; + }); + const { maxField } = metaData.consolidationParams; + const consolidate = groupLevel === 0 && groupedByField === maxField; + const isGroup = maxField ? true : groupedBy.length > 1; + for (const key in currentLevelGroups) { + const subGroups = currentLevelGroups[key]; + const value = subGroups[0][groupedByField]; + const part = {}; + part[groupedByField] = value; + const fakeGroup = [...parentGroup, part]; + const id = JSON.stringify(fakeGroup); + const resId = Array.isArray(value) ? value[0] : value; // not really a resId + const fromServer = subGroups.some((g) => g.fromServer); + const recordIds = []; + for (const g of subGroups) { + recordIds.push(...(g.__record_ids || [])); + } + const row = { + consolidate, + fromServer, + groupedBy, + groupedByField, + groupLevel, + id, + isGroup, + name: this._getRowName(metaData, groupedByField, value), + resId, // not really a resId + recordIds: unique(recordIds), + }; + // if isGroup Generate sub rows + if (isGroup) { + row.rows = this._generateRows(metaData, { + ...params, + groupedBy: groupedBy.slice(1), + groups: subGroups, + parentGroup: fakeGroup, + }); + } + if (resId === false) { + rows.unshift(row); + } else { + rows.push(row); + } + } + + return rows; + } +}) diff --git a/tx_planning_resource/views/resource_resource_views.xml b/tx_planning_resource/views/resource_resource_views.xml new file mode 100644 index 000000000..715561925 --- /dev/null +++ b/tx_planning_resource/views/resource_resource_views.xml @@ -0,0 +1,38 @@ + + + + resource.resource.tree + resource.resource + + + + + + + + + + + + + + + + + Resources + resource.resource + tree + + + Resources allow you to create and manage resources that should be involved in a specific + project phase. You can also set their efficiency level and workload based on their weekly working hours. + + + + + \ No newline at end of file