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()