This commit is contained in:
pengyuthon
2026-05-14 09:09:04 +08:00
59 changed files with 1149 additions and 453 deletions
+26 -26
View File
@@ -23,32 +23,32 @@ patch(UserMenu.prototype, {
this.app_lang_list = session.app_lang_list;
//todo: 演习 shortCutsItem 中的用法,当前是直接 xml 写了展现
//修正 bug,在移动端不会关闭本身
//o_burger_menu position-fixed top-0 bottom-0 start-100 d-flex flex-column flex-nowrap burgerslide burgerslide-enter-active
function preferencesItem(env) {
return {
type: "item",
id: "settings",
description: _t("Preferences"),
callback: async function () {
const actionDescription = await env.services.orm.call("res.users", "action_get");
actionDescription.res_id = env.services.user.userId;
try {
let m = document.getElementsByClassName("o_burger_menu_close");
if (m) {
m[0].click();
}
} catch (e) {
;
}
env.services.action.doAction(actionDescription);
//修正 bug,在移动端不会关闭本身
},
sequence: 50,
};
}
userMenuRegistry.add("profile", preferencesItem, {'force': true, 'menu': this});
userMenuRegistry.add("refresh_current", refresh_current, {'force': true});
// 修正 bug,在移动端不会关闭本身
// o_burger_menu position-fixed top-0 bottom-0 start-100 d-flex flex-column flex-nowrap burgerslide burgerslide-enter-active
// function preferencesItem(env) {
// return {
// type: "item",
// id: "settings",
// description: _t("Preferences"),
// callback: async function () {
// const actionDescription = await env.services.orm.call("res.users", "action_get");
// actionDescription.res_id = env.services.user.userId;
// try {
// let m = document.getElementsByClassName("o_burger_menu_close");
// if (m) {
// m[0].click();
// }
// } catch (e) {
// ;
// }
// env.services.action.doAction(actionDescription);
// //修正 bug,在移动端不会关闭本身
// },
// sequence: 50,
// };
// }
// userMenuRegistry.add("profile", preferencesItem, {'force': true, 'menu': this});
// userMenuRegistry.add("refresh_current", refresh_current, {'force': true});
if (session.app_show_lang) {
userMenuRegistry.add("separator1", separator1, {'force': true})
+1
View File
@@ -18,6 +18,7 @@
'views/attendance_iot_user_views.xml',
'views/attendance_iot_manager_views.xml',
'views/attendance_iot_views.xml',
'views/attendance_iot_log_views.xml',
# 'views/inherit_hr_employee_views.xml',
'wizard/attendance_iot_manager_wizard_views.xml',
'views/iot_message.xml'
+1 -1
View File
@@ -56,7 +56,7 @@ class BusController(Controller):
except Exception as e:
_logger.info(f'抛出异常,异常原因:{e}')
return e
_logger.info(f'考勤机发送过来的数据集合{data}-------我把photo去掉因为太长可')
# _logger.info(f'考勤机发送过来的数据集合{data}-------我把photo去掉因为太长可')
user_id = data.get('logs')[0].get('user_id')
recog_time = data.get('logs')[0].get('recog_time')
if recog_time:
+1
View File
@@ -4,3 +4,4 @@ from . import attendance_iot
from . import attendance_iot_datas
from . import attendance_iot_manager
from . import iot_message
from . import attendance_iot_log
+15
View File
@@ -20,6 +20,7 @@ class AttendanceIot(models.Model):
company_id = fields.Many2one('res.company', string="公司")
remake = fields.Text(string='备注')
employee_ids = fields.Many2many('hr.employee', string="员工")
is_connected = fields.Boolean(string="连接状态", default=False)
def action_init_device(self):
'''
@@ -102,3 +103,17 @@ class AttendanceIot(models.Model):
'value': 'off'
}, ensure_ascii=False)
}])
def action_view_logs(self):
"""查看该设备的日志"""
self.ensure_one()
return {
'name': '考勤机日志',
'type': 'ir.actions.act_window',
'res_model': 'attendance.iot.log',
'view_mode': 'tree,form',
'domain': [('attendance_iot_id', '=', self.id)],
'context': {'default_attendance_iot_id': self.id},
}
@@ -0,0 +1,10 @@
from odoo import api, fields, models
class AttendanceIotLog(models.Model):
_name = 'attendance.iot.log'
_description = '考勤机日志'
_order = 'create_date desc'
attendance_iot_id = fields.Many2one('attendance.iot', string="考勤机设备", required=True, ondelete='cascade')
content = fields.Text(string="内容")
+2 -1
View File
@@ -5,4 +5,5 @@ access_attendance_iot_manager_wizard,attendance.iot.manager.wizard,model_attenda
access_attendance_iot_datas,attendance.iot.datas,model_attendance_iot_datas,base.group_user,1,1,1,1
access_attendance_iot_user,attendance.iot.user,model_attendance_iot_user,base.group_user,1,1,1,1
access_iot_message,iot.message.user,model_iot_message,base.group_user,1,1,1,1
access_attendance_iot_import_user_wizard,access.attendance.iot.import.user.wizard,model_attendance_iot_import_user_wizard,base.group_user,1,1,1,1
access_attendance_iot_import_user_wizard,access.attendance.iot.import.user.wizard,model_attendance_iot_import_user_wizard,base.group_user,1,1,1,1
access_attendance_iot_log,attendance.iot.log,model_attendance_iot_log,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
5 access_attendance_iot_datas attendance.iot.datas model_attendance_iot_datas base.group_user 1 1 1 1
6 access_attendance_iot_user attendance.iot.user model_attendance_iot_user base.group_user 1 1 1 1
7 access_iot_message iot.message.user model_iot_message base.group_user 1 1 1 1
8 access_attendance_iot_import_user_wizard access.attendance.iot.import.user.wizard model_attendance_iot_import_user_wizard base.group_user 1 1 1 1
9 access_attendance_iot_log attendance.iot.log model_attendance_iot_log base.group_user 1 1 1 1
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- 考勤机日志 tree 视图 -->
<record id="attendance_iot_log_tree" model="ir.ui.view">
<field name="name">attendance.iot.log.tree</field>
<field name="model">attendance.iot.log</field>
<field name="arch" type="xml">
<tree>
<field name="attendance_iot_id"/>
<field name="create_date" string="创建日期"/>
<field name="content"/>
</tree>
</field>
</record>
<!-- 考勤机日志 form 视图 -->
<record id="attendance_iot_log_form" model="ir.ui.view">
<field name="name">attendance.iot.log.form</field>
<field name="model">attendance.iot.log</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="attendance_iot_id"/>
<field name="create_date" string="创建日期"/>
<field name="content"/>
</group>
</sheet>
</form>
</field>
</record>
<!-- 考勤机日志 action -->
<record id="attendance_iot_log_action" model="ir.actions.act_window">
<field name="name">考勤机日志</field>
<field name="res_model">attendance.iot.log</field>
<field name="view_mode">tree,form</field>
</record>
</odoo>
@@ -16,7 +16,9 @@
<field name="company_id"/>
<field name="number"/>
<field name="remake"/>
<field name="is_connected"/>
<button name="open_record" type="object" string="详情" class="btn btn-blue"/>
<button name="action_view_logs" type="object" string="日志" class="btn btn-blue"/>
<button name="action_send_sync_sigle" type="object" string="同步数据" class="btn btn-blue"/>
<button name="action_send_open_door_sigle" type="object" string="开门" class="btn btn-blue"/>
<button name="action_send_close_door_sigle" type="object" string="关门" class="btn btn-blue"/>
+10 -8
View File
@@ -37,13 +37,8 @@ class SoongWorkflowMixin(models.AbstractModel):
# setattr(record, field, value)
# return res
def write(self, vals):
if self._context.get('_correction_skip'):
return super(SoongWorkflowMixin, self).write(vals)
for record in self:
if record.is_modify:
record._write({'is_modify': False})
return super(SoongWorkflowMixin, self).write(vals)
def action_is_modify(self):
self.is_modify = False
@api.model
def _compute_default_state(self):
@@ -1318,6 +1313,13 @@ class SoongWorkflowMixin(models.AbstractModel):
done_el.set("invisible", "1")
header_el.append(done_el)
modify_btn_el = etree.SubElement(header_el, "button")
modify_btn_el.set("name", "action_is_modify")
modify_btn_el.set("string", "已修正")
modify_btn_el.set("type", "object")
modify_btn_el.set("invisible", "not is_modify")
modify_btn_el.set("class", "btn btn-primary")
modify_el = etree.Element("field")
modify_el.set("name", "is_modify")
modify_el.set("invisible", "1")
@@ -1460,7 +1462,7 @@ class SoongWorkflowMixin(models.AbstractModel):
arr = []
ext_ids = group._get_external_ids()
for ext_id in ext_ids:
arr.append(ext_ids[ext_id][0])
arr.append(ext_ids[ext_id])
return arr
if view_type == "form":
@@ -124,7 +124,6 @@ class SoongWorkflowRecord(models.Model):
description=f"您有一条流水号为【{rec.code}】标题为【{rec.name}】流程待修正,请尽快修正",
url=url, btntxt="详细信息")
record = self.env[rec.res_model].sudo().browse(rec.res_id).exists()
record = record.with_context(_correction_skip=True)
record.is_modify = True
@api.returns('self', lambda value: value.id)
@@ -72,7 +72,8 @@
<field name="is_log2" stirng="抄送"/>
<field name="name"/>
<field name="code"/>
<field name="employee_ids" string="当前经办人" widget="many2one"/>
<field name="documents_ids" string="附件"/>
<field name="employee_ids" string="当前经办人"/>
<field name="create_uid" string="创建人"/>
</header_search>
<searchpanel>
@@ -585,6 +585,60 @@ class SoongWorkflowCommentWizard(models.TransientModel):
current_user = self.env.user
current_datetime = fields.Datetime.now()
is_admin = current_user.has_group('base.group_system')
# 归档判断
last_log = record_id.log_ids[-1] if record_id.log_ids else None
if last_log:
same_name_logs = record_id.log_ids.filtered(
lambda log: log.name == last_log.name and log.node_btn_name == '4' and log.id != last_log.id
)
all_approved = all(log.approval_state in ['2', '3'] for log in same_name_logs) or not same_name_logs
if all_approved:
workflow_record = self.env['soong.workflow.record'].search([('code', '=', record_id.code)],
limit=1)
has_archive_btn = any(btn_name.name == '归档' for btn_name in self.node_btn_id.node_id.button_ids)
if has_archive_btn:
log_id = record_id.log_ids.filtered(lambda l: l.name == '归档')
employee_id = self.env['hr.employee'].sudo().search(
[('user_id', '=', record_id.log_ids[0].done_by.id)], limit=1)
if workflow_record:
workflow_record.write({
'approve_state': 'end',
'employee_ids': [(6, 0, [employee_id.id])],
'soong_state': 'to_active',
'node_id': False,
})
if not log_id:
self.env["soong.workflow.log"].create({
'name': '归档',
"code": self.code,
"model": self.res_model,
"res_id": self.res_id,
"workflow_id": self.node_btn_id.workflow_id.id,
"done_by": record_id.log_ids[0].done_by.id,
"approval_state": "0",
"node_btn_id": self.node_btn_id.id,
})
is_work_remind = True
else:
log_id.write({
'document_ids': [(6, 0, self.document_ids.ids)],
"approval_state": "2",
'lase_click_date': fields.Datetime.now(),
})
workflow_record.write({
'soong_state': 'active',
'employee_ids': False,
})
else:
if workflow_record:
workflow_record.sudo().write({
'approve_state': 'end',
'employee_ids': False,
'node_id': False,
'soong_state': 'done',
})
record_id.work_end = True
if is_admin:
user_log = record_id.log_ids.filtered(
lambda log: log.done_by == current_user and log.node_btn_name != '1' and log.approval_state == '0'
@@ -679,58 +733,7 @@ class SoongWorkflowCommentWizard(models.TransientModel):
title=record_id._description + "传阅/抄送",
description=description, url=url,
btntxt="查看详情")
# if not record_id.log_ids2.search([('approval_state', '=', '0')]) or record_id.log_ids2:
last_log = record_id.log_ids[-1] if record_id.log_ids else None
if last_log:
same_name_logs = record_id.log_ids.filtered(
lambda log: log.name == last_log.name and log.node_btn_name == '4' and log.id != last_log.id
)
all_approved = all(log.approval_state in ['2', '3'] for log in same_name_logs) or not same_name_logs
if all_approved:
workflow_record = self.env['soong.workflow.record'].search([('code', '=', record_id.code)],
limit=1)
has_archive_btn = any(btn_name.name == '归档' for btn_name in self.node_btn_id.node_id.button_ids)
if has_archive_btn:
log_id = record_id.log_ids.filtered(lambda l: l.name == '归档')
employee_id = self.env['hr.employee'].sudo().search([('user_id', '=', record_id.log_ids[0].done_by.id)], limit=1)
if workflow_record:
workflow_record.write({
'approve_state': 'end',
'employee_ids': [(6, 0, [employee_id.id])],
'soong_state': 'to_active',
'node_id': False,
})
if not log_id:
self.env["soong.workflow.log"].create({
'name': '归档',
"code": self.code,
"model": self.res_model,
"res_id": self.res_id,
"workflow_id": self.node_btn_id.workflow_id.id,
"done_by": record_id.log_ids[0].done_by.id,
"approval_state": "0",
"node_btn_id": self.node_btn_id.id,
})
is_work_remind = True
else:
log_id.write({
'document_ids': [(6, 0, self.document_ids.ids)],
"approval_state": "2",
'lase_click_date': fields.Datetime.now(),
})
workflow_record.write({
'soong_state': 'active',
'employee_ids': False,
})
else:
if workflow_record:
workflow_record.sudo().write({
'approve_state': 'end',
'employee_ids': False,
'node_id': False,
'soong_state': 'done',
})
record_id.work_end = True
# 给申请人发消息
if is_work_remind:
biz_record = self.env[self.res_model].browse(self.res_id)
@@ -5,7 +5,7 @@ import { patch } from "@web/core/utils/patch";
import { deepCopy } from "@web/core/utils/objects";
import { useService } from "@web/core/utils/hooks";
import { url } from "@web/core/utils/urls";
import { clickRefreshButton} from "../actions/client_actions";
import { clickRefreshButton } from "../actions/client_actions";
const WKF_NOTIFY_TYPE = 'wkf_notify'
@@ -17,10 +17,11 @@ patch(NavBar.prototype, {
this.audio.src = this.audio.canPlayType("audio/ogg; codecs=vorbis")
? url("/mail/static/src/audio/ting.ogg")
: url("/mail/static/src/audio/ting.mp3");
const clearCache = () => this.env.bus.trigger("CLEAR-CACHES");
const play = () => {
return this.audio.play()
.then(() => {
clearCache()
clickRefreshButton()
})
.catch((e) => {
@@ -28,13 +29,13 @@ patch(NavBar.prototype, {
});
}
/**
* 监听后端发送的信号,重新加载菜单数据
* 监听后端发送的信号,重新加载菜单数据,清除viewSevice的缓存
* 1. 通知类型为WKF_NOTIFY_TYPE时,直接刷新菜单播放音频
* 2. 其余则通过菜单badge_count对比
*/
this.busService.subscribe("sun_badge_menu.reload_menu_data", (detail) => {
console.log('[sun_badge_menu.reload_menu_data]重新加载数据...')
debugger
// debugger
for (const d of detail) {
if (d?.type === WKF_NOTIFY_TYPE) {
this.menuService.reload();
@@ -48,7 +49,7 @@ patch(NavBar.prototype, {
return value ? value.reduce((count, app) => count + (app?.badge_count || 0), 0) : 0;
}
browser.setTimeout(() => {
debugger
// debugger
const oldCount = getReduceCount(oldMenuApps)
const newMenuApps = this.menuService.getApps()
const newCount = getReduceCount(newMenuApps)
+6
View File
@@ -0,0 +1,6 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
registry.category("user_menuitems").remove("documentation");
registry.category("user_menuitems").remove("profile");
+5 -5
View File
@@ -12,10 +12,10 @@
<menuitem id="yuthon_catering_conf_root_menu" name="配置" sequence="99"/>
</menuitem>
<!-- 餐费报表(员工)一级菜单 -->
<menuitem id="yuthon_salary_meal_views_menu_employee"
name="餐费报表(员工)"
action="yuthon_salary.view_yuthon_meal_expenses_action_readonly"
sequence="2"
web_icon="yuthon_catering,static/description/catering.png"/>
<!-- <menuitem id="yuthon_salary_meal_views_menu_employee"-->
<!-- name="餐费报表(员工)"-->
<!-- action="yuthon_salary.view_yuthon_meal_expenses_action_readonly"-->
<!-- sequence="2"-->
<!-- web_icon="yuthon_catering,static/description/catering.png"/>-->
</data>
</odoo>
@@ -214,71 +214,36 @@ class HrLeaveAllocation(models.Model):
return super(HrLeaveAllocation, self).unlink()
def update_vacation(self):
leave_ids = self.env["hr.leave.allocation"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for leave in leave_ids:
leave_list = []
compensatory_list = []
def action_validate(self):
result = super(HrLeaveAllocation, self).action_validate()
for leave in self:
if leave.holiday_status_id.name != '年假':
continue
vacation_date = leave.date_from.replace(day=1) if leave.date_from else fields.Date.today().replace(day=1)
reports_id = self.env['yuthon.vacation.reports'].search([
('employee_id', '=', leave.employee_id.id),
('vacation_date', '=', leave.date_from)
('vacation_date', '=', vacation_date)
], limit=1)
other_list = []
if leave.holiday_status_id.name == '年假':
current_leave_hours = leave.number_of_days * 8
existing_leave_line = False
if reports_id and reports_id.leave_ids:
existing_leave_line = reports_id.leave_ids.filtered(
lambda l: l.text == '上月额度继承'
)[:1]
if existing_leave_line:
update_leave_vals = {
'valid_date': leave.date_to,
}
existing_leave_line.write(update_leave_vals)
leave_list.append([0, 0, {
'vacation_id': reports_id.id,
'update_type': 'add',
'update_date': fields.Datetime.now(),
'valid_date': leave.date_from,
'update_lines': current_leave_hours,
'text': '年假分配'
}])
elif leave.holiday_status_id.name == '补休':
compensatory_list.append([0, 0, {
'vacation_id': reports_id.id,
'employee_id': reports_id.employee_id.id,
'update_type': 'add',
'valid_date': leave.date_from,
'workday': leave.number_of_hours_display,
'legal_day': 0,
'public_holidays': 0,
'meal_number': 0,
'text': f"上月额度继承"
}])
else:
other_list.append([0, 0, {
'vacation_id': reports_id.id,
'holiday_type': leave.holiday_status_id.id,
'employee_id': leave.employee_id.id,
'update_date': fields.Date.today(),
'update_type': 'add',
'lines': leave.number_of_hours_display / 8,
'text': leave.name
}])
if reports_id:
leave_hours = leave.number_of_days_display * 8
reports_id.write({
'leave_ids': leave_list,
'year_distribution': leave.number_of_days,
'valid_date': leave.date_to,
'overtime_ids': compensatory_list,
'other_fee_ids': other_list
'leave_ids': [(0, 0, {
'vacation_id': reports_id.id,
'update_type': 'add',
'update_date': fields.Datetime.now(),
'valid_date': leave.date_from,
'update_lines': leave_hours,
'text': '年假分配'
})]
})
reports_id.update_line()
return result
def update_employee(self):
leave_ids = self.env["hr.leave.allocation"].browse(
@@ -23,6 +23,7 @@
<field name="name">yuthon.holiday.allocation.form</field>
<field name="model">hr.leave.allocation</field>
<field name="inherit_id" ref="hr_holidays.hr_leave_allocation_view_form_manager"/>
<field name="priority" eval="100"/>
<field name="arch" type="xml">
<xpath expr="//form" position="attributes">
<attribute name="create">0</attribute>
@@ -37,6 +38,7 @@
<field name="name">yuthon.holiday.allocation.form2</field>
<field name="model">hr.leave.allocation</field>
<field name="inherit_id" ref="hr_holidays.hr_leave_allocation_view_form"/>
<field name="priority" eval="100"/>
<field name="arch" type="xml">
<xpath expr="//form" position="attributes">
<attribute name="create">0</attribute>
@@ -69,13 +71,6 @@
<field name="state">code</field>
<field name="code">action = model.action_uninstall_wizard()</field>
</record>
<record id="all_holiday_update_vacation" model="ir.actions.server">
<field name="name">更新报表</field>
<field name="model_id" ref="hr_holidays.model_hr_leave_allocation"/>
<field name="binding_model_id" ref="hr_holidays.model_hr_leave_allocation"/>
<field name="state">code</field>
<field name="code">action = model.update_vacation()</field>
</record>
<record id="all_one_time" model="ir.actions.server">
<field name="name">更新结束时间</field>
<field name="model_id" ref="hr_holidays.model_hr_leave_allocation"/>
@@ -79,6 +79,8 @@ class CustomerPortal(portal.CustomerPortal):
num = float(value or 0) * multiply
if num == 0:
return "0"
if unit == "小时":
return f"{round(num, 1)}{unit}"
return f"{num}{unit}"
data = {
@@ -27,19 +27,19 @@
<field name="numbercall">-1</field>
<field eval="False" name="doall"/>
</record>
<record forcecreate="True" id="attendance_missing_card_judgment" model="ir.cron">
<field name="name">定时任务:考勤缺卡判断</field>
<field name="model_id" ref="model_hr_attendance"/>
<field name="state">code</field>
<field name="code">model.missing_card_judgment()</field>
<field eval="True" name="active"/>
<field name="user_id" ref="base.user_root"/>
<field name="interval_type">days</field>
<field name="interval_number">1</field>
<field name="nextcall" eval="(DateTime.now()).strftime('%Y-%m-%d 23:00:00')"/>
<field name="numbercall">-1</field>
<field eval="False" name="doall"/>
</record>
<!-- <record forcecreate="True" id="attendance_missing_card_judgment" model="ir.cron">-->
<!-- <field name="name">定时任务:考勤缺卡判断</field>-->
<!-- <field name="model_id" ref="model_hr_attendance"/>-->
<!-- <field name="state">code</field>-->
<!-- <field name="code">model.missing_card_judgment()</field>-->
<!-- <field eval="True" name="active"/>-->
<!-- <field name="user_id" ref="base.user_root"/>-->
<!-- <field name="interval_type">days</field>-->
<!-- <field name="interval_number">1</field>-->
<!-- <field name="nextcall" eval="(DateTime.now()).strftime('%Y-%m-%d 23:00:00')"/>-->
<!-- <field name="numbercall">-1</field>-->
<!-- <field eval="False" name="doall"/>-->
<!-- </record>-->
<!-- 粮库相关提醒 -->
<record forcecreate="True" id="card_lc_remind_time_in" model="ir.cron">
@@ -1315,6 +1315,8 @@ class HrAttendance(models.Model):
'absenteeism_card': False
})
return
if self.workflow_id.res_model == 'overtime.approval':
return
current_date_start = datetime.combine(current_date, datetime.min.time()) # 当天 00:00:00 datetime类型
current_date_end = datetime.combine(current_date, datetime.max.time()) # 当天 23:59:59 datetime类型
@@ -208,16 +208,16 @@ class HrAttendanceDetail(models.Model):
if checkin_datetime > sch_check_out_time:
message = f"打卡成功!今日下班时间:{record.checkin_time + timedelta(hours=8)}: {employee_id.name},祝您归途平安!"
# if record.employee_id.name not in ['蒋彬', '麦丽娜']:
self.sudo().env["wecom.apps"].sync_send_message(
category='att', user_id=employee_id.wecom_userid,
message=message,
)
# if record.employee_id.name in ['蒋彬', '麦丽娜']:
# self.sudo().env["wecom.apps"].sync_send_message(
# category='att', user_id='18562027762',
# message=message + f'管理员测试管理员测试{vals}',
# )
if record.employee_id.name not in ['林伟东']:
self.sudo().env["wecom.apps"].sync_send_message(
category='att', user_id=employee_id.wecom_userid,
message=message,
)
else:
self.sudo().env["wecom.apps"].sync_send_message(
category='att', user_id='18562027762',
message=message + f'管理员测试管理员测试{vals}',
)
self.write({'remark': message})
_logger.info(message)
@@ -527,6 +527,7 @@ class OvertimeApprovals(models.Model):
def _compute_overtime_type(self):
for record in self:
current_date = datetime.today()
record.type_overtime = 'work'
if record.start_time and record.end_time:
if record.start_time > record.end_time:
raise UserError("开始时间不能晚于结束时间")
@@ -554,10 +555,16 @@ class OvertimeApprovals(models.Model):
date_str = start_date.strftime('%m月%d日')
date_str = self.remove_leading_zero(date_str)
is_weekend = start_date.weekday() in [5, 6]
if date_str in statutory_set:
# 调班补班日
if date_str in shift_days_set:
record.type_overtime = 'work'
# 法定节假日
elif date_str in statutory_set:
record.type_overtime = 'holiday'
# 周末 或 调休日
elif date_str in holidays_set or is_weekend:
record.type_overtime = 'weekend'
# 普通工作日
else:
record.type_overtime = 'work'
@@ -76,7 +76,7 @@ class YuthonVacationReports(models.Model):
show_date = fields.Date(string='日期', compute='_compute_display_date', store=True)
display_date = fields.Char(string='日期', compute='_compute_display_date')
done_leave = fields.Float(string='年假', compute='_compute_leave', store=True, digits=(16, 3), group_operator=None)
done_working_overtime = fields.Float(string='加班时', compute='_compute_working_overtime', digits=(16, 3),
done_working_overtime = fields.Float(string='加班时', compute='_compute_working_overtime', digits=(16, 1),
store=True, group_operator=None)
leave = fields.Float(string='剩余年假d', compute='_compute_leave', store=True, digits=(16, 3), group_operator=None)
working_overtime = fields.Float(string='剩余补休d', compute='_compute_working_overtime', store=True,
@@ -400,13 +400,14 @@ class YuthonVacationReports(models.Model):
# 2. 剩余部分扣减本年补休剩余(this_year1)
if remaining_overtime_deduct > 0 and rec.this_year1 > 0:
deduct_from_current1 = min(rec.this_year1, remaining_overtime_deduct)
deduct_from_current1 = remaining_overtime_deduct
rec.this_year1 -= deduct_from_current1
rec.used_this_year1 += deduct_from_current1 # 累加到本年已休
remaining_overtime_deduct -= deduct_from_current1
rec._compute_leave()
rec._compute_meal_fee()
rec._compute_parental_leave()
rec._compute_working_overtime()
def update_vacation(self):
@@ -823,13 +824,23 @@ class YuthonVacationReports(models.Model):
is_late = is_early = is_no = is_work = is_abnormal = 0
probation_days = regular_days = expatriation_days = 0
actual_attendance = leave_total_days = 0
is_retired = rec.employee_id.user_state in ('leave', 'withdraw')
is_retired = False
employee = rec.employee_id
vacation_date = rec.vacation_date # 每月1号
if employee and vacation_date:
if employee.user_state in ('leave', 'withdraw'):
contract = employee.contract_id
leave_date = False
# 优先取合同中止日期
if contract and contract.end_date:
leave_date = contract.end_date
if leave_date and leave_date >= vacation_date:
is_retired = False
else:
is_retired = True
# 核心:外出天数统计(按workflow_id去重)
go_out_days = 0.0 # 外出总天数
processed_workflow_ids = set() # 已处理的workflow_id(去重)
# 外派相关变量
go_out_days = 0.0
processed_workflow_ids = set()
is_outside = exp_has_return = False
exp_start_date = exp_end_return = exp_end_date = None
@@ -1535,10 +1546,10 @@ class YuthonVacationReports(models.Model):
if rec.remain_missed_meals:
self._update_meal_lines(rec, [rec], rec.remain_missed_meals)
if rec.leave:
if rec.leave != 0:
leave_hours = rec.leave * 8
self._update_leave_lines(rec, [rec], leave_hours)
if rec.working_overtime:
if rec.working_overtime != 0:
overtime_hours = rec.working_overtime * 8
self._update_over_lines(rec, [rec], overtime_hours)
if rec.employee_id.id not in target_employee_ids:
@@ -1705,8 +1716,8 @@ class YuthonVacationReports(models.Model):
if other_line.holiday_type and other_line.holiday_type.name == '调休':
adjust_holiday_days += other_line.lines or 0
record.working_overtime = max(total_add / 8, 0) + adjust_holiday_days
record.done_working_overtime = total_add1 / 8
record.working_overtime = (total_add / 8) + adjust_holiday_days
record.done_working_overtime = total_add1
record.statutory_leave = legal_total/8
record.cop_leave = total_add2 / 8
@@ -1980,15 +1991,18 @@ class YuthonVacationReports(models.Model):
('text', '=', '上月额度继承'),
], limit=1)
if existing_line:
continue
self.env['vacation.parenting.line'].sudo().create({
'vacation_id': report.id,
'employee_id': report.employee_id.id,
'update_date': update_date,
'end_date': end_date,
'lines': number_of_hours_display,
'text': f"上月额度继承",
})
existing_line.write({
'lines': number_of_hours_display,
})
else:
self.env['vacation.parenting.line'].sudo().create({
'vacation_id': report.id,
'employee_id': report.employee_id.id,
'update_date': update_date,
'end_date': end_date,
'lines': number_of_hours_display,
'text': f"上月额度继承",
})
def _update_meal_lines(self, report, allocations, number_of_hours_display):
for alloc in allocations:
@@ -95,19 +95,19 @@
<field name="hospital_sick_leave" options='{ "fg_color": "white: hospital_sick_leave == False"}'/>
<field name="recovery_sick_leave" options='{ "fg_color": "white: recovery_sick_leave == False"}'/>
<field name="exam_leave" options='{ "fg_color": "white: exam_leave == False"}'/>
<field name="previous_year"/>
<field name="valid_date"/>
<field name="year_distribution"/>
<field name="used_this_year"/>
<field name="remainder_year"/>
<field name="last_holiday"/>
<field name="last_workday"/>
<field name="valid_date1"/>
<field name="year_holiday"/>
<field name="year_workday"/>
<field name="year_sum"/>
<field name="used_this_year1"/>
<field name="working_overtime"/>
<field name="previous_year" column_invisible="1"/>
<field name="valid_date" column_invisible="1"/>
<field name="year_distribution" column_invisible="1"/>
<field name="used_this_year" column_invisible="1"/>
<field name="remainder_year" column_invisible="1"/>
<field name="last_holiday" column_invisible="1"/>
<field name="last_workday" column_invisible="1"/>
<field name="valid_date1" column_invisible="1"/>
<field name="year_holiday" column_invisible="1"/>
<field name="year_workday" column_invisible="1"/>
<field name="year_sum" column_invisible="1"/>
<field name="used_this_year1" column_invisible="1"/>
<field name="working_overtime" column_invisible="1"/>
<button name="log" type="object" string="详情" class="oe_highlight"/>
<button name="button_put" string="发送" invisible="vacation_state == 'confirmed'" type="object"
class="oe_highlight"/>
@@ -849,13 +849,13 @@
(0, 0, {'view_mode': 'tree', 'view_id': ref('yuthon_hr_attendance.view_yuthon_vacation_my_reports_tree')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('yuthon_hr_attendance.view_yuthon_vacation_reports_form1')})]"/>
</record>
<record id="all_vacation_delete" model="ir.actions.server">
<field name="name">初始化日志</field>
<field name="model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>
<field name="binding_model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>
<field name="state">code</field>
<field name="code">action = model.vacation_delete()</field>
</record>
<!-- <record id="all_vacation_delete" model="ir.actions.server">-->
<!-- <field name="name">初始化日志</field>-->
<!-- <field name="model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>-->
<!-- <field name="binding_model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>-->
<!-- <field name="state">code</field>-->
<!-- <field name="code">action = model.vacation_delete()</field>-->
<!-- </record>-->
<record id="all_put" model="ir.actions.server">
<field name="name">发送确认提醒</field>
<field name="model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>
@@ -863,13 +863,14 @@
<field name="state">code</field>
<field name="code">action = model.all_put()</field>
</record>
<record id="all_confirm" model="ir.actions.server">
<field name="name">批量确认</field>
<field name="model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>
<field name="binding_model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>
<field name="state">code</field>
<field name="code">action = model.all_confirm()</field>
</record>
<!-- <record id="all_confirm" model="ir.actions.server">-->
<!-- <field name="name">批量确认</field>-->
<!-- <field name="model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>-->
<!-- <field name="binding_model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>-->
<!-- <field name="state">code</field>-->
<!-- <field name="code">action = model.all_confirm()</field>-->
<!-- <field name="groups_id" eval="[(4, ref('hr_payroll.group_hr_payroll_manager'))]"/>-->
<!-- </record>-->
<record id="all_carryover" model="ir.actions.server">
<field name="name">加班费结转</field>
<field name="model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>
@@ -883,22 +884,22 @@
<field name="binding_model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>
<field name="state">code</field>
<field name="code">action = model.manage_confirmation()</field>
<field name="groups_id" eval="[(4, ref('base.group_system'))]"/>
</record>
<record id="all_update_vacation" model="ir.actions.server">
<field name="name">更新初始(1月)数据</field>
<field name="model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>
<field name="binding_model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>
<field name="state">code</field>
<field name="code">action = model.update_vacation()</field>
</record>
<record id="import_initial_data" model="ir.actions.server">
<field name="name">导入初始数据</field>
<field name="model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>
<field name="binding_model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>
<field name="state">code</field>
<field name="code">action = model.import_initial_data()</field>
<field name="groups_id" eval="[(4, ref('hr_payroll.group_hr_payroll_manager'))]"/>
</record>
<!-- <record id="all_update_vacation" model="ir.actions.server">-->
<!-- <field name="name">更新初始(1月)数据</field>-->
<!-- <field name="model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>-->
<!-- <field name="binding_model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>-->
<!-- <field name="state">code</field>-->
<!-- <field name="code">action = model.update_vacation()</field>-->
<!-- </record>-->
<!-- <record id="import_initial_data" model="ir.actions.server">-->
<!-- <field name="name">导入初始数据</field>-->
<!-- <field name="model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>-->
<!-- <field name="binding_model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>-->
<!-- <field name="state">code</field>-->
<!-- <field name="code">action = model.import_initial_data()</field>-->
<!-- </record>-->
<record id="all_update_line" model="ir.actions.server">
<field name="name">批量重新计算</field>
<field name="model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>
@@ -906,27 +907,27 @@
<field name="state">code</field>
<field name="code">action = model.all_update_line()</field>
</record>
<record id="all_update_working_overtime" model="ir.actions.server">
<field name="name">一次性</field>
<field name="model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>
<field name="binding_model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>
<field name="state">code</field>
<field name="code">action = model.update_is_visible()</field>
</record>
<record id="all_update_data" model="ir.actions.server">
<field name="name">更新报表</field>
<field name="model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>
<field name="binding_model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>
<field name="state">code</field>
<field name="code">action = model.update_data()</field>
</record>
<record id="all_update_holiday" model="ir.actions.server">
<field name="name">更新休假</field>
<field name="model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>
<field name="binding_model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>
<field name="state">code</field>
<field name="code">action = model.update_holiday()</field>
</record>
<!-- <record id="all_update_working_overtime" model="ir.actions.server">-->
<!-- <field name="name">一次性</field>-->
<!-- <field name="model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>-->
<!-- <field name="binding_model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>-->
<!-- <field name="state">code</field>-->
<!-- <field name="code">action = model.update_is_visible()</field>-->
<!-- </record>-->
<!-- <record id="all_update_data" model="ir.actions.server">-->
<!-- <field name="name">更新报表</field>-->
<!-- <field name="model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>-->
<!-- <field name="binding_model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>-->
<!-- <field name="state">code</field>-->
<!-- <field name="code">action = model.update_data()</field>-->
<!-- </record>-->
<!-- <record id="all_update_holiday" model="ir.actions.server">-->
<!-- <field name="name">更新休假</field>-->
<!-- <field name="model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>-->
<!-- <field name="binding_model_id" ref="yuthon_hr_attendance.model_yuthon_vacation_reports"/>-->
<!-- <field name="state">code</field>-->
<!-- <field name="code">action = model.update_holiday()</field>-->
<!-- </record>-->
<record id="details_tree_view" model="ir.ui.view">
<field name="name">details.tree.tree</field>
<field name="model">details.tree</field>
@@ -140,7 +140,7 @@ class YuthonMedicalManage(models.Model):
('user_state', '=', 'in'),
('company_id.name', '=', '集团本部'),
('contract_id.party_id', 'in', party_setting_records),
('contract_id.is_probation', '=', False)
('contract_type_id.name', '!=', '实习协议')
])
for employee in open_employees:
medical_manage_rec = self.env['yuthon.medical.manage'].search([
@@ -750,4 +750,5 @@
<!-- </xpath>-->
<!-- </field>-->
<!-- </record>-->
<menuitem id="menu_personal_information" name="个人信息" parent="yuthon_work_brand.holiday_manage_menu_root_board" action="action_server_my_employee" sequence="45"/>
</odoo>
@@ -246,12 +246,13 @@ class YuthonOnboardApproval(models.Model):
})
else:
contract_type_id = self.env['hr.contract.type'].search([('name', '=', '正式合同(一签)')], limit=1)
self.env['hr.contract'].create({
new_contract = self.env['hr.contract'].create({
'wage': salary,
'job_id': self.job_id.id,
'party_id': self.party_id.id,
'salary_group_id': self.salary_group_id.id,
'department_id': self.department_id1.id,
'state': 'open',
'employee_id': employee_id.id,
'date_start': self.duration_employment,
'contract_type_id': contract_type_id.id,
@@ -263,7 +264,12 @@ class YuthonOnboardApproval(models.Model):
'resource_calendar_id': resource_calendar_id.id,
'is_probation': True,
})
if new_contract.contract_type_id and new_contract.contract_type_id.name != '实习协议':
cron_job = self.env['ir.cron'].sudo().search([
('name', '=', '定时任务:体检费分配')
], limit=1)
if cron_job:
cron_job.sudo().method_direct_trigger()
vacation_date = self.duration_employment.replace(day=1)
report_id = self.env['yuthon.vacation.reports'].search([('employee_id', '=', employee_id.id), ('vacation_date', '=', vacation_date)])
if not report_id:
@@ -1,6 +1,7 @@
from odoo import models, fields, api
from odoo.exceptions import UserError
from dateutil.relativedelta import relativedelta
import re
class YuthonRegularizationPrepareApproval(models.Model):
@@ -10,16 +11,20 @@ class YuthonRegularizationPrepareApproval(models.Model):
name = fields.Char(string="名称")
department_id = fields.Many2one(related='users_id.employee_id.department_id', string="部门")
users_id = fields.Many2one('res.users', string="申请人", default=lambda self: self.env.user.id)
users_id = fields.Many2one('res.users', string="转正人", default=lambda self: self.env.user.id)
confirmation_user_id = fields.Many2one('res.users', string="申请人")
code = fields.Char(string="编号",
default=lambda self: self.env['ir.sequence'].next_by_code('yuthon_regularization_approval_code'))
structure_type_id = fields.Many2one('hr.payroll.structure.type', string="转正前职级")
end_date = fields.Date(string="转正日期", compute="_compute_end_date")
end_date = fields.Date(string="转正日期")
documents_id = fields.Many2one('ir.attachment', string="转正申请书")
documents_id1 = fields.Many2one('ir.attachment', string="转正考核表")
assessment_rating = fields.Char(string="考核评级")
structure_type_id1 = fields.Many2one('hr.payroll.structure.type', string="转正后职级")
is_leave_year = fields.Selection([('yes', '是'), ('no', '否')], string="是否分配年假")
amount_days = fields.Float(string="年假天数")
leave_allocate_attachment = fields.Many2one('ir.attachment', string="证明材料")
is_adjust = fields.Boolean(string='是否调整')
adjust_reason = fields.Text(string="调整原因")
remake = fields.Text(string="备注")
@@ -45,32 +50,110 @@ class YuthonRegularizationPrepareApproval(models.Model):
else:
self.is_adjust = False
@api.depends('users_id')
def _compute_end_date(self):
@api.onchange('users_id')
def _onchange_users_id(self):
for rec in self:
rec.end_date = False
if rec.users_id:
rec.end_date = rec.users_id.employee_ids.contract_id.end_date + relativedelta(days=1)
employee = rec.users_id.employee_ids[:1]
if employee and employee.contract_id and employee.contract_id.end_date:
rec.end_date = employee.contract_id.end_date + relativedelta(days=1)
def end_approval(self):
for rec in self:
version_str = rec.users_id.employee_ids.contract_id.version.lstrip('V')
main_version = version_str.split('.')[0]
contract_type_id = self.env['hr.contract.type'].search([('name', '=', '正式合同(一签)')], limit=1)
new_main_version = int(main_version) + 1
new_version = f"V{new_main_version}.0"
original_contract = rec.users_id.employee_ids.contract_id
original_contract.write({
'state': 'cancel',
})
original_contract.write({'state': 'cancel'})
new_contract_vals = {
'structure_type_id': rec.structure_type_id1.id,
'version': new_version,
'is_probation': False,
'contract_type_id': contract_type_id.id,
'state': 'open',
}
new_contract = original_contract.with_context(skip_contract_code_check=True).copy(new_contract_vals)
rec.users_id.employee_ids.write({
'contract_id': new_contract.id,
})
rec.users_id.employee_ids.write({'contract_id': new_contract.id})
employee = rec.users_id.employee_ids
cron_job = self.env['ir.cron'].sudo().search([
('name', '=', '定时任务:体检费分配')
], limit=1)
if cron_job:
cron_job.sudo().method_direct_trigger()
if rec.is_leave_year == 'yes':
end_date = rec.end_date
amount_days = rec.amount_days
amount_hours = amount_days * 8
# ---------------------- 4. 固定有效期:9月1日 → 次年8月31日 (粮储)----------------------
current_year = end_date.year
# 开始日期:当年 9月1日
date_from = f"{current_year}-09-01"
# 结束日期:次年 8月31日
date_to = f"{current_year + 1}-08-31"
first_day = end_date.replace(day=1)
vacation_report = self.env['yuthon.vacation.reports'].search([
('employee_id', '=', employee.id),
('vacation_date', '=', first_day)
], limit=1)
# if not vacation_report:
# continue
holiday_status = self.env['hr.leave.type'].search([
('name', 'ilike', '年假'),
('company_id.name', '=', '天河粮储')
], limit=1)
alloc = self.sudo().env['hr.leave.allocation'].create({
'name': f'年假发放',
'employee_id': employee.id,
'holiday_status_id': holiday_status.id,
'number_of_days': amount_days,
'date_from': date_from,
'date_to': date_to,
'allocation_type': 'regular',
})
alloc.action_validate()
self.env['vacation.leave.line'].create({
'vacation_id': vacation_report.id,
'employee_id': employee.id,
'update_type': 'add',
'update_date': fields.Datetime.now(),
'update_lines': round(amount_hours, 1),
'allocation_type': 'allocation',
'text': f'年假分配',
})
vacation_report.write({'valid_date': date_to})
vacation_report.update_line()
@api.onchange('is_leave_year')
def _onchange_is_leave_year(self):
for rec in self:
if rec.is_leave_year == 'yes':
rec.amount_days = 0
end_date = rec.end_date
employee = rec.users_id.employee_ids
# ---------------------- 1. 正则提取工龄 ----------------------
work_year_str = employee.social_insurance_time or ''
year_match = re.search(r'(\d+)年', work_year_str)
work_years = int(year_match.group(1)) if year_match else 0
# ---------------------- 2. 工龄对应年假天数 ----------------------
if 1 <= work_years < 10:
annual_days = 5
elif 10 <= work_years < 20:
annual_days = 10
elif work_years >= 20:
annual_days = 15
else:
annual_days = 0
if annual_days == 0:
continue
# ---------------------- 3. 计算额度:(12 - 转正月) / 12 ----------------------
join_month = end_date.month
rec.amount_days = round((12 - join_month) / 12 * annual_days, 1)
@api.onchange('is_issues')
def _onchange_is_issues(self):
@@ -47,7 +47,7 @@ class YuthonRetiredApproval(models.Model):
def end_approval(self):
current_date = fields.Date.today()
self.employee_id.contract_id.termination_date = self.expected_departure_date
if current_date >= self.expected_departure_date:
if not self.expected_departure_date or current_date >= self.expected_departure_date:
self.employee_id.user_state = 'withdraw'
if self.employee_id.contract_id:
self.employee_id.contract_id.state = 'cancel'
@@ -50,12 +50,17 @@
</group>
<group col="2" string="审批详情">
<group colspan="2">
<field name="state" invisible="1"/>
<field name="name" string="标题"/>
<field name="is_back" invisible="1"/>
<field name="confirmation_user_id" options="{'no_create': True}"/>
<field name="users_id" options="{'no_create': True}"/>
<field name="department_id" invisible="1"/>
<field name="end_date"/>
<field name="work_end" invisible="1"/>
<field name="is_leave_year" invisible="state != '人事专员'" required="state == '人事专员'"/>
<field name="amount_days" invisible="is_leave_year != 'yes'"/>
<field name="leave_allocate_attachment" widget="preview_many2one" required="is_leave_year == 'yes'" invisible="is_leave_year != 'yes'"/>
<field name="documents_id" widget="preview_many2one"/>
</group>
<!-- <field name="is_back" invisible="1"/>-->
+2 -2
View File
@@ -74,7 +74,7 @@ class RoomBooking(models.Model):
if '班子' in meeting_type_name:
if meet.issue_library_id.is_class and (meet.issue_library_id.is_class_passed == 'reject' or not meet.issue_library_id.is_class_passed):
meet.issue_library_id.class_char = str(
record.start_datetime.date()) + f'第{record.number_of_meetings}' + record.meeting_type_id.name
record.start_datetime.date()) + f'第{record.number_of_meetings}次' + record.meeting_type_id.name
meet.issue_library_id.issue_state_class = 'meeting'
meet.issue_library_id.issue_state = False
@@ -94,7 +94,7 @@ class RoomBooking(models.Model):
meet.issue_library_id.issue_state = False
elif meet.issue_library_id.is_class and (meet.issue_library_id.is_class_passed == 'r eject' or not meet.issue_library_id.is_class_passed):
meet.issue_library_id.class_char = str(
record.start_datetime.date()) + f'第{record.number_of_meetings}' + record.meeting_type_id.name
record.start_datetime.date()) + f'第{record.number_of_meetings}次' + record.meeting_type_id.name
meet.issue_library_id.issue_state_class = 'meeting'
meet.issue_library_id.issue_state = False
+4 -8
View File
@@ -151,14 +151,10 @@ class IssueLibrary(models.Model):
'target': 'current',
}
def write(self, vals):
if self._context.get('_correction_skip'):
return super(IssueLibrary, self).write(vals)
def action_is_modify(self):
for rec in self:
if rec.issue_state == 'to_modify':
vals['issue_state'] = rec.original_issue_state
vals['original_issue_state'] = False
return super(IssueLibrary, self).write(vals)
rec.issue_state = rec.original_issue_state
rec.original_issue_state = False
def issue_party_approval(self):
issue_active_ids = self.env["issue.library"].browse(
@@ -326,7 +322,7 @@ class IssueLibrary(models.Model):
title="议题修正",
description="您有待修正的议题,请尽快修正",
url=url, btntxt="详细信息")
rec.with_context(_correction_skip=True).write({
rec.write({
'original_issue_state': rec.issue_state,
'issue_state': 'to_modify',
'is_confirm': True,
+22 -16
View File
@@ -21,10 +21,10 @@
<field name="boss_char"/>
<field name="is_legal_trial"/>
<field name="co_organizer_dept" readonly="not is_admin and issue_state not in ['to_do', 'cancel','to_modify', 'draft']"/>
<field name="is_district_state"/>
<field name="is_district_government"/>
<field name="is_district_party"/>
<field name="is_external_person"/>
<field name="is_district_state" column_invisible="1"/>
<field name="is_district_government" column_invisible="1"/>
<field name="is_district_party" column_invisible="1"/>
<field name="is_external_person" column_invisible="1"/>
<field name="issue_state"/>
<field name="code" column_invisible="1"/>
<field name="company_id" column_invisible="1"/>
@@ -150,6 +150,10 @@
<field name="arch" type="xml">
<form string="议题总库" create="0">
<sheet>
<header>
<button name="action_is_modify" class="btn btn-primary" type="object"
string="已修改" invisible="issue_state != 'to_modify'"/>
</header>
<widget name="web_ribbon" title="普件" bg_color="text-bg-success"
invisible="urgency_level != 'normal'"/>
<widget name="web_ribbon" title="急件" bg_color="text-bg-warning"
@@ -387,16 +391,17 @@
<button name="all_meeting_issue" type="object" string="会议安排" class="oe_highlight"/>
<button name="issue_party1_approval" type="object" string="批量审批" class="oe_highlight"/>
</header>
<field name="name"/>
<field name="name" column_invisible="1"/>
<field name="name_text" class="list_width"/>
<field name="department_id"/>
<field name="creart_datetime"/>
<field name="is_party" groups="yuthon_salary.group_jtbb_user" string="党总支"/>
<field name="is_party" groups="yuthon_salary.group_lc_user,yuthon_salary.group_wg_user"
string="党支部会"/>
<field name="party_char"/>
<field name="is_district_state"/>
<field name="is_district_government"/>
<field name="is_district_party"/>
<field name="is_district_state" column_invisible="1"/>
<field name="is_district_government" column_invisible="1"/>
<field name="is_district_party" column_invisible="1"/>
<field name="is_external_person"/>
<field name="issue_state" string="审批状态" />
<field name="issue_state_party"/>
@@ -431,16 +436,17 @@
<button name="all_meeting_issue" type="object" string="会议安排" class="oe_highlight"/>
<button name="issue_party_approval" type="object" string="批量审批" class="oe_highlight"/>
</header>
<field name="name"/>
<field name="name" column_invisible="1"/>
<field name="name_text" class="list_width"/>
<field name="department_id"/>
<field name="creart_datetime"/>
<field name="is_party" groups="yuthon_salary.group_jtbb_user" string="党总支"/>
<field name="is_party" groups="yuthon_salary.group_lc_user,yuthon_salary.group_wg_user"
string="党支部会"/>
<field name="party_char"/>
<field name="is_district_state"/>
<field name="is_district_government"/>
<field name="is_district_party"/>
<field name="is_district_state" column_invisible="1"/>
<field name="is_district_government" column_invisible="1"/>
<field name="is_district_party" column_invisible="1"/>
<field name="is_external_person"/>
<field name="issue_state" string="审批状态" />
<field name="issue_state_party"/>
@@ -582,7 +588,7 @@
['draft', 'to_draft'])]
</field>
<field name="search_view_id" ref="view_issue_library_filter"/>
<field name="context">{'from_action': 'issue_library_action3', 'search_default_to_approval': 1}</field>
<field name="context">{'from_action': 'issue_library_action3'}</field>
<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'tree', 'view_id': ref('yuthon_issue.view_issue_library_tree2')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('yuthon_issue.view_issue_library_form3')})]"/>
@@ -595,7 +601,7 @@
<field name="domain">[('is_push_party', '=', True),('issue_state', 'not in', ['draft', 'to_draft']),
('is_push_depart', '=', True), ('company_id.name', '!=', '集团本部')]
</field>
<field name="context">{'from_action': 'issue_library_action3', 'search_default_to_approval': 1}</field>
<field name="context">{'from_action': 'issue_library_action8'}</field>
<field name="search_view_id" ref="view_issue_library_filter"/>
<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'tree', 'view_id': ref('yuthon_issue.view_issue_library_tree3')}),
@@ -763,7 +769,7 @@
('is_push_depart', '=', True)]
</field>
<field name="search_view_id" ref="view_issue_library_filter"/>
<field name="context">{'from_action': 'issue_library_action4', 'search_default_to_approval': 1}</field>
<field name="context">{'from_action': 'issue_library_action4'}</field>
<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'tree', 'view_id': ref('yuthon_issue.view_issue_library_tree4')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('yuthon_issue.view_issue_library_form4')})]"/>
@@ -929,7 +935,7 @@
('is_push_depart', '=', True)]
</field>
<field name="search_view_id" ref="view_issue_library_filter"/>
<field name="context">{'from_action': 'issue_library_action5', 'search_default_to_approval': 1}</field>
<field name="context">{'from_action': 'issue_library_action5'}</field>
<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'tree', 'view_id': ref('yuthon_issue.view_issue_library_tree5')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('yuthon_issue.view_issue_library_form5')})]"/>
@@ -226,6 +226,7 @@ class ReceivableModifyLine(models.Model):
no_amount = fields.Float(related='bank_id.no_amount', string='未对', store=False)
bank_state = fields.Selection(related='bank_id.bank_state', string='状态', store=False)
receivable_id = fields.Many2one('property.accounts.receivable', string='合同编号')
contract_id = fields.Many2one('property.lease.contract', string='关联租赁合同', compute='_compute_contract_id')
management_type_id = fields.Many2one('yuthon.management.type', related='receivable_id.management_type_id', string='应收类型', store=True)
due_date = fields.Date(related='receivable_id.due_date', string='应收开始', store=False)
due_date_end = fields.Date(related='receivable_id.due_date_end', string='应收结束', store=False)
@@ -240,6 +241,13 @@ class ReceivableModifyLine(models.Model):
# search='_search_lessee_id',
# )
@api.depends('receivable_id')
def _compute_contract_id(self):
for rec in self:
rec.contract_id = False
if rec.receivable_id:
rec.contract_id = rec.receivable_id.contract_id
@api.depends('receivable_id')
def _compute_lessee_id_company(self):
for rec in self:
@@ -13,7 +13,7 @@ class PropertyBillsList(models.Model):
amount_actual = fields.Float(string='实付')
receivables_id = fields.Many2one('property.accounts.receivable', string='应收账单')
contract_id = fields.Many2one('property.lease.contract', string="合同编号")
lessor_id = fields.Many2one('yuthon.property.company', string="支付公司")
lessor_id = fields.Many2one(related='contract_id.lessor_id', string="支付公司")
lessee_id = fields.Many2one(related="contract_id.lessee_id", string="承租方", store=True)
property_name = fields.Char(related='contract_id.property_name', string="物业信息")
workflow_record_id = fields.Many2one('soong.workflow.record', string="流程")
@@ -28,6 +28,13 @@ class PropertyBillsPayable(models.Model):
document_ids = fields.Many2many('ir.attachment', string="附件")
active = fields.Boolean('Active', default=True, tracking=True)
work_end = fields.Boolean(string="结束")
payment_type = fields.Selection([
('deposit_refund', '退押'),
('lease_refund', '退租'),
('engineering', '工程款'),
('agency_fee', '中介费'),
('other', '其他')
], string='付款类型')
def _get_default_name_title(self):
return self._description if self._description else self._name
@@ -240,7 +240,7 @@ class PropertyLeaseContract(models.Model):
electricity_fee_ids = fields.One2many('property.electric.fee.line', 'contract_id', string="物业电表明细行")
other_ids = fields.One2many('property.other.fee.line', 'other_id', string="其他费用明细行")
lease_bank_ids = fields.One2many('property.bank.line', 'contract_id', string="承租方账户明细行")
bank_statements_ids = fields.One2many('property.bank.statements', 'contract_id', string="银行流水明细行")
bank_statements_ids = fields.One2many('receivable.modify.line', 'contract_id', string="银行流水明细行")
contact_state = fields.Selection([('to', '待执行' ), ('ing', '执行中'), ('terminate', '中止'), ('complete', '完成'), ('early_termination', '提前终止'), ('settle', '结清')], string='合同管理状态', default='to')
# 附件
license_ids = fields.Many2many('ir.attachment',
@@ -711,9 +711,8 @@ class PropertyLeaseContract(models.Model):
def _compute_reconciliation_count(self):
for record in self:
record.reconciliation_count = self.env['property.bank.statements'].search_count([('contract_id', '=', record.id),
('name_account', '=', record.lessee_id.name)
])
record.reconciliation_count = self.env['receivable.modify.line'].search_count([('receivable_id.contract_id', '=', self.id)])
def _compute_process_count(self):
for record in self:
record.process_count = self.env['soong.workflow.record'].search_count([('code', '=', record.code)])
@@ -732,9 +731,8 @@ class PropertyLeaseContract(models.Model):
def action_open_reconciliation(self):
self.ensure_one()
action = self.env["ir.actions.actions"]._for_xml_id("yuthon_property.view_property_bank_statements_action1")
action.update({'domain': [('contract_id', '=', self.id),
('name_account', '=', self.lessee_id.name)
action = self.env["ir.actions.actions"]._for_xml_id("yuthon_work_brand.action_receivable_modify_line_wbrand")
action.update({'domain': [('receivable_id.contract_id', '=', self.id),
]
})
return action
@@ -42,6 +42,7 @@
<group>
<field name="is_back" invisible="1"/>
<field name="name"/>
<field name="payment_type"/>
<field name="users_id"/>
<field name="work_end" invisible="1"/>
<field name="company_id" invisible="1"/>
@@ -54,7 +55,7 @@
<group>
<field name="lease_contract_id"/>
<field name="property_name"/>
<field name="lessee_id"/>
<field name="lessee_id" string="收款方"/>
<field name="amount_payable"/>
<field name="deduction_amount"/>
<field name="remake" invisible="not deduction_amount"/>
@@ -336,19 +336,21 @@
</tree>
</field>
</page>
<page string="银行流水" invisible="(state in ['分管领导', '总经理']) and (not work_end)">
<page string="银行流水">
<field name="bank_statements_ids" string=" ">
<tree editable="bottom">
<field name="start_time"/>
<field name="company_id" required="1"/>
<field name="name"/>
<field name="name_account"/>
<field name="contract_account"/>
<field name="remake"/>
<field name="property_ids" widget="many2many_tags" options="{'no_create': True}"/>
<field name="contract_penalty"/>
<field name="contract_id" column_invisible="1"/>
<field name="management_type_id"/>
<field name="due_date"/>
<field name="due_date_end"/>
<field name="reconciliation_date"/>
<field name="has_date"/>
<field name="delivered1"/>
<field name="amount"/>
<field name="original_amount"/>
<field name="voucher_type"/>
<field name="no_amount"/>
<field name="not_collected"/>
<field name="bank_state"/>
</tree>
</field>
</page>
@@ -846,17 +848,19 @@
</page>
<page string="银行流水">
<field name="bank_statements_ids" string=" ">
<tree editable="bottom" delete="false">
<field name="start_time"/>
<field name="company_id" options="{'no_open': True}"/>
<field name="name"/>
<field name="name_account"/>
<field name="contract_account"/>
<field name="remake"/>
<field name="property_ids" widget="many2many_tags" options="{'no_create': True}"/>
<field name="contract_penalty"/>
<tree editable="bottom">
<field name="contract_id" column_invisible="1"/>
<field name="management_type_id"/>
<field name="due_date"/>
<field name="due_date_end"/>
<field name="reconciliation_date"/>
<field name="has_date"/>
<field name="delivered1"/>
<field name="amount"/>
<field name="original_amount"/>
<field name="voucher_type"/>
<field name="no_amount"/>
<field name="not_collected"/>
<field name="bank_state"/>
</tree>
</field>
</page>
@@ -954,18 +958,20 @@
</group>
</group>
</page>
<page string="应收明细">
<page string="实收明细">
<field name="receivable_ids" string=" ">
<tree editable="bottom">
<field name="contract_id" column_invisible="1"/>
<field name="management_type_id"/>
<field name="last_month_table"/>
<field name="this_month_table"/>
<field name="ratio"/>
<field name="billable_quantity"/>
<field name="due_date"/>
<field name="due_date_end"/>
<field name="original_amount" string="应计金额"/>
<field name="last_month_table" column_invisible="1"/>
<field name="this_month_table" column_invisible="1"/>
<field name="ratio" column_invisible="1"/>
<field name="billable_quantity" column_invisible="1"/>
<field name="due_date" string="应收开始日期"/>
<field name="due_date_end" string="应收结束日期"/>
<field name="original_amount" string="收款金额"/>
<field name="payout_date" string="收款日期"/>
<field name="delivered1" string="应收金额"/>
<field name="pre_collection"/>
<field name="actual_coping"/>
<field name="remark"/>
@@ -1112,7 +1118,6 @@
<field name="res_model">property.lease.contract</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_group_by_admin_id': True,'header_search_default_ing': 1}</field>
<field name="domain">[('is_contract', '=', False)]</field>
<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'tree', 'view_id': ref('yuthon_property_approval.view_property_lease_contract_tree1')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('yuthon_property_approval.view_property_lease_contract_form1')})]"/>
@@ -25,7 +25,7 @@ class PropertyBillsWizard(models.TransientModel):
self.bills_list_id.actual_date = self.actual_date
self.bills_list_id.pay_state = 'paid'
receivable = self.env['property.accounts.receivable'].search([('contract_id', '=', self.bills_list_id.contract_id.id),
('management_type_id', '=', self.bills_list_i.management_type_id.id)
('management_type_id', '=', self.bills_list_id.management_type_id.id)
])
if receivable:
receivable.write({'has_date': self.actual_date,
@@ -237,7 +237,7 @@ class PropertyRentalWizard(models.Model):
# 当期应收日期
if new_version:
if start_year_month <= date_field_year_month <= end_year_month:
if start_year_month < date_field_year_month <= end_year_month:
if rent_id.period_rent != 0 and record.contract_sort not in ['guanggao', 'guanzi', 'room']:
self.create_rent_list(record, rent_id, rent_amount, start_date, multiplier)
self.create_management_list(record, start_date, manage_amount)
@@ -37,6 +37,7 @@ class InheritPurchaseOrder(models.Model):
default=lambda self: [(6, 0, [self.env.user.id])])
pay_way = fields.Selection([('onetime', '一次性'), ('installment', '分期')], string="付款方式", default='onetime')
reason_text = fields.Text(string="申请事由")
purchase_type = fields.Selection([('fixed_asset', '固定资产采购'), ('sporadic', '零星采购'), ('comprehensive', '综合类')], string="采购类型", default='comprehensive')
is_modify = fields.Boolean(string="可修改")
pay_line_ids = fields.One2many('purchase.order.pay.line', 'order_id', string="付款详情")
is_company = fields.Boolean(string="本公司", default=True, compute='_compute_is_company',search='_search_part_of_company')
@@ -246,6 +247,7 @@ class InheritPurchaseOrderLine(models.Model):
category_id = fields.Many2one('product.category', string="产品分类")
brand_spec = fields.Char(string="品牌/规格")
category_ids = fields.Many2many('product.category', string="产品分类", compute='_compute_category_id')
purchase_type = fields.Selection(related='order_id.purchase_type', string="采购类型")
@api.depends('category_id')
def _compute_category_id(self):
@@ -261,3 +263,37 @@ class InheritPurchaseOrderLine(models.Model):
])
record.category_ids = [(6, 0, matching_categories.ids)] if matching_categories else [(5,)]
@api.onchange('product_id')
def _onchange_product_id_check_tag(self):
"""检查产品标签是否符合采购类型要求"""
if self.product_id and self.order_id.purchase_type:
tag_names = self.product_id.product_tag_ids.mapped('name')
if self.order_id.purchase_type == 'fixed_asset':
# 固定资产采购:只能选择有固定资产标签的产品
if '固定资产' not in tag_names:
warning = {
'title': '产品不符合要求',
'message': '固定资产采购只能选择产品标签为“固定资产”的产品!'
}
return {'warning': warning, 'value': {'product_id': False}}
elif self.order_id.purchase_type == 'sporadic':
# 零星采购:只能选择没有固定资产标签的产品
if '固定资产' in tag_names:
warning = {
'title': '产品不符合要求',
'message': '零星采购不能选择产品标签为“固定资产”的产品!'
}
return {'warning': warning, 'value': {'product_id': False}}
@api.onchange('order_id')
def _onchange_order_id_set_product_domain(self):
"""根据采购类型设置产品domain"""
if self.order_id:
if self.order_id.purchase_type == 'fixed_asset':
# 固定资产采购:只能选择有固定资产标签的产品
return {'domain': {'product_id': [('product_tag_ids.name', 'in', ['固定资产'])]}}
elif self.order_id.purchase_type == 'sporadic':
# 零星采购:只能选择没有固定资产标签的产品
return {'domain': {'product_id': [('product_tag_ids.name', 'not in', ['固定资产'])]}}
# 综合类:不设置domain,显示所有产品
@@ -41,7 +41,7 @@
</tr>
<tr>
<td class="label">采购标的类型</td>
<td colspan="5"><span t-esc="doc.category_id.name or ''"/></td>
<td colspan="5"><span t-esc="dict(doc._fields['purchase_type'].selection).get(doc.purchase_type, '')"/></td>
</tr>
<tr>
<td class="label">付款方式</td>
@@ -161,6 +161,7 @@
</xpath>
<xpath expr="//field[@name='date_order']" position="before">
<field name="pay_way"/>
<field name="purchase_type" widget="radio"/>
</xpath>
<xpath expr="//div[@name='date_approve']" position="attributes">
<attribute name="invisible">1</attribute>
@@ -180,6 +181,12 @@
<field name="category_id" column_invisible="1"/>
<field name="category_ids" widget="many2many_tags" column_invisible="1"/>
</xpath>
<xpath expr="//field[@name='order_line']//tree" position="inside">
<field name="purchase_type" column_invisible="1"/>
</xpath>
<xpath expr="//field[@name='order_line']//tree//field[@name='product_id']" position="attributes">
<attribute name="domain">purchase_type == 'fixed_asset' and [('product_tag_ids.name', 'in', ['固定资产'])] or purchase_type == 'sporadic' and [('product_tag_ids.name', 'not in', ['固定资产'])] or []</attribute>
</xpath>
<xpath expr="//field[@name='order_line']//tree//field[@name='product_id']" position="after">
<field name="brand_spec" string="品牌/规格" optional="show"/>
</xpath>
@@ -192,9 +199,9 @@
<field name="product_packaging_qty" invisible="not product_id or not product_packaging_id" groups="product.group_stock_packaging" optional="show"/>
<field name="product_packaging_id" invisible="not product_id" context="{'default_product_id': product_id, 'tree_view_ref':'product.product_packaging_tree_view', 'form_view_ref':'product.product_packaging_form_view'}" groups="product.group_stock_packaging" optional="show"/>
</xpath>
<!-- <xpath expr="//field[@name='order_line']" position="attributes">-->
<!-- <attribute name="context">{'default_category_id': category_id}</attribute>-->
<!-- </xpath>-->
<xpath expr="//field[@name='order_line']" position="attributes">
<attribute name="context">{'default_order_id': id, 'purchase_type': purchase_type}</attribute>
</xpath>
<xpath expr="//page[@name='products']" position="after">
<page string="付款详情" invisible="pay_way == 'onetime'">
<field name="pay_line_ids">
+26 -7
View File
@@ -11,8 +11,8 @@ class CumArrearsReport(models.Model):
def get_cum_arrears_report(self, params):
"""
累计欠收报表(数据源:receivable.modify.line):
- 状态为挂账(bank_state = 'registered')
- 应收开始日期(receivable_id.due_date)小于等于查询月月末
- 未收金额大于0(not_collected > 0)
- 排除押金和违约金类型
- 金额取 not_collected(未收)
"""
@@ -29,24 +29,30 @@ class CumArrearsReport(models.Model):
month_end = query_date.replace(day=last_day)
domain = [
('bank_state', '=', 'registered'),
('receivable_id.due_date', '<=', month_end),
('receivable_id.is_invalid', '=', False),
]
keyword = params.get("keyword")
if keyword:
print(f"[累计欠收报表] keyword = {keyword}")
# 处理 False, None, '' 等falsy值,只对非空字符串进行搜索
# 前端可能传递 False 表示无搜索,所以需要特殊处理
if keyword is not False and keyword is not None and isinstance(keyword, str) and keyword.strip():
print(f"[累计欠收报表] 添加搜索条件: {keyword}")
domain += [
'|', '|',
('receivable_id.contract_id.contract_code', 'ilike', keyword),
('receivable_id.property_name', 'ilike', keyword),
('receivable_id.lessee_id.name', 'ilike', keyword),
('receivable_id.contract_id.contract_code', 'ilike', keyword.strip()),
('receivable_id.property_name', 'ilike', keyword.strip()),
('receivable_id.lessee_id.name', 'ilike', keyword.strip()),
]
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
management_type_id = params.get('management_type_id')
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
contract_id = params.get('contract_id')
if condition:
domain.append(('receivable_id.tenure_type', '=', condition))
@@ -57,9 +63,22 @@ class CumArrearsReport(models.Model):
if management_type_id:
domain.append(('receivable_id.management_type_id', '=', management_type_id))
else:
# 排除押金和违约金类型
domain.append(('receivable_id.management_type_id.name', 'not in', ['押金', '违约金']))
if property_id:
domain.append(('receivable_id.property_ids', 'in', [property_id]))
if lessee_id:
domain.append(('receivable_id.lessee_id', '=', lessee_id))
if contract_id:
domain.append(('receivable_id.contract_id', '=', contract_id))
lines = self.search(domain, order="receivable_id asc")
print(f"[累计欠收报表] domain = {domain}")
lines = self.sudo().search(domain, order="receivable_id asc")
print(f"[累计欠收报表] 搜索到的记录数 = {len(lines)}")
# 欠收条件: not_collected > 0
lines = lines.filtered(lambda l: l.not_collected and l.not_collected > 0)
print(f"[累计欠收报表] 过滤后记录数 = {len(lines)}")
rows = []
total_amount = 0.0
+55 -20
View File
@@ -9,24 +9,30 @@ class PreArrearsReport(models.Model):
@api.model
def get_pre_arrears_report(self, params):
"""
前期欠收报表(数据源:receivable.modify.line):
- 查询条件的日期对应应收账单的 due_date(应收日期在查询月内)
- 收款日期(reconciliation_date)晚于应收日期(due_date)的记录就是欠收
- 金额取收款记录的 amount
- 按应收账单聚合,每张账单只出一行(汇总该账单下所有欠收收款金额)
前期欠收报表(数据源:receivable.modify.line):
- 查询条件的日期对应应收账单的 due_date(应收日期在查询月及之前)
- 欠收条件:
- 收款日期在查询月之后(查询月时点还没收到钱)
- 或者 not_collected > 0 (部分收款,还没交完)
- 金额取值:
- 如果 not_collected > 0,取 not_collected (当前还欠的金额)
- 否则取 amount (已收金额)
- 按应收账单聚合,每张账单只出一行(汇总该账单下所有欠收收款金额)
"""
today = datetime.date.today()
this_month_first = today.replace(day=1)
date_start = params.get("date_start")
date_end = params.get("date_end")
# 应收日期在查询日期之前(不含查询月)
cutoff_date = datetime.datetime.fromisoformat(date_start).date() if date_start else this_month_first
# 应收日期在查询月及之前(包含查询月)
cutoff_date = datetime.datetime.fromisoformat(date_end).date() if date_end else this_month_first
# 查询月范围(用于判断收款是否在查询月内)
query_month_start = datetime.datetime.fromisoformat(date_start).date() if date_start else this_month_first
query_month_end = datetime.datetime.fromisoformat(date_end).date() if date_end else this_month_first
# 核心逻辑:due_date < cutoff_date,且收款日期 > 应收日期
# 核心逻辑:due_date <= cutoff_date(查询月及之前的应收)
domain = [
('reconciliation_date', '!=', False),
('receivable_id.due_date', '<', cutoff_date),
('receivable_id.due_date', '<=', cutoff_date),
('receivable_id.is_invalid', '=', False),
]
@@ -65,24 +71,43 @@ class PreArrearsReport(models.Model):
domain.append(('receivable_id.management_type_id.name', 'not in', ['押金', '违约金']))
lines = self.sudo().search(domain, order="receivable_id asc, reconciliation_date asc")
# 欠收条件:reconciliation_date > due_date 且不在同一个月(跨月才算欠收)
# 欠收条件(同时满足):
# 1. reconciliation_date >= query_month_start (收款日期不能小于查询月)
# 2. 满足以下任一:
# a. reconciliation_date > query_month_end (收款日期在查询月之后,查询月时点还没收到钱)
# b. not_collected > 0 (部分收款,还没交完)
lines = lines.filtered(
lambda l: l.reconciliation_date and l.receivable_id.due_date
and l.reconciliation_date > l.receivable_id.due_date
and not (
l.reconciliation_date.year == l.receivable_id.due_date.year
and l.reconciliation_date.month == l.receivable_id.due_date.month
)
lambda l: l.reconciliation_date and l.reconciliation_date >= query_month_start and (
(l.reconciliation_date > query_month_end) or
(l.not_collected and l.not_collected > 0)
)
)
# 调试日志
print(f"[前期欠收报表] 查询到的 lines 数量: {len(lines)}")
for line in lines:
print(f" - line.id={line.id}, receivable_id={line.receivable_id.id}, "
f"due_date={line.receivable_id.due_date}, "
f"reconciliation_date={line.reconciliation_date}, "
f"not_collected={line.not_collected}, amount={line.amount}")
# 按应收账单聚合
rec_map = {}
for line in lines:
rid = line.receivable_id.id
if rid not in rec_map:
rec_map[rid] = {'rec': line.receivable_id, 'amount': 0.0}
rec_map[rid]['amount'] += line.amount or 0.0
rec_map[rid] = {'rec': line.receivable_id, 'amount': 0.0, 'reconciliation_dates': []}
# 金额取值:有未收金额取not_collected,否则取amount
rec_map[rid]['amount'] += (line.not_collected if (line.not_collected and line.not_collected > 0) else line.amount) or 0.0
# 收集收款日期
if line.reconciliation_date:
rec_map[rid]['reconciliation_dates'].append(line.reconciliation_date)
# 调试日志
print(f"[前期欠收报表] 聚合后的 rec_map 数量: {len(rec_map)}")
for rid, data in rec_map.items():
print(f" - receivable_id={rid}, amount={data['amount']}")
rows = []
total_amount = 0.0
@@ -103,6 +128,13 @@ class PreArrearsReport(models.Model):
}
elif rec:
property_val = rec.property_name or ''
# 应收开始日期
due_date_str = rec.due_date.strftime('%Y-%m-%d') if rec.due_date else ''
# 收款日期(取所有收款日期,用逗号分隔)
reconciliation_dates = data.get('reconciliation_dates', [])
reconciliation_date_str = ', '.join([d.strftime('%Y-%m-%d') for d in sorted(set(reconciliation_dates))]) if reconciliation_dates else ''
rows.append({
"序号": idx,
@@ -110,6 +142,7 @@ class PreArrearsReport(models.Model):
'model': 'property.lease.contract',
'id': contract.id,
'display_name': contract.contract_code,
'view_id': self.env.ref('yuthon_property_approval.view_property_lease_contract_form1').id,
} if contract else "",
"物业名称": property_val,
"承租方名称": {
@@ -118,6 +151,8 @@ class PreArrearsReport(models.Model):
'display_name': rec.lessee_id.name,
} if rec.lessee_id else "",
"收款类型": rec.management_type_id.name if rec.management_type_id else "",
"应收开始日期": due_date_str,
"收款日期": reconciliation_date_str,
"金额": amount,
"所属公司": contract.lessor_id.name if contract and contract.lessor_id else "",
"管理员": {
@@ -8,48 +8,26 @@ class ReceivableModifyFilterDomains(models.Model):
@api.model
def get_receivable_filter_domains(self, params):
"""根据当前筛选条件,返回物业/承租方/合同的候选 ID 集合,用于三个下拉框互相过滤。"""
# 注意:这里不按日期限制,只根据当前组合条件(公司/权属/片区/收款类型/物业/承租方/合同)
# 来计算三类对象的全集,避免不同报表各自的日期口径导致候选集为空。
domain = [
('receivable_id.is_invalid', '=', False),
]
"""根据当前筛选条件,返回物业/承租方/合同的候选 ID 集合,用于三个下拉框互相过滤。
注意:
- 候选集不应受收款类型、是否无效等业务条件过滤
- 只根据用户已选择的物业/承租方/合同进行互相过滤
- 具体的业务过滤由报表查询方法自己处理
"""
domain = []
keyword = params.get("keyword")
if keyword:
domain += [
'|', '|',
('receivable_id.contract_id.contract_code', 'ilike', keyword),
('receivable_id.property_name', 'ilike', keyword),
('receivable_id.lessee_id.name', 'ilike', keyword),
]
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
management_type_id = params.get('management_type_id')
# 只根据已选择的条件进行互相过滤,不应用业务过滤
property_id = params.get('property_id')
lessee_id = params.get('lessee_id')
contract_id = params.get('contract_id')
if condition:
domain.append(('receivable_id.tenure_type', '=', condition))
if partner_id:
domain.append(('receivable_id.precinct_id', '=', partner_id))
if company_id:
domain.append(('receivable_id.company_id', '=', company_id))
if property_id:
domain.append(('receivable_id.property_ids', 'in', [property_id]))
if lessee_id:
domain.append(('receivable_id.lessee_id', '=', lessee_id))
if contract_id:
domain.append(('receivable_id.contract_id', '=', contract_id))
if management_type_id:
domain.append(('receivable_id.management_type_id', '=', management_type_id))
else:
# 与各报表保持一致:默认剔除押金、违约金类型
domain.append(('receivable_id.management_type_id.name', 'not in', ['押金', '违约金']))
lines = self.sudo().search(domain)
receivables = lines.mapped('receivable_id')
+287
View File
@@ -0,0 +1,287 @@
# -*- coding: utf-8 -*-
import datetime
import calendar
from odoo import api, models
class TextSummaryReport(models.Model):
_name = 'text.summary.report'
_description = '文字汇总报表'
@api.model
def get_text_summary_report(self, params):
"""
生成文字汇总报表(类似图中的格式)
:param params: 筛选参数(date_start/company_id/partner_id等)
:return: 分组汇总数据
"""
today = datetime.date.today()
# 处理日期参数
date_str = params.get("date_start")
if date_str:
# 支持 YYYY-MM 和 YYYY-MM-DD 两种格式
if len(date_str) == 7: # YYYY-MM 格式
year, month = map(int, date_str.split('-'))
query_date = datetime.date(year, month, 1)
else: # YYYY-MM-DD 格式
query_date = datetime.datetime.fromisoformat(date_str).date()
else:
query_date = today
# 获取筛选条件
company_id = params.get('company_id')
precinct_id = params.get('partner_id')
# 构建报表数据结构
report_data = {
'summary_info': {
'统计时间': f"{query_date.year}-{query_date.month:02d}",
'统计片区': '全部' if not precinct_id else self._get_precinct_name(precinct_id),
'所属公司': '全部' if not company_id else self._get_company_name(company_id),
'创建时间': datetime.date.today().strftime("%Y-%m-%d"),
},
'groups': [
{
'title': '租金累计欠收',
'items': self._get_rent_arrears_data(query_date, company_id, precinct_id)
},
{
'title': '违约金累计欠收',
'items': self._get_penalty_arrears_data(query_date, company_id, precinct_id)
},
{
'title': '本月预收',
'items': self._get_month_precollection_data(query_date, company_id, precinct_id)
},
{
'title': '其他收入',
'items': self._get_other_income_data(query_date, company_id, precinct_id)
},
{
'title': '管理费支出',
'items': self._get_management_fee_data(query_date, company_id, precinct_id)
},
]
}
return report_data
def _get_precinct_name(self, precinct_id):
"""获取片区名称"""
try:
precinct = self.env['res.users'].browse(precinct_id)
return precinct.name if precinct else '未知'
except:
return '未知'
def _get_company_name(self, company_id):
"""获取公司名称"""
try:
company = self.env['yuthon.property.company'].browse(company_id)
return company.name if company else '未知'
except:
return '未知'
def _get_rent_arrears_data(self, query_date, company_id, precinct_id):
"""获取租金累计欠收数据 - 从累计欠收报表获取"""
params = {
'date_start': query_date.strftime("%Y-%m-%d"),
'company_id': company_id,
'partner_id': precinct_id,
}
# 调用累计欠收报表
result = self.env['receivable.modify.line'].get_cum_arrears_report(params)
rows = result.get('rows', [])
items = []
# 按片区分组
area_map = {}
for row in rows:
area = row.get('管理员', {}).get('display_name', '未分配') if isinstance(row.get('管理员'), dict) else row.get('管理员', '未分配')
if area not in area_map:
area_map[area] = []
property_name = row.get('物业名称', {})
if isinstance(property_name, dict):
property_name = property_name.get('display_name', '未命名物业')
amount = row.get('未收金额', 0.0)
area_map[area].append(f"{property_name} 欠收:{amount:.2f}元")
for area, texts in area_map.items():
items.append({
'area': area,
'content': '。'.join(texts[:10]) + '。' # 每个片区最多显示10条
})
return items if items else [{'area': '暂无数据', 'content': '暂无租金累计欠收数据。'}]
def _get_penalty_arrears_data(self, query_date, company_id, precinct_id):
"""获取违约金累计欠收数据 - 从违约金欠收报表获取"""
params = {
'date_start': query_date.strftime("%Y-%m-%d"),
'company_id': company_id,
'partner_id': precinct_id,
}
# 调用违约金欠收报表
result = self.env['property.accounts.receivable'].get_penalty_arrears_report(params)
rows = result.get('rows', [])
items = []
# 按片区分组
area_map = {}
for row in rows:
area = row.get('管理员', {}).get('display_name', '未分配') if isinstance(row.get('管理员'), dict) else row.get('管理员', '未分配')
if area not in area_map:
area_map[area] = []
property_name = row.get('物业名称', {})
if isinstance(property_name, dict):
property_name = property_name.get('display_name', '未命名物业')
amount = row.get('总未收', 0.0)
area_map[area].append(f"{property_name} 违约金欠收:{amount:.2f}元")
for area, texts in area_map.items():
items.append({
'area': area,
'content': '。'.join(texts[:10]) + '。'
})
return items if items else [{'area': '暂无数据', 'content': '暂无违约金累计欠收数据。'}]
def _get_month_precollection_data(self, query_date, company_id, precinct_id):
"""获取本月预收数据 - 从本月预收报表获取"""
params = {
'date_start': query_date.strftime("%Y-%m-%d"),
'company_id': company_id,
'partner_id': precinct_id,
}
# 调用本月预收报表
result = self.env['receivable.modify.line'].get_monthly_precollection_report(params)
rows = result.get('rows', [])
items = []
# 按片区分组
area_map = {}
for row in rows:
area = row.get('管理员', {}).get('display_name', '未分配') if isinstance(row.get('管理员'), dict) else row.get('管理员', '未分配')
if area not in area_map:
area_map[area] = []
property_name = row.get('物业名称', {})
if isinstance(property_name, dict):
property_name = property_name.get('display_name', '未命名物业')
amount = row.get('金额', 0.0)
area_map[area].append(f"{property_name}本月预收:{amount:.2f}元")
for area, texts in area_map.items():
items.append({
'area': area,
'content': '。'.join(texts[:10]) + '。'
})
return items if items else [{'area': '暂无数据', 'content': '暂无本月预收数据。'}]
def _get_other_income_data(self, query_date, company_id, precinct_id):
"""获取其他收入数据 - 从停车费报表获取"""
params = {
'date_start': query_date.strftime("%Y-%m-%d"),
'company_id': company_id,
'partner_id': precinct_id,
}
# 调用停车费本月实收报表
result = self.env['property.accounts.receivable'].get_parking_fee_current_month_report(params)
rows = result.get('rows', [])
items = []
# 按片区分组
area_map = {}
total_parking = 0.0
for row in rows:
area = row.get('管理员', {}).get('display_name', '未分配') if isinstance(row.get('管理员'), dict) else row.get('管理员', '未分配')
if area not in area_map:
area_map[area] = []
property_name = row.get('物业名称', {})
if isinstance(property_name, dict):
property_name = property_name.get('display_name', '未命名物业')
amount = row.get('金额', 0.0)
total_parking += amount
area_map[area].append(f"{property_name}停车费:{amount:.2f}元")
for area, texts in area_map.items():
items.append({
'area': area,
'content': '。'.join(texts[:10]) + '。'
})
# 如果停车费有数据,添加汇总信息
if total_parking > 0:
items.append({
'area': '合计',
'content': f"停车费总收入:{total_parking:.2f}元。"
})
return items if items else [{'area': '暂无数据', 'content': '暂无其他收入数据。'}]
def _get_management_fee_data(self, query_date, company_id, precinct_id):
"""获取管理费支出数据 - 从管理费相关报表获取"""
# 管理费支出需要根据实际业务逻辑查询
# 这里查询管理费类型的应收记录作为支出参考
# 查询管理费类型
management_types = self.env['yuthon.management.type'].search([('name', 'ilike', '管理')])
params = {
'date_start': query_date.strftime("%Y-%m-%d"),
'company_id': company_id,
'partner_id': precinct_id,
'management_type_id': management_types[0].id if management_types else None,
}
# 调用归属本月报表获取管理费数据
result = self.env['receivable.modify.line'].get_current_month_received_report(params)
rows = result.get('rows', [])
items = []
# 按片区分组
area_map = {}
total_fee = 0.0
for row in rows:
area = row.get('管理员', {}).get('display_name', '未分配') if isinstance(row.get('管理员'), dict) else row.get('管理员', '未分配')
if area not in area_map:
area_map[area] = []
property_name = row.get('物业名称', {})
if isinstance(property_name, dict):
property_name = property_name.get('display_name', '未命名物业')
amount = row.get('金额', 0.0)
total_fee += amount
area_map[area].append(f"{property_name}管理费 支出:{amount:.2f}元")
for area, texts in area_map.items():
items.append({
'area': area,
'content': '。'.join(texts[:10]) + '。'
})
# 如果管理费有数据,添加汇总信息
if total_fee > 0:
items.append({
'area': '合计',
'content': f"管理费总支出:{total_fee:.2f}元。"
})
return items if items else [{'area': '暂无数据', 'content': '暂无管理费支出数据。'}]
@@ -5,8 +5,6 @@ from odoo import fields, models, api
class YuthonContractReport(models.Model):
_inherit = 'property.lease.contract'
_description = '合同信息报表'
@api.model
def get_contract_info_report(self, params):
@@ -216,7 +216,7 @@ class ReceivableReport extends Component {
type: 'ir.actions.act_window',
res_model: model,
res_id: id,
views: [[false, 'form'], [false, 'search']],
views: [[view_id || false, 'form'], [false, 'search']],
target: 'new'
});
}
@@ -0,0 +1,52 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { Component, onMounted, useState } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
import { standardActionServiceProps } from "@web/webclient/actions/action_service";
class TextSummaryReport extends Component {
setup() {
this.serviceOrm = useService("orm");
this.state = useState({
reportData: null,
dateStart: "",
loading: false,
});
this.config = this.props.action.context.report_config || {};
onMounted(() => {
// 页面加载时自动执行搜索
this.onSearch();
});
}
async onSearch() {
this.state.loading = true;
const params = {
date_start: this.state.dateStart,
};
try {
const result = await this.serviceOrm.call(
this.config.model || 'text.summary.report',
this.config.method || 'get_text_summary_report',
[params]
);
this.state.reportData = result;
} catch (error) {
console.error('报表查询失败:', error);
this.state.reportData = null;
} finally {
this.state.loading = false;
}
}
}
TextSummaryReport.props = { ...standardActionServiceProps };
TextSummaryReport.template = "text_summary_report.TextSummaryReport";
registry.category("actions").add("text_summary_report_client", TextSummaryReport);
@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="text_summary_report.TextSummaryReport">
<div class="container-fluid" style="height: calc(100vh - 80px); display: flex; flex-direction: column;">
<!-- 搜索区域 -->
<div class="card mb-3 flex-shrink-0">
<div class="card-body">
<div class="row">
<div class="col-md-2">
<label class="form-label">年月:</label>
<input
type="month"
class="form-control"
t-model="state.dateStart"
/>
</div>
<div class="col-md-10 d-flex align-items-end justify-end">
<button class="btn btn-primary" t-on-click="onSearch">
<span t-if="state.loading" class="fa fa-spinner fa-spin"></span>
查询
</button>
</div>
</div>
</div>
</div>
<!-- 报表信息栏 -->
<div t-if="state.reportData" class="card mb-3 flex-shrink-0">
<div class="card-body">
<div class="row">
<div class="col-md-3">
<span class="text-muted">统计时间:</span>
<span t-esc="state.reportData.summary_info['统计时间']"></span>
</div>
<div class="col-md-3">
<span class="text-muted">统计片区:</span>
<span t-esc="state.reportData.summary_info['统计片区']"></span>
</div>
<div class="col-md-3">
<span class="text-muted">所属公司:</span>
<span t-esc="state.reportData.summary_info['所属公司']"></span>
</div>
<div class="col-md-3 text-right">
<span class="text-muted">创建时间:</span>
<span t-esc="state.reportData.summary_info['创建时间']"></span>
</div>
</div>
</div>
</div>
<!-- 报表内容区域 - 可滚动 -->
<div t-if="state.reportData" class="card flex-grow-1 overflow-auto" style="min-height: 0;">
<div class="card-body">
<t t-foreach="state.reportData.groups" t-as="group" t-key="group_index">
<div class="mb-4">
<div class="bg-danger text-white p-2 mb-2">
<strong t-esc="group.title"></strong>
</div>
<div class="border-left-4 border-danger pl-3">
<t t-foreach="group.items" t-as="item" t-key="item_index">
<div class="mb-2">
<div class="font-weight-bold text-primary" t-esc="item.area"></div>
<div class="text-sm text-gray-700" t-esc="item.content"></div>
</div>
</t>
</div>
</div>
</t>
</div>
</div>
<!-- 加载提示 -->
<div t-if="state.loading" class="flex-grow-1 flex items-center justify-center">
<div class="text-center">
<i class="fa fa-spinner fa-spin fa-3x"></i>
<p>加载中...</p>
</div>
</div>
<!-- 空状态提示 -->
<div t-if="!state.loading &amp;&amp; !state.reportData" class="flex-grow-1 flex items-center justify-center">
<div class="text-center text-muted">
<i class="fa fa-file-text-o fa-3x mb-2"></i>
<p>请点击查询按钮获取报表数据</p>
</div>
</div>
</div>
</t>
</templates>
+3 -1
View File
@@ -13,11 +13,13 @@
"dateMode": "single",
"columns": [
{"headerName": "序号", "field": "序号", "width": 45, "flex": 0, "pinned": "left"},
{"headerName": "应收开始日期", "field": "应收开始日期", "flex": 1},
{"headerName": "收款日期", "field": "收款日期", "flex": 1},
{"headerName": "金额", "field": "金额", "flex": 1, "type": "number"},
{"headerName": "合同编号", "field": "合同编号", "flex": 1.2},
{"headerName": "物业名称", "field": "物业名称", "flex": 2.5},
{"headerName": "承租方名称", "field": "承租方名称", "flex": 1.4},
{"headerName": "收款类型", "field": "收款类型", "flex": 1},
{"headerName": "金额", "field": "金额", "flex": 1, "type": "number"},
{"headerName": "所属公司", "field": "所属公司", "flex": 1},
{"headerName": "管理员", "field": "管理员", "flex": 1}
]
+1 -1
View File
@@ -4,7 +4,7 @@ from odoo import models, fields, api, exceptions, _
class ParkingFeeApproval(models.Model):
_name = 'parking.fee.approval'
_description = '停车费发放'
_description = '停车票发放'
_inherit = ['soong.workflow.mixin', 'mail.thread.main.attachment', 'mail.activity.mixin']
+26 -25
View File
@@ -7,7 +7,7 @@ from odoo.osv import expression
from urllib.parse import quote
import logging
logger = logging.getLogger(__name__)
_logger = logging.getLogger(__name__)
def get_is_leader_domain(is_leader: bool):
@@ -86,12 +86,8 @@ class YuthonWillTask(http.Controller):
users_id = int(kwargs.get('users_id'))
_filter = kwargs.get('filter')
state = kwargs.get('state')
order = 'task_datetime desc' if state == 'done' else 'task_datetime desc'
order = 'task_datetime desc' if state == 'done' else 'task_datetime asc'
domain = [('users_ids', 'in', [users_id])]
logger.info(f"{kwargs}")
logger.info(f"{kwargs}")
logger.info(f"{kwargs}")
if _filter == 'leader':
domain = [('user_role_ids.name', 'in', ['董事长', '分管领导', '总经理', '部门副经理', '副总经理', '司机'])]
# expression.AND([domain, get_is_leader_domain(True)])
@@ -111,26 +107,31 @@ class YuthonWillTask(http.Controller):
state_dict = dict(WillTask.fields_get(allfields=['task_state'])['task_state']['selection'])
result_list = []
task_ids = request.env['yuthon.will.task'].sudo().search(domain, offset=start, limit=20, order=order)
user_id = request.env['res.users'].sudo().search([('id', '=', users_id)])
company_id = user_id.company_id
for task in task_ids:
weekday_str = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"][task.start_date.weekday()]
result = {
'id': task.id,
'name': task.name,
'employee_id': task.employee_id.name or '',
'employee_ids': ', '.join(task.employee_ids.mapped('name')) if task.employee_ids else '',
'employee_num': len(task.employee_ids),
'start_date': fields.Date.to_string(task.start_date),
'weekday': weekday_str,
'request_hour_from': '',
'request_hour_to': '',
'state': task.task_state,
'display_state': state_dict.get(task.task_state),
'task_type': task.task_type
}
hour_dict = dict(request_hour_selection)
result['request_hour_from'] = hour_dict.get(task.request_hour_from)
result['request_hour_to'] = hour_dict.get(task.request_hour_to)
result_list.append(result)
# _logger.info(f"当前用户{user_id.name}--对应公司{company_id.name}--任务公司{task.company_id.name}")
if company_id.id == task.company_id.id:
weekday_str = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"][task.start_date.weekday()]
result = {
'id': task.id,
'name': task.name,
'employee_id': task.employee_id.name or '',
'employee_ids': ', '.join(task.employee_ids.mapped('name')) if task.employee_ids else '',
'employee_num': ','.join(task.employee_ids.mapped('name')) if task.employee_ids else '',
'start_date': fields.Date.to_string(task.start_date),
'weekday': weekday_str,
'address': task.address or '',
'request_hour_from': '',
'request_hour_to': '',
'state': task.task_state,
'display_state': state_dict.get(task.task_state),
'task_type': task.task_type
}
hour_dict = dict(request_hour_selection)
result['request_hour_from'] = hour_dict.get(task.request_hour_from)
result['request_hour_to'] = hour_dict.get(task.request_hour_to)
result_list.append(result)
return json.dumps({
'data': result_list,
'code': 200,
@@ -309,8 +309,8 @@
<t t-if="record.task_week.raw_value">
<t t-esc="record.task_week.raw_value"/>
</t>
<t t-if="record.request_hour_from.raw_value">
<t t-esc="record.request_hour_from.raw_value"/> - <t t-esc="record.request_hour_to.raw_value"/>
<t t-if="record.request_hour_from.value and record.request_hour_to.value">
<t t-esc="record.request_hour_from.value"/> - <t t-esc="record.request_hour_to.value"/>
</t>
</div>
<!-- 参与人 -->
+12 -5
View File
@@ -228,6 +228,12 @@
action="attendance_iot.attendance_iot_action"
sequence="80"/>
<menuitem id="menu_attendance_iot_log_board"
name="考勤机日志"
parent="hr_attendance_root_board_menu"
action="attendance_iot.attendance_iot_log_action"
sequence="81"/>
<!-- <menuitem-->
<!-- id="menu_att_report"-->
<!-- name="报表"-->
@@ -302,11 +308,11 @@
parent="attendance_resources_setting_root_board"
sequence="8"
action="yuthon_hr_attendance.view_overtime_page_action"/>
<menuitem id="menu_hr_resource_calendar_board"
name="法定节假日表"
parent="attendance_resources_setting_root_board"
sequence="10"
action="yuthon_hr_attendance.resource_calendar_leaves_action_from_calendar"/>
<!-- <menuitem id="menu_hr_resource_calendar_board"-->
<!-- name="法定节假日表"-->
<!-- parent="attendance_resources_setting_root_board"-->
<!-- sequence="10"-->
<!-- action="yuthon_hr_attendance.resource_calendar_leaves_action_from_calendar"/>-->
<menuitem id="holiday_set_type_board2"
name="休假类型"
parent="attendance_resources_setting_root_board"
@@ -779,6 +785,7 @@
<menuitem id="holiday_manage_menu_root_board" name="我的" web_icon="yuthon_work_brand,static/description/holiday.png" sequence="3">
<menuitem id="menu_workflow_approval_request_new" name="新建流程" action="soong_workflow.action_kklw_workflow_approval_request" sequence="5" groups="base.group_user"/>
<menuitem id="menu_soong_workflow_todo_record" name="待办流程" action="soong_workflow.soong_workflow_record_todo_action" sequence="10" groups="base.group_user"/>
<menuitem id="yuthon_salary_meal_views_menu_employee" name="我的餐费" action="yuthon_salary.view_yuthon_meal_expenses_action_readonly" sequence="11" groups="base.group_user"/>
<menuitem id="menu_soong_workflow_done_record" name="已办流程" action="soong_workflow.soong_workflow_record_done_action" sequence="15" groups="base.group_user"/>
<menuitem id="menu_soong_workflow_department_record" name="流程查询" action="soong_workflow.soong_workflow_department_record_action" sequence="20" groups="base.group_user"/>
<menuitem id="menu_workflow_approval_request_admin" name="流程监控" action="soong_workflow.soong_workflow_record_action_admin" sequence="25"/>
+1
View File
@@ -61,6 +61,7 @@
<menuitem id="inventory_asset_fixed_root" name="固定资产" action="maintenance.hr_equipment_action" sequence="20"/>
</menuitem>
<menuitem id="inventory_setting_product_tag_root" name="产品模版" action="product.product_tag_action" sequence="80"/>
<menuitem id="inventory_setting_putaway_root" name="上架规则" action="stock.action_putaway_tree" sequence="90"/>
</menuitem>
</menuitem>
</odoo>