chore(gzth): 天投云 Odoo 17 定制补丁汇总
本提交汇总生产环境长期定制改动(2024-11 ~ 2026-05): - web 前端:list/calendar/form 状态指示/确认框/选择创建对话框等交互与中文化调整 - mail:mail_thread、models 定制(含多公司/权限相关处理) - hr / hr_attendance:考勤与员工逻辑定制 - odoo/addons/base:ir_rule 记录规则调整 - bus/websocket、maintenance 视图、purchase_stock manifest、hr_contract zh_CN 翻译
This commit is contained in:
@@ -834,6 +834,7 @@ class WebsocketConnectionHandler:
|
|||||||
try:
|
try:
|
||||||
response = cls._get_handshake_response(request.httprequest.headers)
|
response = cls._get_handshake_response(request.httprequest.headers)
|
||||||
socket = request.httprequest._HTTPRequest__environ['socket']
|
socket = request.httprequest._HTTPRequest__environ['socket']
|
||||||
|
# socket = request.httprequest._HTTPRequest__environ['werkzeug.socket']
|
||||||
session, db, httprequest = request.session, request.db, request.httprequest
|
session, db, httprequest = request.session, request.db, request.httprequest
|
||||||
response.call_on_close(lambda: cls._serve_forever(
|
response.call_on_close(lambda: cls._serve_forever(
|
||||||
Websocket(socket, session),
|
Websocket(socket, session),
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class HrEmployeePrivate(models.Model):
|
|||||||
birthday = fields.Date('Date of Birth', groups="hr.group_hr_user", tracking=True)
|
birthday = fields.Date('Date of Birth', groups="hr.group_hr_user", tracking=True)
|
||||||
ssnid = fields.Char('SSN No', help='Social Security Number', groups="hr.group_hr_user", tracking=True)
|
ssnid = fields.Char('SSN No', help='Social Security Number', groups="hr.group_hr_user", tracking=True)
|
||||||
sinid = fields.Char('SIN No', help='Social Insurance Number', groups="hr.group_hr_user", tracking=True)
|
sinid = fields.Char('SIN No', help='Social Insurance Number', groups="hr.group_hr_user", tracking=True)
|
||||||
identification_id = fields.Char(string='Identification No', groups="hr.group_hr_user", tracking=True)
|
identification_id = fields.Char(string='Identification No', tracking=True)
|
||||||
passport_id = fields.Char('Passport No', groups="hr.group_hr_user", tracking=True)
|
passport_id = fields.Char('Passport No', groups="hr.group_hr_user", tracking=True)
|
||||||
bank_account_id = fields.Many2one(
|
bank_account_id = fields.Many2one(
|
||||||
'res.partner.bank', 'Bank Account Number',
|
'res.partner.bank', 'Bank Account Number',
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class HrAttendance(models.Model):
|
|||||||
employee_id = fields.Many2one('hr.employee', string="Employee", default=_default_employee, required=True, ondelete='cascade', index=True)
|
employee_id = fields.Many2one('hr.employee', string="Employee", default=_default_employee, required=True, ondelete='cascade', index=True)
|
||||||
department_id = fields.Many2one('hr.department', string="Department", related="employee_id.department_id",
|
department_id = fields.Many2one('hr.department', string="Department", related="employee_id.department_id",
|
||||||
readonly=True)
|
readonly=True)
|
||||||
check_in = fields.Datetime(string="Check In", default=fields.Datetime.now, required=True, tracking=True)
|
check_in = fields.Datetime(string="Check In", tracking=True)
|
||||||
check_out = fields.Datetime(string="Check Out", tracking=True)
|
check_out = fields.Datetime(string="Check Out", tracking=True)
|
||||||
worked_hours = fields.Float(string='Worked Hours', compute='_compute_worked_hours', store=True, readonly=True)
|
worked_hours = fields.Float(string='Worked Hours', compute='_compute_worked_hours', store=True, readonly=True)
|
||||||
color = fields.Integer(compute='_compute_color')
|
color = fields.Integer(compute='_compute_color')
|
||||||
|
|||||||
@@ -98,13 +98,13 @@ class HrEmployee(models.Model):
|
|||||||
end_tz = now_tz
|
end_tz = now_tz
|
||||||
end_naive = end_tz.astimezone(pytz.utc).replace(tzinfo=None)
|
end_naive = end_tz.astimezone(pytz.utc).replace(tzinfo=None)
|
||||||
|
|
||||||
hours = sum(
|
# hours = sum(
|
||||||
att.worked_hours or 0
|
# att.worked_hours or 0
|
||||||
for att in employee.attendance_ids.filtered(
|
# for att in employee.attendance_ids.filtered(
|
||||||
lambda att: att.check_in >= start_naive and att.check_out and att.check_out <= end_naive
|
# lambda att: att.check_in >= start_naive and att.check_out and att.check_out <= end_naive
|
||||||
)
|
# )
|
||||||
)
|
# )
|
||||||
|
hours = 0
|
||||||
employee.hours_last_month = round(hours, 2)
|
employee.hours_last_month = round(hours, 2)
|
||||||
employee.hours_last_month_display = "%g" % employee.hours_last_month
|
employee.hours_last_month_display = "%g" % employee.hours_last_month
|
||||||
|
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ msgstr "日历不匹配: 员工日历与合约日历不匹配。可能会导
|
|||||||
#: model:ir.model.fields.selection,name:hr_contract.selection__hr_contract__state__cancel
|
#: model:ir.model.fields.selection,name:hr_contract.selection__hr_contract__state__cancel
|
||||||
#: model:ir.model.fields.selection,name:hr_contract.selection__hr_contract_history__state__cancel
|
#: model:ir.model.fields.selection,name:hr_contract.selection__hr_contract_history__state__cancel
|
||||||
msgid "Cancelled"
|
msgid "Cancelled"
|
||||||
msgstr "已取消"
|
msgstr "已中止"
|
||||||
|
|
||||||
#. module: hr_contract
|
#. module: hr_contract
|
||||||
#: model:ir.model,name:hr_contract.model_res_company
|
#: model:ir.model,name:hr_contract.model_res_company
|
||||||
|
|||||||
@@ -4422,7 +4422,7 @@ class MailThread(models.AbstractModel):
|
|||||||
|
|
||||||
payload = self._notify_by_web_push_prepare_payload(message, msg_vals=msg_vals)
|
payload = self._notify_by_web_push_prepare_payload(message, msg_vals=msg_vals)
|
||||||
payload = self._truncate_payload(payload)
|
payload = self._truncate_payload(payload)
|
||||||
if len(devices) < MAX_DIRECT_PUSH:
|
if False:
|
||||||
session = Session()
|
session = Session()
|
||||||
devices_to_unlink = set()
|
devices_to_unlink = set()
|
||||||
for device in devices:
|
for device in devices:
|
||||||
|
|||||||
@@ -176,13 +176,15 @@ class BaseModel(models.AbstractModel):
|
|||||||
def _mail_track_get_field_sequence(self, fname):
|
def _mail_track_get_field_sequence(self, fname):
|
||||||
""" Find tracking sequence of a given field, given their name. Current
|
""" Find tracking sequence of a given field, given their name. Current
|
||||||
parameter 'tracking' should be an integer, but attributes with True
|
parameter 'tracking' should be an integer, but attributes with True
|
||||||
are still supported; old naming 'track_sequence' also. """
|
are still supported; old naming 'track_sequence' also.
|
||||||
sequence = getattr(
|
此文件因物业水电表日期字段报keyerror做屏蔽暂时解决
|
||||||
self._fields[fname], 'tracking',
|
"""
|
||||||
getattr(self._fields[fname], 'track_sequence', 100)
|
# sequence = getattr(
|
||||||
)
|
# self._fields[fname], 'tracking',
|
||||||
if sequence is True:
|
# getattr(self._fields[fname], 'track_sequence', 100)
|
||||||
sequence = 100
|
# )
|
||||||
|
# if sequence is True:
|
||||||
|
sequence = 100
|
||||||
return sequence
|
return sequence
|
||||||
|
|
||||||
def _message_get_default_recipients(self):
|
def _message_get_default_recipients(self):
|
||||||
|
|||||||
@@ -569,8 +569,7 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<search string="Search">
|
<search string="Search">
|
||||||
<field string="Equipment" name="name" filter_domain="[
|
<field string="Equipment" name="name" filter_domain="[
|
||||||
'|', '|', '|',
|
'|', '|', '|', ('name', 'ilike', self), ('model', 'ilike', self), ('serial_no', 'ilike', self), ('partner_ref', 'ilike', self)]"/>
|
||||||
('name', 'ilike', self), ('model', 'ilike', self), ('serial_no', 'ilike', self), ('partner_ref', 'ilike', self)]"/>
|
|
||||||
<field string="Category" name="category_id"/>
|
<field string="Category" name="category_id"/>
|
||||||
<field name="owner_user_id"/>
|
<field name="owner_user_id"/>
|
||||||
<filter string="My Equipment" name="my" domain="[('owner_user_id', '=', uid)]"/>
|
<filter string="My Equipment" name="my" domain="[('owner_user_id', '=', uid)]"/>
|
||||||
@@ -599,9 +598,9 @@
|
|||||||
</search>
|
</search>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="hr_equipment_action" model="ir.actions.act_window">
|
<record id="hr_equipment_action" model="ir.actions.act_window">
|
||||||
<field name="name">Equipment</field>
|
<field name="name">固定资产</field>
|
||||||
<field name="res_model">maintenance.equipment</field>
|
<field name="res_model">maintenance.equipment</field>
|
||||||
<field name="view_mode">kanban,tree,form</field>
|
<field name="view_mode">kanban,tree,form</field>
|
||||||
<field name="view_id" ref="hr_equipment_view_kanban"/>
|
<field name="view_id" ref="hr_equipment_view_kanban"/>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
'depends': ['stock_account', 'purchase'],
|
'depends': ['stock_account', 'purchase'],
|
||||||
'data': [
|
'data': [
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
'data/purchase_stock_data.xml',
|
# 'data/purchase_stock_data.xml',
|
||||||
'data/mail_templates.xml',
|
'data/mail_templates.xml',
|
||||||
'report/vendor_delay_report.xml',
|
'report/vendor_delay_report.xml',
|
||||||
'views/purchase_views.xml',
|
'views/purchase_views.xml',
|
||||||
|
|||||||
@@ -7,10 +7,7 @@ import { useChildRef } from "@web/core/utils/hooks";
|
|||||||
import { Component } from "@odoo/owl";
|
import { Component } from "@odoo/owl";
|
||||||
|
|
||||||
export const deleteConfirmationMessage = _t(
|
export const deleteConfirmationMessage = _t(
|
||||||
`Ready to make your record disappear into thin air? Are you sure?
|
`你确定要删除吗?`
|
||||||
It will be gone forever!
|
|
||||||
|
|
||||||
Think twice before you click that 'Delete' button!`
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export class ConfirmationDialog extends Component {
|
export class ConfirmationDialog extends Component {
|
||||||
|
|||||||
@@ -323,7 +323,7 @@ export class CalendarController extends Component {
|
|||||||
}
|
}
|
||||||
deleteRecord(record) {
|
deleteRecord(record) {
|
||||||
this.displayDialog(ConfirmationDialog, {
|
this.displayDialog(ConfirmationDialog, {
|
||||||
title: _t("Bye-bye, record!"),
|
title: _t(" "),
|
||||||
body: deleteConfirmationMessage,
|
body: deleteConfirmationMessage,
|
||||||
confirm: () => {
|
confirm: () => {
|
||||||
this.model.unlinkRecord(record.id);
|
this.model.unlinkRecord(record.id);
|
||||||
|
|||||||
@@ -10,20 +10,22 @@
|
|||||||
data-hotkey="s"
|
data-hotkey="s"
|
||||||
t-on-click.stop="save"
|
t-on-click.stop="save"
|
||||||
data-tooltip="Save manually"
|
data-tooltip="Save manually"
|
||||||
aria-label="Save manually"
|
style="background-color: blue !important;color: white !important;"
|
||||||
|
aria-label="Save manually"
|
||||||
t-ref="save">
|
t-ref="save">
|
||||||
<i class="fa fa-cloud-upload fa-fw" />
|
<i class="fa fa-cloud-upload fa-fw"/>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="o_form_button_cancel btn btn-light px-1 py-0 lh-sm"
|
class="o_form_button_cancel btn btn-light px-1 py-0 lh-sm"
|
||||||
data-hotkey="j"
|
data-hotkey="j"
|
||||||
t-on-click.stop="discard"
|
t-on-click.stop="discard"
|
||||||
data-tooltip="Discard changes"
|
data-tooltip="Discard changes"
|
||||||
|
style="background-color: red !important;color: white !important;"
|
||||||
aria-label="Discard changes">
|
aria-label="Discard changes">
|
||||||
<i class="fa fa-undo fa-fw" />
|
<i class="fa fa-undo fa-fw"/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
t-if="!props.model.root.isNew and indicatorMode === 'invalid'"
|
t-if="!props.model.root.isNew and indicatorMode === 'invalid'"
|
||||||
class="text-danger small ms-2"
|
class="text-danger small ms-2"
|
||||||
|
|||||||
@@ -536,7 +536,7 @@ export class ListController extends Component {
|
|||||||
body = _t("Are you sure you want to delete these records?");
|
body = _t("Are you sure you want to delete these records?");
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
title: _t("Bye-bye, record!"),
|
title: _t(" "),
|
||||||
body,
|
body,
|
||||||
confirmLabel: _t("Delete"),
|
confirmLabel: _t("Delete"),
|
||||||
confirm: () => this.model.root.deleteRecords(),
|
confirm: () => this.model.root.deleteRecords(),
|
||||||
|
|||||||
@@ -983,7 +983,7 @@ export class ListRenderer extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get getEmptyRowIds() {
|
get getEmptyRowIds() {
|
||||||
let nbEmptyRow = Math.max(0, 4 - this.props.list.records.length);
|
let nbEmptyRow = Math.max(0, 1 - this.props.list.records.length);
|
||||||
if (nbEmptyRow > 0 && this.displayRowCreates) {
|
if (nbEmptyRow > 0 && this.displayRowCreates) {
|
||||||
nbEmptyRow -= 1;
|
nbEmptyRow -= 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export class SelectCreateDialog extends Component {
|
|||||||
const translatedText = _t("No records found!");
|
const translatedText = _t("No records found!");
|
||||||
this.busy = false; // flag used to ensure we only call once the onSelected/onUnselect props
|
this.busy = false; // flag used to ensure we only call once the onSelected/onUnselect props
|
||||||
this.baseViewProps = {
|
this.baseViewProps = {
|
||||||
display: { searchPanel: false },
|
display: { searchPanel: true },
|
||||||
editable: false, // readonly
|
editable: false, // readonly
|
||||||
noBreadcrumbs: true,
|
noBreadcrumbs: true,
|
||||||
noContentHelp: markup(`<p>${escape(translatedText)}</p>`),
|
noContentHelp: markup(`<p>${escape(translatedText)}</p>`),
|
||||||
|
|||||||
@@ -939,7 +939,7 @@ QUnit.module("Views", ({ beforeEach }) => {
|
|||||||
await click(target, ".o_cw_popover_delete");
|
await click(target, ".o_cw_popover_delete");
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
target.querySelector(".modal-title").textContent,
|
target.querySelector(".modal-title").textContent,
|
||||||
"Bye-bye, record!",
|
" ",
|
||||||
"should display the confirm message"
|
"should display the confirm message"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -212,12 +212,10 @@ class IrRule(models.Model):
|
|||||||
'unlink': _("unlink"),
|
'unlink': _("unlink"),
|
||||||
}
|
}
|
||||||
user_description = f"{self.env.user.name} (id={self.env.user.id})"
|
user_description = f"{self.env.user.name} (id={self.env.user.id})"
|
||||||
operation_error = _("Uh-oh! Looks like you have stumbled upon some top-secret records.\n\n" \
|
operation_error = "对不起,你没有访问的权限: %s", user_description
|
||||||
"Sorry, %s doesn't have '%s' access to:", user_description, operations[operation])
|
|
||||||
failing_model = _("- %s (%s)", description, model)
|
failing_model = _("- %s (%s)", description, model)
|
||||||
|
|
||||||
resolution_info = _("If you really, really need access, perhaps you can win over your friendly administrator with a batch of freshly baked cookies.")
|
resolution_info = ""
|
||||||
|
|
||||||
if not self.user_has_groups('base.group_no_one') or not self.env.user.has_group('base.group_user'):
|
if not self.user_has_groups('base.group_no_one') or not self.env.user.has_group('base.group_user'):
|
||||||
records.invalidate_recordset()
|
records.invalidate_recordset()
|
||||||
return AccessError(f"{operation_error}\n{failing_model}\n\n{resolution_info}")
|
return AccessError(f"{operation_error}\n{failing_model}\n\n{resolution_info}")
|
||||||
|
|||||||
Reference in New Issue
Block a user