diff --git a/application/accounts/models.py b/application/accounts/models.py index d62636a..3529fd5 100644 --- a/application/accounts/models.py +++ b/application/accounts/models.py @@ -5,13 +5,20 @@ from application.hrm_mgnt.models import EmployeeInformation class AccountProfile(models.Model): + ROLE_CHOICES = [ + ('all_permissions', 'All Permissions'), + ('department_permissions', 'Department Permissions'), + ('own_permissions', 'Own Permissions'), + ] + user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='profile') employee_information = models.OneToOneField(EmployeeInformation, on_delete=models.CASCADE, related_name='account_profile', verbose_name='员工信息') + role = models.CharField(max_length=255, choices=ROLE_CHOICES, verbose_name='角色', default='own_permissions') class Meta: verbose_name = '账户信息' verbose_name_plural = '账户信息' def __str__(self): - return self.user.username + return self.employee_information.name diff --git a/application/pjt_mgnt/forms.py b/application/pjt_mgnt/forms.py index 9e4f367..d931c1e 100644 --- a/application/pjt_mgnt/forms.py +++ b/application/pjt_mgnt/forms.py @@ -55,3 +55,11 @@ class EmployeeProjectIncomeSettlementForm(forms.ModelForm): 'contribution_rate': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '贡献率'}), 'sales_income': forms.NumberInput(attrs={'class': 'form-control', 'placeholder': '销售收入(元)'}), } + + def __init__(self, *args, **kwargs): + super(EmployeeProjectIncomeSettlementForm, self).__init__(*args, **kwargs) + + self.fields['primary_department'] = forms.ChoiceField( + choices=[('', '---------')] + [(dept.department_name, dept.department_name) for dept in PrimaryDepartment.objects.all()], + widget=forms.Select(attrs={'class': 'form-control'}), + label="一级部门") \ No newline at end of file diff --git a/application/pjt_mgnt/models.py b/application/pjt_mgnt/models.py index 959e5ca..1ce4950 100644 --- a/application/pjt_mgnt/models.py +++ b/application/pjt_mgnt/models.py @@ -62,12 +62,6 @@ class ProjectLedger(models.Model): verbose_name = '项目台账' verbose_name_plural = '项目台账' - permissions = [ - ('view_all', 'Can view all records'), - ('view_department', 'Can view department records'), - ('view_own', 'Can view own records') - ] - def __str__(self): return self.project_name @@ -254,6 +248,7 @@ class EmployeeProjectIncomeSettlement(models.Model): blank=True ) name = models.CharField(max_length=255, verbose_name='姓名', help_text='员工姓名', null=True, blank=True) + primary_department = models.CharField(max_length=255, verbose_name='一级部门', null=True, blank=True) contribution_rate = models.DecimalField( max_digits=5, decimal_places=2, @@ -276,16 +271,26 @@ class EmployeeProjectIncomeSettlement(models.Model): verbose_name = '项目组员收入结算表' verbose_name_plural = '项目组员收入结算表' - permissions = [ - ('view_all', 'Can view all records'), - ('view_department', 'Can view department records'), - ('view_own', 'Can view own records') - ] - def __str__(self): return f"Record #{self.record_id} - Project: {self.project_name}, Year-Month: {self.year_month}, Employee: {self.name or 'N/A'}" def save(self, *args, **kwargs): + primary_department_name = PrimaryDepartment.objects.filter(department_name=self.primary_department).first() + name = EmployeeInformation.objects.filter(name=self.name).first() + + if self.primary_department: + if not primary_department_name: + raise ValueError("一级部门不存在") + + if self.name: + if not name: + raise ValueError("姓名不存在") + + if self.pk is None: + # 新增操作,进行唯一性校验 + if EmployeeProjectIncomeSettlement.objects.filter(project_name=self.project_name, year_month=self.year_month, name=self.name, primary_department=self.primary_department).exists(): + raise IntegrityError("该部门下的员工在同一年月的同一项目下已存在一个项目结算。") + # 只在贡献率不为空时计算销售收入 if all([self.contribution_rate, self.total_amount_including_tax]): self.sales_income = self.total_amount_including_tax * (self.contribution_rate / 100) diff --git a/application/pjt_mgnt/views.py b/application/pjt_mgnt/views.py index 9a29f24..876cae0 100644 --- a/application/pjt_mgnt/views.py +++ b/application/pjt_mgnt/views.py @@ -241,7 +241,7 @@ def proj_ledger_list_delete(request): @custom_permission_required('pjt_mgnt.view_employeeprojectincomesettlement') -@permission_based_queryset('pjt_mgnt', 'EmployeeProjectIncomeSettlement', 'record_id', 'name') +@permission_based_queryset('pjt_mgnt', 'EmployeeProjectIncomeSettlement', 'record_id', 'name', 'primary_department') def emp_proj_income_list_view(request): """ 基础数据-项目管理-项目组员收入结算表-列表视图 @@ -354,8 +354,8 @@ def emp_proj_income_list_modify(request): except ValueError: return JsonResponse({"message": "无效的日期格式"}, status=400) - if 'record_id' in request.POST: - instance = EmployeeProjectIncomeSettlement.objects.get(record_id=request.POST['record_id']) + if 'id' in request.POST: + instance = EmployeeProjectIncomeSettlement.objects.get(record_id=request.POST['id']) form = EmployeeProjectIncomeSettlementForm(data, instance=instance) else: form = EmployeeProjectIncomeSettlementForm(data) @@ -367,9 +367,9 @@ def emp_proj_income_list_modify(request): form_html = render_to_string('form_partial.html', {'form': form}, request) return JsonResponse({"form_html": form_html, "errors": form.errors}, status=400) elif request.method == 'GET': - if 'record_id' in request.GET: + if 'id' in request.GET: try: - instance = EmployeeProjectIncomeSettlement.objects.get(record_id=request.GET['record_id']) + instance = EmployeeProjectIncomeSettlement.objects.get(record_id=request.GET['id']) form = EmployeeProjectIncomeSettlementForm(instance=instance) form.fields['year_month'].initial = instance.year_month.strftime('%Y-%m') except EmployeeProjectIncomeSettlement.DoesNotExist: diff --git a/application/rsc_mgnt/forms.py b/application/rsc_mgnt/forms.py index 217bf7b..08c69a8 100644 --- a/application/rsc_mgnt/forms.py +++ b/application/rsc_mgnt/forms.py @@ -67,7 +67,7 @@ class MembershipAccountsRegistryForm(forms.ModelForm): class StoredValueCardRegistrationForm(forms.ModelForm): class Meta: model = StoredValueCardRegistration - fields = '__all__' + exclude = ['usage_records'] widgets = { 'merchant_name': forms.TextInput(attrs={'class': 'form-control'}), 'merchant_type': forms.TextInput(attrs={'class': 'form-control'}), diff --git a/common/auth.py b/common/auth.py index b7c639c..a69e58f 100644 --- a/common/auth.py +++ b/common/auth.py @@ -42,21 +42,18 @@ def permission_based_queryset(app_name, model_name, id_field, leader_field=None, except AccountProfile.DoesNotExist: return JsonResponse({'message': '您的账户未关联到员工信息,请联系管理员。'}, status=405) - view_all_perm = f'{app_name}.view_all' - view_department_perm = f'{app_name}.view_department' - model = apps.get_model(app_name, model_name) - if current_user.has_perm(view_all_perm): + if account_profile.role == 'all_permissions': query_set = model.objects.all().order_by(f'-{id_field}') - elif department_field and current_user.has_perm(view_department_perm): + elif department_field and account_profile.role == 'department_permissions': filter_kwargs = {department_field: employee.primary_department} query_set = model.objects.filter(**filter_kwargs).order_by(f'-{id_field}') - elif leader_field: + elif leader_field and account_profile.role == 'own_permissions': filter_kwargs = {leader_field: employee.name} query_set = model.objects.filter(**filter_kwargs).order_by(f'-{id_field}') else: - return JsonResponse({'message': '您没有权限查看任何数据。'}, status=403) + query_set = model.objects.none() request.query_set = query_set diff --git a/common/views.py b/common/views.py index 3c9b56a..85288e7 100644 --- a/common/views.py +++ b/common/views.py @@ -1,23 +1,17 @@ import json -import uuid -from datetime import datetime -from urllib.parse import quote - -from django.apps import apps from django.core.exceptions import ValidationError from django.shortcuts import render - from django.contrib.auth.decorators import login_required -from django.http import JsonResponse, HttpResponse -from django.utils.module_loading import import_string -from django.utils.timezone import make_aware +from django.http import JsonResponse from django.views.decorators.csrf import csrf_protect - -from application.fac_mgnt.models import InvoiceRecord from application.org_mgnt.models import SecondaryDepartment, PrimaryDepartment -from application.pjt_mgnt.models import ProjectLedger -import pandas as pd - +from django.http import HttpResponse +from django.utils.http import quote +from django.apps import apps +from openpyxl import Workbook +from openpyxl.utils import get_column_letter +from datetime import datetime +from django.utils.timezone import make_aware def error_page(request): return render(request, 'error_page.html') @@ -128,16 +122,28 @@ def export_data(request): field_names = [field.name for field in model._meta.fields] verbose_names = [field.verbose_name for field in model._meta.fields] - # 创建 DataFrame 并设置 verbose_name 作为列名 - df = pd.DataFrame(data, columns=field_names) - df.columns = verbose_names + # 创建 Excel 工作簿 + wb = Workbook() + ws = wb.active + + # 写入表头 + for col_num, verbose_name in enumerate(verbose_names, 1): + col_letter = get_column_letter(col_num) + ws[f'{col_letter}1'] = verbose_name + + # 写入数据 + for row_num, record in enumerate(data, 2): + for col_num, field_name in enumerate(field_names, 1): + col_letter = get_column_letter(col_num) + # 如果字段在记录中不存在,使用空字符串 + ws[f'{col_letter}{row_num}'] = record.get(field_name, '') # 使用模型的 verbose_name 作为文件名的一部分 model_verbose_name = model._meta.verbose_name + '导出结果.xlsx' encoded_filename = quote(model_verbose_name) - response = HttpResponse(content_type='application/vnd.ms-excel') + response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') response['Content-Disposition'] = f'attachment; filename*=UTF-8\'\'{encoded_filename}' - df.to_excel(response, index=False) + wb.save(response) - return response + return response \ No newline at end of file