flower_bs 7 timmar sedan
förälder
incheckning
29e9fc77c8
100 ändrade filer med 1245 tillägg och 78 borttagningar
  1. 1 1
      bound/admin.py
  2. 2 2
      bound/filter.py
  3. 64 0
      bound/migrations/0026_materialstatistics_ending_quantity_and_more.py
  4. 37 0
      bound/migrations/0027_rename_ending_quantity_materialstatistics_closing_quantity_and_more.py
  5. 111 0
      bound/migrations/0028_materialhistory_alter_batchlogmodel_options_and_more.py
  6. 20 0
      bound/migrations/0029_rename_batchlogmodel_batchoperatelogmodel_and_more.py
  7. 54 3
      bound/models.py
  8. 2 2
      bound/serializers.py
  9. 6 6
      bound/views.py
  10. 1 0
      container/admin.py
  11. 58 0
      container/migrations/0031_batchlogmodel_goods_std_batchlogmodel_goods_unit_and_more.py
  12. 132 2
      container/models.py
  13. 2 2
      container/views.py
  14. 60 0
      data_base/test_erp copy.py
  15. 5 5
      erp/views.py
  16. 262 1
      reportcenter/files.py
  17. 61 0
      reportcenter/filter.py
  18. 16 0
      reportcenter/migrations/0004_delete_goodssummarymodel.py
  19. 0 31
      reportcenter/models.py
  20. 91 1
      reportcenter/serializers.py
  21. 10 2
      reportcenter/urls.py
  22. 228 2
      reportcenter/views.py
  23. 1 0
      templates/dist/spa/css/11.676916e5.css
  24. 1 0
      templates/dist/spa/css/12.be283b5e.css
  25. 0 0
      templates/dist/spa/css/13.eb31c91a.css
  26. 0 0
      templates/dist/spa/css/14.f57b1220.css
  27. 0 0
      templates/dist/spa/css/15.65fea8cc.css
  28. 0 0
      templates/dist/spa/css/16.296f042c.css
  29. 0 0
      templates/dist/spa/css/17.865457f7.css
  30. 0 0
      templates/dist/spa/css/18.a5d7d7ca.css
  31. 0 0
      templates/dist/spa/css/19.bb6b4a4d.css
  32. 0 0
      templates/dist/spa/css/20.601677c3.css
  33. 0 0
      templates/dist/spa/css/21.71123cd8.css
  34. 0 0
      templates/dist/spa/css/22.f721cf95.css
  35. 0 0
      templates/dist/spa/css/23.ed8e81e9.css
  36. 0 1
      templates/dist/spa/css/24.20ec1b8f.css
  37. 0 0
      templates/dist/spa/css/24.eed22a1c.css
  38. 0 0
      templates/dist/spa/css/25.4b9e275f.css
  39. 1 0
      templates/dist/spa/css/26.e3a9ceb7.css
  40. 0 0
      templates/dist/spa/css/27.01a9029f.css
  41. 0 0
      templates/dist/spa/css/28.9b0c5133.css
  42. 0 0
      templates/dist/spa/css/29.0d4c4716.css
  43. 0 0
      templates/dist/spa/css/30.e2633675.css
  44. 0 0
      templates/dist/spa/css/31.8f3f6188.css
  45. 0 0
      templates/dist/spa/css/32.97f5bf6a.css
  46. 0 0
      templates/dist/spa/css/33.9478c981.css
  47. 0 0
      templates/dist/spa/css/34.c4652654.css
  48. 0 0
      templates/dist/spa/css/35.7a23b7fb.css
  49. 0 0
      templates/dist/spa/css/36.0faa4aeb.css
  50. 0 1
      templates/dist/spa/css/7.3b0ca86f.css
  51. 1 0
      templates/dist/spa/css/7.4ad5acff.css
  52. 0 1
      templates/dist/spa/css/8.88208c94.css
  53. 1 0
      templates/dist/spa/css/8.958ee95c.css
  54. 1 1
      templates/dist/spa/index.html
  55. 1 0
      templates/dist/spa/js/11.3bc8a128.js
  56. BIN
      templates/dist/spa/js/11.3bc8a128.js.gz
  57. BIN
      templates/dist/spa/js/11.d2c5553c.js.gz
  58. 1 0
      templates/dist/spa/js/12.0737aae8.js
  59. BIN
      templates/dist/spa/js/12.0737aae8.js.gz
  60. BIN
      templates/dist/spa/js/13.2df05f8d.js.gz
  61. 1 1
      templates/dist/spa/js/11.d2c5553c.js
  62. BIN
      templates/dist/spa/js/13.37ae32b9.js.gz
  63. BIN
      templates/dist/spa/js/14.67f088cd.js.gz
  64. 1 1
      templates/dist/spa/js/12.10622ab0.js
  65. BIN
      templates/dist/spa/js/12.10622ab0.js.gz
  66. 1 1
      templates/dist/spa/js/13.2df05f8d.js
  67. BIN
      templates/dist/spa/js/15.0995ef23.js.gz
  68. BIN
      templates/dist/spa/js/16.0827bf08.js.gz
  69. 1 1
      templates/dist/spa/js/14.67f088cd.js
  70. BIN
      templates/dist/spa/js/16.742400d0.js.gz
  71. 1 1
      templates/dist/spa/js/15.a1d31fc2.js
  72. BIN
      templates/dist/spa/js/15.a1d31fc2.js.gz
  73. 1 1
      templates/dist/spa/js/16.0827bf08.js
  74. BIN
      templates/dist/spa/js/18.23d853e1.js.gz
  75. BIN
      templates/dist/spa/js/18.ca785134.js.gz
  76. BIN
      templates/dist/spa/js/19.139fb325.js.gz
  77. 1 1
      templates/dist/spa/js/17.9c73eb9c.js
  78. BIN
      templates/dist/spa/js/17.9c73eb9c.js.gz
  79. 1 1
      templates/dist/spa/js/18.ca785134.js
  80. BIN
      templates/dist/spa/js/20.0f678196.js.gz
  81. BIN
      templates/dist/spa/js/20.13c39907.js.gz
  82. BIN
      templates/dist/spa/js/21.3722ac7c.js.gz
  83. 1 1
      templates/dist/spa/js/19.139fb325.js
  84. BIN
      templates/dist/spa/js/21.8c4f178b.js.gz
  85. 1 1
      templates/dist/spa/js/20.13c39907.js
  86. BIN
      templates/dist/spa/js/22.0807fc2b.js.gz
  87. BIN
      templates/dist/spa/js/22.746a0ce0.js.gz
  88. 1 1
      templates/dist/spa/js/21.3722ac7c.js
  89. BIN
      templates/dist/spa/js/23.07b5e369.js.gz
  90. 1 1
      templates/dist/spa/js/22.746a0ce0.js
  91. BIN
      templates/dist/spa/js/24.18faef2e.js.gz
  92. 0 1
      templates/dist/spa/js/24.d98a09d7.js
  93. BIN
      templates/dist/spa/js/24.d98a09d7.js.gz
  94. 1 1
      templates/dist/spa/js/23.aa80f2a2.js
  95. BIN
      templates/dist/spa/js/23.aa80f2a2.js.gz
  96. BIN
      templates/dist/spa/js/25.51321a20.js.gz
  97. BIN
      templates/dist/spa/js/26.336b155c.js.gz
  98. 1 0
      templates/dist/spa/js/26.463d394c.js
  99. BIN
      templates/dist/spa/js/26.463d394c.js.gz
  100. 0 0
      templates/dist/spa/js/25.51321a20.js

+ 1 - 1
bound/admin.py

@@ -5,6 +5,6 @@ admin.site.register(BoundListModel)
 admin.site.register(BoundDetailModel)
 admin.site.register(BoundBatchModel)
 admin.site.register(OutBoundDetailModel)
-admin.site.register(BatchLogModel)
+admin.site.register(BatchOperateLogModel)
 
 

+ 2 - 2
bound/filter.py

@@ -1,5 +1,5 @@
 from django_filters import FilterSet
-from .models import BoundListModel, BoundDetailModel,BoundBatchModel,OutBatchModel,BatchLogModel,OutBoundDetailModel,MaterialStatistics,OutBoundDemandModel
+from .models import BoundListModel, BoundDetailModel,BoundBatchModel,OutBatchModel,BatchOperateLogModel,OutBoundDetailModel,MaterialStatistics,OutBoundDemandModel
 
 class OutBoundDemandFilter(FilterSet):
     class Meta:
@@ -235,7 +235,7 @@ class OutBatchFilter(FilterSet):
         }   
 class BatchlogFilter(FilterSet):
     class Meta:
-        model = BatchLogModel
+        model = BatchOperateLogModel
         fields = {
             "id": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
             'log_type': ['icontains', 'exact'],

+ 64 - 0
bound/migrations/0026_materialstatistics_ending_quantity_and_more.py

@@ -0,0 +1,64 @@
+# Generated by Django 4.1.2 on 2025-07-26 18:40
+
+from decimal import Decimal
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('bound', '0025_alter_batchlogmodel_options'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='materialstatistics',
+            name='ending_quantity',
+            field=models.DecimalField(decimal_places=3, default=Decimal('0'), max_digits=10, verbose_name='期末数量'),
+        ),
+        migrations.AddField(
+            model_name='materialstatistics',
+            name='initial_quantity',
+            field=models.DecimalField(decimal_places=3, default=Decimal('0'), max_digits=10, verbose_name='期初数量'),
+        ),
+        migrations.AddField(
+            model_name='materialstatistics',
+            name='period_end',
+            field=models.DateTimeField(blank=True, null=True, verbose_name='统计结束时间'),
+        ),
+        migrations.AddField(
+            model_name='materialstatistics',
+            name='period_in',
+            field=models.DecimalField(decimal_places=3, default=Decimal('0'), max_digits=10, verbose_name='期间入库'),
+        ),
+        migrations.AddField(
+            model_name='materialstatistics',
+            name='period_out',
+            field=models.DecimalField(decimal_places=3, default=Decimal('0'), max_digits=10, verbose_name='期间出库'),
+        ),
+        migrations.AddField(
+            model_name='materialstatistics',
+            name='period_start',
+            field=models.DateTimeField(blank=True, null=True, verbose_name='统计起始时间'),
+        ),
+        migrations.AlterField(
+            model_name='materialstatistics',
+            name='goods_code',
+            field=models.CharField(max_length=50, unique=True, verbose_name='商品编码'),
+        ),
+        migrations.AlterField(
+            model_name='materialstatistics',
+            name='goods_desc',
+            field=models.CharField(max_length=100, verbose_name='商品描述'),
+        ),
+        migrations.AlterField(
+            model_name='materialstatistics',
+            name='goods_std',
+            field=models.CharField(blank=True, default='待填写', max_length=50, null=True, verbose_name='商品规格'),
+        ),
+        migrations.AlterField(
+            model_name='materialstatistics',
+            name='goods_unit',
+            field=models.CharField(default='待填写', max_length=20, verbose_name='商品单位'),
+        ),
+    ]

