diff --git a/excel_report_designer/models/report_excel.py b/excel_report_designer/models/report_excel.py index 9c9832614..a19ff576a 100644 --- a/excel_report_designer/models/report_excel.py +++ b/excel_report_designer/models/report_excel.py @@ -366,6 +366,34 @@ class ReportExcel(models.Model): 'align': 'center', 'valign': 'vcenter' }) + # 定义备注格式 + note_format = workbook.add_format({ + 'font_size': 10, + 'border': 1, + 'align': 'left', + 'valign': 'top', + 'text_wrap': True + }) + # 定义审批字段格式 + approval_format = workbook.add_format({ + 'font_size': 10, + 'border': 1, + 'align': 'center', + 'valign': 'vcenter' + }) + # 定义靠右无边框显示的格式 + right_align_format = workbook.add_format({ + 'font_size': 10, + 'align': 'right', + 'valign': 'vcenter' + }) + # 定义靠右有边框显示的格式 + right_format = workbook.add_format({ + 'font_size': 10, + 'align': 'right', + 'valign': 'vcenter', + 'border': 1, + }) header_format = workbook.add_format({'font_size': 11, 'bold': True, 'border': 1}) data_format = workbook.add_format({'font_size': 10, 'border': 1}) domain = self._get_payslip_domain(data) @@ -375,6 +403,7 @@ class ReportExcel(models.Model): for record in records: group_id = record.salary_group_id.id if record.salary_group_id else None grouped_records[group_id].append(record) + for group_id, group_records in grouped_records.items(): if group_id: group_name = self.env['yuthon.salary.group'].browse(group_id).name @@ -383,21 +412,107 @@ class ReportExcel(models.Model): # 创建新的工作表 sheet = workbook.add_worksheet(group_name[:31]) # 写入公司和年月信息 - sheet.merge_range(0, 0, 0, len(data['field_label']) - 1, f'员工{data["current_year"]}年{data["current_month"]}工资表({company_name})', title_format) + headers = ['序号'] + data['field_label'] + columns_data = [[] for _ in range(len(headers) - 1)] + float_totals = [0] * (len(headers) - 1) + + current_year = int(data['current_year']) + current_month = int(data['current_month']) + valid_columns = [] + for index, field_name in enumerate(data['fields_name']): + field = self.env[data['model_name']]._fields.get(field_name) + if not field: + continue + # 检查字段是否存在于 yuthon.payroll.common 和 yuthon.payroll.temporary 模型中 + common_model = self.env['yuthon.payroll.common'] + temp_model = self.env['yuthon.payroll.temporary'] + common_records = common_model.search([('name', '=', field.string)]) + temp_records = temp_model.search([('name', '=', field.string)]) + if not common_records and not temp_records: + # 若两个模型中都没找到该字段,跳过日期判断,直接认为有效 + valid_columns.append(index) + continue + + valid = False + if field.type == 'float': + # 分别处理 common_records + for rec in common_records: + start_date = rec.start_date + end_date = rec.end_date + if not end_date: + end_date = datetime.date(current_year + 99, current_month, 1) + if start_date and end_date: + start_year_month = (start_date.year, start_date.month) + end_year_month = (end_date.year, end_date.month) + current_year_month = (current_year, current_month) + if start_year_month <= current_year_month <= end_year_month: + valid = True + break + # 如果 common_records 未找到有效记录,再处理 temp_records + if not valid: + for rec in temp_records: + start_date = rec.start_date + end_date = rec.end_date + if not end_date: + end_date = datetime.date(current_year + 99, current_month, 1) + if start_date and end_date: + start_year_month = (start_date.year, start_date.month) + end_year_month = (end_date.year, end_date.month) + current_year_month = (current_year, current_month) + if start_year_month <= current_year_month <= end_year_month: + valid = True + break + else: + valid = True + + if valid: + valid_columns.append(index) + + for record in group_records: + col_num = 1 + for index, field_name in enumerate(data['fields_name']): + if index in valid_columns and index < len(columns_data): + value = record[field_name] + columns_data[index].append(value) + field = self.env[data['model_name']]._fields.get(field_name) + if field and field.type == 'float': + float_totals[index] += float(value) if value else 0 + col_num += 1 + + # 记录实际显示的列索引 + visible_cols = [0] + first_float_col_index = None + for index in range(len(headers) - 1): + if index in valid_columns and not all([x == 0 for x in columns_data[index]]): + visible_cols.append(index + 1) + if first_float_col_index is None and self.env[data['model_name']]._fields[ + data['fields_name'][index]].type == 'float': + first_float_col_index = len(visible_cols) - 1 + actual_header_length = len(visible_cols) + + # 写入大表头,根据实际显示列数确定合并范围 + company_year_month_text = f'员工{data["current_year"]}年{data["current_month"]}工资表({company_name})' + sheet.merge_range(0, 0, 0, actual_header_length - 1, company_year_month_text, title_format) + + # 添加单位和日期 + current_date_str = datetime.datetime.now().strftime('%Y年%m月%d日') + sheet.write(1, actual_header_length - 1, f"单位:元 日期:{current_date_str}", right_align_format) + # 写入表头 - headers = ['序号']+data['field_label'] col_num = 0 - for label in headers: - sheet.write(1, col_num, label, header_format) + for index in visible_cols: + sheet.write(2, col_num, headers[index], header_format) col_num += 1 - row_num = 2 + # 写入数据 + row_num = 3 serial_number = 1 for record in group_records: col_num = 0 sheet.write(row_num, col_num, serial_number, serial_number_format) col_num += 1 - for field_name in data['fields_name']: + for index in visible_cols[1:]: + field_name = data['fields_name'][index - 1] field = self.env[data['model_name']]._fields.get(field_name) value = record[field_name] if isinstance(value, datetime.date): @@ -421,6 +536,45 @@ class ReportExcel(models.Model): col_num += 1 row_num += 1 serial_number += 1 # 序号递增 + + if first_float_col_index is not None: + # 合并从首列到首个float类型字段所在列的前一列的单元格 + sheet.merge_range(row_num, 0, row_num, first_float_col_index - 1, "总计", serial_number_format) + # 调整起始列索引 + start_col = first_float_col_index + for col_index, visible_col in enumerate(visible_cols[first_float_col_index:]): + field_name = data['fields_name'][visible_col - 1] + field = self.env[data['model_name']]._fields.get(field_name) + if field and field.type == 'float': + # 计算正确的列索引 + target_col = start_col + col_index + sheet.write(row_num, target_col, float_totals[visible_col - 1], right_format) + else: + # 计算正确的列索引 + target_col = start_col + col_index + sheet.write(row_num, target_col, "", right_format) + + # 写入备注,根据实际显示列数确定合并范围 + start_row = row_num + 1 + end_row = start_row + 4 + note_text = "备注" + sheet.merge_range(start_row, 0, end_row, actual_header_length - 1, note_text, note_format) + + col_num = 0 + # 记录表头实际占据的列数 + header_column_count = len(visible_cols) + for index in visible_cols: + sheet.write(2, col_num, headers[index], header_format) + col_num += 1 + + # 添加审批字段行 + approval_row = end_row + 1 + approval_fields = ["领导审批:", "财务审核:", "部门审核:", "制表:"] + approval_text = " ".join(approval_fields) + + # 合并单元格,使其宽度和表头一致 + sheet.merge_range(approval_row, 0, approval_row, header_column_count - 1, approval_text, approval_format) + # 创建劳务信息工作表 labor_sheet = workbook.add_worksheet('劳务信息') labor_fields = ['employee_id', 'labor_amount'] @@ -431,20 +585,39 @@ class ReportExcel(models.Model): labor_sheet.merge_range(0, 0, 0, len(labor_headers) - 1, labor_title, title_format) title_width = len(labor_title) + 2 # 适当增加一些宽度余量 labor_sheet.set_column(0, len(labor_headers) - 1, title_width) + # 添加单位和日期信息 + current_time = datetime.datetime.now().strftime('%Y-%m-%d') + info_text = f"单位:元 日期:{current_time}" + labor_sheet.merge_range(1, 0, 1, len(labor_headers) - 1, info_text, right_align_format) # 写入表头 col_num = 0 for label in labor_headers: - labor_sheet.write(1, col_num, label, header_format) + labor_sheet.write(2, col_num, label, header_format) col_num += 1 labor_records = self.env['yuthon.payroll.labor'].search([ ('current_year', '=', data['current_year']), ('current_month', '=', data['current_month']) ]) - row_num = 2 + row_num = 3 serial_number = 1 + column_totals = [0] * len(labor_headers) + first_float_col_index = None + + # 找出首个 float 类型字段所在列的索引 + for col_index, field_name in enumerate(['serial_number'] + labor_fields): + field = self.env['yuthon.payroll.labor']._fields.get(field_name) if field_name in labor_fields else None + if field and field.type == 'float': + first_float_col_index = col_index + break + for labor_record in labor_records: col_num = 0 labor_sheet.write(row_num, col_num, serial_number, serial_number_format) + if first_float_col_index is not None and col_num >= first_float_col_index: + try: + column_totals[col_num] += float(serial_number) + except (ValueError, TypeError): + pass col_num += 1 for field_name in labor_fields: field = self.env['yuthon.payroll.labor']._fields.get(field_name) @@ -465,9 +638,28 @@ class ReportExcel(models.Model): selection_options = dict(field.selection) value = selection_options.get(value, value) labor_sheet.write(row_num, col_num, value, data_format) + if first_float_col_index is not None and col_num >= first_float_col_index: + try: + column_totals[col_num] += float(value) + except (ValueError, TypeError): + pass col_num += 1 row_num += 1 serial_number += 1 + # 写入总计行 + total_row = row_num + if first_float_col_index is not None: + labor_sheet.merge_range(total_row, 0, total_row, first_float_col_index - 1, '合计', serial_number_format) + for col_num in range(first_float_col_index, len(labor_headers)): + labor_sheet.write(total_row, col_num, column_totals[col_num], data_format) + + end_row = total_row # 假设 end_row 是总计行 + approval_row = end_row + 1 + approval_fields = ["领导审批:", "财务审核:", "部门审核:", "制表:"] + approval_text = " ".join(approval_fields) + + # 合并单元格,使其宽度和表头一致 + labor_sheet.merge_range(approval_row, 0, approval_row, len(labor_headers) - 1, approval_text, approval_format) # 创建退休人员费用工作表 retired_sheet = workbook.add_worksheet('退休人员费用') @@ -479,20 +671,38 @@ class ReportExcel(models.Model): retired_sheet.merge_range(0, 0, 0, len(labor_headers) - 1, retired_title, title_format) title_width = len(retired_title) + 2 # 适当增加一些宽度余量 retired_sheet.set_column(0, len(retired_headers) - 1, title_width) + # 添加单位和日期信息 + current_time = datetime.datetime.now().strftime('%Y-%m-%d') + info_text = f"单位:元 日期:{current_time}" + retired_sheet.merge_range(1, 0, 1, len(retired_headers) - 1, info_text, right_align_format) # 写入表头 col_num = 0 for label in retired_headers: - retired_sheet.write(1, col_num, label, header_format) + retired_sheet.write(2, col_num, label, header_format) col_num += 1 retired_records = self.env['yuthon.payroll.retired'].search([ ('current_year', '=', data['current_year']), ('current_month', '=', data['current_month']) ]) - row_num = 2 + row_num = 3 serial_number = 1 + column_totals = [0] * len(retired_headers) + first_float_col_index = None + + for col_index, field_name in enumerate(['serial_number'] + retired_fields): + field = self.env['yuthon.payroll.retired']._fields.get(field_name) if field_name in retired_fields else None + if field and field.type == 'float': + first_float_col_index = col_index + break + for retired_record in retired_records: col_num = 0 retired_sheet.write(row_num, col_num, serial_number, serial_number_format) + if first_float_col_index is not None and col_num >= first_float_col_index: + try: + column_totals[col_num] += float(serial_number) + except (ValueError, TypeError): + pass col_num += 1 for field_name in retired_fields: field = self.env['yuthon.payroll.retired']._fields.get(field_name) @@ -513,9 +723,28 @@ class ReportExcel(models.Model): selection_options = dict(field.selection) value = selection_options.get(value, value) retired_sheet.write(row_num, col_num, value, data_format) + if first_float_col_index is not None and col_num >= first_float_col_index: + try: + column_totals[col_num] += float(value) + except (ValueError, TypeError): + pass col_num += 1 row_num += 1 serial_number += 1 + # 写入总计行 + total_row = row_num + if first_float_col_index is not None: + retired_sheet.merge_range(total_row, 0, total_row, first_float_col_index - 1, '合计', serial_number_format) + for col_num in range(first_float_col_index, len(retired_headers)): + retired_sheet.write(total_row, col_num, column_totals[col_num], data_format) + + end_row = total_row # 假设 end_row 是总计行 + approval_row = end_row + 1 + approval_fields = ["领导审批:", "财务审核:", "部门审核:", "制表:"] + approval_text = " ".join(approval_fields) + + # 合并单元格,使其宽度和表头一致 + retired_sheet.merge_range(approval_row, 0, approval_row, len(labor_headers) - 1, approval_text, approval_format) # 关闭工作簿 workbook.close() @@ -548,7 +777,6 @@ class ReportExcel(models.Model): chinese_month = month_mapping.get(current_month) target_name = f"{current_year}年-{chinese_month}" return [ - ('is_salary_push', '=', True), - ('is_salary_release', '=', True), + ('is_push', '=', False), ('payslip_run_id.name', '=', target_name) - ] \ No newline at end of file + ] diff --git a/yuthon_hr_employee/views/yuthon_hr_employee_views.xml b/yuthon_hr_employee/views/yuthon_hr_employee_views.xml index e4f03fc7e..33fd3882b 100644 --- a/yuthon_hr_employee/views/yuthon_hr_employee_views.xml +++ b/yuthon_hr_employee/views/yuthon_hr_employee_views.xml @@ -390,6 +390,19 @@ + + + hr.employee.inherit.del.form + hr.employee + + + + + + + + + hr.employee.inherit.search hr.employee @@ -408,7 +421,14 @@ code action = model.action_hr_employee() - + + 通讯录 + hr.employee + tree,kanban,form,search + + tree,kanban,form,activity,graph,pivot,hierarchy diff --git a/yuthon_salary/models/inherit_hr_payslip.py b/yuthon_salary/models/inherit_hr_payslip.py index 0f9ff1e0f..bdf1e2025 100644 --- a/yuthon_salary/models/inherit_hr_payslip.py +++ b/yuthon_salary/models/inherit_hr_payslip.py @@ -8,6 +8,7 @@ class InheritHrPayslip(models.Model): company_id = fields.Many2one('res.company', string="公司", default=lambda self: self.env.company) employee_id = fields.Many2one('hr.employee', string="姓名") + date_of_entry = fields.Date(related='employee_id.entry_date', string="入职时间", store=True) base_salary = fields.Float(string='基本工资', compute='_compute_salary', store=True) bank_account_id = fields.Many2one(string='银行账号', related='employee_id.bank_account_id', store=True) tax_payable = fields.Float(string='应补(退)税额', store=True) diff --git a/yuthon_salary/models/yuthon_payroll_labor.py b/yuthon_salary/models/yuthon_payroll_labor.py index d6aae88ed..c25e2bd9d 100644 --- a/yuthon_salary/models/yuthon_payroll_labor.py +++ b/yuthon_salary/models/yuthon_payroll_labor.py @@ -7,6 +7,7 @@ class YuthonPayrollLabor(models.Model): employee_id = fields.Many2one('hr.employee', string='姓名') current_year = fields.Integer(string='年份') + date_of_entry = fields.Date(related='employee_id.entry_date', string="入职时间", store=True) current_month = fields.Selection([ ('1', '1月份'), ('2', '2月份'), diff --git a/yuthon_salary/views/inherit_hr_payslip_views.xml b/yuthon_salary/views/inherit_hr_payslip_views.xml index 3f255cc4e..76a7a39aa 100644 --- a/yuthon_salary/views/inherit_hr_payslip_views.xml +++ b/yuthon_salary/views/inherit_hr_payslip_views.xml @@ -7,9 +7,14 @@ - custom_documents_button + custom_salary_validation_button employee_id + +
+
+
1 @@ -32,6 +37,7 @@ 1 + @@ -85,12 +91,10 @@ hr.payslip.tree hr.payslip - -
-
+ - + + @@ -241,6 +245,7 @@