Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
pengyuthon
2025-12-17 10:22:46 +08:00
29 changed files with 650 additions and 185 deletions
@@ -7,6 +7,12 @@ class MenuInherit(models.Model):
is_phone = fields.Boolean("是否手机显示", default=True)
def update_is_phone(self):
menu_ids = self.env["ir.ui.menu"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for menu in menu_ids:
menu.is_phone = False
def load_web_menus(self, debug):
web_menus = {}
menus = self.search_read([], fields=['id', 'is_phone'])
+12 -12
View File
@@ -725,18 +725,18 @@ class SoongWorkflowMixin(models.AbstractModel):
wkf_rec = wkf_obj.search([("model_id", "=", model), ('is_enable', '=', True)])
for node in wkf_rec.node_ids:
for button in node.button_ids:
# if self.env.user.has_group("soong_workflow.group_soong_workflow_manager"):
# btn_exec_action_el = etree.SubElement(header_el, "button")
# btn_exec_action_el.set("name", "btn_exec_action")
# btn_exec_action_el.set("string", button.name)
# btn_exec_action_el.set("type", "object")
# if button.is_highlight:
# btn_exec_action_el.set("class", "oe_highlight")
# if button.has_icon:
# btn_exec_action_el.set("icon", button.icon)
# btn_exec_action_el.set("invisible", "state != '%s'" % node.node_name)
# btn_exec_action_el.set("context", "{'btn_key':'%s','active_model':'%s'}" % (button.btn_key, self._name),)
# else:
if self.env.user.has_group("soong_workflow.group_soong_workflow_manager"):
btn_exec_action_el = etree.SubElement(header_el, "button")
btn_exec_action_el.set("name", "btn_exec_action")
btn_exec_action_el.set("string", button.name)
btn_exec_action_el.set("type", "object")
if button.is_highlight:
btn_exec_action_el.set("class", "oe_highlight")
if button.has_icon:
btn_exec_action_el.set("icon", button.icon)
btn_exec_action_el.set("invisible", "state != '%s'" % node.node_name)
btn_exec_action_el.set("context", "{'btn_key':'%s','active_model':'%s'}" % (button.btn_key, self._name),)
else:
is_apostille = False
if button.btn_hide:
if button.review_type == "individual":
+10
View File
@@ -11,6 +11,16 @@
parent="menu_soong_workflow_root"
action="action_kklw_workflow_approval_request"
sequence="1"/>
<menuitem id="menu_soong_workflow_todo_record"
name="待办"
parent="menu_soong_workflow_root"
action="soong_workflow_record_todo_action"
sequence="2"/>
<menuitem id="menu_soong_workflow_done_record"
name="已办"
parent="menu_soong_workflow_root"
action="soong_workflow_record_done_action"
sequence="3"/>
<menuitem id="menu_soong_workflow_other_record"
name="流程查询"
parent="menu_soong_workflow_root"
@@ -199,6 +199,27 @@
<field name="search_view_id" ref="soong_workflow_record_search"/>
<field name="view_id" ref="soong_workflow_record_my_tree"/>
</record>
<record id="soong_workflow_record_todo_action" model="ir.actions.act_window">
<field name="name">待办</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">soong.workflow.record</field>
<field name="view_mode">tree,kanban,search</field>
<!-- <field name="context">{'check_employee': 1, 'search_default_is_start':1, 'search_default_is_to_do':1}</field>-->
<field name="context">{'header_search_default_approve_state': 1, 'header_search_default_to_do':1, 'check_employee': 1}</field>
<field name="domain">[('users_ids', 'in', uid), ('reclaim_state', '=', False), ('soong_state', '=', 'to_do')]</field>
<field name="search_view_id" ref="soong_workflow_record_search1"/>
<field name="view_id" ref="soong_workflow_record_my_tree"/>
</record>
<record id="soong_workflow_record_done_action" model="ir.actions.act_window">
<field name="name">已办</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">soong.workflow.record</field>
<field name="view_mode">tree,kanban,search</field>
<field name="context">{'header_search_default_approve_state': 1, 'header_search_default_to_do':1, 'check_employee': 1}</field>
<field name="domain">[('users_ids', 'in', uid), ('reclaim_state', '=', False), ('soong_state', '=', 'done')]</field>
<field name="search_view_id" ref="soong_workflow_record_search1"/>
<field name="view_id" ref="soong_workflow_record_my_tree"/>
</record>
<record id="soong_workflow_hr_employee_action" model="ir.actions.act_window">
<field name="name">人事相关流程</field>
<field name="type">ir.actions.act_window</field>
@@ -8,7 +8,6 @@
decoration-muted="task_state == 'cancel'"
decoration-danger="priority == 'very_urgent'"
decoration-info="priority == 'urgent'"
decoration-primary="priority == 'general'"
decoration-success="priority == 'important'">
<field name="task_time" />
<field name="start_date" string="任务日期" />
@@ -62,8 +61,6 @@
<field name="arch" type="xml">
<form string="日程">
<sheet>
<widget name="web_ribbon" title="不紧急不重要" bg_color="text-bg-secondary"
invisible="priority != 'general'" />
<widget name="web_ribbon" title="重要不紧急" bg_color="text-bg-success"
invisible="priority != 'important'" />
<widget name="web_ribbon" title="紧急不重要" bg_color="text-bg-warning"
+1
View File
@@ -10,6 +10,7 @@
'security/ir.model.access.csv',
'views/system_version_views.xml',
'views/ir_cron_act_views.xml',
'views/yuthon_timed_tasks_views.xml',
'views/menu.xml',
],
'assets': {
+1
View File
@@ -3,4 +3,5 @@ from . import inherit_ir_attachment
from . import system_version
from . import fields_piyin
from . import ir_cron_act
from . import yuthon_timed_tasks
# from . import res_partner
+22
View File
@@ -0,0 +1,22 @@
from odoo import models, fields, api
class YuthonTimedTasks(models.Model):
_name = 'yuthon.timed.tasks'
_description = '定时任务'
sequence = fields.Integer(string='序号')
name = fields.Char(string='名称')
notice_type = fields.Selection([('attendance', '出勤'), ('schedule', '日程'), ('announcement', '公告'),
('attendance_confirmation', '考勤确认单'), ('salary_slip', '薪资条'),
('process', '流程'), ('other', '其他')], string='类型')
active = fields.Boolean(string='有效', default=True)
runtime = fields.Datetime(string="运行时间")
interval_number = fields.Integer(string='间隔')
interval_type = fields.Selection([('minutes', '分钟'),
('hours', '小时'),
('days', '天'),
('weeks', '周'),
('months', '月')], string='运行周期', default='months')
model_id = fields.Many2one("ir.model", string="模型")
+1
View File
@@ -1,3 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_system_version,system_version,model_system_version,base.group_system,1,1,1,1
access_ir_cron_type,ir.cron.type,model_ir_cron_type,base.group_system,1,1,1,1
access_yuthon_timed_tasks,yuthon.timed.tasks,model_yuthon_timed_tasks,base.group_system,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_system_version system_version model_system_version base.group_system 1 1 1 1
3 access_ir_cron_type ir.cron.type model_ir_cron_type base.group_system 1 1 1 1
4 access_yuthon_timed_tasks yuthon.timed.tasks model_yuthon_timed_tasks base.group_system 1 1 1 1
+1 -1
View File
@@ -12,7 +12,7 @@
<menuitem id="timed_tasks_menu_root"
name="定时任务"
parent="system_version_menu_root_board"
action="base.ir_cron_act"
action="yuthon_base.view_yuthon_timed_tasks_action"
sequence="5"/>
<menuitem id="cron_type_menu_root"
name="配置"
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_yuthon_timed_tasks_tree" model="ir.ui.view">
<field name="name">yuthon.timed.tasks.tree</field>
<field name="model">yuthon.timed.tasks</field>
<field name="arch" type="xml">
<tree string="定时任务管理" editable="bottom">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="notice_type"/>
<field name="model_id"/>
<field name="runtime"/>
<field name="interval_number"/>
<field name="interval_type"/>
<field name="active"/>
</tree>
</field>
</record>
<record id="view_yuthon_timed_tasks_action" model="ir.actions.act_window">
<field name="name">定时任务管理</field>
<field name="res_model">yuthon.timed.tasks</field>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_yuthon_timed_tasks_tree"/>
</record>
</odoo>
+6 -6
View File
@@ -221,16 +221,14 @@ class HrLeave(models.Model):
def _compute_duration(self):
super()._compute_duration()
for holiday in self:
# 如果是自然日:
# 如果是自然日:(按小时请假)
if holiday.holiday_status_id.type_of_work == "1":
if holiday.request_date_from and holiday.request_date_to:
days = (holiday.request_date_to - holiday.request_date_from).days + 1
hours = days * 8
else:
days = 0
hours = 0.0
holiday.number_of_hours = hours
holiday.number_of_days = days
if holiday.holiday_status_id.type_of_work == "0":
if holiday.request_hour_from and holiday.request_hour_to:
start_hour = float(holiday.request_hour_from)
@@ -245,8 +243,10 @@ class HrLeave(models.Model):
holiday.number_of_hours = hour
else:
holiday.number_of_hours = 0
days = (holiday.request_date_to - holiday.request_date_from).days + 1
holiday.number_of_days = days
# 按天请假
days = holiday.batch_id._compute_duration_day(holiday.holiday_status_id, holiday.request_date_from , holiday.request_date_to)
holiday.number_of_days = days
def _compute_request_unit_hours(self):
if not self.env.context.get('no_change_request_unit_hours'):
@@ -607,7 +607,7 @@ class HolidaysRequestBatch(models.Model):
else:
days = 0
return days
if holiday_status_id.type_of_work == 0:
if holiday_status_id.type_of_work == "0":
calendar_leaves = self.env['resource.calendar.leaves'].search([("calendar_id", "=", "行政考勤工作日")])
holidays_list = []
shift_days_list = []
@@ -326,6 +326,20 @@
<field name="numbercall">-1</field>
<field eval="False" name="doall"/>
</record>
<record forcecreate="True" id="task_remind_out" model="ir.cron">
<field name="name">定时任务:测试提醒</field>
<field name="model_id" ref="model_hr_attendance"/>
<field name="state">code</field>
<field name="code">model.task_remind()</field>
<field eval="True" name="active"/>
<field name="user_id" ref="base.user_root"/>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="nextcall" eval="(DateTime.now() + timedelta(days=1)).strftime('%Y-%m-%d 17:00:00')"/>
<field name="numbercall">-1</field>
<field eval="False" name="doall"/>
</record>
</data>
</odoo>
@@ -63,6 +63,13 @@ class HrAttendance(models.Model):
is_last_month = fields.Boolean(string="是否为上月数据", compute='_compute_month_status', store=True)
correct_result = fields.Text(string="修正原因")
def task_remind(self):
all_employees = self.env['hr.employee'].search([('name', 'in', ['Administrator', '叶华锋'])])
for employee in all_employees:
self.sudo().env["wecom.apps"].sync_send_message_textcard(
category='att', user_id=employee.wecom_userid,
description="记得打卡", title="测试提醒",btntxt="详细信息", )
def create_attendance_data_holiday(self):
"""
定时任务create_attendance_data_holiday
+5 -1
View File
@@ -72,7 +72,11 @@
</group>
<group>
<field name="name" required="1"/>
<field name="notice_text" widget="tinymce"/>
</group>
<div>
<field name="notice_text" widget="tinymce"/>
</div>
<group>
<field name="documents_ids" widget="preview_many2many"/>
</group>
<group col="4" string="发布范围">
@@ -95,7 +95,7 @@
<field name="model">meter.archives.line</field>
<field name="arch" type="xml">
<tree string="电表任务" create="1" editable="bottom" limit="15"
js_class="custom_meter_button" decoration-success="not is_push" default_order="meter_date_month desc">
js_class="custom_meter_button" decoration-success="is_push" default_order="meter_date_month desc">
<header>
<!-- <button name="delete_dec_2025_meter_data" type="object" string="删除" class="oe_highlight"/>-->
<button name="push_task_push_wizard" type="object" string="推送" class="oe_highlight"/>
@@ -98,7 +98,7 @@
<field name="name">meter.water.line.tree</field>
<field name="model">meter.water.line</field>
<field name="arch" type="xml">
<tree string="水表任务" create="0" editable="bottom" js_class="custom_water_button" decoration-success="not is_push"
<tree string="水表任务" create="0" editable="bottom" js_class="custom_water_button" decoration-success="is_push"
default_order="meter_date_month desc" limit="15">
<header>
<button name="push_task_push_wizard" type="object" string="推送" class="oe_highlight"/>
@@ -86,7 +86,6 @@ class MeterPriceWizard(models.TransientModel):
hydropower_log = self.env['hydropower.logs'].search(search_domain, limit=1)
log_data = {
'meter_type': 'electric',
'logs_date': start_date,
'select_time': self.select_time,
'select_year': self.select_year,
'line_loss': self.line_loss,
@@ -86,7 +86,6 @@ class WaterPriceWizard(models.TransientModel):
hydropower_log = self.env['hydropower.logs'].search(search_domain, limit=1)
log_data = {
'meter_type': 'water',
'logs_date': start_date,
'select_time': self.select_time,
'select_year': self.select_year,
'line_loss': self.line_loss,
@@ -833,94 +833,6 @@ class PropertyAccountsReceivable(models.Model):
for receivable in receivable_ids:
receivable._compute_property_ids()
def de_duplicate(self):
# 查询所有记录并按 id 升序排列
records = self.search([], order='id ASC')
# 用于存储已存在的组合
unique_combinations = set()
records_to_delete = self.browse() # 创建一个空记录集
# 遍历所有记录,找出重复项
for record in records:
combination = (
record.contract_id.id,
record.property_id.id,
record.lessee_id.id,
record.due_date
)
# 如果组合已存在,则添加到删除列表
if combination in unique_combinations:
records_to_delete += record
else:
# 否则添加到唯一组合集合
unique_combinations.add(combination)
# 删除重复记录
if records_to_delete:
records_to_delete.unlink()
def initialize_reconciliation(self):
receivable_ids = self.env["property.accounts.receivable"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for receivable in receivable_ids:
receivable.delivered1 = receivable.submitted
receivable.submitted = 0
receivable.is_reconciliation = True
due_year = receivable.due_date.year
due_month = receivable.due_date.month
# 构建日期范围条件
import calendar
last_day = calendar.monthrange(due_year, due_month)[1]
# 构建日期范围条件(使用正确的最后一天)
start_date = f"{due_year}-{due_month:02d}-01"
end_date = f"{due_year}-{due_month:02d}-{last_day}"
# 搜索条件使用大于等于当月第一天,小于等于当月最后一天
statements = self.env['property.bank.statements'].search([
('name_account', '=', receivable.lessee_id.name),
('bank_state', '=', 'reconciled'),
('start_time', '>=', start_date),
('start_time', '<=', end_date),
])
if statements:
for statement in statements:
receivable.payout_date = statement.start_time.date()
else:
receivable.payout_date = receivable.due_date
# @api.model_create_multi
# def create(self, vals_list):
# records = super().create(vals_list)
# for record in records:
# if record.lessee_id and record.contract_id:
# lease_address_ids = record.contract_id.lease_ids.mapped('address_id.id')
# property_ids_value = [(6, 0, lease_address_ids)] if lease_address_ids else [(6, 0, [])]
# statements = self.env['property.bank.statements'].search([
# ('name_account', '=', record.lessee_id.name),
# ('bank_state', '=', 'not_reconciled'),
# ])
# contract_id_value = [(4, record.contract_id.id)] if record.contract_id else []
# for statement in statements:
# statement.write({
# 'property_ids': property_ids_value,
# 'contract_ids': contract_id_value,
# })
#
# contract_account_map = {}
# for vals in vals_list:
# if 'contract_id' in vals and vals['contract_id']:
# contract_id = vals['contract_id']
# if contract_id not in contract_account_map:
# contract_account = self.env['contract.accounts.receivable'].search([
# ('contract_id', '=', contract_id)
# ], limit=1)
# if not contract_account:
# contract_account = self.env['contract.accounts.receivable'].create({
# 'contract_id': contract_id
# })
# contract_account.onchange_contract_id()
#
# return records
@api.onchange('penalty_date')
def _onchange_penalty_date(self):
for record in self:
@@ -947,32 +859,7 @@ class PropertyAccountsReceivable(models.Model):
penalty = days_difference * receivable.receivable_id.difference_amount
receivable.receivable_id.write({'penalty': penalty})
# 批量撤销
def batch_receivable_return(self):
archives_ids = self.env["property.accounts.receivable"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for rec in archives_ids:
# 银行流水对应的已对账改为未对账
due_year = rec.due_date.year
due_month = rec.due_date.month
import calendar
last_day = calendar.monthrange(due_year, due_month)[1]
# 构建日期范围条件(使用正确的最后一天)
start_date = f"{due_year}-{due_month:02d}-01"
end_date = f"{due_year}-{due_month:02d}-{last_day}"
# 搜索条件使用大于等于当月第一天,小于等于当月最后一天
statements = self.env['property.bank.statements'].search([
('name_account', '=', rec.lessee_id.name),
('bank_state', '=', 'reconciled'),
('start_time', '>=', start_date),
('start_time', '<=', end_date),
])
rec.is_reconciliation = False
for statement in statements:
statement.write({'bank_state': 'not_reconciled'})
# 批量退回
# 批量退回
def action_refuse_all(self):
model_type_data = {
'水费': 'meter.water.line',
@@ -983,25 +870,25 @@ class PropertyAccountsReceivable(models.Model):
}
archives_ids = self.env["property.accounts.receivable"].browse(
self._context.get('active_ids', self._context.get('active_id')))
valid_management_types = list(model_type_data.keys())
valid_management_types = list(model_type_data.values())
for rec in archives_ids:
if rec.is_reconciliation == True:
raise UserError("已对账的记录不能退回!请先银行流水撤销再退回")
model_type = model_type_data.get(rec.management_type_id.name)
if model_type not in valid_management_types:
rec.unlink()
continue
if model_type in ('meter.archives.line', 'meter.water.line'):
year = rec.due_date_end.year
month = rec.due_date_end.month
first_day = date(year, month, 1)
if month == 12:
next_month_first_day = date(year + 1, 1, 1)
else:
next_month_first_day = date(year, month + 1, 1)
last_day = next_month_first_day - timedelta(days=1)
if not rec.charging_range:
raise UserError("请输入计费区间, 按照应为'YYYY-MM-DD 到 YYYY-MM-DD格式")
charging_range = rec.charging_range.strip()
start_str, end_str = [x.strip() for x in charging_range.split("到")]
start_date = (datetime.strptime(end_str, "%Y-%m-%d").date()).replace(day=1)
end_date = datetime.strptime(end_str, "%Y-%m-%d").date()
model_id = self.env[model_type].search([
('contract_id', '=', rec.contract_id.id),
('meter_date', '>=', first_day),
('meter_date', '<=', last_day)
('meter_date', '>=', start_date),
('meter_date', '<=', end_date)
])
else:
last_day = rec.due_date + relativedelta(months=1) - timedelta(days=1)
@@ -452,26 +452,12 @@
</search>
</field>
</record>
<!-- <record id="initialize_reconciliation" model="ir.actions.server">-->
<!-- <field name="name">初始化对账</field>-->
<!-- <field name="model_id" ref="yuthon_property.model_property_accounts_receivable"/>-->
<!-- <field name="binding_model_id" ref="yuthon_property.model_property_accounts_receivable"/>-->
<!-- <field name="state">code</field>-->
<!-- <field name="code">action = model.initialize_reconciliation()</field>-->
<!-- </record>-->
<!-- <record id="initialize_update_property" model="ir.actions.server">-->
<!-- <field name="name">更新物业信息</field>-->
<!-- <field name="model_id" ref="yuthon_property.model_property_accounts_receivable"/>-->
<!-- <field name="binding_model_id" ref="yuthon_property.model_property_accounts_receivable"/>-->
<!-- <field name="state">code</field>-->
<!-- <field name="code">action = model.update_property_ids()</field>-->
<!-- </record>-->
<!-- <record id="de_duplicate_server" model="ir.actions.server">-->
<!-- <field name="name">删除重复数据</field>-->
<!-- <field name="model_id" ref="yuthon_property.model_property_accounts_receivable"/>-->
<!-- <field name="binding_model_id" ref="yuthon_property.model_property_accounts_receivable"/>-->
<!-- <field name="state">code</field>-->
<!-- <field name="code">action = model.de_duplicate()</field>-->
<!-- </record>-->
<record id="property_accounts_receivable_action" model="ir.actions.act_window">
<field name="name">应收账单</field>
@@ -608,13 +594,6 @@
<field name="state">code</field>
<field name="code">action = model.all_penalty_date()</field>
</record>
<record id="all_button_return_property" model="ir.actions.server">
<field name="name">批量撤销</field>
<field name="model_id" ref="yuthon_property.model_property_accounts_receivable"/>
<field name="binding_model_id" ref="yuthon_property.model_property_accounts_receivable"/>
<field name="state">code</field>
<field name="code">action = model.batch_receivable_return()</field>
</record>
<record id="all_action_refuse_all" model="ir.actions.server">
<field name="name">批量退回</field>
<field name="model_id" ref="yuthon_property.model_property_accounts_receivable"/>
+4 -1
View File
@@ -6,7 +6,7 @@
'category': 'zhou',
'version': '0.1',
'license': 'LGPL-3',
'depends': ['base', 'web', 'yuthon_property','yuthon_property_approval'],
'depends': ['base', 'web', 'yuthon_property','yuthon_property_approval', 'hr_attendance'],
'data': [
'views/menu.xml',
'views/ledger_report_views.xml',
@@ -16,6 +16,7 @@
'views/cumulative_income_report.xml',
'views/overdue_amount_report.xml',
'views/yuthon_report_res_partner.xml',
'views/att_report_view.xml',
],
'installable': True,
'application': True,
@@ -38,6 +39,8 @@
'yuthon_report/static/src/components/yuthon_report/yuthon_report.xml',
'yuthon_report/static/src/components/yuthon_report/yuthon_contract_report.js',
'yuthon_report/static/src/components/yuthon_report/yuthon_contract_report.xml',
'yuthon_report/static/src/components/yuthon_report/att_report.js',
'yuthon_report/static/src/components/yuthon_report/att_report.xml',
'yuthon_report/static/src/components/yuthon_report/receivable_message_report.js',
'yuthon_report/static/src/components/yuthon_report/receivable_message.xml',
'yuthon_report/static/src/components/yuthon_report/yuthon_report.scss',
+2 -1
View File
@@ -5,4 +5,5 @@ from . import receivable_report
from . import receivables_message_report
from . import yuthon_contract_report
from . import cumulative_income_report
from . import overdue_amount_report
from . import overdue_amount_report
from . import att_report
+138
View File
@@ -0,0 +1,138 @@
import datetime
from odoo import fields, models, api
class AttendanceReport(models.Model):
_inherit = 'hr.attendance'
def _get_common_domain_and_employees(self, params):
vacation_domain = []
if params.get("date_start"):
date_start = datetime.datetime.fromisoformat(params.get("date_start")).date()
vacation_domain.append(("vacation_date", ">=", date_start))
if params.get("date_end"):
date_end = datetime.datetime.fromisoformat(params.get("date_end")).date()
vacation_domain.append(("vacation_date", "<=", date_end))
salary_group_id = params.get('structure_id', False)
department_id = params.get('department_id', False)
company_id = params.get('company_id', False)
employee_domain = []
if salary_group_id:
employee_domain.append(('contract_id.salary_group_id', '=', salary_group_id))
if department_id:
employee_domain.append(('department_id', '=', department_id))
if company_id:
employee_domain.append(('company_id', '=', company_id))
employee_ids = []
if employee_domain:
employee_ids = self.env['hr.employee'].search(employee_domain).ids
if employee_ids:
vacation_domain.append(("employee_id", "in", employee_ids))
return vacation_domain, employee_ids
# ---------------------- 年假/其他假期报表 ----------------------
@api.model
def get_attendance_report(self, params):
vacation_domain, employee_ids = self._get_common_domain_and_employees(params)
if employee_ids and not employee_ids:
return []
vacation_reports = self.env['yuthon.vacation.reports'].search(vacation_domain, limit=1000)
result_list = []
for report in vacation_reports:
employee_name = report.employee_id.name or ""
company_name = report.company_id.name or ""
for leave_line in report.leave_ids.filtered(lambda l: l.workflow_record_id):
result_list.append({
"员工": employee_name,
"公司": company_name,
"天数": leave_line.lines or 0.0,
"类型": "年假",
"流程": leave_line.workflow_record_id.name or ""
})
for fee_line in report.other_fee_ids.filtered(lambda l: l.workflow_record_id):
result_list.append({
"员工": employee_name,
"公司": company_name,
"天数": fee_line.lines or 0.0,
"类型": fee_line.holiday_type.name or "其他假期",
"流程": fee_line.workflow_record_id.name or ""
})
return result_list
# ---------------------- 误餐费报表(外出) ----------------------
@api.model
def get_meal_fee_report(self, params):
vacation_domain, employee_ids = self._get_common_domain_and_employees(params)
if employee_ids and not employee_ids:
return []
vacation_reports = self.env['yuthon.vacation.reports'].search(vacation_domain, limit=1000)
result_list = []
for report in vacation_reports:
employee_name = report.employee_id.name or ""
company_name = report.company_id.name or ""
for meal_line in report.meal_fee_ids.filtered(lambda l: l.workflow_record_id):
result_list.append({
"员工": employee_name,
"公司": company_name,
"次数": meal_line.update_lines or 0,
"类型": "外出",
"流程": meal_line.workflow_record_id.name or ""
})
return result_list
# ---------------------- 加班费报表 ----------------------
@api.model
def get_overtime_report(self, params):
vacation_domain, employee_ids = self._get_common_domain_and_employees(params)
if employee_ids and not employee_ids:
return []
vacation_reports = self.env['yuthon.vacation.reports'].search(vacation_domain, limit=1000)
result_list = []
for report in vacation_reports:
employee_name = report.employee_id.name or ""
company_name = report.company_id.name or ""
for overtime_line in report.overtime_ids.filtered(lambda l: l.workflow_record_id):
overtime_detail = {}
if overtime_line.workday > 0:
overtime_detail["加班类型"] = "工作日"
overtime_detail["时长(小时)"] = overtime_line.workday
elif overtime_line.legal_day > 0:
overtime_detail["加班类型"] = "法定节假日"
overtime_detail["时长(小时)"] = overtime_line.legal_day
elif overtime_line.public_holidays > 0:
overtime_detail["加班类型"] = "公休日"
overtime_detail["时长(小时)"] = overtime_line.public_holidays
else:
overtime_detail["加班类型"] = "无"
overtime_detail["时长(小时)"] = 0.0
overtime_method_map = {
'leave': '补休',
'fee': '加班费结算'
}
compensation_method = overtime_method_map.get(overtime_line.overtime_methods, '补休')
result_list.append({
"员工": employee_name,
"公司": company_name,
"类型": "加班",
"补偿方式": compensation_method,
"加班类型": overtime_detail["加班类型"],
"时长(小时)": overtime_detail["时长(小时)"],
"流程": overtime_line.workflow_record_id.name or ""
})
return result_list
@api.model
def get_domain(self, company_id=False):
if not company_id:
return [], []
department_records = self.env['hr.department'].search([('company_id', '=', company_id)])
department_ids = [dept.id for dept in department_records]
salary_group_records = self.env['yuthon.salary.group'].search([('company_id', '=', company_id)])
salary_group_ids = [struct.id for struct in salary_group_records]
return department_ids, salary_group_ids
@@ -0,0 +1,179 @@
/** @odoo-module1 **/
import {registry} from "@web/core/registry";
import {Component, onMounted, useRef, useState} from "@odoo/owl";
import {useBus, useService} from "@web/core/utils/hooks";
import {
standardActionServiceProps
} from "@web/webclient/actions/action_service";
import {DateTimeInput} from "@web/core/datetime/datetime_input";
import {CheckBox} from "@web/core/checkbox/checkbox";
import {Many2XAutocomplete} from "@web/views/fields/relational_utils";
import {formatDate} from "@web/core/l10n/dates"
const {DateTime} = luxon;
class ReceivableReport extends Component {
setup() {
this.gridRef = useRef("grid");
console.log(this.props.action.context.report_config)
this.serviceOrm = useService("orm")
this.state = useState({
startDate: DateTime.now().startOf("month"),
endDate: DateTime.now().endOf("month"),
keyword: "",
booleanValue: true,
selectDepartment: {},
selectCompany: {},
selectStructure: {},
domainDepartment:[],
domainStructure:[]
});
this.config = this.props.action.context.report_config || {};
onMounted(async () => {
// 加载 ag-grid
//await loadJS("https://cdn.jsdelivr.net/npm/ag-grid-community/dist/ag-grid-community.min.js");
this.gridOptions = {
localeText: AG_GRID_LOCALE_CN,
theme: agGrid.themeMaterial,
columnDefs: this.config.columns || [],
rowData: [],
sideBar: true,
enableCharts: true,
cellSelection: true,
defaultColDef: {
sortable: true,
filter: true,
width: 200,
minWidth: 150,
flex: false,
resizable: false
},
getContextMenuItems(params) {
const items = params.defaultItems.slice();
items.push("chartRange");
return items;
},
};
this.grid = new agGrid.createGrid(this.gridRef.el, this.gridOptions);
});
}
/**
* 改变布尔值
* @param e
*/
getDomainCompany() {
return []
}
getDomainStructure() {
console.log(this.state)
const isCompanyEmpty = Object.keys(this.state.selectCompany).length === 0;
if (isCompanyEmpty) {
return [];
} else {
return [['id','in', this.state.domainStructure]];
}
}
getDomainDepartment() {
console.log(this.state)
const isCompanyEmpty = Object.keys(this.state.selectCompany).length === 0;
if (isCompanyEmpty) {
return [];
} else {
return [['id','in', this.state.domainDepartment]];
}
}
async searchDomain() {
let results = await this.serviceOrm.call(
'hr.attendance',
'get_domain',
[],
{
company_id: this.state.selectCompany?.id,
}
)
console.log(results)
this.state.domainDepartment = results[0]
this.state.domainStructure = results[1]
}
async onSearch() {
console.log(this.state)
let startDate = formatDate(this.state.startDate, {format: "yyyy-MM-dd"})
let endDate = formatDate(this.state.endDate, {format: "yyyy-MM-dd"})
const params = {
date_start: `${startDate} 00:00:00`,
date_end: `${endDate} 23:59:59`,
department_id: this.state.selectDepartment?.id,
company_id: this.state.selectCompany?.id,
structure_id: this.state.selectStructure?.id,
};
const result = await this.serviceOrm.call(
this.config.model,
this.config.method,
[params]
);
console.log(result)
this.grid.setGridOption('rowData', result);
}
onExportExcel() {
this.grid.exportDataAsExcel({
fileName: 'my-data.xlsx',
sheetName: 'Sheet1'
});
}
async onUpdateCompany(v) {
this.state.selectCompany = v[0] || {}
await this.searchDomain()
}
async onUpdateStructure(v) {
this.state.selectStructure = v[0] || {}
}
async onUpdateDepartment(v) {
this.state.selectDepartment = v[0]
}
async onChangeStartDate(date) {
console.log(date)
if (Array.isArray(date)) {
this.state.startDate = date[0];
this.state.endDate = date[1]
}
}
async onChangeEndDate(date) {
if (Array.isArray(date)) {
this.state.startDate = date[0];
this.state.endDate = date[1]
}
}
}
ReceivableReport.props = {...standardActionServiceProps};
ReceivableReport.template = "att_report.ReceivableReport";
ReceivableReport.components = {
DateTimeInput,
CheckBox,
Many2XAutocomplete,
};
registry.category("actions").add("att_report_client", ReceivableReport);
@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="att_report.ReceivableReport">
<div class="container-fluid d-flex flex-column h-100 mt-2 pb-3">
<!-- 筛选卡片 -->
<div class="card mb-2">
<div class="card-body">
<!-- 搜索区域 -->
<div class="row mb-2" t-ref="search_area">
<!-- 应收日期范围 -->
<div class="col-3">
<label>日期范围</label>
<div class="d-flex align-items-center gap-2">
<DateTimeInput
range="true"
value="state.startDate"
type="'date'"
placeholder="fromPlaceholder"
onChange="(date) => this.onChangeStartDate(date)"
/>
<i class="oi oi-arrow-right"></i>
<DateTimeInput
range="true"
value="state.endDate"
type="'date'"
placeholder="toPlaceholder"
onChange="(date) => this.onChangeEndDate(date)"
/>
</div>
</div>
<!-- 所属公司 -->
<div class="col-3">
<label>所属公司</label>
<Many2XAutocomplete
value="state.selectCompany?.display_name"
getDomain="getDomainCompany"
resModel="'res.company'"
fieldString="'所属公司'"
activeActions="{}"
update.bind="onUpdateCompany"
/>
</div>
<div class="col-3">
<label>部门</label>
<Many2XAutocomplete
value="state.selectDepartment?.display_name"
getDomain.bind="getDomainDepartment"
resModel="'hr.department'"
fieldString="'部门'"
activeActions="{}"
update.bind="onUpdateDepartment"
/>
</div>
<div class="col-3">
<label>薪资分组</label>
<Many2XAutocomplete
value="state.selectStructure?.display_name"
getDomain.bind="getDomainStructure"
resModel="'yuthon.salary.group'"
fieldString="'薪资分组'"
activeActions="{}"
update.bind="onUpdateStructure"
/>
</div>
<!-- 操作按钮 -->
<div class="col-12 mt-3 d-flex justify-content-end gap-2">
<button class="btn btn-primary" t-on-click="onSearch">搜索</button>
<button class="btn btn-success" t-on-click="onExportExcel">导出</button>
</div>
</div>
</div>
</div>
<!-- ag-grid 表格区域 -->
<div class="row flex-grow-1">
<div class="col-12 h-100">
<div class="ag-theme-alpine w-100 h-100" t-ref="grid"></div>
</div>
</div>
</div>
</t>
</templates>
+88
View File
@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="action_leave_deduction_report" model="ir.actions.client">
<field name="name">请假扣款报表</field>
<field name="tag">get_attendance_report</field>
<field name="context">
{
"sideBar": true,
"report_config": {
"model": "hr.attendance",
"method": "get_attendance_report",
"columns": [
{"headerName": "员工", "field": "员工", "flex": 1},
{"headerName": "公司", "field": "公司", "flex": 1},
{"headerName": "天数", "field": "天数", "flex": 1},
{"headerName": "类型", "field": "类型", "flex": 1},
{"headerName": "流程", "field": "流程", "flex": 1}
]
}
}
</field>
</record>
<record id="action_meal_fee_report" model="ir.actions.client">
<field name="name">误餐费报表</field>
<field name="tag">get_meal_fee_report</field>
<field name="context">
{
"sideBar": true,
"report_config": {
"model": "hr.attendance",
"method": "get_meal_fee_report",
"columns": [
{"headerName": "员工", "field": "员工", "flex": 1},
{"headerName": "公司", "field": "公司", "flex": 1},
{"headerName": "次数", "field": "次数", "flex": 1},
{"headerName": "类型", "field": "类型", "flex": 1},
{"headerName": "流程", "field": "流程", "flex": 1}
]
}
}
</field>
</record>
<record id="action_overtime_report" model="ir.actions.client">
<field name="name">加班费报表</field>
<field name="tag">get_overtime_report</field>
<field name="context">
{
"sideBar": true,
"report_config": {
"model": "hr.attendance",
"method": "get_overtime_report",
"columns": [
{"headerName": "员工", "field": "员工", "flex": 1},
{"headerName": "公司", "field": "公司", "flex": 1},
{"headerName": "类型", "field": "类型", "flex": 1},
{"headerName": "补偿方式", "field": "补偿方式", "flex": 1},
{"headerName": "加班类型", "field": "加班类型", "flex": 1},
{"headerName": "时长(小时)", "field": "时长(小时)", "flex": 1},
{"headerName": "流程", "field": "流程", "flex": 1}
]
}
}
</field>
</record>
<menuitem
id="menu_leave_deduction_report"
name="请假扣款报表"
parent="yuthon_property_approval.property_approval_report_menu"
action="action_leave_deduction_report"
sequence="8"
/>
<menuitem
id="menu_meal_fee_report"
name="误餐费报表"
parent="yuthon_property_approval.property_approval_report_menu"
action="action_meal_fee_report"
sequence="9"
/>
<menuitem
id="menu_overtime_report"
name="加班费报表"
parent="yuthon_property_approval.property_approval_report_menu"
action="action_overtime_report"
sequence="10"
/>
</data>
</odoo>
+6 -5
View File
@@ -65,8 +65,8 @@ class YuthonWillTask(models.Model):
('22', '22:00'), ('22.25', '22:15'), ('22.5', '22:30'), ('22.75', '22:45'),
('23', '23:00'), ('23.25', '23:15'), ('23.5', '23:30'), ('23.75', '23:45')], string='结束时间', default='18')
priority = fields.Selection([('general', '不紧急不重要'), ('important', '重要不紧急'),
('urgent', '紧急不重要'), ('very_urgent', '重要且紧急')], string='优先级', default='general')
priority = fields.Selection([('important', '重要不紧急'),
('urgent', '紧急不重要'), ('very_urgent', '重要且紧急')], string='优先级')
code = fields.Char(string="任务编号", default=lambda self: self.env['ir.sequence'].next_by_code('task_code'))
user_id = fields.Many2one('res.users', string="创建人", default=lambda self: self.env.user)
company_id = fields.Many2one('res.company', string="公司", default=lambda self: self.env.company)
@@ -141,7 +141,7 @@ class YuthonWillTask(models.Model):
from_time = hour_mapping.get(record.request_hour_from, "")
to_time = hour_mapping.get(record.request_hour_to, "")
if date_str and from_time and to_time:
record.task_time = f"{date_str} ({from_time}--{to_time})"
record.task_time = f"{date_str} {from_time}"
else:
record.task_time = ""
@@ -204,11 +204,12 @@ class YuthonWillTask(models.Model):
def add_comment(self):
self.ensure_one()
employee_list = [i.id for i in self.employee_ids] + [i.id for i in self.employee_ids2]
admin = self.env['hr.employee'].search([('name', '=', 'Administrator')])
employee_list = [i.id for i in self.employee_ids] + [i.id for i in self.employee_ids2] + admin.id
for employee_id in self.env['hr.employee'].search([('id', 'in', employee_list)]):
if not employee_id.wecom_userid:
raise UserError("请同步{}的企业微信".format(employee_id.name))
url = f'http:://oa.thtzjt.com/webh5#/journey?id={self.id}'
url = f'https://oa.thtzjt.com/webh5#/journey?id={self.id}'
data_name = "事项名称: " + self.name + '\n'
data_user_ids = '参与人: ' + ', '.join(self.employee_ids.mapped('name')) + '\n'
data_employee_id = ('司机:' + ', '.join(self.employee_ids2.mapped('name')) + '\n') if self.employee_ids2 else ''
@@ -8,7 +8,6 @@
decoration-muted = "task_state == 'cancel'"
decoration-danger="priority == 'very_urgent'"
decoration-info="priority == 'urgent'"
decoration-primary="priority == 'general'"
decoration-success="priority == 'important'">
<field name='start_date' column_invisible="1"/>
<field name='task_time' />
@@ -49,7 +48,6 @@
<field name="arch" type="xml">
<form string="日程">
<sheet>
<widget name="web_ribbon" title="不紧急不重要" bg_color="text-bg-secondary" invisible="priority != 'general'"/>
<widget name="web_ribbon" title="重要不紧急" bg_color="text-bg-success" invisible="priority != 'important'"/>
<widget name="web_ribbon" title="紧急不重要" bg_color="text-bg-warning" invisible="priority != 'urgent'"/>
<widget name="web_ribbon" title="重要且紧急" bg_color="text-bg-danger" invisible="priority != 'very_urgent'"/>
@@ -120,7 +118,6 @@
decoration-muted = "task_state == 'cancel'"
decoration-danger="priority == 'very_urgent'"
decoration-info="priority == 'urgent'"
decoration-primary="priority == 'general'"
decoration-success="priority == 'important'" create="false" edit="false" js_class="custom_task_tree_button">
<field name='start_date' column_invisible="1"/>
<field name='task_time' />
@@ -148,8 +145,6 @@
<field name="arch" type="xml">
<form string="日程" create="false">
<sheet>
<widget name="web_ribbon" title="不紧急不重要" bg_color="text-bg-secondary"
invisible="priority != 'general'"/>
<widget name="web_ribbon" title="重要不紧急" bg_color="text-bg-success"
invisible="priority != 'important'"/>
<widget name="web_ribbon" title="紧急不重要" bg_color="text-bg-warning"