+ 37 - 0
bound/migrations/0027_rename_ending_quantity_materialstatistics_closing_quantity_and_more.py

@@ -0,0 +1,37 @@
+# Generated by Django 4.1.2 on 2025-07-26 18:54
+
+from decimal import Decimal
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('bound', '0026_materialstatistics_ending_quantity_and_more'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='materialstatistics',
+            old_name='ending_quantity',
+            new_name='closing_quantity',
+        ),
+        migrations.RenameField(
+            model_name='materialstatistics',
+            old_name='initial_quantity',
+            new_name='opening_quantity',
+        ),
+        migrations.AddField(
+            model_name='materialstatistics',
+            name='net_change',
+            field=models.DecimalField(decimal_places=3, default=Decimal('0'), max_digits=10, verbose_name='净变化量'),
+        ),
+        migrations.AddIndex(
+            model_name='materialstatistics',
+            index=models.Index(fields=['goods_code', 'period_end'], name='materialsta_goods_c_8bd630_idx'),
+        ),
+        migrations.AddIndex(
+            model_name='materialstatistics',
+            index=models.Index(fields=['period_end'], name='materialsta_period__e6bd70_idx'),
+        ),
+    ]

+ 111 - 0
bound/migrations/0028_materialhistory_alter_batchlogmodel_options_and_more.py

@@ -0,0 +1,111 @@
+# Generated by Django 4.1.2 on 2025-07-28 09:48
+
+from decimal import Decimal
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('bound', '0027_rename_ending_quantity_materialstatistics_closing_quantity_and_more'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='MaterialHistory',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('change_time', models.DateTimeField(verbose_name='变动时间')),
+                ('in_quantity', models.DecimalField(decimal_places=3, default=Decimal('0'), max_digits=10, verbose_name='入库数量')),
+                ('out_quantity', models.DecimalField(decimal_places=3, default=Decimal('0'), max_digits=10, verbose_name='出库数量')),
+                ('change_type', models.CharField(max_length=20, verbose_name='变动类型')),
+                ('opening_quantity', models.DecimalField(decimal_places=3, default=Decimal('0'), max_digits=10, verbose_name='期初数量')),
+                ('closing_quantity', models.DecimalField(decimal_places=3, default=Decimal('0'), max_digits=10, verbose_name='期末数量')),
+            ],
+            options={
+                'verbose_name': '物料变动历史',
+                'verbose_name_plural': '物料变动历史',
+                'db_table': 'material_history',
+                'ordering': ['-change_time'],
+            },
+        ),
+        migrations.AlterModelOptions(
+            name='batchlogmodel',
+            options={'verbose_name': 'Batch Log', 'verbose_name_plural': 'Batch Log'},
+        ),
+        migrations.RemoveIndex(
+            model_name='materialstatistics',
+            name='materialsta_goods_c_8bd630_idx',
+        ),
+        migrations.RemoveIndex(
+            model_name='materialstatistics',
+            name='materialsta_period__e6bd70_idx',
+        ),
+        migrations.RemoveField(
+            model_name='materialstatistics',
+            name='closing_quantity',
+        ),
+        migrations.RemoveField(
+            model_name='materialstatistics',
+            name='net_change',
+        ),
+        migrations.RemoveField(
+            model_name='materialstatistics',
+            name='opening_quantity',
+        ),
+        migrations.RemoveField(
+            model_name='materialstatistics',
+            name='period_end',
+        ),
+        migrations.RemoveField(
+            model_name='materialstatistics',
+            name='period_in',
+        ),
+        migrations.RemoveField(
+            model_name='materialstatistics',
+            name='period_out',
+        ),
+        migrations.RemoveField(
+            model_name='materialstatistics',
+            name='period_start',
+        ),
+        migrations.AlterField(
+            model_name='materialstatistics',
+            name='goods_code',
+            field=models.CharField(max_length=255, unique=True, verbose_name='商品编码'),
+        ),
+        migrations.AlterField(
+            model_name='materialstatistics',
+            name='goods_desc',
+            field=models.CharField(max_length=255, verbose_name='商品描述'),
+        ),
+        migrations.AlterField(
+            model_name='materialstatistics',
+            name='goods_std',
+            field=models.CharField(blank=True, default='待填写', max_length=255, null=True, verbose_name='商品标准'),
+        ),
+        migrations.AlterField(
+            model_name='materialstatistics',
+            name='goods_unit',
+            field=models.CharField(default='待填写', max_length=255, verbose_name='商品单位'),
+        ),
+        migrations.AddField(
+            model_name='materialhistory',
+            name='batch_log',
+            field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='material_history', to='bound.batchlogmodel', verbose_name='关联批次日志'),
+        ),
+        migrations.AddField(
+            model_name='materialhistory',
+            name='material_stats',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='history_records', to='bound.materialstatistics', verbose_name='关联物料'),
+        ),
+        migrations.AddIndex(
+            model_name='materialhistory',
+            index=models.Index(fields=['material_stats', 'change_time'], name='material_hi_materia_1a9e04_idx'),
+        ),
+        migrations.AddIndex(
+            model_name='materialhistory',
+            index=models.Index(fields=['change_time'], name='material_hi_change__806999_idx'),
+        ),
+    ]

+ 20 - 0
bound/migrations/0029_rename_batchlogmodel_batchoperatelogmodel_and_more.py

@@ -0,0 +1,20 @@
+# Generated by Django 4.1.2 on 2025-07-28 10:20
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('bound', '0028_materialhistory_alter_batchlogmodel_options_and_more'),
+    ]
+
+    operations = [
+        migrations.RenameModel(
+            old_name='BatchLogModel',
+            new_name='BatchOperateLogModel',
+        ),
+        migrations.DeleteModel(
+            name='MaterialHistory',
+        ),
+    ]

+ 54 - 3
bound/models.py

@@ -148,6 +148,24 @@ class MaterialStatistics(models.Model):
     def bound_batches(self):
         return BoundBatchModel.objects.filter(goods_code=self.goods_code).exclude(goods_in_location_qty=0).order_by('bound_batch_order')
 
+    @classmethod
+    def get_period_summary(cls, goods_code, start_date, end_date):
+        """获取指定时间段的库存变动汇总"""
+        period_data = cls.objects.filter(
+            goods_code=goods_code,
+            history_records__change_time__range=(start_date, end_date)
+        ).annotate(
+            period_in=Sum('history_records__in_quantity'),
+            period_out=Sum('history_records__out_quantity'),
+        ).first()
+        
+        if period_data:
+            return {
+                'period_in': period_data.period_in or Decimal('0'),
+                'period_out': period_data.period_out or Decimal('0')
+            }
+        return {'period_in': Decimal('0'), 'period_out': Decimal('0')}
+    
     class Meta:
         db_table = 'materialstatistics'
         verbose_name = '物料统计'
@@ -157,6 +175,9 @@ class MaterialStatistics(models.Model):
 
 
 
+
+
+
 @receiver(pre_save, sender=BoundBatchModel)
 def store_original_goods_code(sender, instance, **kwargs):
     """保存前记录原始物料编码(仅更新时生效)"""
@@ -223,7 +244,38 @@ def update_material_statistics(sender, instance, **kwargs):
         refresh_stats(original_code)      # 更新旧物料(仅刷新数量)
         refresh_stats(current_code, force_desc=True)  # 更新新物料(全量刷新)
 
