diff --git a/yuthon_hr_attendance/__manifest__.py b/yuthon_hr_attendance/__manifest__.py
index 8c91aec25..d6a06fe4d 100644
--- a/yuthon_hr_attendance/__manifest__.py
+++ b/yuthon_hr_attendance/__manifest__.py
@@ -49,6 +49,7 @@
'views/yuthon_duty_setting_views.xml',
'views/yuthon_duty_plan_views.xml',
'views/yuthon_abnormal_approval_views.xml',
+ 'views/expatriation_approval.xml',
'views/yuthon_abnormal_setting_views.xml',
'views/yuthon_adjustments_approval_views.xml',
'views/yuthon_shift_approval_views.xml',
@@ -59,6 +60,7 @@
'views/hr_attendance_month_views.xml',
'views/wifi_message_view.xml',
'views/hr_attendance_portal_templates.xml',
+ 'views/meal_approval.xml',
'wizard/hr_attendance_month_wizard_views.xml',
'wizard/yuthon_delete_planning_wizard_views.xml',
'wizard/attendance_controls_wizard.xml',
diff --git a/yuthon_hr_attendance/data/attendance_code_data.xml b/yuthon_hr_attendance/data/attendance_code_data.xml
index 127fced47..f783f773b 100644
--- a/yuthon_hr_attendance/data/attendance_code_data.xml
+++ b/yuthon_hr_attendance/data/attendance_code_data.xml
@@ -8,6 +8,22 @@
+
+ 外派申请序列号
+ expatriation_approval
+ WP%(y)s%(month)s%(day)s
+ 3
+
+
+
+
+ 报餐申请序列号
+ meal_approval
+ ME%(y)s%(month)s%(day)s
+ 3
+
+
+
外出申请序列号
go_approval
diff --git a/yuthon_hr_attendance/models/__init__.py b/yuthon_hr_attendance/models/__init__.py
index 2d2cbfa36..43e103b1a 100644
--- a/yuthon_hr_attendance/models/__init__.py
+++ b/yuthon_hr_attendance/models/__init__.py
@@ -27,6 +27,8 @@ from . import hr_attendance_wechat
from . import hr_attendance_detail_wechat
from . import hr_attendance_month
from . import wifi_message
+from . import expatriation_approval
+from . import meal_approval
# from odoo.tools import SQL
diff --git a/yuthon_hr_attendance/models/expatriation_approval.py b/yuthon_hr_attendance/models/expatriation_approval.py
new file mode 100644
index 000000000..f2f94fb6f
--- /dev/null
+++ b/yuthon_hr_attendance/models/expatriation_approval.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+from odoo import api, fields, models, tools, _
+from datetime import datetime, time, timedelta
+from odoo.exceptions import UserError
+
+
+class ExpatriationApproval(models.Model):
+ _name = 'expatriation.approval'
+ _inherit = ['mail.thread.main.attachment', 'mail.activity.mixin', 'soong.workflow.mixin']
+ _description = '外派驻点审批'
+
+ name = fields.Char(string="标题", compute='_compute_name')
+ code = fields.Char(string="编号", default=lambda self: self.env['ir.sequence'].next_by_code('overtime_approval'))
+ 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)
+ expatriates_ids = fields.Many2many('hr.employee', string="外派人员")
+ start_date = fields.Date(string="开始日期")
+ end_date = fields.Date(string="结束日期")
+ urgency_level = fields.Selection([('normal', '普件'), ('urgent', '急件'), ('emergency', '特急件')], string='紧急度',
+ default='normal')
+ active = fields.Boolean('Active', default=True, tracking=True)
+ reason_application = fields.Text(string="外派事由")
+ document_ids = fields.Many2many('ir.attachment', string="附件")
+ work_end = fields.Boolean(string="结束")
+ is_issues = fields.Boolean(string='是否上会')
+ issues_result = fields.Html(string="会议结果")
+ related_issues_id = fields.Many2one('issue.library', string="关联议题")
+ def _get_default_name_title(self):
+ return self._description if self._description else self._name
+
+ name_title = fields.Char(default=_get_default_name_title)
+ self_users_ids = fields.Many2many('res.users', string="当前经办人", default=lambda self: [(6, 0, [self.env.user.id])])
+
+ @api.onchange('is_issues')
+ def _onchange_is_issues(self):
+ if self.is_issues:
+ issue_id = self.env['issue.library'].create({'name': self.name, 'is_invisible_workflow': True})
+ self.related_issues_id = issue_id.id
+ self.env['issue.library.approval.line'].create({
+ 'issue_library_id': issue_id.id,
+ 'workflow_name': self.name,
+ 'approval_type': self.workflow_id.name,
+ 'res_model': self._name,
+ 'res_id': self.id,
+ 'code': self.code,
+ 'approval_date': fields.Datetime.now(),
+ })
+
diff --git a/yuthon_hr_attendance/models/hr_attendance.py b/yuthon_hr_attendance/models/hr_attendance.py
index 16aa39331..b638a4835 100644
--- a/yuthon_hr_attendance/models/hr_attendance.py
+++ b/yuthon_hr_attendance/models/hr_attendance.py
@@ -23,6 +23,7 @@ class HrAttendance(models.Model):
is_early_card = fields.Boolean(string="早退", group_operator='count_true')
is_late_card = fields.Boolean(string="迟到", group_operator='count_true')
is_abnormal = fields.Boolean(string="异常", group_operator='count_true')
+ is_expatriation = fields.Boolean(string="外派", group_operator='count_true')
is_approval_no = fields.Boolean()
is_approval_early = fields.Boolean()
is_approval_late = fields.Boolean()
diff --git a/yuthon_hr_attendance/models/meal_approval.py b/yuthon_hr_attendance/models/meal_approval.py
new file mode 100644
index 000000000..809e93e9a
--- /dev/null
+++ b/yuthon_hr_attendance/models/meal_approval.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+from odoo import api, fields, models, tools, _
+from datetime import datetime, time, timedelta
+from odoo.exceptions import UserError
+
+
+class MealApproval(models.Model):
+ _name = 'meal.approval'
+ _description = '报餐单'
+ _inherit = ['mail.thread.main.attachment', 'mail.activity.mixin', 'soong.workflow.mixin']
+
+ name = fields.Char(string="标题", compute='_compute_name')
+ code = fields.Char(string="编号", default=lambda self: self.env['ir.sequence'].next_by_code('meal_approval'))
+ 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)
+ meal_date = fields.Date(string="用餐日期", default=lambda self: fields.Date.today())
+ reason_application = fields.Text(string="事由")
+ urgency_level = fields.Selection([('normal', '普件'), ('urgent', '急件'), ('emergency', '特急件')], string='紧急度',
+ default='normal')
+ work_end = fields.Boolean(string="结束")
+ def _get_default_name_title(self):
+ return self._description if self._description else self._name
+
+ name_title = fields.Char(default=_get_default_name_title)
+ self_users_ids = fields.Many2many('res.users', string="当前经办人", default=lambda self: [(6, 0, [self.env.user.id])])
diff --git a/yuthon_hr_attendance/security/ir.model.access.csv b/yuthon_hr_attendance/security/ir.model.access.csv
index dfebb624c..b29bd641d 100644
--- a/yuthon_hr_attendance/security/ir.model.access.csv
+++ b/yuthon_hr_attendance/security/ir.model.access.csv
@@ -72,3 +72,5 @@ access_applet_security,applet.security,model_applet_security,base.group_user,1,1
access_applet_security_setting,applet.security.setting,model_applet_security_setting,base.group_user,1,1,1,1
access_wifi_message,wifi.message,model_wifi_message,base.group_user,1,1,1,1
access_planning_slot_update_display_name_wizard,access_planning_slot_update_display_name_wizard,model_planning_slot_update_display_name_wizard,base.group_user,1,1,1,1
+access_expatriation_approval,expatriation.approval,model_expatriation_approval,base.group_user,1,1,1,1
+access_meal_approval, meal.approval,model_meal_approval,base.group_user,1,1,1,1
diff --git a/yuthon_hr_attendance/views/all_attendance_menu.xml b/yuthon_hr_attendance/views/all_attendance_menu.xml
index e6fefdf8b..cb93e8bd5 100644
--- a/yuthon_hr_attendance/views/all_attendance_menu.xml
+++ b/yuthon_hr_attendance/views/all_attendance_menu.xml
@@ -216,4 +216,8 @@
parent="menu_hr_attendance_approval_request" sequence="10" action="approval_request_remove_holiday"/>
+
+
diff --git a/yuthon_hr_attendance/views/expatriation_approval.xml b/yuthon_hr_attendance/views/expatriation_approval.xml
new file mode 100644
index 000000000..330928570
--- /dev/null
+++ b/yuthon_hr_attendance/views/expatriation_approval.xml
@@ -0,0 +1,77 @@
+
+
+ expatriation.approval.tree
+ expatriation.approval
+
+
+
+
+
+
+
+
+
+
+
+ expatriation.approval.form
+ expatriation.approval
+
+
+
+
+
+ 外派申请
+ expatriation.approval
+ tree,form
+
+
\ No newline at end of file
diff --git a/yuthon_hr_attendance/views/meal_approval.xml b/yuthon_hr_attendance/views/meal_approval.xml
new file mode 100644
index 000000000..4c13b914e
--- /dev/null
+++ b/yuthon_hr_attendance/views/meal_approval.xml
@@ -0,0 +1,57 @@
+
+
+ meal.approval.tree
+ meal.approval
+
+
+
+
+
+
+
+
+
+ meal.approval.form
+ meal.approval
+
+
+
+
+
+ 报餐申请
+ meal.approval
+ tree,form
+
+
diff --git a/yuthon_property_approval/wizard/click_reconciliation_wizard.py b/yuthon_property_approval/wizard/click_reconciliation_wizard.py
index 022db3e2a..d980a96a1 100644
--- a/yuthon_property_approval/wizard/click_reconciliation_wizard.py
+++ b/yuthon_property_approval/wizard/click_reconciliation_wizard.py
@@ -92,7 +92,6 @@ class ClickReconciliationWizard(models.Model):
modify = self.env['receivable.modify.line'].search([
('receivable_id', '=', rec.id),
('bank_id', '=', statements[0].id),
- ('not_collected', '=', rec.rent),
('is_write', '=', True),
('is_penalty', '=', False),
('reconciliation_date', '=', (fields.Datetime.from_string(statements[0].start_time) - timedelta(hours=8)).date()),
@@ -102,7 +101,6 @@ class ClickReconciliationWizard(models.Model):
self.env['receivable.modify.line'].create({
'is_write': True,
'is_penalty': False,
- 'not_collected': rec.rent,
'bank_id': statements[0].id,
'reconciliation_date': (fields.Datetime.from_string(statements[0].start_time) - timedelta(hours=8)).date(),
'receivable_id': rec.id,
diff --git a/yuthon_property_approval/wizard/property_reconciliation_wizard.py b/yuthon_property_approval/wizard/property_reconciliation_wizard.py
index 667bf13e5..bb7027242 100644
--- a/yuthon_property_approval/wizard/property_reconciliation_wizard.py
+++ b/yuthon_property_approval/wizard/property_reconciliation_wizard.py
@@ -296,16 +296,27 @@ class PropertyReconciliationWizard(models.Model):
def create_reconciliation_vals(self):
"""
- 按已生成的明细行 分配金额
+ 按已生成的明细行 分配金额(优先处理押金)
:return:
"""
remaining_amount = self.bank_id.no_amount
- rent_lines = [line for line in self.reconciliation_line_ids if line.collection_type == "rent"]
- other_lines = [line for line in self.reconciliation_line_ids if line.collection_type != "rent"]
+ deposit_lines = [line for line in self.reconciliation_line_ids if line.collection_type == "deposit"] # 押金行
+ rent_lines = [line for line in self.reconciliation_line_ids if line.collection_type == "rent"] # 租金行
+ other_lines = [line for line in self.reconciliation_line_ids if line.collection_type not in ["deposit", "rent"]]
- # 核心修改:按「到期日期最早」且「金额最小」排序租金行
- # 1. 优先按 due_date 升序(最早的日期先处理)
- # 2. 日期相同则按 rent 升序(金额最小的先处理)
+ # 优先处理押金行
+ sorted_deposit_lines = sorted(deposit_lines, key=lambda line: line.due_date)
+ for line in sorted_deposit_lines:
+ if remaining_amount <= 0:
+ break
+ deposit = line.rent or 0
+ line.amount = 0
+ assign_amount = min(deposit, remaining_amount)
+ line.amount = assign_amount
+ remaining_amount -= assign_amount
+ line.onchange_remake()
+
+ # 3. 处理租金行(原有逻辑不变,按“日期最早+金额最小”排序)
sorted_rent_lines = sorted(
rent_lines,
key=lambda line: (
@@ -313,31 +324,25 @@ class PropertyReconciliationWizard(models.Model):
line.rent or 0
)
)
- sorted_other_lines = sorted(
- other_lines,
- key=lambda line: line.due_date
- )
-
- # 先处理所有租金类型的行
for line in sorted_rent_lines:
if remaining_amount <= 0:
break
rent = line.rent or 0
- line.amount = 0 # 重置金额
- # 分配不超过租金和剩余金额的数值
+ line.amount = 0
assign_amount = min(rent, remaining_amount)
line.amount = assign_amount
remaining_amount -= assign_amount
line.onchange_remake()
- # 租金行分配完后,处理其他类型的行
+ # 4. 处理其他类型行(原有逻辑不变,按日期排序)
if remaining_amount > 0:
+ sorted_other_lines = sorted(other_lines, key=lambda line: line.due_date)
for line in sorted_other_lines:
if remaining_amount <= 0:
break
- rent = line.rent or 0
+ amount_needed = line.rent or 0
line.amount = 0
- assign_amount = min(rent, remaining_amount)
+ assign_amount = min(amount_needed, remaining_amount)
line.amount = assign_amount
remaining_amount -= assign_amount
line.onchange_remake()