utils.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. from django.db.models import Sum, F
  2. from .models import ContainerListModel,ContainerDetailModel,ContainerOperationModel,TaskModel,ContainerWCSModel
  3. from bound.models import BoundBatchModel,BoundDetailModel,BoundListModel
  4. from .serializers import *
  5. from rest_framework.response import Response
  6. from rest_framework.views import APIView
  7. from django.utils import timezone
  8. from decimal import Decimal
  9. import logging
  10. logger = logging.getLogger(__name__)
  11. def update_container_category_for_container(container_id):
  12. """
  13. 按需更新单个托盘的分类和批次信息
  14. """
  15. try:
  16. container = ContainerListModel.objects.get(id=container_id)
  17. # 获取托盘所有有效物料详情
  18. details = container.details.filter(
  19. is_delete=False
  20. ).exclude(status=3).annotate(
  21. available_qty=F('goods_qty') - F('goods_out_qty')
  22. ).filter(available_qty__gt=0)
  23. # 计算总物料量
  24. total_qty = details.aggregate(total=Sum('available_qty'))['total'] or 0
  25. # 收集批次信息
  26. batch_info = []
  27. for detail in details:
  28. batch = detail.batch
  29. if batch:
  30. batch_info.append({
  31. "batch_id": batch.id,
  32. "check_status": batch.check_status,
  33. "bound_number": batch.bound_number,
  34. "qty": float(detail.available_qty) # 转换为 float
  35. })
  36. # 确定托盘分类
  37. if total_qty == 0:
  38. # 空盘
  39. category = 2 if container.status == 2 else 3 # 2=在库, 3=不在库
  40. else:
  41. # 有物料
  42. batch_count = details.values('batch').distinct().count()
  43. category = 0 if batch_count == 1 else 1 # 0=整盘, 1=散盘
  44. # 更新
  45. container.category = category
  46. container.batch_info = batch_info
  47. container.save(update_fields=['category', 'batch_info'])
  48. return True
  49. except Exception as e:
  50. logger.error(f"更新托盘分类时出错: {e}", exc_info=True)
  51. return False
  52. def batch_update_container_categories(container_ids):
  53. """
  54. 批量更新多个托盘的分类和批次信息
  55. """
  56. updated_count = 0
  57. error_ids = []
  58. for container_id in container_ids:
  59. try:
  60. container = ContainerListModel.objects.get(id=container_id)
  61. # 获取托盘所有有效物料详情
  62. details = container.details.filter(
  63. is_delete=False
  64. ).exclude(status=3).annotate(
  65. available_qty=F('goods_qty') - F('goods_out_qty')
  66. ).filter(available_qty__gt=0)
  67. # 计算总物料量
  68. total_qty = details.aggregate(total=Sum('available_qty'))['total'] or 0
  69. # 收集批次信息,合并批次数量
  70. batch_info = []
  71. batch_map = {}
  72. for detail in details:
  73. batch = detail.batch
  74. if batch:
  75. if batch.id in batch_map:
  76. batch_map[batch.id]['qty'] += float(detail.available_qty) # 转换为 float
  77. else:
  78. batch_map[batch.id] = {
  79. "batch_id": batch.id,
  80. "check_status": batch.check_status,
  81. "bound_number": batch.bound_number,
  82. "qty": float(detail.available_qty) # 转换为 float
  83. }
  84. batch_info = list(batch_map.values())
  85. # 确定托盘分类
  86. if total_qty == 0:
  87. # 空盘
  88. category = 2 if container.current_location not in ['N/A', '103','203'] else 3 # 2=在库, 3=不在库
  89. else:
  90. # 有物料
  91. batch_count = details.values('batch').distinct().count()
  92. category = 0 if batch_count == 1 else 1 # 0=整盘, 1=散盘
  93. # 更新
  94. container.category = category
  95. container.batch_info = batch_info
  96. container.save(update_fields=['category', 'batch_info'])
  97. updated_count += 1
  98. except Exception as e:
  99. logger.error(f"更新托盘 {container_id} 分类时出错: {e}", exc_info=True)
  100. error_ids.append(container_id)
  101. return {
  102. "updated": updated_count,
  103. "errors": error_ids,
  104. "total": len(container_ids)
  105. }
  106. def update_container_categories_task():
  107. """后台任务:更新所有托盘的分类和批次信息"""
  108. container_ids = ContainerListModel.objects.values_list('id', flat=True)
  109. result = batch_update_container_categories(list(container_ids))
  110. logger.info(f"后台任务更新托盘分类: 成功 {result['updated']}, 失败 {len(result['errors'])}")
  111. logger.info(f"后台任务更新托盘分类: 成功 {result['updated']}, 失败 {len(result['errors'])}")
  112. return result
  113. def reconcile_material_history():
  114. """从后往前修复物料变动历史记录"""
  115. from container.models import MaterialStatistics, MaterialChangeHistory
  116. all_materials = MaterialStatistics.objects.all()
  117. for material in all_materials:
  118. # 获取该物料的所有变动历史记录(按时间倒序)
  119. history_records = MaterialChangeHistory.objects.filter(
  120. goods_code = material.goods_code
  121. ).order_by('-change_time')
  122. if not history_records.exists():
  123. continue
  124. # 从物料统计中获取当前库存作为最后一个记录的期末数量
  125. current_quantity = material.total_quantity
  126. # 从后往前处理每个历史记录
  127. for i, record in enumerate(history_records):
  128. # 最后一个记录:使用当前库存作为期末数量
  129. if i == 0:
  130. record.closing_quantity = current_quantity
  131. else:
  132. # 前一个记录的期末数量就是当前记录的期初数量
  133. record.closing_quantity = history_records[i-1].opening_quantity
  134. # 计算期初数量(期末 + 出库 - 入库)
  135. record.opening_quantity = (
  136. record.closing_quantity
  137. + record.out_quantity
  138. - record.in_quantity
  139. )
  140. # 更新记录
  141. record.save()
  142. # 更新当前数量为当前记录的期初数量(用于下一个记录)
  143. current_quantity = record.opening_quantity
  144. # 验证第一个记录的期初数量是否合理
  145. first_record = history_records.last()
  146. if first_record.opening_quantity < 0:
  147. logger.info(f"警告:物料 {material.goods_code} 的期初库存为负值: {first_record.opening_quantity}")
  148. # # 更新物料统计的总库存(应该与最后一个记录的期末数量一致)
  149. # material.total_quantity = history_records.first().closing_quantity
  150. # material.save()
  151. logger.info("物料变动历史记录修复完成")