-
+# 物料统计的扩展方法
+def get_material_period_report(goods_code, start_date, end_date):
+    """获取物料在特定时间段的变动报告"""
+    stats = MaterialStatistics.objects.filter(goods_code=goods_code).first()
+    if not stats:
+        return None
+    
+    # 获取时间段的变动汇总
+    period_summary = stats.get_period_summary(start_date, end_date)
+    from container.models import MaterialChangeHistory
+    # 计算期初数量(时间段开始前的最新库存)
+    opening_record = MaterialChangeHistory.objects.filter(
+        material_stats=stats,
+        change_time__lt=start_date
+    ).order_by('-change_time').first()
+    
+    opening_quantity = opening_record.closing_quantity if opening_record else Decimal('0')
+    
+    # 计算期末数量
+    period_net = period_summary['period_in'] - period_summary['period_out']
+    closing_quantity = opening_quantity + period_net
+    
+    return {
+        'goods_code': goods_code,
+        'goods_desc': stats.goods_desc,
+        'opening_quantity': opening_quantity,
+        'period_in': period_summary['period_in'],
+        'period_out': period_summary['period_out'],
+        'net_change': period_net,
+        'closing_quantity': closing_quantity,
+        'current_quantity': stats.total_quantity
+    }
 
 class OutBatchModel(models.Model):
     CONTAINER_STATUS = (
@@ -338,7 +390,7 @@ class OutBoundDetailModel(models.Model):
         verbose_name_plural = "OutBound Detail"
         ordering = ['-id']
 
-class BatchLogModel(models.Model):
+class BatchOperateLogModel(models.Model):
     BATCH_LOG_TYPE = (
         (0, '入库'),
         (1, '出库'),
@@ -363,7 +415,6 @@ class BatchLogModel(models.Model):
         db_table = 'batchlog'
         verbose_name = 'Batch Log'
         verbose_name_plural = "Batch Log"
-        ordering = ['-id']
 
 # 利用创建好的批次来与申请单相对应       
 class BoundDetailModel(models.Model):

+ 2 - 2
bound/serializers.py

@@ -1,5 +1,5 @@
 from rest_framework import serializers
-from .models import BoundListModel, BoundDetailModel, BoundBatchModel,OutBatchModel,BatchLogModel,OutBoundDetailModel,MaterialStatistics,OutBoundDemandModel
+from .models import BoundListModel, BoundDetailModel, BoundBatchModel,OutBatchModel,BatchOperateLogModel,OutBoundDetailModel,MaterialStatistics,OutBoundDemandModel
 from utils import datasolve
 
 class OutBoundDemandModelSerializer(serializers.ModelSerializer):
@@ -248,7 +248,7 @@ class OutBatchPostSerializer(serializers.ModelSerializer):
 
 class BatchLogGetSerializer(serializers.ModelSerializer):
     class Meta:
-        model = BatchLogModel
+        model = BatchOperateLogModel
         fields = '__all__'
         read_only_fields = ['id', 'openid']
 

+ 6 - 6
bound/views.py

@@ -9,7 +9,7 @@ from rest_framework.response import Response
 from rest_framework.exceptions import APIException
 from django.utils import timezone
 from django.db.models import Sum
-from .models import BoundListModel, BoundDetailModel,BoundBatchModel, BatchLogModel, OutBatchModel,OutBoundDetailModel,MaterialStatistics,OutBoundDemandModel
+from .models import BoundListModel, BoundDetailModel,BoundBatchModel, BatchOperateLogModel, OutBatchModel,OutBoundDetailModel,MaterialStatistics,OutBoundDemandModel
 # from .files import FileListRenderCN, FileDetailRenderCN
 
 from .serializers import BoundListGetSerializer,BoundListPostSerializer,BoundBatchGetSerializer,BoundBatchPostSerializer,BoundDetailGetSerializer,BoundDetailPostSerializer
@@ -560,7 +560,7 @@ class BoundBatchViewSet(viewsets.ModelViewSet):
             raise APIException({"detail": "{}".format(e)})
     
     def add_batch_log(self, data, log_type, goods_qty):
-        choices_dict = dict(BatchLogModel.BATCH_LOG_TYPE)
+        choices_dict = dict(BatchOperateLogModel.BATCH_LOG_TYPE)
         log_type_name = choices_dict.get(log_type, "未知类型")
         
         try:
@@ -585,7 +585,7 @@ class BoundBatchViewSet(viewsets.ModelViewSet):
         }
         
         # 创建日志记录
-        BatchLogModel.objects.create(**log_data)
+        BatchOperateLogModel.objects.create(**log_data)
         return True
 
 
@@ -886,7 +886,7 @@ class OutBoundBatchViewSet(viewsets.ModelViewSet):
             return Response(serializer.data, status=200, headers=headers)
 
     def add_batch_log(self, data, log_type, goods_qty):
-        choices_dict = dict(BatchLogModel.BATCH_LOG_TYPE)
+        choices_dict = dict(BatchOperateLogModel.BATCH_LOG_TYPE)
         log_type_name = choices_dict.get(log_type, "未知类型")
 
         try:
@@ -911,7 +911,7 @@ class OutBoundBatchViewSet(viewsets.ModelViewSet):
         }
 
         # 创建日志记录
-        BatchLogModel.objects.create(**log_data)
+        BatchOperateLogModel.objects.create(**log_data)
         return True 
 
 # 批次操作日志类视图      
@@ -935,7 +935,7 @@ class BoundBatchLogViewSet(viewsets.ModelViewSet):
 
     def get_queryset(self):
 
-        return BatchLogModel.objects.filter( is_delete=False)
+        return BatchOperateLogModel.objects.filter( is_delete=False)
         
 
     def get_serializer_class(self):

+ 1 - 0
container/admin.py

@@ -6,5 +6,6 @@ admin.site.register(ContainerDetailModel)
 admin.site.register(ContainerOperationModel)
 admin.site.register(ContainerWCSModel)
 admin.site.register(TaskModel)
+admin.site.register(batchLogModel)
 
 # Register your models here.

+ 58 - 0
container/migrations/0031_batchlogmodel_goods_std_batchlogmodel_goods_unit_and_more.py

@@ -0,0 +1,58 @@
+# Generated by Django 4.1.2 on 2025-07-28 16:22
+
+from decimal import Decimal
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('bound', '0029_rename_batchlogmodel_batchoperatelogmodel_and_more'),
+        ('container', '0030_batchlogmodel_detail_logs_alter_batchlogmodel_table'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='batchlogmodel',
+            name='goods_std',
+            field=models.CharField(blank=True, max_length=50, null=True, verbose_name='货品规格'),
+        ),
+        migrations.AddField(
+            model_name='batchlogmodel',
+            name='goods_unit',
+            field=models.CharField(blank=True, max_length=50, null=True, verbose_name='货品单位'),
+        ),
+        migrations.CreateModel(
+            name='MaterialChangeHistory',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('goods_code', models.CharField(max_length=50, verbose_name='货品编码')),
+                ('goods_desc', models.CharField(max_length=100, verbose_name='货品描述')),
+                ('goods_std', models.CharField(blank=True, max_length=50, null=True, verbose_name='货品规格')),
+                ('goods_unit', models.CharField(blank=True, max_length=50, null=True, verbose_name='货品单位')),
+                ('change_time', models.DateTimeField(verbose_name='变动时间')),
+                ('in_quantity', models.DecimalField(decimal_places=3, default=Decimal('0'), max_digits=10, verbose_name='入库数量')),
+                ('out_quantity', models.DecimalField(decimal_places=3, default=Decimal('0'), max_digits=10, verbose_name='出库数量')),
+                ('change_type', models.CharField(max_length=20, verbose_name='变动类型')),
+                ('opening_quantity', models.DecimalField(decimal_places=3, default=Decimal('0'), max_digits=10, verbose_name='期初数量')),
+                ('closing_quantity', models.DecimalField(decimal_places=3, default=Decimal('0'), max_digits=10, verbose_name='期末数量')),
+                ('batch_log', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='material_history', to='container.batchlogmodel', verbose_name='关联批次日志')),
+                ('material_stats', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='history_records', to='bound.materialstatistics', verbose_name='关联物料')),
+            ],
+            options={
+                'verbose_name': '物料变动历史',
+                'verbose_name_plural': '物料变动历史',
+                'db_table': 'material_change_history',
+                'ordering': ['-change_time'],
+            },
+        ),
+        migrations.AddIndex(
+            model_name='materialchangehistory',
+            index=models.Index(fields=['material_stats', 'change_time'], name='material_ch_materia_3907f7_idx'),
+        ),
+        migrations.AddIndex(
+            model_name='materialchangehistory',
+            index=models.Index(fields=['change_time'], name='material_ch_change__9c5815_idx'),
+        ),
+    ]

+ 132 - 2
container/models.py

@@ -1,5 +1,5 @@
 from django.db import models
-from bound.models import BoundBatchModel,BoundDetailModel,OutBatchModel,BoundListModel,BoundListModel
+from bound.models import BoundBatchModel,BoundDetailModel,OutBatchModel,BoundListModel,BoundListModel,MaterialStatistics
 from django.utils import timezone
 from django.db.models.signals import pre_save, post_save
 from django.dispatch import receiver
@@ -247,6 +247,8 @@ class batchLogModel(models.Model):
     log_type = models.CharField(max_length=20, choices=LOG_TYPES, verbose_name='日志类型')
     goods_code = models.CharField(max_length=50, verbose_name='货品编码')
     goods_desc = models.CharField(max_length=100, verbose_name='货品描述')
+    goods_std = models.CharField(max_length=50, verbose_name='货品规格', null=True, blank=True)
+    goods_unit = models.CharField(max_length=50, verbose_name='货品单位', null=True, blank=True)
     goods_in_qty = models.DecimalField(max_digits=10, decimal_places=3, default=Decimal('0'),verbose_name='新入数量', null=True, blank=True)
     goods_out_qty = models.DecimalField(max_digits=10, decimal_places=3, default=Decimal('0'),verbose_name='新出数量', null=True, blank=True)
     detail_logs = models.ManyToManyField(
@@ -280,6 +282,7 @@ class batchLogModel(models.Model):
         self.goods_out_qty = total_new_out - total_old_out
         self.save()
         
+        
         # 同时更新批次统计数据
         self.update_batch_stats()
     
@@ -349,16 +352,19 @@ def aggregate_to_batch_log(container_log):
                 log_type=log_type,
                 goods_code=batch.goods_code,
                 goods_desc=batch.goods_desc,
+                goods_std=batch.goods_std,
+                goods_unit=batch.goods_unit,
                 create_time=container_log.create_time
             )
             logger.info(f"创建新批次日志 {batch_log.id}")
+            # create_material_history(batch_log, update=True)
+
         
         # 将托盘日志关联到批次日志
         batch_log.detail_logs.add(container_log)
         
         # 从关联日志更新批次日志数据
         batch_log.update_from_details()
-        
         # 根据日志类型添加关联单据信息
         if log_type == 'out' and not batch_log.bound:
             from bound.models import OutBoundDetailModel
@@ -391,8 +397,69 @@ def aggregate_to_batch_log(container_log):
     except Exception as e:
         logger.error(f"聚合托盘日志时出错: {e}", exc_info=True)
         raise
