增加预算表的显示功能
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.
@@ -3,7 +3,7 @@ from django.contrib.admin.actions import delete_selected
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from .models import Branch, EquipmentImage, Drawing, PublicScreen
|
from .models import Branch, EquipmentImage, Drawing, PublicScreen
|
||||||
from .models import Activity, Branch, Event, Contact, VideoTerminal
|
from .models import Activity, Branch, Event, Contact, VideoTerminal, Budget, EquipmentBudget, InfrastructureBudget, BudgetTemplate, TemplateEquipmentItem, TemplateInfrastructureItem
|
||||||
from django_select2.forms import Select2Widget
|
from django_select2.forms import Select2Widget
|
||||||
from unfold.admin import ModelAdmin
|
from unfold.admin import ModelAdmin
|
||||||
from django.contrib.admin import AdminSite
|
from django.contrib.admin import AdminSite
|
||||||
@@ -192,3 +192,96 @@ class VideoTerminalAdmin(ModelAdmin):
|
|||||||
list_filter = ('terminal_type', 'branch')
|
list_filter = ('terminal_type', 'branch')
|
||||||
search_fields = ('branch__name', 'description')
|
search_fields = ('branch__name', 'description')
|
||||||
autocomplete_fields = ['branch']
|
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.
@@ -180,3 +180,148 @@ class Evaluation(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.activity.name} - {self.branch.name}"
|
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>
|
<span class="flex-grow block border-t border-black" aria-hidden="true" role="presentation"></span>
|
||||||
</h2>
|
</h2>
|
||||||
<!-- Component ends here -->
|
<!-- 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">
|
<div class="bg-white rounded-lg shadow-md p-4 mb-6">
|
||||||
<h2 class="text-2xl font-bold mb-4">基本信息</h2>
|
<h2 class="text-2xl font-bold mb-4">基本信息</h2>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ urlpatterns = [
|
|||||||
path('api/', include(router.urls)),
|
path('api/', include(router.urls)),
|
||||||
path('', views.BranchAll, name='branch-all'),
|
path('', views.BranchAll, name='branch-all'),
|
||||||
path('branch/<int:branch_id>/', views.branch_detail, name='branch-detail'),
|
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('branch/info/', views.Branchinfo, name='branchinfo'),
|
||||||
path('statistics/', views.Statistics, name='statistics'),
|
path('statistics/', views.Statistics, name='statistics'),
|
||||||
path('contact/', views.contact_list, name='contact-list'),
|
path('contact/', views.contact_list, name='contact-list'),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from rest_framework import viewsets
|
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 .serializers import BranchSerializer, ActivitySerializer, EvaluationSerializer
|
||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import render, redirect
|
||||||
from .models import PublicScreen
|
from .models import PublicScreen
|
||||||
@@ -55,6 +55,12 @@ def branch_detail(request, branch_id):
|
|||||||
equipment_images = branch.equipment_images.all()
|
equipment_images = branch.equipment_images.all()
|
||||||
public_screens = branch.public_screens.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 = {
|
context = {
|
||||||
'branch': branch,
|
'branch': branch,
|
||||||
@@ -62,10 +68,62 @@ def branch_detail(request, branch_id):
|
|||||||
'events': events,
|
'events': events,
|
||||||
'equipment_images': equipment_images,
|
'equipment_images': equipment_images,
|
||||||
'public_screens': public_screens,
|
'public_screens': public_screens,
|
||||||
|
'budgets': budgets,
|
||||||
|
'activities': activities,
|
||||||
|
'budget_templates': budget_templates,
|
||||||
}
|
}
|
||||||
return render(request, 'branch_detail.html', context)
|
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的数量,首页显示
|
# 在页面上显示所有的branch以及active的数量,首页显示
|
||||||
def BranchAll(request):
|
def BranchAll(request):
|
||||||
branches = Branch.objects.exclude(activity__isnull=True).order_by('name')
|
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,
|
'selected_type_name': selected_type_name,
|
||||||
}
|
}
|
||||||
return render(request, 'video_terminals.html', context)
|
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