models.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. from django.db import models
  2. from bound.models import BoundBatchModel,BoundDetailModel,OutBatchModel,BoundListModel,BoundListModel
  3. from django.utils import timezone
  4. from django.db.models.signals import pre_save, post_save
  5. from django.dispatch import receiver
  6. # Create your models here.
  7. # 主表:托盘数据
  8. class ContainerListModel(models.Model):
  9. CONTAINER_STATUS = (
  10. (0, '空置'),
  11. (1, '入库中'),
  12. (2, '在库'),
  13. (3, '出库中'),
  14. (4, '已出库'),
  15. (5, '空托盘组')
  16. )
  17. container_code = models.IntegerField( verbose_name='托盘编号',unique=True)
  18. current_location = models.CharField(max_length=50, verbose_name='当前库位', default='N/A')
  19. target_location = models.CharField(max_length=50, verbose_name='目标库位', default='N/A')
  20. available= models.BooleanField(default=True, verbose_name='可用')
  21. status = models.IntegerField(choices=CONTAINER_STATUS, default=0, verbose_name='托盘状态')
  22. last_operation = models.DateTimeField(auto_now=True, verbose_name='最后操作时间')
  23. class Meta:
  24. db_table = 'container_list'
  25. verbose_name = 'ContainerList'
  26. verbose_name_plural = "ContainerList"
  27. ordering = ['-container_code']
  28. class ContainerDetailModel(models.Model):
  29. BATCH_STATUS=(
  30. (0, '空盘'),
  31. (1, '组盘'),
  32. (2, '在库'),
  33. (3, '已出库')
  34. )
  35. BATCH_CLASS = (
  36. (1, '成品'),
  37. (2, '空盘'),
  38. (3, '散盘'),
  39. )
  40. month = models.IntegerField(verbose_name='月份')
  41. container = models.ForeignKey(ContainerListModel, on_delete=models.CASCADE, related_name='details')
  42. batch = models.ForeignKey(BoundBatchModel, on_delete=models.CASCADE, verbose_name='批次',blank=True, null=True)
  43. goods_class = models.IntegerField(verbose_name='货品类别',choices=BATCH_CLASS, default=1)
  44. goods_code = models.CharField(max_length=50, verbose_name='货品编码')
  45. goods_desc = models.CharField(max_length=100, verbose_name='货品描述')
  46. goods_qty = models.IntegerField(verbose_name='数量')
  47. goods_out_qty = models.IntegerField(verbose_name='出库数量', default=0)
  48. goods_weight = models.DecimalField(max_digits=10, decimal_places=3, verbose_name='重量')
  49. status = models.IntegerField(choices=BATCH_STATUS,default=0, verbose_name='状态')
  50. creater = models.CharField(max_length=50, verbose_name='创建人')
  51. create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
  52. update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
  53. is_delete = models.BooleanField(default=False, verbose_name='是否删除')
  54. class Meta:
  55. db_table = 'container_detail'
  56. verbose_name = 'ContainerDetail'
  57. verbose_name_plural = "ContainerDetail"
  58. ordering = ['-id']
  59. indexes = [
  60. models.Index(fields=['container']),
  61. models.Index(fields=['batch']),
  62. models.Index(fields=['goods_code']),
  63. models.Index(fields=['status']),
  64. ]
  65. def __str__(self):
  66. return f"{self.container.container_code} - {self.batch.bound_number if self.batch else 'N/A'} - {self.goods_code}"
  67. def save(self, *args, **kwargs):
  68. """
  69. 更新托盘上的物料数量,更新批次上的
  70. goods_in_qty(组盘数目)
  71. ,goods_in_location_qty(在库数目)
  72. ,goods_out_qty(出库数目)
  73. """
  74. # 在保存前调用信号处理器
  75. if self.pk:
  76. # 获取状态变化前的状态
  77. original = ContainerDetailModel.objects.get(pk=self.pk)
  78. original_status = original.status
  79. super().save(*args, **kwargs)
  80. # 更新批次数据
  81. if self.batch:
  82. # 避免循环更新 - 仅在数量相关字段变更时才更新
  83. if 'update_fields' not in kwargs or any(field in kwargs['update_fields'] for field in ['goods_qty', 'goods_out_qty','is_delete']):
  84. self.update_batch_stats()
  85. # # 根据出库数量更新状态
  86. # if self.goods_out_qty > self.goods_qty:
  87. # # 出库数量不能大于总数量
  88. # self.goods_out_qty = self.goods_qty
  89. # self.save(update_fields=['goods_out_qty'])
  90. # 根据当前数量状态更新状态
  91. if self.goods_out_qty >= self.goods_qty:
  92. self.status = 3 # 已出库
  93. super().save(*args, **kwargs)
  94. elif self.goods_qty - self.goods_out_qty > 0 and self.goods_out_qty > 0:
  95. self.status = 2 # 在库
  96. super().save(*args, **kwargs)
  97. if self.status == 3 and self.goods_qty - self.goods_out_qty > 0 :
  98. self.status = 2 # 在库
  99. super().save(*args, **kwargs)
  100. # 更新货物分类(关键修改点)
  101. if not kwargs.get('skip_class_update', False):
  102. # 获取托盘上所有物料(排除已删除和已出库的)
  103. details = ContainerDetailModel.objects.filter(
  104. container=self.container.id,
  105. is_delete=False
  106. ).exclude(status=3)
  107. # 统计不同批次数目
  108. batch_count = details.values('batch').distinct().count()
  109. new_class = 1 if batch_count == 1 else 3
  110. # 批量更新所有相关物料的goods_class
  111. details.exclude(goods_class=new_class).update(goods_class=new_class)
  112. # 如果当前物料的class需要更新
  113. if self.goods_class != new_class:
  114. self.goods_class = new_class
  115. super().save(update_fields=['goods_class'])
  116. def update_batch_stats(self):
  117. """更新批次的统计数据"""
  118. if not self.batch:
  119. return
  120. # 聚合托盘明细数据
  121. stats = ContainerDetailModel.objects.filter(
  122. batch=self.batch,
  123. is_delete=False
  124. ).aggregate(
  125. total_qty=models.Sum('goods_qty'),
  126. total_out_qty=models.Sum('goods_out_qty')
  127. )
  128. # 更新批次数据
  129. self.batch.goods_in_qty = stats['total_qty'] or 0
  130. self.batch.goods_in_location_qty = (stats['total_qty'] or 0) - (stats['total_out_qty'] or 0)
  131. self.batch.goods_out_qty = stats['total_out_qty'] or 0
  132. self.batch.save()
  133. class ContainerDetailLogModel(models.Model):
  134. """托盘明细变更日志模型"""
  135. LOG_TYPES = (
  136. ('create', '创建'),
  137. ('update', '更新'),
  138. ('delete', '删除'),
  139. ('out', '出库'),
  140. ('status_change', '状态变更'),
  141. )
  142. # 关联的托盘明细
  143. container_detail = models.ForeignKey(
  144. ContainerDetailModel,
  145. on_delete=models.CASCADE,
  146. related_name='logs'
  147. )
  148. # 日志类型
  149. log_type = models.CharField(
  150. max_length=20,
  151. choices=LOG_TYPES,
  152. verbose_name='日志类型'
  153. )
  154. # 原值
  155. old_goods_qty = models.IntegerField(verbose_name='原数量', null=True, blank=True)
  156. old_goods_out_qty = models.IntegerField(verbose_name='原出库数量', null=True, blank=True)
  157. old_status = models.IntegerField(
  158. choices=ContainerDetailModel.BATCH_STATUS,
  159. null=True,
  160. blank=True,
  161. verbose_name='原状态'
  162. )
  163. # 新值
  164. new_goods_qty = models.IntegerField(verbose_name='新数量', null=True, blank=True)
  165. new_goods_out_qty = models.IntegerField(verbose_name='新出库数量', null=True, blank=True)
  166. new_status = models.IntegerField(
  167. choices=ContainerDetailModel.BATCH_STATUS,
  168. null=True,
  169. blank=True,
  170. verbose_name='新状态'
  171. )
  172. # 元信息
  173. creater = models.CharField(max_length=50, verbose_name='操作人')
  174. create_time = models.DateTimeField(auto_now_add=True, verbose_name='操作时间')
  175. class Meta:
  176. db_table = 'container_detail_log'
  177. verbose_name = '托盘明细变更日志'
  178. verbose_name_plural = "托盘明细变更日志"
  179. ordering = ['-create_time']
  180. def __str__(self):
  181. return f"{self.container_detail} - {self.get_log_type_display()} - {self.create_time.strftime('%Y-%m-%d %H:%M:%S')}"
  182. @receiver(pre_save, sender=ContainerDetailModel)
  183. def container_detail_pre_save(sender, instance, **kwargs):
  184. """在托盘明细保存前记录变更"""
  185. # 检查是否是新建对象
  186. if instance.pk:
  187. try:
  188. # 获取数据库中的当前对象
  189. old_instance = ContainerDetailModel.objects.get(pk=instance.pk)
  190. except ContainerDetailModel.DoesNotExist:
  191. return
  192. # 创建日志记录
  193. log = ContainerDetailLogModel(container_detail=instance, creater=instance.creater)
  194. # 检查数量变化
  195. if old_instance.goods_qty != instance.goods_qty:
  196. log.old_goods_qty = old_instance.goods_qty
  197. log.new_goods_qty = instance.goods_qty
  198. log.log_type = 'update'
  199. log.save()
  200. # 检查出库数量变化
  201. if old_instance.goods_out_qty != instance.goods_out_qty:
  202. log.old_goods_out_qty = old_instance.goods_out_qty
  203. log.new_goods_out_qty = instance.goods_out_qty
  204. log.log_type = 'out'
  205. log.save()
  206. # # 检查状态变化
  207. # if old_instance.status != instance.status:
  208. # log.old_status = old_instance.status
  209. # log.new_status = instance.status
  210. # log.log_type = 'status_change'
  211. # # 保存日志记录
  212. # if hasattr(log, 'log_type'):
  213. # log.save()
  214. @receiver(post_save, sender=ContainerDetailModel)
  215. def container_detail_post_save(sender, instance, created, **kwargs):
  216. """在托盘明细保存后记录创建"""
  217. if created:
  218. # 为新建的对象创建日志
  219. ContainerDetailLogModel.objects.create(
  220. container_detail=instance,
  221. log_type='create',
  222. creater=instance.creater,
  223. new_goods_qty=instance.goods_qty,
  224. new_status=instance.status
  225. )
  226. # 明细表:操作记录 记录每次出入库的记录,使用goods来进行盘点,使用托盘码来进行托盘的操作记录
  227. class ContainerOperationModel(models.Model):
  228. OPERATION_TYPES = (
  229. ('container','组盘'),
  230. ('inbound', '入库'),
  231. ('outbound', '出库'),
  232. ('adjust', '调整'),
  233. )
  234. month = models.IntegerField(verbose_name='月份')
  235. container = models.ForeignKey(ContainerListModel, on_delete=models.CASCADE, related_name='operations')
  236. operation_type = models.CharField(max_length=20, choices=OPERATION_TYPES, verbose_name='操作类型')
  237. bound_id = models.IntegerField(verbose_name='出库申请', null=True, blank=True)
  238. batch = models.ForeignKey(BoundBatchModel, on_delete=models.CASCADE, verbose_name='批次',null=True, blank=True)
  239. goods_code = models.CharField(max_length=50, verbose_name='货品编码')
  240. goods_desc = models.CharField(max_length=100, verbose_name='货品描述')
  241. goods_qty = models.IntegerField(verbose_name='数量')
  242. goods_weight = models.DecimalField(max_digits=10, decimal_places=3, verbose_name='重量')
  243. operator = models.CharField(max_length=50, verbose_name='操作人')
  244. timestamp = models.DateTimeField(auto_now_add=True, verbose_name='操作时间')
  245. from_location = models.CharField(max_length=50, null=True, verbose_name='原库位')
  246. to_location = models.CharField(max_length=50, null=True, verbose_name='目标库位')
  247. memo = models.TextField(null=True, verbose_name='备注')
  248. is_delete = models.BooleanField(default=False, verbose_name='是否删除')
  249. class Meta:
  250. db_table = 'container_operation'
  251. verbose_name = 'ContainerOperation'
  252. verbose_name_plural = "ContainerOperation"
  253. ordering = ['-timestamp']
  254. class ContainerWCSModel(models.Model):
  255. TASK_STATUS = (
  256. (100, '等待中'),
  257. (101, '处理中'),
  258. (102, '已暂停'),
  259. (103, '入库中'),
  260. (200, '已发送'),
  261. (300, '已完成'),
  262. (400, '失败'),
  263. )
  264. taskid = models.CharField(max_length=50, verbose_name='任务ID')
  265. batch = models.ForeignKey(BoundBatchModel, on_delete=models.CASCADE, verbose_name='关联批次',null=True, blank=True )
  266. batch_out = models.ForeignKey(OutBatchModel, on_delete=models.CASCADE, verbose_name='出库批次' , null=True, blank=True )
  267. bound_list = models.ForeignKey(BoundListModel, on_delete=models.CASCADE, verbose_name='关联出库单',null=True, blank=True )
  268. batch_number = models.CharField(max_length=50, verbose_name='批次号',null=True, blank=True )
  269. sequence = models.BigIntegerField(verbose_name='任务顺序')
  270. priority = models.IntegerField(default=100, verbose_name='优先级')
  271. month = models.IntegerField(verbose_name='月份')
  272. tasktype = models.CharField(max_length=50, verbose_name='任务类型')
  273. tasknumber = models.IntegerField(verbose_name='任务号',unique=True)
  274. order_number = models.IntegerField(verbose_name='c_number')
  275. container = models.CharField(max_length=50, verbose_name='托盘号')
  276. current_location = models.CharField(max_length=50, verbose_name='当前库位')
  277. target_location = models.CharField(max_length=50, verbose_name='目标库位')
  278. message = models.TextField(verbose_name='消息')
  279. working = models.IntegerField(default = 1,verbose_name='工作状态')
  280. status = models.IntegerField(choices=TASK_STATUS, default=100, verbose_name='状态')
  281. create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
  282. is_delete = models.BooleanField(default=False, verbose_name='是否删除')
  283. class Meta:
  284. db_table = 'container_wcs'
  285. verbose_name = 'ContainerWCS'
  286. verbose_name_plural = "ContainerWCS"
  287. ordering = ['-create_time']
  288. def to_dict(self):
  289. return {
  290. 'container': self.container,
  291. 'current_location': self.current_location,
  292. 'month' : self.month,
  293. 'target_location': self.target_location,
  294. 'tasktype': self.tasktype,
  295. 'taskid': self.taskid,
  296. 'taskNumber': self.tasknumber-20000000000,
  297. 'message': self.message,
  298. 'container': self.container,
  299. 'status': self.status
  300. }
  301. def __str__(self):
  302. return f"{self.taskid} - {self.get_status_display()}"
  303. # 这里的批次详情是主入库申请单下的子批次
  304. class TaskModel(models.Model):
  305. task_wcs = models.ForeignKey(ContainerWCSModel, on_delete=models.CASCADE, related_name='tasks')
  306. batch_detail = models.ForeignKey(BoundDetailModel, on_delete=models.CASCADE, verbose_name='批次详情')
  307. container_detail = models.ForeignKey(ContainerDetailModel, on_delete=models.CASCADE, verbose_name='托盘明细')
  308. class Meta:
  309. db_table = 'task'
  310. verbose_name = 'Task'
  311. verbose_name_plural = "Task"
  312. ordering = ['-id']
  313. class out_batch_detail(models.Model):
  314. out_bound = models.ForeignKey(BoundListModel, on_delete=models.CASCADE, related_name='out_batch_details')
  315. container = models.ForeignKey(ContainerListModel, on_delete=models.CASCADE, related_name='out_batch_details')
  316. container_detail = models.ForeignKey(ContainerDetailModel, on_delete=models.CASCADE, verbose_name='托盘明细')
  317. out_goods_qty = models.IntegerField(verbose_name='数量')
  318. last_out_goods_qty = models.IntegerField(verbose_name='上次出库数量', default=0)
  319. working = models.IntegerField(default = 1,verbose_name='工作状态')
  320. is_delete = models.BooleanField(default=False, verbose_name='是否删除')
  321. class Meta:
  322. db_table = 'out_batch_detail'
  323. verbose_name = 'OutBatchDetail'
  324. verbose_name_plural = "OutBatchDetail"
  325. ordering = ['container']