+# # 批次日志的信号处理器
+@receiver(post_save, sender=batchLogModel)
+def update_material_history(sender, instance, created, **kwargs):
+    """批次日志保存后更新物料统计"""
+    if created:
+        # 创建物料变动历史记录
+        create_material_history(instance, update=True)
+    
+    else:
+        # 更新物料变动历史记录
+        MaterialChangeHistory_obj =MaterialChangeHistory.objects.get(batch_log=instance)
+        MaterialChangeHistory_obj.in_quantity = instance.goods_in_qty or Decimal('0')
+        MaterialChangeHistory_obj.out_quantity = instance.goods_out_qty or Decimal('0')
+        MaterialChangeHistory_obj.change_type = instance.log_type
+        MaterialChangeHistory_obj.closing_quantity = MaterialChangeHistory_obj.opening_quantity + MaterialChangeHistory_obj.in_quantity - MaterialChangeHistory_obj.out_quantity
+        MaterialChangeHistory_obj.save()
+
+def create_material_history( instance, update):
+    """为每条批次日志创建物料变动历史记录"""
+    from bound.models import MaterialStatistics
+ 
+    if update:
+        # 获取或创建物料统计
+        stats, _ = MaterialStatistics.objects.get_or_create(
+
+            goods_code=instance.goods_code,
+            defaults={
+                'goods_desc': instance.goods_desc,
+                'goods_std': instance.goods_std or '待填写',
+                'goods_unit': instance.goods_unit or '待填写',
+            }
+        )
+        
+        # 计算期初数量(变动前的库存)
+        opening_quantity = stats.total_quantity
+        
+        # 计算期末数量
+        closing_quantity = opening_quantity
+        if instance.goods_in_qty:
+            closing_quantity += instance.goods_in_qty
+        if instance.goods_out_qty:
+            closing_quantity -= instance.goods_out_qty
         
 
+        MaterialChangeHistory.objects.create(
+            material_stats=stats,
+            batch_log=instance,
+            goods_code=instance.goods_code,
+            goods_desc=instance.goods_desc,
+            goods_std=instance.goods_std,
+            goods_unit=instance.goods_unit,
+            change_time=instance.create_time,
+            in_quantity=instance.goods_in_qty or Decimal('0'),
+            out_quantity=instance.goods_out_qty or Decimal('0'),
+            change_type=instance.log_type,
+            opening_quantity=opening_quantity,
+            closing_quantity=closing_quantity
+        )
+        
+        # 更新物料统计的实时库存
+        stats.total_quantity = closing_quantity
+        stats.save()      
+
 
 # 简化的信号处理器
 @receiver(post_save, sender=ContainerDetailLogModel)
@@ -475,7 +542,70 @@ def container_detail_post_save(sender, instance, created, **kwargs):
             new_status=instance.status
         )
 
+
+class MaterialChangeHistory(models.Model):
+    """物料库存变动历史记录(与批次日志多对一)"""
+    material_stats = models.ForeignKey(
+        MaterialStatistics,
+        on_delete=models.CASCADE,
+        related_name='history_records',
+        verbose_name="关联物料"
+    )
+    
+    # 与批次日志建立多对一关系
+    batch_log = models.ForeignKey(batchLogModel,on_delete=models.CASCADE, related_name='material_history', verbose_name="关联批次日志",primary_key=False)
+
+    goods_code = models.CharField(max_length=50, verbose_name='货品编码')
+    goods_desc = models.CharField(max_length=100, verbose_name='货品描述')
+    goods_std = models.CharField(max_length=50, verbose_name='货品规格', null=True, blank=True)
+    goods_unit = models.CharField(max_length=50, verbose_name='货品单位', null=True, blank=True)
+    # 时间戳记录(使用批次日志的时间)
+    change_time = models.DateTimeField(
+        verbose_name="变动时间"
+    )
+    
+    # 库存变动情况
+    in_quantity = models.DecimalField(
+        max_digits=10, decimal_places=3,
+        default=Decimal('0'),
+        verbose_name="入库数量"
+    )
+    
+    out_quantity = models.DecimalField(
+        max_digits=10, decimal_places=3,
+        default=Decimal('0'),
+        verbose_name="出库数量"
+    )
+    
+    # 变更类型(从批次日志获取)
+    change_type = models.CharField(
+        max_length=20,
+        verbose_name="变动类型"
+    )
     
+    # 变更时的库存快照
+    opening_quantity = models.DecimalField(
+        max_digits=10, decimal_places=3,
+        default=Decimal('0'),
+        verbose_name="期初数量"
+    )
+    
+    closing_quantity = models.DecimalField(
+        max_digits=10, decimal_places=3,
+        default=Decimal('0'),
+        verbose_name="期末数量"
+    )
+    
+    class Meta:
+        db_table = 'material_change_history'
+        verbose_name = '物料变动历史'
+        verbose_name_plural = "物料变动历史"
+        ordering = ['-change_time']
+        indexes = [
+            models.Index(fields=['material_stats', 'change_time']),
+            models.Index(fields=['change_time']),
+        ]
+   
 
 
 # 明细表:操作记录 记录每次出入库的记录,使用goods来进行盘点,使用托盘码来进行托盘的操作记录

+ 2 - 2
container/views.py

@@ -18,7 +18,7 @@ from .models import ContainerListModel,ContainerDetailModel,ContainerOperationMo
 from bound.models import BoundDetailModel,BoundListModel,OutBoundDetailModel
 from bin.views import LocationAllocation,base_location
 from bin.models import LocationModel,LocationContainerLink,LocationGroupModel
-from bound.models import BoundBatchModel,OutBatchModel,BatchLogModel
+from bound.models import BoundBatchModel,OutBatchModel,BatchOperateLogModel
 
 from .serializers import ContainerDetailGetSerializer,ContainerDetailPostSerializer,ContainerDetailSimpleGetSerializer
 from .serializers import ContainerListGetSerializer,ContainerListPostSerializer
@@ -2445,7 +2445,7 @@ class OutDetailViewSet(viewsets.ModelViewSet):
             for obj in out_batch_detail_all:
                 obj.working = 0
                 obj.save()
-                BatchLogModel.objects.create(
+                BatchOperateLogModel.objects.create(
                     batch_id = obj.container_detail.batch,
                     log_type = 1,
                     log_date = timezone.now(),

+ 60 - 0
data_base/test_erp copy.py

@@ -0,0 +1,60 @@
+import os
+import django
+import sys
+from decimal import Decimal
+from django.utils import timezone
+from datetime import timedelta, datetime
+
+def setup_django():
+
+    project_path = "D:/code/vue/greater_wms"
+    sys.path.append(project_path)
+    
+
+    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'greaterwms.settings')
+    django.setup()
+
+def test_summary_methods():
+
+    try:
+        from bound.models import MaterialStatistics
+        
+        print("测试报表数据生成方法...")
+        
+
+        end_date = timezone.now()
+        start_date = end_date - timedelta(days=7)
+        
+        # ===== 测试方法1:update_summary =====
+        print(" 测试 update_summary 方法...")
+
+        
+        print(" 所有报表功能测试通过")
+        return True
+    
+    except Exception as e:
+        print(f" 测试失败:{str(e)}")
+        import traceback
+        traceback.print_exc()
+        return False
+
+def main():
+    try:
+        print(" 开始测试物料统计报表功能")
+        
+        # 执行测试
+        success = test_summary_methods()
+        
+        if success:
+            print(" 测试成功:报表功能正常")
+        else:
+            print(" 测试失败:报表功能异常")
+    
+    except Exception as e:
+        print(f" 测试失败:{str(e)}")
+        import traceback
+        traceback.print_exc()
+
+if __name__ == "__main__":
+    setup_django()
+    main()

+ 5 - 5
erp/views.py

@@ -21,7 +21,7 @@ from .parsers import TextJSONRenderer
 import logging
 import time
 from django.db import transaction
-from bound.models import BoundListModel, BoundBatchModel, OutBatchModel,BoundDetailModel,OutBoundDetailModel,BatchLogModel
+from bound.models import BoundListModel, BoundBatchModel, OutBatchModel,BoundDetailModel,OutBoundDetailModel,BatchOperateLogModel
 from warehouse.models import ProductListModel
 from rest_framework.response import Response
 import json
@@ -495,7 +495,7 @@ class GenerateInbound(APIView):
             ))
 
             # 创建日志记录
