增加预算表的显示功能
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,194 +1,287 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin.actions import delete_selected
|
||||
from django.urls import reverse
|
||||
from django.utils.html import format_html
|
||||
from .models import Branch, EquipmentImage, Drawing, PublicScreen
|
||||
from .models import Activity, Branch, Event, Contact, VideoTerminal
|
||||
from django_select2.forms import Select2Widget
|
||||
from unfold.admin import ModelAdmin
|
||||
from django.contrib.admin import AdminSite
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
from django.contrib.auth.admin import GroupAdmin as BaseGroupAdmin
|
||||
from django.db.models import Count, Q
|
||||
|
||||
from django.utils.html import format_html
|
||||
from django.urls import reverse
|
||||
from django import forms
|
||||
|
||||
|
||||
# 定义Activity的内联admin类
|
||||
class ActivityInline(admin.TabularInline): # 改为更紧凑的表格形式内联(原StackedInline为堆叠形式)
|
||||
model = Activity
|
||||
extra = 1 # 在Branch表单中默认显示1个额外的Activity表单(可根据需求调整数值)
|
||||
fields = ('name', 'scope', 'start_time', 'end_time') # 限制内联表单显示的字段
|
||||
readonly_fields = ('start_time',) # 可选:将开始时间设为只读字段(避免误修改)
|
||||
# 可选:添加帮助文本提示
|
||||
help_texts = {
|
||||
'name': '请输入活动名称(必填)',
|
||||
'scope': '选择活动范围(内部/外部)'
|
||||
}
|
||||
|
||||
|
||||
admin.site.unregister(User)
|
||||
admin.site.unregister(Group)
|
||||
|
||||
|
||||
@admin.register(User)
|
||||
class UserAdmin(BaseUserAdmin, ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
@admin.register(Group)
|
||||
class GroupAdmin(BaseGroupAdmin, ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
@admin.register(Branch)
|
||||
class BranchAdmin(ModelAdmin):
|
||||
inlines = [ActivityInline]
|
||||
list_display = ('name', 'location', 'category', 'display_mature_status', 'background_color')
|
||||
search_fields = ['name', 'location']
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'fields': ('name', 'location', 'contact_info', 'description', 'category', 'is_mature', 'background_color')
|
||||
}),
|
||||
)
|
||||
|
||||
def display_mature_status(self, obj):
|
||||
return '💼' if obj.is_mature else '📒'
|
||||
display_mature_status.short_description = '是否成熟'
|
||||
|
||||
def activity_count(self, obj):
|
||||
return obj.activity_set.count()
|
||||
|
||||
activity_count.short_description = '活动数量'
|
||||
|
||||
def get_queryset(self, request):
|
||||
queryset = super().get_queryset(request)
|
||||
queryset = queryset.annotate(
|
||||
inspection_person_count=Count('contact', filter=Q(contact__category='机房/设备间巡检人')))
|
||||
return queryset
|
||||
|
||||
actions = ['set_branches_to_type_b']
|
||||
|
||||
def set_branches_to_type_b(self, request, queryset):
|
||||
queryset.update(category='B型')
|
||||
|
||||
set_branches_to_type_b.short_description = '将选中的分支机构统一改为B型'
|
||||
|
||||
|
||||
# 注册Activity模型(如果需要的话,虽然在这个示例中我们主要关注Branch)
|
||||
@admin.register(Activity)
|
||||
class ActivityAdmin(ModelAdmin):
|
||||
list_display = ('branch', 'scope', 'name', 'start_time', 'end_time')
|
||||
search_fields = ["branch__name"] # 改为关联Branch模型的name字段
|
||||
add_form_template = 'admin/huodong/add_form.html'
|
||||
|
||||
autocomplete_fields = ['branch']
|
||||
|
||||
|
||||
@admin.register(Event)
|
||||
class EventAdmin(ModelAdmin):
|
||||
list_display = ('name', 'start_time', 'end_time', 'description')
|
||||
filter_horizontal = ('branches',)
|
||||
|
||||
|
||||
class ContactAdminForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Contact
|
||||
fields = '__all__'
|
||||
widgets = {
|
||||
# 使用复选框实现多选
|
||||
'category': forms.CheckboxSelectMultiple(choices=Contact.CATEGORY_CHOICES)
|
||||
}
|
||||
|
||||
|
||||
class EquipmentImageAdmin(admin.ModelAdmin):
|
||||
def bulk_delete_selected(self, request, queryset):
|
||||
count = queryset.count()
|
||||
queryset.delete()
|
||||
self.message_user(request, f'已成功删除{count}条设备图片记录')
|
||||
bulk_delete_selected.short_description = '批量删除选中的设备图片'
|
||||
actions = ['bulk_delete_selected']
|
||||
list_display = ['id', 'branch', 'uploaded_at', 'delete_link']
|
||||
|
||||
def delete_link(self, obj):
|
||||
url = reverse('admin:huodong_equipmentimage_delete', args=[obj.id])
|
||||
return format_html('<a class="deletelink" href="{}">删除</a>', url)
|
||||
delete_link.short_description = '操作'
|
||||
list_filter = ['branch']
|
||||
search_fields = ['branch__name']
|
||||
|
||||
autocomplete_fields = ['branch']
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
form = super().get_form(request, obj, **kwargs)
|
||||
# 设置默认分支机构为最后一个设备间图片的分支机构
|
||||
try:
|
||||
latest_image = EquipmentImage.objects.latest('uploaded_at')
|
||||
form.base_fields['branch'].initial = latest_image.branch_id
|
||||
except EquipmentImage.DoesNotExist:
|
||||
pass
|
||||
return form
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
super().save_model(request, obj, form, change)
|
||||
# Count images for the current branch
|
||||
count = EquipmentImage.objects.filter(branch=obj.branch).count()
|
||||
self.message_user(request, f"【{obj.branch.name}】已经有{count}张设备间图片。")
|
||||
|
||||
admin.site.register(EquipmentImage, EquipmentImageAdmin)
|
||||
|
||||
class DrawingAdmin(admin.ModelAdmin):
|
||||
def bulk_delete_selected(self, request, queryset):
|
||||
count = queryset.count()
|
||||
queryset.delete()
|
||||
self.message_user(request, f'已成功删除{count}条图纸记录')
|
||||
bulk_delete_selected.short_description = '批量删除选中的图纸'
|
||||
actions = ['bulk_delete_selected']
|
||||
list_display = ('id', 'branch', 'uploaded_at', 'delete_link')
|
||||
|
||||
def delete_link(self, obj):
|
||||
url = reverse('admin:huodong_drawing_delete', args=[obj.id])
|
||||
return format_html('<a class="deletelink" href="{}">删除</a>', url)
|
||||
delete_link.short_description = '操作'
|
||||
list_filter = ('branch', 'uploaded_at')
|
||||
search_fields = ('branch__name',)
|
||||
autocomplete_fields = ['branch']
|
||||
|
||||
admin.site.register(Drawing, DrawingAdmin)
|
||||
|
||||
class PublicScreenAdmin(ModelAdmin):
|
||||
def delete_link(self, obj):
|
||||
if obj.id:
|
||||
delete_url = reverse('admin:huodong_publicscreen_delete', args=[obj.id])
|
||||
return format_html('<a href="{}" class="text-red-600 hover:text-red-900">删除</a>', delete_url)
|
||||
return '-'
|
||||
delete_link.short_description = '操作'
|
||||
|
||||
def bulk_delete_selected(self, request, queryset):
|
||||
count = queryset.count()
|
||||
queryset.delete()
|
||||
self.message_user(request, f'已成功删除{count}条公共电子屏记录')
|
||||
bulk_delete_selected.short_description = '批量删除选中的公共电子屏'
|
||||
|
||||
list_display = ('id', 'branch', 'screen_type', 'last_drill', 'delete_link')
|
||||
list_filter = ['branch', 'screen_type']
|
||||
autocomplete_fields = ['branch']
|
||||
actions = ['bulk_delete_selected']
|
||||
|
||||
admin.site.register(PublicScreen, PublicScreenAdmin)
|
||||
@admin.register(Contact)
|
||||
class ContactAdmin(ModelAdmin):
|
||||
form = ContactAdminForm # 指定自定义表单
|
||||
list_display = ('branch', 'category', 'name', 'phone', 'email') # 可选:显示分类字段
|
||||
|
||||
autocomplete_fields = ['branch']
|
||||
|
||||
@admin.register(VideoTerminal)
|
||||
class VideoTerminalAdmin(ModelAdmin):
|
||||
list_display = ('branch', 'terminal_type', 'description', 'created_at')
|
||||
list_filter = ('terminal_type', 'branch')
|
||||
search_fields = ('branch__name', 'description')
|
||||
autocomplete_fields = ['branch']
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin.actions import delete_selected
|
||||
from django.urls import reverse
|
||||
from django.utils.html import format_html
|
||||
from .models import Branch, EquipmentImage, Drawing, PublicScreen
|
||||
from .models import Activity, Branch, Event, Contact, VideoTerminal, Budget, EquipmentBudget, InfrastructureBudget, BudgetTemplate, TemplateEquipmentItem, TemplateInfrastructureItem
|
||||
from django_select2.forms import Select2Widget
|
||||
from unfold.admin import ModelAdmin
|
||||
from django.contrib.admin import AdminSite
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
from django.contrib.auth.admin import GroupAdmin as BaseGroupAdmin
|
||||
from django.db.models import Count, Q
|
||||
|
||||
from django.utils.html import format_html
|
||||
from django.urls import reverse
|
||||
from django import forms
|
||||
|
||||
|
||||
# 定义Activity的内联admin类
|
||||
class ActivityInline(admin.TabularInline): # 改为更紧凑的表格形式内联(原StackedInline为堆叠形式)
|
||||
model = Activity
|
||||
extra = 1 # 在Branch表单中默认显示1个额外的Activity表单(可根据需求调整数值)
|
||||
fields = ('name', 'scope', 'start_time', 'end_time') # 限制内联表单显示的字段
|
||||
readonly_fields = ('start_time',) # 可选:将开始时间设为只读字段(避免误修改)
|
||||
# 可选:添加帮助文本提示
|
||||
help_texts = {
|
||||
'name': '请输入活动名称(必填)',
|
||||
'scope': '选择活动范围(内部/外部)'
|
||||
}
|
||||
|
||||
|
||||
admin.site.unregister(User)
|
||||
admin.site.unregister(Group)
|
||||
|
||||
|
||||
@admin.register(User)
|
||||
class UserAdmin(BaseUserAdmin, ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
@admin.register(Group)
|
||||
class GroupAdmin(BaseGroupAdmin, ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
@admin.register(Branch)
|
||||
class BranchAdmin(ModelAdmin):
|
||||
inlines = [ActivityInline]
|
||||
list_display = ('name', 'location', 'category', 'display_mature_status', 'background_color')
|
||||
search_fields = ['name', 'location']
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'fields': ('name', 'location', 'contact_info', 'description', 'category', 'is_mature', 'background_color')
|
||||
}),
|
||||
)
|
||||
|
||||
def display_mature_status(self, obj):
|
||||
return '💼' if obj.is_mature else '📒'
|
||||
display_mature_status.short_description = '是否成熟'
|
||||
|
||||
def activity_count(self, obj):
|
||||
return obj.activity_set.count()
|
||||
|
||||
activity_count.short_description = '活动数量'
|
||||
|
||||
def get_queryset(self, request):
|
||||
queryset = super().get_queryset(request)
|
||||
queryset = queryset.annotate(
|
||||
inspection_person_count=Count('contact', filter=Q(contact__category='机房/设备间巡检人')))
|
||||
return queryset
|
||||
|
||||
actions = ['set_branches_to_type_b']
|
||||
|
||||
def set_branches_to_type_b(self, request, queryset):
|
||||
queryset.update(category='B型')
|
||||
|
||||
set_branches_to_type_b.short_description = '将选中的分支机构统一改为B型'
|
||||
|
||||
|
||||
# 注册Activity模型(如果需要的话,虽然在这个示例中我们主要关注Branch)
|
||||
@admin.register(Activity)
|
||||
class ActivityAdmin(ModelAdmin):
|
||||
list_display = ('branch', 'scope', 'name', 'start_time', 'end_time')
|
||||
search_fields = ["branch__name"] # 改为关联Branch模型的name字段
|
||||
add_form_template = 'admin/huodong/add_form.html'
|
||||
|
||||
autocomplete_fields = ['branch']
|
||||
|
||||
|
||||
@admin.register(Event)
|
||||
class EventAdmin(ModelAdmin):
|
||||
list_display = ('name', 'start_time', 'end_time', 'description')
|
||||
filter_horizontal = ('branches',)
|
||||
|
||||
|
||||
class ContactAdminForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Contact
|
||||
fields = '__all__'
|
||||
widgets = {
|
||||
# 使用复选框实现多选
|
||||
'category': forms.CheckboxSelectMultiple(choices=Contact.CATEGORY_CHOICES)
|
||||
}
|
||||
|
||||
|
||||
class EquipmentImageAdmin(admin.ModelAdmin):
|
||||
def bulk_delete_selected(self, request, queryset):
|
||||
count = queryset.count()
|
||||
queryset.delete()
|
||||
self.message_user(request, f'已成功删除{count}条设备图片记录')
|
||||
bulk_delete_selected.short_description = '批量删除选中的设备图片'
|
||||
actions = ['bulk_delete_selected']
|
||||
list_display = ['id', 'branch', 'uploaded_at', 'delete_link']
|
||||
|
||||
def delete_link(self, obj):
|
||||
url = reverse('admin:huodong_equipmentimage_delete', args=[obj.id])
|
||||
return format_html('<a class="deletelink" href="{}">删除</a>', url)
|
||||
delete_link.short_description = '操作'
|
||||
list_filter = ['branch']
|
||||
search_fields = ['branch__name']
|
||||
|
||||
autocomplete_fields = ['branch']
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
form = super().get_form(request, obj, **kwargs)
|
||||
# 设置默认分支机构为最后一个设备间图片的分支机构
|
||||
try:
|
||||
latest_image = EquipmentImage.objects.latest('uploaded_at')
|
||||
form.base_fields['branch'].initial = latest_image.branch_id
|
||||
except EquipmentImage.DoesNotExist:
|
||||
pass
|
||||
return form
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
super().save_model(request, obj, form, change)
|
||||
# Count images for the current branch
|
||||
count = EquipmentImage.objects.filter(branch=obj.branch).count()
|
||||
self.message_user(request, f"【{obj.branch.name}】已经有{count}张设备间图片。")
|
||||
|
||||
admin.site.register(EquipmentImage, EquipmentImageAdmin)
|
||||
|
||||
class DrawingAdmin(admin.ModelAdmin):
|
||||
def bulk_delete_selected(self, request, queryset):
|
||||
count = queryset.count()
|
||||
queryset.delete()
|
||||
self.message_user(request, f'已成功删除{count}条图纸记录')
|
||||
bulk_delete_selected.short_description = '批量删除选中的图纸'
|
||||
actions = ['bulk_delete_selected']
|
||||
list_display = ('id', 'branch', 'uploaded_at', 'delete_link')
|
||||
|
||||
def delete_link(self, obj):
|
||||
url = reverse('admin:huodong_drawing_delete', args=[obj.id])
|
||||
return format_html('<a class="deletelink" href="{}">删除</a>', url)
|
||||
delete_link.short_description = '操作'
|
||||
list_filter = ('branch', 'uploaded_at')
|
||||
search_fields = ('branch__name',)
|
||||
autocomplete_fields = ['branch']
|
||||
|
||||
admin.site.register(Drawing, DrawingAdmin)
|
||||
|
||||
class PublicScreenAdmin(ModelAdmin):
|
||||
def delete_link(self, obj):
|
||||
if obj.id:
|
||||
delete_url = reverse('admin:huodong_publicscreen_delete', args=[obj.id])
|
||||
return format_html('<a href="{}" class="text-red-600 hover:text-red-900">删除</a>', delete_url)
|
||||
return '-'
|
||||
delete_link.short_description = '操作'
|
||||
|
||||
def bulk_delete_selected(self, request, queryset):
|
||||
count = queryset.count()
|
||||
queryset.delete()
|
||||
self.message_user(request, f'已成功删除{count}条公共电子屏记录')
|
||||
bulk_delete_selected.short_description = '批量删除选中的公共电子屏'
|
||||
|
||||
list_display = ('id', 'branch', 'screen_type', 'last_drill', 'delete_link')
|
||||
list_filter = ['branch', 'screen_type']
|
||||
autocomplete_fields = ['branch']
|
||||
actions = ['bulk_delete_selected']
|
||||
|
||||
admin.site.register(PublicScreen, PublicScreenAdmin)
|
||||
@admin.register(Contact)
|
||||
class ContactAdmin(ModelAdmin):
|
||||
form = ContactAdminForm # 指定自定义表单
|
||||
list_display = ('branch', 'category', 'name', 'phone', 'email') # 可选:显示分类字段
|
||||
|
||||
autocomplete_fields = ['branch']
|
||||
|
||||
@admin.register(VideoTerminal)
|
||||
class VideoTerminalAdmin(ModelAdmin):
|
||||
list_display = ('branch', 'terminal_type', 'description', 'created_at')
|
||||
list_filter = ('terminal_type', 'branch')
|
||||
search_fields = ('branch__name', 'description')
|
||||
autocomplete_fields = ['branch']
|
||||
|
||||
|
||||
# 预算相关Admin配置
|
||||
|
||||
# 设备预算内联
|
||||
class EquipmentBudgetInline(admin.TabularInline):
|
||||
model = EquipmentBudget
|
||||
extra = 1
|
||||
fields = ('project', 'model', 'unit_price', 'procurement_method', 'quantity', 'subtotal')
|
||||
readonly_fields = ('subtotal',) # 小计自动计算,设为只读
|
||||
|
||||
# 基础设施预算内联
|
||||
class InfrastructureBudgetInline(admin.TabularInline):
|
||||
model = InfrastructureBudget
|
||||
extra = 1
|
||||
fields = ('name', 'remarks', 'unit_price', 'unit', 'quantity', 'subtotal', 'description')
|
||||
readonly_fields = ('subtotal',) # 小计自动计算,设为只读
|
||||
|
||||
# 预算Admin
|
||||
@admin.register(Budget)
|
||||
class BudgetAdmin(ModelAdmin):
|
||||
list_display = ('branch', 'name', 'total_budget', 'created_at')
|
||||
list_filter = ('branch',)
|
||||
search_fields = ('branch__name', 'name')
|
||||
autocomplete_fields = ['branch']
|
||||
readonly_fields = ('total_budget',) # 总预算自动计算,设为只读
|
||||
inlines = [EquipmentBudgetInline, InfrastructureBudgetInline]
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
super().save_model(request, obj, form, change)
|
||||
# 保存后重新计算总预算
|
||||
obj.update_total_budget()
|
||||
|
||||
# 设备预算Admin(如果需要单独管理)
|
||||
@admin.register(EquipmentBudget)
|
||||
class EquipmentBudgetAdmin(ModelAdmin):
|
||||
list_display = ('budget', 'project', 'model', 'unit_price', 'procurement_method', 'quantity', 'subtotal')
|
||||
list_filter = ('budget__branch', 'project', 'procurement_method')
|
||||
search_fields = ('budget__branch__name', 'project', 'model')
|
||||
autocomplete_fields = ['budget']
|
||||
readonly_fields = ('subtotal',)
|
||||
|
||||
# 基础设施预算Admin(如果需要单独管理)
|
||||
@admin.register(InfrastructureBudget)
|
||||
class InfrastructureBudgetAdmin(ModelAdmin):
|
||||
list_display = ('budget', 'name', 'unit_price', 'unit', 'quantity', 'subtotal')
|
||||
list_filter = ('budget__branch', 'name')
|
||||
search_fields = ('budget__branch__name', 'name', 'description')
|
||||
autocomplete_fields = ['budget']
|
||||
readonly_fields = ('subtotal',)
|
||||
|
||||
|
||||
# 预算模板相关Admin配置
|
||||
|
||||
# 模板设备项内联
|
||||
class TemplateEquipmentItemInline(admin.TabularInline):
|
||||
model = TemplateEquipmentItem
|
||||
extra = 1
|
||||
|
||||
# 模板基础设施项内联
|
||||
class TemplateInfrastructureItemInline(admin.TabularInline):
|
||||
model = TemplateInfrastructureItem
|
||||
extra = 1
|
||||
|
||||
# 模板设备项Admin(如果需要单独管理)
|
||||
@admin.register(TemplateEquipmentItem)
|
||||
class TemplateEquipmentItemAdmin(ModelAdmin):
|
||||
list_display = ('template', 'project', 'model', 'unit_price', 'procurement_method')
|
||||
list_filter = ('template', 'project', 'procurement_method')
|
||||
search_fields = ('template__name', 'project', 'model')
|
||||
autocomplete_fields = ['template']
|
||||
|
||||
# 模板基础设施项Admin(如果需要单独管理)
|
||||
@admin.register(TemplateInfrastructureItem)
|
||||
class TemplateInfrastructureItemAdmin(ModelAdmin):
|
||||
list_display = ('template', 'name', 'unit_price', 'unit')
|
||||
list_filter = ('template', 'unit')
|
||||
search_fields = ('template__name', 'name', 'description')
|
||||
autocomplete_fields = ['template']
|
||||
|
||||
# 预算模板Admin
|
||||
@admin.register(BudgetTemplate)
|
||||
class BudgetTemplateAdmin(ModelAdmin):
|
||||
list_display = ('name', 'description', 'is_default', 'created_at')
|
||||
list_filter = ('is_default',)
|
||||
search_fields = ('name', 'description')
|
||||
inlines = [TemplateEquipmentItemInline, TemplateInfrastructureItemInline]
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
# 如果设置为默认模板,将其他模板的默认状态取消
|
||||
if obj.is_default:
|
||||
BudgetTemplate.objects.exclude(id=obj.id).update(is_default=False)
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# Generated by Django 5.0.6 on 2025-12-03 07:44
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('huodong', '0029_alter_activity_end_time_alter_activity_start_time_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Budget',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
('total_budget', models.DecimalField(decimal_places=2, default=0, max_digits=15, verbose_name='总预算')),
|
||||
('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='huodong.activity', verbose_name='活动')),
|
||||
('branch', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='huodong.branch', verbose_name='分支机构')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '预算主表',
|
||||
'verbose_name_plural': '预算主表',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EquipmentBudget',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('project', models.CharField(max_length=255, verbose_name='项目')),
|
||||
('model', models.CharField(max_length=255, verbose_name='型号')),
|
||||
('unit_price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='单价')),
|
||||
('procurement_method', models.CharField(choices=[('本地询价采购', '本地询价采购'), ('订单采购', '订单采购'), ('按照总部配置要求本地询价采购', '按照总部配置要求本地询价采购'), ('本地询价采购或订单采购', '本地询价采购或订单采购')], max_length=50, verbose_name='采购方式')),
|
||||
('quantity', models.IntegerField(default=1, verbose_name='数量')),
|
||||
('subtotal', models.DecimalField(decimal_places=2, default=0, max_digits=12, verbose_name='小计')),
|
||||
('budget', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='equipment_budgets', to='huodong.budget', verbose_name='预算主表')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '设备预算明细',
|
||||
'verbose_name_plural': '设备预算明细',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='InfrastructureBudget',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='名称')),
|
||||
('remarks', models.TextField(blank=True, verbose_name='备注')),
|
||||
('unit_price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='单价')),
|
||||
('unit', models.CharField(max_length=20, verbose_name='单位')),
|
||||
('description', models.TextField(blank=True, verbose_name='说明')),
|
||||
('quantity', models.IntegerField(default=1, verbose_name='数量')),
|
||||
('subtotal', models.DecimalField(decimal_places=2, default=0, max_digits=12, verbose_name='小计')),
|
||||
('budget', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='infrastructure_budgets', to='huodong.budget', verbose_name='预算主表')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '基础设施预算明细',
|
||||
'verbose_name_plural': '基础设施预算明细',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,60 @@
|
||||
# Generated by Django 5.0.6 on 2025-12-03 08:16
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('huodong', '0030_budget_equipmentbudget_infrastructurebudget'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='BudgetTemplate',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='模板名称')),
|
||||
('description', models.TextField(blank=True, verbose_name='模板描述')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
('is_default', models.BooleanField(default=False, verbose_name='是否默认模板')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '预算模板',
|
||||
'verbose_name_plural': '预算模板',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TemplateEquipmentItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('project', models.CharField(max_length=255, verbose_name='项目')),
|
||||
('model', models.CharField(max_length=255, verbose_name='型号')),
|
||||
('unit_price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='参考单价')),
|
||||
('procurement_method', models.CharField(choices=[('本地询价采购', '本地询价采购'), ('订单采购', '订单采购'), ('按照总部配置要求本地询价采购', '按照总部配置要求本地询价采购'), ('本地询价采购或订单采购', '本地询价采购或订单采购')], max_length=50, verbose_name='采购方式')),
|
||||
('template', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='equipment_items', to='huodong.budgettemplate', verbose_name='模板')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '模板设备项',
|
||||
'verbose_name_plural': '模板设备项',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TemplateInfrastructureItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='名称')),
|
||||
('remarks', models.TextField(blank=True, verbose_name='备注')),
|
||||
('unit_price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='参考单价')),
|
||||
('unit', models.CharField(max_length=20, verbose_name='单位')),
|
||||
('description', models.TextField(blank=True, verbose_name='说明')),
|
||||
('template', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='infrastructure_items', to='huodong.budgettemplate', verbose_name='模板')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '模板基础设施项',
|
||||
'verbose_name_plural': '模板基础设施项',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.0.6 on 2025-12-03 09:15
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('huodong', '0031_budgettemplate_templateequipmentitem_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='budget',
|
||||
name='activity',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='budget',
|
||||
name='name',
|
||||
field=models.CharField(default=1, max_length=255, verbose_name='预算名称'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,182 +1,327 @@
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
class Branch(models.Model):
|
||||
CATEGORY_CHOICES = (
|
||||
('A型', 'A型'),
|
||||
('B型', 'B型'),
|
||||
('C型', 'C型'),
|
||||
('不适用', '不适用'),
|
||||
)
|
||||
name = models.CharField(max_length=255, unique=True, verbose_name='分支机构名称')
|
||||
location = models.CharField(max_length=255, verbose_name='所在省份')
|
||||
contact_info = models.CharField(max_length=255, verbose_name='主要联系人')
|
||||
description = models.TextField(blank=True, verbose_name='备注')
|
||||
background_color = models.CharField(max_length=7, default='#EFF6FF', verbose_name='背景色',
|
||||
help_text='使用#RRGGBB格式的颜色代码')
|
||||
category = models.CharField(max_length=10, choices=CATEGORY_CHOICES, default='C型', verbose_name='分类')
|
||||
is_mature = models.BooleanField(default=False, verbose_name='是否成熟')
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.name} 💼' if self.is_mature else self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = '分支机构'
|
||||
verbose_name_plural = '分支机构(基础信息)'
|
||||
|
||||
|
||||
class Contact(models.Model):
|
||||
branch = models.ForeignKey(Branch, on_delete=models.CASCADE, verbose_name='分支机构')
|
||||
CATEGORY_CHOICES = [
|
||||
('机房/设备间巡检人', '机房/设备间巡检人'),
|
||||
('信息安全联系人', '信息安全联系人'),
|
||||
('兼岗', '兼岗'),
|
||||
('安全员', '安全员')
|
||||
# 可以添加更多类别
|
||||
]
|
||||
# 修改为支持多选的 CharField
|
||||
category = models.CharField(
|
||||
max_length=255, # 增大长度(原50可能不足)
|
||||
choices=CATEGORY_CHOICES,
|
||||
verbose_name='联系人分类',
|
||||
help_text='按住 Ctrl/Command 键多选(值将以逗号分隔存储)'
|
||||
)
|
||||
name = models.CharField(max_length=255, verbose_name='姓名')
|
||||
phone = models.CharField(max_length=20, verbose_name='电话')
|
||||
email = models.EmailField(blank=True, verbose_name='邮箱,可不填')
|
||||
description = models.TextField(blank=True, verbose_name='描述,可不填')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = '联系人群'
|
||||
verbose_name_plural = '联系人群'
|
||||
|
||||
|
||||
class Activity(models.Model):
|
||||
branch = models.ForeignKey(Branch, on_delete=models.CASCADE, verbose_name='分支机构')
|
||||
name = models.CharField(max_length=255, verbose_name='活动名称')
|
||||
scope = models.CharField(max_length=255, choices=(
|
||||
('新建', '新建'),
|
||||
('搬迁', '搬迁'),
|
||||
('原址装修', '原址装修'),
|
||||
('撤销', '撤销'),
|
||||
('其他技术问题', '其他技术问题')
|
||||
|
||||
), verbose_name='活动类型')
|
||||
start_time = models.DateField(verbose_name='开始日期')
|
||||
end_time = models.DateField(blank=True, null=True, verbose_name='结束日期') # 可以为空,表示活动尚未结束
|
||||
location = models.CharField(max_length=255, verbose_name='所在地点')
|
||||
description = models.TextField(verbose_name='其它内容')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = '运营活动内容'
|
||||
verbose_name_plural = '运营活动内容(新建搬迁装修和技术)'
|
||||
|
||||
|
||||
class EquipmentImage(models.Model):
|
||||
branch = models.ForeignKey(Branch, related_name='equipment_images', on_delete=models.CASCADE)
|
||||
image = models.ImageField(upload_to='equipment_room_images/')
|
||||
uploaded_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"设备间图片 {self.id} - {self.branch.name}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = '设备间图'
|
||||
verbose_name_plural = '设备间图'
|
||||
|
||||
|
||||
# 图纸的类
|
||||
class Drawing(models.Model):
|
||||
branch = models.ForeignKey(Branch, related_name='drawings', on_delete=models.CASCADE)
|
||||
image = models.ImageField(upload_to='drawings/')
|
||||
uploaded_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"图纸 {self.id} - {self.branch.name}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = '图纸'
|
||||
verbose_name_plural = '图纸'
|
||||
|
||||
|
||||
# 公共电子屏
|
||||
class PublicScreen(models.Model):
|
||||
SCREEN_TYPES = (
|
||||
('marquee', '跑马灯'),
|
||||
('advertisement', '广告屏'),
|
||||
('information', '信息发布屏'),
|
||||
)
|
||||
branch = models.ForeignKey(Branch, on_delete=models.CASCADE, related_name='public_screens')
|
||||
image = models.ImageField(upload_to='public_screen_images/', null=True, blank=True)
|
||||
screen_type = models.CharField(max_length=20, choices=SCREEN_TYPES, verbose_name='功能类型', null=True, blank=True)
|
||||
description = models.TextField(blank=True, null=True, verbose_name='功能描述')
|
||||
last_drill = models.ForeignKey('Event', on_delete=models.SET_NULL, blank=True, null=True, related_name='public_screens', verbose_name='最后演练事件')
|
||||
created_at = models.DateTimeField(default=timezone.now)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.branch.name} - {self.get_screen_type_display()} {self.id}'
|
||||
|
||||
class Meta:
|
||||
verbose_name = '公共电子屏'
|
||||
verbose_name_plural = '公共电子屏'
|
||||
|
||||
|
||||
class Event(models.Model):
|
||||
branches = models.ManyToManyField(Branch, related_name='events', verbose_name='分支机构')
|
||||
name = models.CharField(max_length=255, verbose_name='事件名称')
|
||||
start_time = models.DateField(verbose_name='开始时间')
|
||||
end_time = models.DateField(blank=True, null=True, verbose_name='结束时间') # 可以为空,表示活动尚未结束
|
||||
description = models.TextField(verbose_name='事件描述')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = '运营事件'
|
||||
verbose_name_plural = '运营事件(其它)'
|
||||
|
||||
|
||||
class VideoTerminal(models.Model):
|
||||
TERMINAL_TYPES = (
|
||||
('polycom', '宝利通终端'),
|
||||
('zte', '中兴终端'),
|
||||
('logitech', '罗技摄像头'),
|
||||
('laptop_tv', '笔记本加电视'),
|
||||
('laptop_projector', '笔记本加投影仪'),
|
||||
('other', '其它'),
|
||||
)
|
||||
branch = models.ForeignKey(Branch, on_delete=models.CASCADE, related_name='video_terminals', verbose_name='分支机构')
|
||||
terminal_type = models.CharField(max_length=20, choices=TERMINAL_TYPES, verbose_name='设备类型')
|
||||
description = models.TextField(blank=True, verbose_name='设备描述')
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.branch.name} - {self.get_terminal_type_display()}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = '视频设备终端'
|
||||
verbose_name_plural = '视频设备终端'
|
||||
|
||||
|
||||
class Evaluation(models.Model):
|
||||
activity = models.ForeignKey(Activity, on_delete=models.CASCADE)
|
||||
branch = models.ForeignKey(Branch, on_delete=models.CASCADE)
|
||||
score = models.DecimalField(max_digits=4, decimal_places=2)
|
||||
comment = models.TextField()
|
||||
file_path = models.CharField(max_length=255, blank=True, null=True)
|
||||
status = models.CharField(max_length=20,
|
||||
choices=(('pending', '待审核'), ('approved', '已通过'), ('rejected', '已拒绝')),
|
||||
default='pending')
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.activity.name} - {self.branch.name}"
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
class Branch(models.Model):
|
||||
CATEGORY_CHOICES = (
|
||||
('A型', 'A型'),
|
||||
('B型', 'B型'),
|
||||
('C型', 'C型'),
|
||||
('不适用', '不适用'),
|
||||
)
|
||||
name = models.CharField(max_length=255, unique=True, verbose_name='分支机构名称')
|
||||
location = models.CharField(max_length=255, verbose_name='所在省份')
|
||||
contact_info = models.CharField(max_length=255, verbose_name='主要联系人')
|
||||
description = models.TextField(blank=True, verbose_name='备注')
|
||||
background_color = models.CharField(max_length=7, default='#EFF6FF', verbose_name='背景色',
|
||||
help_text='使用#RRGGBB格式的颜色代码')
|
||||
category = models.CharField(max_length=10, choices=CATEGORY_CHOICES, default='C型', verbose_name='分类')
|
||||
is_mature = models.BooleanField(default=False, verbose_name='是否成熟')
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.name} 💼' if self.is_mature else self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = '分支机构'
|
||||
verbose_name_plural = '分支机构(基础信息)'
|
||||
|
||||
|
||||
class Contact(models.Model):
|
||||
branch = models.ForeignKey(Branch, on_delete=models.CASCADE, verbose_name='分支机构')
|
||||
CATEGORY_CHOICES = [
|
||||
('机房/设备间巡检人', '机房/设备间巡检人'),
|
||||
('信息安全联系人', '信息安全联系人'),
|
||||
('兼岗', '兼岗'),
|
||||
('安全员', '安全员')
|
||||
# 可以添加更多类别
|
||||
]
|
||||
# 修改为支持多选的 CharField
|
||||
category = models.CharField(
|
||||
max_length=255, # 增大长度(原50可能不足)
|
||||
choices=CATEGORY_CHOICES,
|
||||
verbose_name='联系人分类',
|
||||
help_text='按住 Ctrl/Command 键多选(值将以逗号分隔存储)'
|
||||
)
|
||||
name = models.CharField(max_length=255, verbose_name='姓名')
|
||||
phone = models.CharField(max_length=20, verbose_name='电话')
|
||||
email = models.EmailField(blank=True, verbose_name='邮箱,可不填')
|
||||
description = models.TextField(blank=True, verbose_name='描述,可不填')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = '联系人群'
|
||||
verbose_name_plural = '联系人群'
|
||||
|
||||
|
||||
class Activity(models.Model):
|
||||
branch = models.ForeignKey(Branch, on_delete=models.CASCADE, verbose_name='分支机构')
|
||||
name = models.CharField(max_length=255, verbose_name='活动名称')
|
||||
scope = models.CharField(max_length=255, choices=(
|
||||
('新建', '新建'),
|
||||
('搬迁', '搬迁'),
|
||||
('原址装修', '原址装修'),
|
||||
('撤销', '撤销'),
|
||||
('其他技术问题', '其他技术问题')
|
||||
|
||||
), verbose_name='活动类型')
|
||||
start_time = models.DateField(verbose_name='开始日期')
|
||||
end_time = models.DateField(blank=True, null=True, verbose_name='结束日期') # 可以为空,表示活动尚未结束
|
||||
location = models.CharField(max_length=255, verbose_name='所在地点')
|
||||
description = models.TextField(verbose_name='其它内容')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = '运营活动内容'
|
||||
verbose_name_plural = '运营活动内容(新建搬迁装修和技术)'
|
||||
|
||||
|
||||
class EquipmentImage(models.Model):
|
||||
branch = models.ForeignKey(Branch, related_name='equipment_images', on_delete=models.CASCADE)
|
||||
image = models.ImageField(upload_to='equipment_room_images/')
|
||||
uploaded_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"设备间图片 {self.id} - {self.branch.name}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = '设备间图'
|
||||
verbose_name_plural = '设备间图'
|
||||
|
||||
|
||||
# 图纸的类
|
||||
class Drawing(models.Model):
|
||||
branch = models.ForeignKey(Branch, related_name='drawings', on_delete=models.CASCADE)
|
||||
image = models.ImageField(upload_to='drawings/')
|
||||
uploaded_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"图纸 {self.id} - {self.branch.name}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = '图纸'
|
||||
verbose_name_plural = '图纸'
|
||||
|
||||
|
||||
# 公共电子屏
|
||||
class PublicScreen(models.Model):
|
||||
SCREEN_TYPES = (
|
||||
('marquee', '跑马灯'),
|
||||
('advertisement', '广告屏'),
|
||||
('information', '信息发布屏'),
|
||||
)
|
||||
branch = models.ForeignKey(Branch, on_delete=models.CASCADE, related_name='public_screens')
|
||||
image = models.ImageField(upload_to='public_screen_images/', null=True, blank=True)
|
||||
screen_type = models.CharField(max_length=20, choices=SCREEN_TYPES, verbose_name='功能类型', null=True, blank=True)
|
||||
description = models.TextField(blank=True, null=True, verbose_name='功能描述')
|
||||
last_drill = models.ForeignKey('Event', on_delete=models.SET_NULL, blank=True, null=True, related_name='public_screens', verbose_name='最后演练事件')
|
||||
created_at = models.DateTimeField(default=timezone.now)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.branch.name} - {self.get_screen_type_display()} {self.id}'
|
||||
|
||||
class Meta:
|
||||
verbose_name = '公共电子屏'
|
||||
verbose_name_plural = '公共电子屏'
|
||||
|
||||
|
||||
class Event(models.Model):
|
||||
branches = models.ManyToManyField(Branch, related_name='events', verbose_name='分支机构')
|
||||
name = models.CharField(max_length=255, verbose_name='事件名称')
|
||||
start_time = models.DateField(verbose_name='开始时间')
|
||||
end_time = models.DateField(blank=True, null=True, verbose_name='结束时间') # 可以为空,表示活动尚未结束
|
||||
description = models.TextField(verbose_name='事件描述')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = '运营事件'
|
||||
verbose_name_plural = '运营事件(其它)'
|
||||
|
||||
|
||||
class VideoTerminal(models.Model):
|
||||
TERMINAL_TYPES = (
|
||||
('polycom', '宝利通终端'),
|
||||
('zte', '中兴终端'),
|
||||
('logitech', '罗技摄像头'),
|
||||
('laptop_tv', '笔记本加电视'),
|
||||
('laptop_projector', '笔记本加投影仪'),
|
||||
('other', '其它'),
|
||||
)
|
||||
branch = models.ForeignKey(Branch, on_delete=models.CASCADE, related_name='video_terminals', verbose_name='分支机构')
|
||||
terminal_type = models.CharField(max_length=20, choices=TERMINAL_TYPES, verbose_name='设备类型')
|
||||
description = models.TextField(blank=True, verbose_name='设备描述')
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.branch.name} - {self.get_terminal_type_display()}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = '视频设备终端'
|
||||
verbose_name_plural = '视频设备终端'
|
||||
|
||||
|
||||
class Evaluation(models.Model):
|
||||
activity = models.ForeignKey(Activity, on_delete=models.CASCADE)
|
||||
branch = models.ForeignKey(Branch, on_delete=models.CASCADE)
|
||||
score = models.DecimalField(max_digits=4, decimal_places=2)
|
||||
comment = models.TextField()
|
||||
file_path = models.CharField(max_length=255, blank=True, null=True)
|
||||
status = models.CharField(max_length=20,
|
||||
choices=(('pending', '待审核'), ('approved', '已通过'), ('rejected', '已拒绝')),
|
||||
default='pending')
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.activity.name} - {self.branch.name}"
|
||||
|
||||
|
||||
# 预算相关模型
|
||||
class Budget(models.Model):
|
||||
"""预算主表"""
|
||||
branch = models.ForeignKey(Branch, on_delete=models.CASCADE, verbose_name='分支机构')
|
||||
name = models.CharField(max_length=255, verbose_name='预算名称')
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||
total_budget = models.DecimalField(max_digits=15, decimal_places=2, default=0, verbose_name='总预算')
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.branch.name} - {self.name} 预算"
|
||||
|
||||
class Meta:
|
||||
verbose_name = '预算主表'
|
||||
verbose_name_plural = '预算主表'
|
||||
|
||||
|
||||
class EquipmentBudget(models.Model):
|
||||
"""设备预算明细"""
|
||||
BUDGET_TYPES = (
|
||||
('本地询价采购', '本地询价采购'),
|
||||
('订单采购', '订单采购'),
|
||||
('按照总部配置要求本地询价采购', '按照总部配置要求本地询价采购'),
|
||||
('本地询价采购或订单采购', '本地询价采购或订单采购')
|
||||
)
|
||||
budget = models.ForeignKey(Budget, related_name='equipment_budgets', on_delete=models.CASCADE, verbose_name='预算主表')
|
||||
project = models.CharField(max_length=255, verbose_name='项目')
|
||||
model = models.CharField(max_length=255, verbose_name='型号')
|
||||
unit_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='单价')
|
||||
procurement_method = models.CharField(max_length=50, choices=BUDGET_TYPES, verbose_name='采购方式')
|
||||
quantity = models.IntegerField(default=1, verbose_name='数量')
|
||||
subtotal = models.DecimalField(max_digits=12, decimal_places=2, default=0, verbose_name='小计')
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.subtotal = self.unit_price * self.quantity
|
||||
super().save(*args, **kwargs)
|
||||
# 更新预算主表的总预算
|
||||
self.update_total_budget()
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
super().delete(*args, **kwargs)
|
||||
# 更新预算主表的总预算
|
||||
self.update_total_budget()
|
||||
|
||||
def update_total_budget(self):
|
||||
"""更新预算主表的总预算"""
|
||||
total = self.budget.equipment_budgets.aggregate(total=models.Sum('subtotal'))['total'] or 0
|
||||
total += self.budget.infrastructure_budgets.aggregate(total=models.Sum('subtotal'))['total'] or 0
|
||||
self.budget.total_budget = total
|
||||
self.budget.save()
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.project} - {self.model}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = '设备预算明细'
|
||||
verbose_name_plural = '设备预算明细'
|
||||
|
||||
|
||||
class InfrastructureBudget(models.Model):
|
||||
"""基础设施预算明细"""
|
||||
budget = models.ForeignKey(Budget, related_name='infrastructure_budgets', on_delete=models.CASCADE, verbose_name='预算主表')
|
||||
name = models.CharField(max_length=255, verbose_name='名称')
|
||||
remarks = models.TextField(blank=True, verbose_name='备注')
|
||||
unit_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='单价')
|
||||
unit = models.CharField(max_length=20, verbose_name='单位')
|
||||
description = models.TextField(blank=True, verbose_name='说明')
|
||||
quantity = models.IntegerField(default=1, verbose_name='数量')
|
||||
subtotal = models.DecimalField(max_digits=12, decimal_places=2, default=0, verbose_name='小计')
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.subtotal = self.unit_price * self.quantity
|
||||
super().save(*args, **kwargs)
|
||||
# 更新预算主表的总预算
|
||||
self.update_total_budget()
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
super().delete(*args, **kwargs)
|
||||
# 更新预算主表的总预算
|
||||
self.update_total_budget()
|
||||
|
||||
def update_total_budget(self):
|
||||
"""更新预算主表的总预算"""
|
||||
total = self.budget.equipment_budgets.aggregate(total=models.Sum('subtotal'))['total'] or 0
|
||||
total += self.budget.infrastructure_budgets.aggregate(total=models.Sum('subtotal'))['total'] or 0
|
||||
self.budget.total_budget = total
|
||||
self.budget.save()
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = '基础设施预算明细'
|
||||
verbose_name_plural = '基础设施预算明细'
|
||||
|
||||
|
||||
class BudgetTemplate(models.Model):
|
||||
"""预算模板"""
|
||||
name = models.CharField(max_length=255, verbose_name='模板名称')
|
||||
description = models.TextField(blank=True, verbose_name='模板描述')
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||
is_default = models.BooleanField(default=False, verbose_name='是否默认模板')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = '预算模板'
|
||||
verbose_name_plural = '预算模板'
|
||||
|
||||
|
||||
class TemplateEquipmentItem(models.Model):
|
||||
"""模板设备项"""
|
||||
template = models.ForeignKey(BudgetTemplate, related_name='equipment_items', on_delete=models.CASCADE, verbose_name='模板')
|
||||
project = models.CharField(max_length=255, verbose_name='项目')
|
||||
model = models.CharField(max_length=255, verbose_name='型号')
|
||||
unit_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='参考单价')
|
||||
procurement_method = models.CharField(max_length=50, choices=EquipmentBudget.BUDGET_TYPES, verbose_name='采购方式')
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.project} - {self.model}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = '模板设备项'
|
||||
verbose_name_plural = '模板设备项'
|
||||
|
||||
|
||||
class TemplateInfrastructureItem(models.Model):
|
||||
"""模板基础设施项"""
|
||||
template = models.ForeignKey(BudgetTemplate, related_name='infrastructure_items', on_delete=models.CASCADE, verbose_name='模板')
|
||||
name = models.CharField(max_length=255, verbose_name='名称')
|
||||
remarks = models.TextField(blank=True, verbose_name='备注')
|
||||
unit_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='参考单价')
|
||||
unit = models.CharField(max_length=20, verbose_name='单位')
|
||||
description = models.TextField(blank=True, verbose_name='说明')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = '模板基础设施项'
|
||||
verbose_name_plural = '模板基础设施项'
|
||||
|
||||
@@ -11,6 +11,125 @@
|
||||
<span class="flex-grow block border-t border-black" aria-hidden="true" role="presentation"></span>
|
||||
</h2>
|
||||
<!-- Component ends here -->
|
||||
|
||||
<!-- 投入预算表 -->
|
||||
<div class="bg-white rounded-lg shadow-md p-4 mb-6">
|
||||
<h2 class="text-2xl font-bold mb-4">投入预算表</h2>
|
||||
<p class="text-gray-700 mb-4">分支机构项目的预算表,包括设备和基础设施明细</p>
|
||||
|
||||
<!-- 预算模板导入功能 -->
|
||||
{% if budget_templates %} <!-- 只有当有模板时才显示导入功能 -->
|
||||
<div class="mb-6 p-4 bg-blue-50 rounded-lg">
|
||||
<h3 class="text-lg font-semibold mb-2">预算模板导入</h3>
|
||||
<p class="text-sm text-gray-600 mb-3">从模板一键导入预算,快速生成预算表</p>
|
||||
|
||||
<form method="POST" action="{% url 'import-budget-template' branch.id %}">
|
||||
{% csrf_token %}
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label for="template" class="block text-sm font-medium text-gray-700 mb-1">选择模板</label>
|
||||
<select id="template" name="template" class="w-full p-2 border border-gray-300 rounded-md">
|
||||
{% for template in budget_templates %}
|
||||
<option value="{{ template.id }}">{{ template.name }}{% if template.is_default %} (默认){% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="budget_name" class="block text-sm font-medium text-gray-700 mb-1">预算名称</label>
|
||||
<input type="text" id="budget_name" name="budget_name" placeholder="请输入预算名称" class="w-full p-2 border border-gray-300 rounded-md">
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<button type="submit" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md">
|
||||
一键导入
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if budgets %}
|
||||
{% for budget in budgets %}
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg font-semibold mb-3">{{ budget.activity.name }} - 总预算: ¥{{ budget.total_budget }}</h3>
|
||||
|
||||
<!-- 设备预算部分 -->
|
||||
<div class="mb-4">
|
||||
<h4 class="text-md font-semibold mb-2">设备预算明细</h4>
|
||||
{% if budget.equipment_budgets.all %}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">项目</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">型号</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">单价</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">采购方式</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">数量</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">小计</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
{% for equipment in budget.equipment_budgets.all %}
|
||||
<tr>
|
||||
<td class="px-6 py-4 whitespace-nowrap">{{ equipment.project }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">{{ equipment.model }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">¥{{ equipment.unit_price }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">{{ equipment.get_procurement_method_display }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">{{ equipment.quantity }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">¥{{ equipment.subtotal }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-gray-500 italic">暂无设备预算明细</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- 基础设施预算部分 -->
|
||||
<div>
|
||||
<h4 class="text-md font-semibold mb-2">基础设施预算明细</h4>
|
||||
{% if budget.infrastructure_budgets.all %}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">名称</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">备注</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">单价</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">单位</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">数量</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">小计</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">说明</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
{% for infrastructure in budget.infrastructure_budgets.all %}
|
||||
<tr>
|
||||
<td class="px-6 py-4 whitespace-nowrap">{{ infrastructure.name }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">{{ infrastructure.remarks }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">¥{{ infrastructure.unit_price }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">{{ infrastructure.unit }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">{{ infrastructure.quantity }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">¥{{ infrastructure.subtotal }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">{{ infrastructure.description }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-gray-500 italic">暂无基础设施预算明细</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p class="text-gray-500 italic">暂无预算信息</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow-md p-4 mb-6">
|
||||
<h2 class="text-2xl font-bold mb-4">基本信息</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from . import views
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'branches', views.BranchViewSet, basename='branches')
|
||||
router.register(r'activities', views.ActivityViewSet, basename='activities')
|
||||
router.register(r'evaluations', views.EvaluationViewSet, basename='evaluations')
|
||||
|
||||
urlpatterns = [
|
||||
path('api/', include(router.urls)),
|
||||
path('', views.BranchAll, name='branch-all'),
|
||||
path('branch/<int:branch_id>/', views.branch_detail, name='branch-detail'),
|
||||
path('branch/info/', views.Branchinfo, name='branchinfo'),
|
||||
path('statistics/', views.Statistics, name='statistics'),
|
||||
path('contact/', views.contact_list, name='contact-list'),
|
||||
path('equipment-images/', views.equipment_images, name='equipment-images'),
|
||||
path('public-screens/', views.public_screens, name='public-screens'),
|
||||
path('video-terminals/', views.video_terminal_list, name='video-terminals'),
|
||||
path('export/xls/', views.export_branches_xls, name='export-branches-xls'),
|
||||
path('export/pdf/', views.export_branches_pdf, name='export-branches-pdf'),
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from . import views
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'branches', views.BranchViewSet, basename='branches')
|
||||
router.register(r'activities', views.ActivityViewSet, basename='activities')
|
||||
router.register(r'evaluations', views.EvaluationViewSet, basename='evaluations')
|
||||
|
||||
urlpatterns = [
|
||||
path('api/', include(router.urls)),
|
||||
path('', views.BranchAll, name='branch-all'),
|
||||
path('branch/<int:branch_id>/', views.branch_detail, name='branch-detail'),
|
||||
path('branch/<int:branch_id>/import-budget/', views.import_budget_template, name='import-budget-template'),
|
||||
path('branch/info/', views.Branchinfo, name='branchinfo'),
|
||||
path('statistics/', views.Statistics, name='statistics'),
|
||||
path('contact/', views.contact_list, name='contact-list'),
|
||||
path('equipment-images/', views.equipment_images, name='equipment-images'),
|
||||
path('public-screens/', views.public_screens, name='public-screens'),
|
||||
path('video-terminals/', views.video_terminal_list, name='video-terminals'),
|
||||
path('export/xls/', views.export_branches_xls, name='export-branches-xls'),
|
||||
path('export/pdf/', views.export_branches_pdf, name='export-branches-pdf'),
|
||||
]
|
||||
@@ -1,5 +1,5 @@
|
||||
from rest_framework import viewsets
|
||||
from .models import Branch, Activity, Evaluation, Event, VideoTerminal
|
||||
from .models import Branch, Activity, Evaluation, Event, VideoTerminal, Budget, EquipmentBudget, InfrastructureBudget, BudgetTemplate, TemplateEquipmentItem, TemplateInfrastructureItem
|
||||
from .serializers import BranchSerializer, ActivitySerializer, EvaluationSerializer
|
||||
from django.shortcuts import render, redirect
|
||||
from .models import PublicScreen
|
||||
@@ -55,6 +55,12 @@ def branch_detail(request, branch_id):
|
||||
equipment_images = branch.equipment_images.all()
|
||||
public_screens = branch.public_screens.all()
|
||||
|
||||
# 获取预算数据
|
||||
budgets = Budget.objects.filter(branch=branch).select_related('activity').prefetch_related('equipment_budgets', 'infrastructure_budgets').order_by('-created_at')
|
||||
|
||||
# 获取预算模板
|
||||
budget_templates = BudgetTemplate.objects.all()
|
||||
|
||||
# 准备上下文数据
|
||||
context = {
|
||||
'branch': branch,
|
||||
@@ -62,10 +68,62 @@ def branch_detail(request, branch_id):
|
||||
'events': events,
|
||||
'equipment_images': equipment_images,
|
||||
'public_screens': public_screens,
|
||||
'budgets': budgets,
|
||||
'activities': activities,
|
||||
'budget_templates': budget_templates,
|
||||
}
|
||||
return render(request, 'branch_detail.html', context)
|
||||
|
||||
|
||||
def import_budget_template(request, branch_id):
|
||||
"""从模板导入预算"""
|
||||
if request.method == 'POST':
|
||||
template_id = request.POST.get('template')
|
||||
budget_name = request.POST.get('budget_name', '导入预算')
|
||||
|
||||
if template_id:
|
||||
try:
|
||||
# 获取模板和分支机构
|
||||
template = BudgetTemplate.objects.get(pk=template_id)
|
||||
branch = Branch.objects.get(pk=branch_id)
|
||||
|
||||
# 创建预算
|
||||
budget = Budget.objects.create(
|
||||
branch=branch,
|
||||
name=budget_name
|
||||
)
|
||||
|
||||
# 导入设备预算项
|
||||
for equipment_item in template.equipment_items.all():
|
||||
EquipmentBudget.objects.create(
|
||||
budget=budget,
|
||||
project=equipment_item.project,
|
||||
model=equipment_item.model,
|
||||
unit_price=equipment_item.unit_price,
|
||||
procurement_method=equipment_item.procurement_method,
|
||||
quantity=1 # 默认数量为1
|
||||
)
|
||||
|
||||
# 导入基础设施预算项
|
||||
for infrastructure_item in template.infrastructure_items.all():
|
||||
InfrastructureBudget.objects.create(
|
||||
budget=budget,
|
||||
name=infrastructure_item.name,
|
||||
remarks=infrastructure_item.remarks,
|
||||
unit_price=infrastructure_item.unit_price,
|
||||
unit=infrastructure_item.unit,
|
||||
description=infrastructure_item.description,
|
||||
quantity=1 # 默认数量为1
|
||||
)
|
||||
|
||||
# 更新总预算
|
||||
budget.update_total_budget()
|
||||
except Exception as e:
|
||||
print(f"导入预算模板失败: {e}")
|
||||
|
||||
return redirect('branch-detail', branch_id=branch_id)
|
||||
|
||||
|
||||
# 在页面上显示所有的branch以及active的数量,首页显示
|
||||
def BranchAll(request):
|
||||
branches = Branch.objects.exclude(activity__isnull=True).order_by('name')
|
||||
@@ -442,3 +500,100 @@ def video_terminal_list(request):
|
||||
'selected_type_name': selected_type_name,
|
||||
}
|
||||
return render(request, 'video_terminals.html', context)
|
||||
|
||||
|
||||
# 预算相关视图
|
||||
|
||||
def create_budget(request, branch_id):
|
||||
branch = Branch.objects.get(pk=branch_id)
|
||||
activities = Activity.objects.filter(branch=branch)
|
||||
|
||||
if request.method == 'POST':
|
||||
activity_id = request.POST.get('activity')
|
||||
if activity_id:
|
||||
activity = Activity.objects.get(pk=activity_id)
|
||||
budget = Budget.objects.create(branch=branch, activity=activity)
|
||||
return redirect('budget_detail', branch_id=branch_id, budget_id=budget.id)
|
||||
|
||||
context = {
|
||||
'branch': branch,
|
||||
'activities': activities,
|
||||
}
|
||||
return render(request, 'create_budget.html', context)
|
||||
|
||||
|
||||
def budget_detail(request, branch_id, budget_id):
|
||||
branch = Branch.objects.get(pk=branch_id)
|
||||
budget = Budget.objects.get(pk=budget_id, branch=branch)
|
||||
|
||||
context = {
|
||||
'branch': branch,
|
||||
'budget': budget,
|
||||
}
|
||||
return render(request, 'budget_detail.html', context)
|
||||
|
||||
|
||||
def add_equipment_budget(request, branch_id, budget_id):
|
||||
branch = Branch.objects.get(pk=branch_id)
|
||||
budget = Budget.objects.get(pk=budget_id, branch=branch)
|
||||
|
||||
if request.method == 'POST':
|
||||
project = request.POST.get('project')
|
||||
model = request.POST.get('model')
|
||||
unit_price = request.POST.get('unit_price')
|
||||
procurement_method = request.POST.get('procurement_method')
|
||||
quantity = request.POST.get('quantity', 1)
|
||||
|
||||
if project and model and unit_price and procurement_method:
|
||||
EquipmentBudget.objects.create(
|
||||
budget=budget,
|
||||
project=project,
|
||||
model=model,
|
||||
unit_price=unit_price,
|
||||
procurement_method=procurement_method,
|
||||
quantity=quantity
|
||||
)
|
||||
|
||||
return redirect('budget_detail', branch_id=branch_id, budget_id=budget_id)
|
||||
|
||||
|
||||
def add_infrastructure_budget(request, branch_id, budget_id):
|
||||
branch = Branch.objects.get(pk=branch_id)
|
||||
budget = Budget.objects.get(pk=budget_id, branch=branch)
|
||||
|
||||
if request.method == 'POST':
|
||||
name = request.POST.get('name')
|
||||
remarks = request.POST.get('remarks')
|
||||
unit_price = request.POST.get('unit_price')
|
||||
unit = request.POST.get('unit')
|
||||
description = request.POST.get('description')
|
||||
quantity = request.POST.get('quantity', 1)
|
||||
|
||||
if name and unit_price and unit:
|
||||
InfrastructureBudget.objects.create(
|
||||
budget=budget,
|
||||
name=name,
|
||||
remarks=remarks,
|
||||
unit_price=unit_price,
|
||||
unit=unit,
|
||||
description=description,
|
||||
quantity=quantity
|
||||
)
|
||||
|
||||
return redirect('budget_detail', branch_id=branch_id, budget_id=budget_id)
|
||||
|
||||
|
||||
def delete_equipment_budget(request, branch_id, budget_id, equipment_budget_id):
|
||||
branch = Branch.objects.get(pk=branch_id)
|
||||
budget = Budget.objects.get(pk=budget_id, branch=branch)
|
||||
equipment_budget = EquipmentBudget.objects.get(pk=equipment_budget_id, budget=budget)
|
||||
equipment_budget.delete()
|
||||
return redirect('budget_detail', branch_id=branch_id, budget_id=budget_id)
|
||||
|
||||
|
||||
def delete_infrastructure_budget(request, branch_id, budget_id, infrastructure_budget_id):
|
||||
branch = Branch.objects.get(pk=branch_id)
|
||||
budget = Budget.objects.get(pk=budget_id, branch=branch)
|
||||
infrastructure_budget = InfrastructureBudget.objects.get(pk=infrastructure_budget_id, budget=budget)
|
||||
infrastructure_budget.delete()
|
||||
return redirect('budget_detail', branch_id=branch_id, budget_id=budget_id)
|
||||
|
||||
Reference in New Issue
Block a user