-            log_list.append(BatchLogModel(
+            log_list.append(BatchOperateLogModel(
                 batch_id=batch,
                 log_type=0,
                 log_date=timezone.now(),
@@ -512,7 +512,7 @@ class GenerateInbound(APIView):
             with transaction.atomic():
                 BoundBatchModel.objects.bulk_create(batch_list)
                 BoundDetailModel.objects.bulk_create(detail_list)
-                BatchLogModel.objects.bulk_create(log_list)
+                BatchOperateLogModel.objects.bulk_create(log_list)
         except Exception as e:
             raise Exception(f"批次数据保存失败: {str(e)}")
  
@@ -821,7 +821,7 @@ class GenerateOutbound(APIView):
             ))
 
             # 生成批次操作日志
-            log_list.append(BatchLogModel(
+            log_list.append(BatchOperateLogModel(
                 batch_id=batch_obj,
                 log_type=1,
                 log_date=timezone.now(),
@@ -838,7 +838,7 @@ class GenerateOutbound(APIView):
             with transaction.atomic():
                 OutBatchModel.objects.bulk_create(batch_list)
                 OutBoundDetailModel.objects.bulk_create(detail_list)
-                BatchLogModel.objects.bulk_create(log_list)
+                BatchOperateLogModel.objects.bulk_create(log_list)
         except Exception as e:
             raise Exception(f"出库批次数据保存失败: {str(e)}")
 

+ 262 - 1
reportcenter/files.py

@@ -9,7 +9,6 @@ def file_headers_list():
             'business_type', 
             'iout_type', 
             'department', 
-       
             'warehouse_name', 
             'goods_code', 
             'goods_desc', 
@@ -51,4 +50,266 @@ class FileFlowListRenderCN(CSVStreamingRenderer):
     header = file_headers_list()
     labels = cn_data_header_list()
 
+"""
 
+class MaterialChangeHistory(models.Model):
+
+    material_stats = models.ForeignKey(
+        MaterialStatistics,
+        on_delete=models.CASCADE,
+        related_name='history_records',
+        verbose_name="关联物料"
+    )
+    
+    # 与批次日志建立多对一关系
+    batch_log = models.ForeignKey(batchLogModel,on_delete=models.CASCADE, related_name='material_history', verbose_name="关联批次日志",primary_key=False)
+
+    goods_code = models.CharField(max_length=50, verbose_name='货品编码')
+    goods_desc = models.CharField(max_length=100, verbose_name='货品描述')
+    goods_std = models.CharField(max_length=50, verbose_name='货品规格', null=True, blank=True)
+    goods_unit = models.CharField(max_length=50, verbose_name='货品单位', null=True, blank=True)
+    # 时间戳记录(使用批次日志的时间)
+    change_time = models.DateTimeField(
+        verbose_name="变动时间"
+    )
+    
+    # 库存变动情况
+    in_quantity = models.DecimalField(
+        max_digits=10, decimal_places=3,
+        default=Decimal('0'),
+        verbose_name="入库数量"
+    )
+    
+    out_quantity = models.DecimalField(
+        max_digits=10, decimal_places=3,
+        default=Decimal('0'),
+        verbose_name="出库数量"
+    )
+    
+    # 变更类型(从批次日志获取)
+    change_type = models.CharField(
+        max_length=20,
+        verbose_name="变动类型"
+    )
+    
+    # 变更时的库存快照
+    opening_quantity = models.DecimalField(
+        max_digits=10, decimal_places=3,
+        default=Decimal('0'),
+        verbose_name="期初数量"
+    )
+    
+    closing_quantity = models.DecimalField(
+        max_digits=10, decimal_places=3,
+        default=Decimal('0'),
+        verbose_name="期末数量"
+    
+class batchLogModel(models.Model):
+
+    LOG_TYPES = (
+        ('create', '创建'),
+        ('update', '更新'),
+        ('delete', '删除'),
+        ('out', '出库'),
+        ('cancel_out', '取消出库'),
+        ('status_change', '状态变更'),
+    )
+    bound = models.ForeignKey(BoundListModel, on_delete=models.CASCADE, related_name='logs', null=True, blank=True)
+    batch = models.ForeignKey(BoundBatchModel, on_delete=models.CASCADE, related_name='logs')
+    log_type = models.CharField(max_length=20, choices=LOG_TYPES, verbose_name='日志类型')
+    goods_code = models.CharField(max_length=50, verbose_name='货品编码')
+    goods_desc = models.CharField(max_length=100, verbose_name='货品描述')
+    goods_std = models.CharField(max_length=50, verbose_name='货品规格', null=True, blank=True)
+    goods_unit = models.CharField(max_length=50, verbose_name='货品单位', null=True, blank=True)
+    goods_in_qty = models.DecimalField(max_digits=10, decimal_places=3, default=Decimal('0'),verbose_name='新入数量', null=True, blank=True)
+    goods_out_qty = models.DecimalField(max_digits=10, decimal_places=3, default=Decimal('0'),verbose_name='新出数量', null=True, blank=True)
+    detail_logs = models.ManyToManyField(
+        ContainerDetailLogModel,
+        related_name='batch_logs',
+        verbose_name='关联托盘日志'
+    )
+    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')   
+
+    
+class ContainerDetailLogModel(models.Model):
+ 
+    LOG_TYPES = (
+        ('create', '创建'),
+        ('update', '更新'),
+        ('delete', '删除'),
+        ('out', '出库'),
+        ('cancel_out', '取消出库'),
+        ('status_change', '状态变更'),
+    )
+    
+    # 关联的托盘明细
+    container_detail = models.ForeignKey(
+        ContainerDetailModel, 
+        on_delete=models.CASCADE,
+        related_name='logs'
+    )
+    
+    # 日志类型
+    log_type = models.CharField(
+        max_length=20,
+        choices=LOG_TYPES,
+        verbose_name='日志类型'
+    )
+    
+    # 原值
+    old_goods_qty = models.DecimalField(max_digits=10, decimal_places=3, default=Decimal('0'),verbose_name='原数量', null=True, blank=True)
+    old_goods_out_qty = models.DecimalField(max_digits=10, decimal_places=3, default=Decimal('0'),verbose_name='原出库数量', null=True, blank=True)
+    old_status = models.IntegerField(
+        choices=ContainerDetailModel.BATCH_STATUS,
+        null=True,
+        blank=True,
+        verbose_name='原状态'
+    )
+    
+    # 新值
+    new_goods_qty = models.DecimalField(max_digits=10, decimal_places=3, default=Decimal('0'),verbose_name='新数量', null=True, blank=True)
+    new_goods_out_qty = models.DecimalField(max_digits=10, decimal_places=3, default=Decimal('0'),verbose_name='新出库数量', null=True, blank=True)
+    new_status = models.IntegerField(
+        choices=ContainerDetailModel.BATCH_STATUS,
+        null=True,
+        blank=True,
+        verbose_name='新状态'
+    )
+    
+    # 元信息
+    creater = models.CharField(max_length=50, verbose_name='操作人')
+    create_time = models.DateTimeField(auto_now_add=True, verbose_name='操作时间')
+    tobatchlog = models.BooleanField(default=False, verbose_name='是否转移到批次日志')
+
+"""
+def  MaterialChangeHistory_file_headers_list():
+    return [
+        'id',
+        'batch_log',
+        'goods_code',
+        'goods_desc',
+        'goods_std',
+        'goods_unit',
+        'change_time',
+        'in_quantity',
+        'out_quantity',
+        'change_type',
+        'opening_quantity',
+        'closing_quantity'
+    ]
+
+def  MaterialChangeHistory_cn_data_header_list():
+    return dict([
+        ('id', '序号'),
+        ('batch_log', '批次日志'),
+        ('goods_code', '货品编码'),
+        ('goods_desc', '货品描述'),
+        ('goods_std', '货品规格'),
+        ('goods_unit', '货品单位'),
+        ('change_time', '变动时间'),
+        ('in_quantity', '入库数量'),
+        ('out_quantity', '出库数量'),
+        ('change_type', '变动类型'),
+        ('opening_quantity', '期初数量'),
+        ('closing_quantity', '期末数量')
+    ])
+    
+
+class MaterialChangeHistoryRenderCN(CSVStreamingRenderer):
+    header = MaterialChangeHistory_file_headers_list()
+    labels = MaterialChangeHistory_cn_data_header_list()
+    
+def batchLog_file_headers_list():
+    return [
+        'id',
+        'log_type',
+        'batch_code',
+        'goods_code',
+        'goods_desc',
+        'goods_std',
+        'goods_in_qty',
+        'goods_out_qty',
+        'goods_unit',
+        'check_status',
+        'create_time',
+        'detail_logs',
+    ]
+
+def batchLog_cn_data_header_list():    
+    return dict([
+        ('id', '序号'),
+        ('bound', '绑定'),
+        ('batch', '批次'),
+        ('log_type', '日志类型'),
+        ('goods_code', '货品编码'),
+        ('goods_desc', '货品描述'),
+        ('goods_std', '货品规格'),
+        ('goods_unit', '货品单位'),
+        ('goods_in_qty', '新入数量'),
+        ('goods_out_qty', '新出数量'),
+        ('detail_logs', '托盘日志'),
+        ('create_time', '创建时间'),
+        ('check_status', '质检状态(0:未质检,1:已质检,2:质检不合格)')
+    ])
+
+class batchLogRenderCN(CSVStreamingRenderer):
+    header = batchLog_file_headers_list()
+    labels = batchLog_cn_data_header_list()
+    
+def ContainerDetailLog_file_headers_list():
+    return [
+        'id',
+        'batch',
+        'container_code',
+        'goods_code',
+        'goods_desc',
+        'detail_goods_qty',
+        'goods_qty',
+        'goods_out_qty',
+        'batch_goods_qty',
+        'batch_goods_in_qty',
+        'batch_goods_in_location_qty',
+        'batch_goods_out_qty',
+        'create_time',
+        'log_type',
+        'old_goods_qty',
+        'old_goods_out_qty',
+        'old_status',
+        'new_goods_qty',
+        'new_goods_out_qty',
+        'new_status',
+        'creater',
+        'tobatchlog',
+        'container_detail'
+    ]
+
+def ContainerDetailLog_cn_data_header_list():
+    return dict([
+        ('id', '序号'),
+        ('batch', '批次'),
+        ('container_code', '托盘编码'),
+        ('goods_code', '货品编码'),
+        ('goods_desc', '货品描述'),
+        ('detail_goods_qty', '托盘货品数量'),
+        ('goods_qty', '货品数量'),
+        ('goods_out_qty', '出库数量'),
+        ('batch_goods_qty', '批次货品数量'),
+        ('batch_goods_in_qty', '批次入库数量'),
+        ('batch_goods_in_location_qty', '批次入库库位数量'),
+        ('batch_goods_out_qty', '批次出库数量'),
+        ('create_time', '创建时间'),
+        ('log_type', '日志类型'),
+        ('old_goods_qty', '原数量'),
+        ('old_goods_out_qty', '原出库数量'),
+        ('old_status', '原状态'),
+        ('new_goods_qty', '新数量'),
+        ('new_goods_out_qty', '新出库数量'),
+        ('new_status', '新状态'),
+        ('creater', '操作人'),
+        ('tobatchlog', '是否转移到批次日志'),
+        ('container_detail', '托盘明细')
+    ])
+
+class ContainerDetailLogRenderCN(CSVStreamingRenderer):
+    header = ContainerDetailLog_file_headers_list()
+    labels = ContainerDetailLog_cn_data_header_list()

+ 61 - 0
reportcenter/filter.py

@@ -1,6 +1,8 @@
 from django_filters import FilterSet
 from .models import flowModel
+from django_filters import rest_framework as filters
 
+from container.models import MaterialChangeHistory,batchLogModel,ContainerDetailLogModel
 class FlowFilter(FilterSet):
     class Meta:
         model = flowModel
@@ -23,4 +25,63 @@ class FlowFilter(FilterSet):
             'goods_out': ['exact', 'gt', 'lt', 'gte', 'lte'],
             'goods_notes': ['exact', 'icontains'],
             'creator': ['exact', 'icontains']
+        }
+
+class MaterialChangeHistoryFilter(FilterSet):
+    batchLogModel_batch_code = filters.CharFilter(field_name='batch_log__bound_number', lookup_expr='icontains')
+    class Meta:
+        model = MaterialChangeHistory
+        fields = {
+            'batch_log': ['exact'],
+            'goods_code': ['exact', 'icontains'],
+            'goods_desc': ['exact', 'icontains'],
+            'goods_std': ['exact', 'icontains'],
+            'goods_unit': ['exact', 'icontains'],
+            'change_time': ['exact', 'range'],
+            'in_quantity': ['exact', 'gt', 'lt', 'gte', 'lte'],
+            'out_quantity': ['exact', 'gt', 'lt', 'gte', 'lte'],
+            'change_type': ['exact', 'icontains'],
+            'opening_quantity': ['exact', 'gt', 'lt', 'gte', 'lte'],
+            'closing_quantity': ['exact', 'gt', 'lt', 'gte', 'lte']
+        }
+
+class batchLogFilter(FilterSet):
+    batch_code__icontains = filters.CharFilter(field_name='batch__bound_number', lookup_expr='icontains')
+    bound_code__icontains = filters.CharFilter(field_name='bound__bound_code', lookup_expr='icontains')
+    class Meta:
+        model = batchLogModel
+        fields = {
+            "id": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "bound": ['exact'],
+            "batch": ['exact'],
+            "log_type": ['exact', 'icontains'],
+            "goods_code": ['exact', 'icontains'],
+            "goods_desc": ['exact', 'icontains'],
+            "goods_in_qty": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "goods_out_qty": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "detail_logs": ['exact'],
+            "create_time": ['exact', 'gt', 'gte', 'lt', 'lte', 'range'],
+        }
+
+class ContainerDetailLogFilter(FilterSet):
+    goods_code = filters.CharFilter(field_name='container_detail__goods_code', lookup_expr='exact')
+    goods_code__icontains = filters.CharFilter(field_name='container_detail__goods_code', lookup_expr='icontains')
+    container_code = filters.CharFilter(field_name='container_detail__container__container_code', lookup_expr='exact')
+    batch = filters.CharFilter(field_name='container_detail__batch__bound_number', lookup_expr='exact')
+    batch__icontains = filters.CharFilter(field_name='container_detail__batch__bound_number', lookup_expr='icontains')
+    class Meta:
+        model = ContainerDetailLogModel
+        fields = {
+            "id": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "container_detail": ['exact'],
+            "log_type": ['exact', 'icontains'],
+            "old_goods_qty": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "old_goods_out_qty": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "old_status": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "new_goods_qty": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "new_goods_out_qty": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "new_status": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "creater": ['exact', 'icontains'],
+            "create_time": ['exact', 'gt', 'gte', 'lt', 'lte', 'range'],
+         
         }

+ 16 - 0
reportcenter/migrations/0004_delete_goodssummarymodel.py

@@ -0,0 +1,16 @@
+# Generated by Django 4.1.2 on 2025-07-26 17:55
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('reportcenter', '0003_goodssummarymodel'),
+    ]
+
+    operations = [
+        migrations.DeleteModel(
+            name='GoodsSummaryModel',
+        ),
+    ]

+ 0 - 31
reportcenter/models.py

@@ -43,34 +43,3 @@ class flowModel(models.Model):
         ]
     
 
-class GoodsSummaryModel(models.Model):
-    """货品变动汇总模型(按goods_code聚合)"""
-    goods_code = models.CharField(max_length=50, verbose_name='货品编码', primary_key=True)
-    goods_desc = models.CharField(max_length=100, verbose_name='货品描述')
-    total_in = models.DecimalField(
-        max_digits=15, 
-        decimal_places=3,
-        default=Decimal('0'),
-        verbose_name='总入库量'
-    )
-    total_out = models.DecimalField(
-        max_digits=15, 
-        decimal_places=3,
-        default=Decimal('0'),
-        verbose_name='总出库量'
-    )
-    net_change = models.DecimalField(
-        max_digits=15,
-        decimal_places=3,
-        default=Decimal('0'),
-        verbose_name='净变化量'
-    )
-    last_updated = models.DateTimeField(auto_now=True, verbose_name='最后更新时间')
-
-    class Meta:
-        db_table = 'goods_summary'
-        verbose_name = '货品汇总'
-        verbose_name_plural = "货品汇总"
-
-    def __str__(self):
-        return f"{self.goods_code} | 入库:{self.total_in} 出库:{self.total_out}"

+ 91 - 1
reportcenter/serializers.py

@@ -1,6 +1,96 @@
 from rest_framework import serializers
 from .models import flowModel
-from utils import datasolve
+from container.models import MaterialChangeHistory,batchLogModel,ContainerDetailLogModel
+from decimal import Decimal
+
+
+
+class MaterialChangeHistorySerializer(serializers.ModelSerializer):
+    class Meta:
+        model = MaterialChangeHistory
+        fields = '__all__'
+
+class batchLogSerializer(serializers.ModelSerializer):
+    
+    # 定义批次日志的序列化器,用于获取操作,字段只读
+    bound_code = serializers.SerializerMethodField()
+    batch_code = serializers.CharField(source='batch.bound_number', read_only=True)
+    goods_unit = serializers.CharField(source='batch.goods_unit', read_only=True)
+    check_status = serializers.IntegerField(source='batch.check_status', read_only=True)
+    create_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M', required=False)
+    class Meta:
+        # 指定模型和排除字段
+        model = batchLogModel
+        fields= '__all__'
+        read_only_fields = ['id']
+
+    def get_bound_code(self, obj):
+        """ 动态序列化关联的批次数据 """
+        return obj.bound.bound_code if obj.bound else '无主单,涉及手动操作'
+
+class ContainerDetailLogSerializer(serializers.ModelSerializer):
+    batch = serializers.SerializerMethodField()
+    container_code = serializers.SerializerMethodField()
+    goods_code = serializers.SerializerMethodField()
+    goods_desc = serializers.SerializerMethodField()
+    detail_goods_qty = serializers.SerializerMethodField()
+    goods_qty = serializers.SerializerMethodField()
+    goods_out_qty = serializers.SerializerMethodField()
+
+    batch_goods_qty = serializers.SerializerMethodField() #计划入库数量 
+    batch_goods_in_qty = serializers.SerializerMethodField() #实际入库数量 
+    batch_goods_in_location_qty = serializers.SerializerMethodField() #实际在库数量 
+    batch_goods_out_qty = serializers.SerializerMethodField()
+    create_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M', required=False)
+    class Meta:
+        # 指定模型和排除字段
+        model = ContainerDetailLogModel
+        fields= '__all__'
+        read_only_fields = ['id']
+
+    def get_batch(self, obj):
+        """ 动态序列化关联的批次数据 """
+        return obj.container_detail.batch.bound_number if obj.container_detail.batch else 'N/A'
+    def get_container_code(self, obj):
+        return obj.container_detail.container.container_code if obj.container_detail.container.container_code else 'N/A'
+    def get_goods_code(self, obj):
+        return obj.container_detail.goods_code if obj.container_detail.goods_code else 'N/A'
+    def get_goods_desc(self, obj):
+        return obj.container_detail.goods_desc if obj.container_detail.goods_desc else 'N/A'
+    
+    # 获取托盘最小单元detail的入库数量
+    def get_detail_goods_qty(self, obj):
+        return obj.container_detail.goods_qty if obj.container_detail.goods_qty else Decimal('0')
+    
+    # 获取托盘最小单元detail的 入库操作数量
+    def get_goods_qty(self, obj):
+       
+        new_goods_qty = obj.new_goods_qty if obj.new_goods_qty else Decimal('0')
+        old_goods_qty = obj.old_goods_qty if obj.old_goods_qty else Decimal('0')
+
+        goods_qty = new_goods_qty - old_goods_qty
+        return goods_qty if goods_qty else Decimal('0')
+    
+    # 获取托盘最小单元detail的 出库操作数量
+    def get_goods_out_qty(self, obj):
+        
+        new_goods_out_qty = obj.new_goods_out_qty if obj.new_goods_out_qty else Decimal('0')
+        old_goods_out_qty = obj.old_goods_out_qty if obj.old_goods_out_qty else Decimal('0')
+        goods_out_qty = new_goods_out_qty - old_goods_out_qty
+        return goods_out_qty if goods_out_qty else Decimal('0')
+    
+    # 获取批次的计划入库数量
+    def get_batch_goods_qty(self, obj):
+        return obj.container_detail.batch.goods_qty if obj.container_detail.batch.goods_qty else Decimal('0')
+    # 获取批次的实际入库数量
+    def get_batch_goods_in_qty(self, obj):
+        return obj.container_detail.batch.goods_in_qty if obj.container_detail.batch.goods_in_qty else Decimal('0')
+    # 获取批次的实际在库数量
+    def get_batch_goods_in_location_qty(self, obj):
+        return obj.container_detail.batch.goods_in_location_qty if obj.container_detail.batch.goods_in_location_qty else Decimal('0')
+    # 获取批次的出库数量
+    def get_batch_goods_out_qty(self, obj):
+        return obj.container_detail.batch.goods_out_qty if obj.container_detail.batch.goods_out_qty else Decimal('0')
 
 class flowSerializer(serializers.ModelSerializer):
     problematic_field = serializers.IntegerField(allow_null=True)

+ 10 - 2
reportcenter/urls.py

@@ -1,8 +1,16 @@
 from django.urls import path, re_path
 from . import views
-
 urlpatterns = [
-    path(r'flow/', views.FlowsStatsViewSet.as_view({"get": "list", "post": "create"}), name="management"),
+    path(r'flow/', views.FlowsStatsViewSet.as_view({"get": "list",  }), name="management"),
     path(r'file/', views.FileListDownloadView.as_view({"get": "list"}), name="flowfile"),
 
+    path(r'MaterialChangeHistory/', views.MaterialChangeHistoryViewSet.as_view({"get": "list",  }), name="management"),
+    path(r'MaterialChangeHistory/file/', views.MaterialChangeHistoryDownloadView.as_view({"get": "list"}), name="flowfile"),    
+
+    path(r'batchLog/', views.batchLogViewSet.as_view({"get": "list",  }), name="management"),
+    path(r'batchLog/file/', views.batchLogDownloadView.as_view({"get": "list"}), name="flowfile"),    
+
+    path(r'ContainerDetailLog/', views.ContainerDetailLogViewSet.as_view({"get": "list",  }), name="management"),
+    path(r'ContainerDetailLog/file/', views.ContainerDetailLogDownloadView.as_view({"get": "list"}), name="flowfile"),    
+
 ]

+ 228 - 2
reportcenter/views.py

@@ -12,8 +12,234 @@ from rest_framework import status
 
 from reportcenter.models import flowModel as flowlist
 from . import serializers
-from .filter import FlowFilter
-from .files import  FileFlowListRenderCN
+from .filter import FlowFilter, MaterialChangeHistoryFilter, batchLogFilter, ContainerDetailLogFilter
+from .files import  FileFlowListRenderCN, MaterialChangeHistoryRenderCN, batchLogRenderCN, ContainerDetailLogRenderCN
+
+from container.models import MaterialChangeHistory,batchLogModel,ContainerDetailLogModel
+
+"""
+
+    path(r'MaterialChangeHistory/', views.MaterialChangeHistoryViewSet.as_view({"get": "list",  }), name="management"),
+    path(r'MaterialChangeHistory/file/', views.MaterialChangeHistoryDownloadView.as_view({"get": "list"}), name="flowfile"),    
+
+    path(r'batchLog/', views.batchLogViewSet.as_view({"get": "list",  }), name="management"),
+    path(r'batchLog/file/', views.batchLogDownloadView.as_view({"get": "list"}), name="flowfile"),    
+
+    path(r'ContainerDetailLog/', views.ContainerDetailLogViewSet.as_view({"get": "list",  }), name="management"),
+    path(r'ContainerDetailLog/file/', views.ContainerDetailLogDownloadView.as_view({"get": "list"}), name="flowfile"),    
+
+"""
+
+class MaterialChangeHistoryViewSet(viewsets.ModelViewSet):
+    filter_backends = [DjangoFilterBackend, OrderingFilter, ]
+    ordering_fields = ['id', "change_time", "create_time", "update_time", ]
+    filter_class = MaterialChangeHistoryFilter
+    pagination_class = MyPageNumberPagination
+
+    def get_queryset(self):
+        return MaterialChangeHistory.objects.all()
+
+    def get_serializer_class(self):
+        if self.action in ['list', 'create']:
+            return serializers.MaterialChangeHistorySerializer
+        else:
+            return self.http_method_not_allowed(request=self.request)
+        
+class MaterialChangeHistoryDownloadView(viewsets.ModelViewSet):
+    renderer_classes = (MaterialChangeHistoryRenderCN, ) + tuple(api_settings.DEFAULT_RENDERER_CLASSES)
+    filter_backends = [DjangoFilterBackend, OrderingFilter, ]
+    ordering_fields = ['id', "change_time", "create_time", "update_time", ]
+    filter_class = MaterialChangeHistoryFilter
+
+    def get_project(self):
+        try:
+            id = self.kwargs.get('pk')
+            return id
+        except:
+            return None
+
+    def get_queryset(self):
+        id = self.get_project()
+        if self.request.user:
+            if id is None:
+                return MaterialChangeHistory.objects.filter()
+            else:
+                return MaterialChangeHistory.objects.filter(id=id)
+        else:
+            return MaterialChangeHistory.objects.none()
+
+    def get_serializer_class(self):
+        if self.action in ['list']:
+            return serializers.MaterialChangeHistorySerializer
+        else:
+            return self.http_method_not_allowed(request=self.request)
+
+    def get_render(self, data):
+        # lang = self.request.META.get('HTTP_LANGUAGE')
+        # if lang:
+        #     if lang == 'zh-hans':
+        #         return FileListRenderCN().render(data)
+        #     else:
+        #         return FileListRenderEN().render(data)
+        # else:
+        #     return FileListRenderEN().render(data)
+        return MaterialChangeHistoryRenderCN().render(data)
+
+
+    def list(self, request, *args, **kwargs):
+        from datetime import datetime
+        dt = datetime.now()
+        data = (
+            serializers.MaterialChangeHistorySerializer(instance).data
+            for instance in self.filter_queryset(self.get_queryset())
+        )
+        renderer = self.get_render(data)
+        response = StreamingHttpResponse(
+            renderer,
+            content_type="text/csv"
+        )
+        response['Content-Disposition'] = "attachment; filename='MaterialChangeHistory_{}.csv'".format(str(dt.strftime('%Y%m%d%H%M%S%f')))
+        return response
+
+class batchLogViewSet(viewsets.ModelViewSet):
+    filter_backends = [DjangoFilterBackend, OrderingFilter, ]
+    ordering_fields = ['id', "create_time", "update_time", ]
+    filter_class = batchLogFilter
+    pagination_class = MyPageNumberPagination
+
+    def get_queryset(self):
+        return batchLogModel.objects.all()
+
+    def get_serializer_class(self):
+        if self.action in ['list', 'create']:
+            return serializers.batchLogSerializer
+        else:
+            return self.http_method_not_allowed(request=self.request)
+
+class batchLogDownloadView(viewsets.ModelViewSet):
+    renderer_classes = (batchLogRenderCN, ) + tuple(api_settings.DEFAULT_RENDERER_CLASSES)
+    filter_backends = [DjangoFilterBackend, OrderingFilter, ]
+    ordering_fields = ['id', "create_time", "update_time", ]
+    filter_class = batchLogFilter
+
+    def get_project(self):
+        try:
+            id = self.kwargs.get('pk')
+            return id
+        except:
+            return None
+    def get_queryset(self):
+        id = self.get_project()
+        if self.request.user:
+            if id is None:
+                return batchLogModel.objects.filter()
+            else:
+                return batchLogModel.objects.filter(id=id)
+        else:
+            return batchLogModel.objects.none()
+
+    def get_serializer_class(self):
+        if self.action in ['list']:
+            return serializers.batchLogSerializer
+        else:
+            return self.http_method_not_allowed(request=self.request)
+
+    def get_render(self, data):
+        # lang = self.request.META.get('HTTP_LANGUAGE')
+        # if lang:
+        #     if lang == 'zh-hans':
+        #         return FileListRenderCN().render(data)
+        #     else:
+        #         return FileListRenderEN().render(data)
+        # else:
+        #     return FileListRenderEN().render(data)
+        return batchLogRenderCN().render(data)
+
+
+    def list(self, request, *args, **kwargs):
+        from datetime import datetime
+        dt = datetime.now()
+        data = (
+            serializers.batchLogSerializer(instance).data
+            for instance in self.filter_queryset(self.get_queryset()))
+        renderer = self.get_render(data)
+        response = StreamingHttpResponse(
+            renderer,
+            content_type="text/csv"
+        )
+        response['Content-Disposition'] = "attachment; filename='batchLog_{}.csv'".format(str(dt.strftime('%Y%m%d%H%M%S%f')))
+        return response
+
+class ContainerDetailLogViewSet(viewsets.ModelViewSet):
+    filter_backends = [DjangoFilterBackend, OrderingFilter, ]
+    ordering_fields = ['id', "create_time", "update_time", ]
+    filter_class = ContainerDetailLogFilter
+    pagination_class = MyPageNumberPagination
+    def get_queryset(self):
+        return ContainerDetailLogModel.objects.all()
+
+    def get_serializer_class(self):
+        if self.action in ['list', 'create']:
+            return serializers.ContainerDetailLogSerializer
+        else:
+            return self.http_method_not_allowed(request=self.request)
+
+class ContainerDetailLogDownloadView(viewsets.ModelViewSet):
+    renderer_classes = (ContainerDetailLogRenderCN, ) + tuple(api_settings.DEFAULT_RENDERER_CLASSES)
+    filter_backends = [DjangoFilterBackend, OrderingFilter, ]
+    ordering_fields = ['id', "create_time", "update_time", ]
+    filter_class = ContainerDetailLogFilter
+
+    def get_project(self):
+        try:
+            id = self.kwargs.get('pk')
+            return id
+        except:
+            return None
+
+    def get_queryset(self):
+        id = self.get_project()
+        if self.request.user:
+            if id is None:
+                return ContainerDetailLogModel.objects.filter()
+            else:
+                return ContainerDetailLogModel.objects.filter(id=id)
+        else:
+            return ContainerDetailLogModel.objects.none()
+
+    def get_serializer_class(self):
+        if self.action in ['list']:
+            return serializers.ContainerDetailLogSerializer
+        else:
+            return self.http_method_not_allowed(request=self.request)
+
+    def get_render(self, data):
+        # lang = self.request.META.get('HTTP_LANGUAGE')
+        # if lang:
+        #     if lang == 'zh-hans':
+        #         return FileListRenderCN().render(data)
+        #     else:
+        #         return FileListRenderEN().render(data)
+        # else:
+        #     return FileListRenderEN().render(data)
+        return ContainerDetailLogRenderCN().render(data)
+
+    def list(self, request, *args, **kwargs):
+        from datetime import datetime
+        dt = datetime.now()
+        data = (
+            serializers.ContainerDetailLogSerializer(instance).data
+            for instance in self.filter_queryset(self.get_queryset()))
+        renderer = self.get_render(data)
+        response = StreamingHttpResponse(
+            renderer,
+            content_type="text/csv"
+        )
+        response['Content-Disposition'] = "attachment; filename='ContainerDetailLog_{}.csv'".format(str(dt.strftime('%Y%m%d%H%M%S%f')))
+        return response
+
+
+
 
 
 class FileListDownloadView(viewsets.ModelViewSet):

+ 1 - 0
templates/dist/spa/css/11.676916e5.css

@@ -0,0 +1 @@
+.q-date__calendar-item--selected[data-v-42aeea3c]{transition:all 0.3s ease;background-color:#1976d2!important}.q-date__range[data-v-42aeea3c]{background-color:rgba(25,118,210,0.1)}.custom-title[data-v-42aeea3c]{font-size:0.9rem;font-weight:500}.custom-timeline[data-v-42aeea3c]{--q-timeline-color:#e0e0e0}.custom-node .q-timeline__dot[data-v-42aeea3c]{background:#485573!important;border:2px solid #5c6b8c!important}.custom-node .q-timeline__content[data-v-42aeea3c]{color:#485573}

+ 1 - 0
templates/dist/spa/css/12.be283b5e.css

@@ -0,0 +1 @@
+.q-date__calendar-item--selected[data-v-4233add0]{transition:all 0.3s ease;background-color:#1976d2!important}.q-date__range[data-v-4233add0]{background-color:rgba(25,118,210,0.1)}.custom-title[data-v-4233add0]{font-size:0.9rem;font-weight:500}.custom-timeline[data-v-4233add0]{--q-timeline-color:#e0e0e0}.custom-node .q-timeline__dot[data-v-4233add0]{background:#485573!important;border:2px solid #5c6b8c!important}.custom-node .q-timeline__content[data-v-4233add0]{color:#485573}

templates/dist/spa/css/11.eb31c91a.css → templates/dist/spa/css/13.eb31c91a.css


templates/dist/spa/css/12.f57b1220.css → templates/dist/spa/css/14.f57b1220.css


templates/dist/spa/css/13.65fea8cc.css → templates/dist/spa/css/15.65fea8cc.css


templates/dist/spa/css/14.296f042c.css → templates/dist/spa/css/16.296f042c.css


templates/dist/spa/css/15.865457f7.css → templates/dist/spa/css/17.865457f7.css


templates/dist/spa/css/16.a5d7d7ca.css → templates/dist/spa/css/18.a5d7d7ca.css


templates/dist/spa/css/17.bb6b4a4d.css → templates/dist/spa/css/19.bb6b4a4d.css


templates/dist/spa/css/18.601677c3.css → templates/dist/spa/css/20.601677c3.css


templates/dist/spa/css/19.71123cd8.css → templates/dist/spa/css/21.71123cd8.css


templates/dist/spa/css/20.f721cf95.css → templates/dist/spa/css/22.f721cf95.css


templates/dist/spa/css/21.ed8e81e9.css → templates/dist/spa/css/23.ed8e81e9.css


+ 0 - 1
templates/dist/spa/css/24.20ec1b8f.css

@@ -1 +0,0 @@
-.q-date__calendar-item--selected[data-v-71400de4]{transition:all 0.3s ease;background-color:#1976d2!important}.q-date__range[data-v-71400de4]{background-color:rgba(25,118,210,0.1)}[data-v-71400de4] .q-field__label{margin-top:8px;align-self:center}[data-v-71400de4] .q-field__control-container{padding-left:50px;margin-top:-5px}

templates/dist/spa/css/22.eed22a1c.css → templates/dist/spa/css/24.eed22a1c.css


templates/dist/spa/css/23.4b9e275f.css → templates/dist/spa/css/25.4b9e275f.css


+ 1 - 0
templates/dist/spa/css/26.e3a9ceb7.css

@@ -0,0 +1 @@
+.q-date__calendar-item--selected[data-v-7fd21ee6]{transition:all 0.3s ease;background-color:#1976d2!important}.q-date__range[data-v-7fd21ee6]{background-color:rgba(25,118,210,0.1)}[data-v-7fd21ee6] .q-field__label{margin-top:8px;align-self:center}[data-v-7fd21ee6] .q-field__control-container{padding-left:50px;margin-top:-5px}

templates/dist/spa/css/25.01a9029f.css → templates/dist/spa/css/27.01a9029f.css


templates/dist/spa/css/26.9b0c5133.css → templates/dist/spa/css/28.9b0c5133.css


templates/dist/spa/css/27.0d4c4716.css → templates/dist/spa/css/29.0d4c4716.css


templates/dist/spa/css/28.e2633675.css → templates/dist/spa/css/30.e2633675.css


templates/dist/spa/css/29.8f3f6188.css → templates/dist/spa/css/31.8f3f6188.css


templates/dist/spa/css/30.97f5bf6a.css → templates/dist/spa/css/32.97f5bf6a.css


templates/dist/spa/css/31.9478c981.css → templates/dist/spa/css/33.9478c981.css


templates/dist/spa/css/32.c4652654.css → templates/dist/spa/css/34.c4652654.css


templates/dist/spa/css/33.7a23b7fb.css → templates/dist/spa/css/35.7a23b7fb.css


templates/dist/spa/css/34.0faa4aeb.css → templates/dist/spa/css/36.0faa4aeb.css


+ 0 - 1
templates/dist/spa/css/7.3b0ca86f.css

@@ -1 +0,0 @@
-.q-date__calendar-item--selected[data-v-3c509fda]{transition:all 0.3s ease;background-color:#1976d2!important}.q-date__range[data-v-3c509fda]{background-color:rgba(25,118,210,0.1)}.custom-title[data-v-3c509fda]{font-size:0.9rem;font-weight:500}.custom-timeline[data-v-3c509fda]{--q-timeline-color:#e0e0e0}.custom-node .q-timeline__dot[data-v-3c509fda]{background:#485573!important;border:2px solid #5c6b8c!important}.custom-node .q-timeline__content[data-v-3c509fda]{color:#485573}

+ 1 - 0
templates/dist/spa/css/7.4ad5acff.css

@@ -0,0 +1 @@
+.q-date__calendar-item--selected[data-v-f1f025a6]{transition:all 0.3s ease;background-color:#1976d2!important}.q-date__range[data-v-f1f025a6]{background-color:rgba(25,118,210,0.1)}.custom-title[data-v-f1f025a6]{font-size:0.9rem;font-weight:500}.custom-timeline[data-v-f1f025a6]{--q-timeline-color:#e0e0e0}.custom-node .q-timeline__dot[data-v-f1f025a6]{background:#485573!important;border:2px solid #5c6b8c!important}.custom-node .q-timeline__content[data-v-f1f025a6]{color:#485573}

+ 0 - 1
templates/dist/spa/css/8.88208c94.css

@@ -1 +0,0 @@
-.q-date__calendar-item--selected[data-v-9265d28c]{transition:all 0.3s ease;background-color:#1976d2!important}.q-date__range[data-v-9265d28c]{background-color:rgba(25,118,210,0.1)}.custom-title[data-v-9265d28c]{font-size:0.9rem;font-weight:500}.custom-timeline[data-v-9265d28c]{--q-timeline-color:#e0e0e0}.custom-node .q-timeline__dot[data-v-9265d28c]{background:#485573!important;border:2px solid #5c6b8c!important}.custom-node .q-timeline__content[data-v-9265d28c]{color:#485573}

+ 1 - 0
templates/dist/spa/css/8.958ee95c.css

@@ -0,0 +1 @@
+.q-date__calendar-item--selected[data-v-7f5e95fc]{transition:all 0.3s ease;background-color:#1976d2!important}.q-date__range[data-v-7f5e95fc]{background-color:rgba(25,118,210,0.1)}.custom-title[data-v-7f5e95fc]{font-size:0.9rem;font-weight:500}.custom-timeline[data-v-7f5e95fc]{--q-timeline-color:#e0e0e0}.custom-node .q-timeline__dot[data-v-7f5e95fc]{background:#485573!important;border:2px solid #5c6b8c!important}.custom-node .q-timeline__content[data-v-7f5e95fc]{color:#485573}

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 1
templates/dist/spa/index.html


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 0
templates/dist/spa/js/11.3bc8a128.js


BIN
templates/dist/spa/js/11.3bc8a128.js.gz


BIN
templates/dist/spa/js/11.d2c5553c.js.gz


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 0
templates/dist/spa/js/12.0737aae8.js


BIN
templates/dist/spa/js/12.0737aae8.js.gz


BIN
templates/dist/spa/js/13.2df05f8d.js.gz


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 1
templates/dist/spa/js/11.d2c5553c.js


BIN
templates/dist/spa/js/13.37ae32b9.js.gz


BIN
templates/dist/spa/js/14.67f088cd.js.gz


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 1
templates/dist/spa/js/12.10622ab0.js


BIN
templates/dist/spa/js/12.10622ab0.js.gz


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 1
templates/dist/spa/js/13.2df05f8d.js


BIN
templates/dist/spa/js/15.0995ef23.js.gz


BIN
templates/dist/spa/js/16.0827bf08.js.gz


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 1
templates/dist/spa/js/14.67f088cd.js


BIN
templates/dist/spa/js/16.742400d0.js.gz


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 1
templates/dist/spa/js/15.a1d31fc2.js


BIN
templates/dist/spa/js/15.a1d31fc2.js.gz


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 1
templates/dist/spa/js/16.0827bf08.js


BIN
templates/dist/spa/js/18.23d853e1.js.gz


BIN
templates/dist/spa/js/18.ca785134.js.gz


BIN
templates/dist/spa/js/19.139fb325.js.gz


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 1
templates/dist/spa/js/17.9c73eb9c.js


BIN
templates/dist/spa/js/17.9c73eb9c.js.gz


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 1
templates/dist/spa/js/18.ca785134.js


BIN
templates/dist/spa/js/20.0f678196.js.gz


BIN
templates/dist/spa/js/20.13c39907.js.gz


BIN
templates/dist/spa/js/21.3722ac7c.js.gz


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 1
templates/dist/spa/js/19.139fb325.js


BIN
templates/dist/spa/js/21.8c4f178b.js.gz


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 1
templates/dist/spa/js/20.13c39907.js


BIN
templates/dist/spa/js/22.0807fc2b.js.gz


BIN
templates/dist/spa/js/22.746a0ce0.js.gz


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 1
templates/dist/spa/js/21.3722ac7c.js


BIN
templates/dist/spa/js/23.07b5e369.js.gz


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 1
templates/dist/spa/js/22.746a0ce0.js


BIN
templates/dist/spa/js/24.18faef2e.js.gz


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 1
templates/dist/spa/js/24.d98a09d7.js


BIN
templates/dist/spa/js/24.d98a09d7.js.gz


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 1
templates/dist/spa/js/23.aa80f2a2.js


BIN
templates/dist/spa/js/23.aa80f2a2.js.gz


BIN
templates/dist/spa/js/25.51321a20.js.gz


BIN
templates/dist/spa/js/26.336b155c.js.gz


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 0
templates/dist/spa/js/26.463d394c.js


BIN
templates/dist/spa/js/26.463d394c.js.gz


+ 0 - 0
templates/dist/spa/js/25.51321a20.js


Vissa filer visades inte eftersom för många filer har ändrats