models.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  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']):
  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. def update_batch_stats(self):
  101. """更新批次的统计数据"""
  102. if not self.batch:
  103. return
  104. # 聚合托盘明细数据
  105. stats = ContainerDetailModel.objects.filter(
  106. batch=self.batch,
  107. is_delete=False
  108. ).aggregate(
  109. total_qty=models.Sum('goods_qty'),
  110. total_out_qty=models.Sum('goods_out_qty')
  111. )
  112. # 更新批次数据
  113. self.batch.goods_in_qty = stats['total_qty'] or 0
  114. self.batch.goods_in_location_qty = (stats['total_qty'] or 0) - (stats['total_out_qty'] or 0)
  115. self.batch.goods_out_qty = stats['total_out_qty'] or 0
  116. self.batch.save()
  117. class ContainerDetailLogModel(models.Model):
  118. """托盘明细变更日志模型"""
  119. LOG_TYPES = (
  120. ('create', '创建'),
  121. ('update', '更新'),
  122. ('delete', '删除'),
  123. ('out', '出库'),
  124. ('status_change', '状态变更'),
  125. )
  126. # 关联的托盘明细
  127. container_detail = models.ForeignKey(
  128. ContainerDetailModel,
  129. on_delete=models.CASCADE,
  130. related_name='logs'
  131. )
  132. # 日志类型
  133. log_type = models.CharField(
  134. max_length=20,
  135. choices=LOG_TYPES,
  136. verbose_name='日志类型'
  137. )
  138. # 原值
  139. old_goods_qty = models.IntegerField(verbose_name='原数量', null=True, blank=True)
  140. old_goods_out_qty = models.IntegerField(verbose_name='原出库数量', null=True, blank=True)
  141. old_status = models.IntegerField(
  142. choices=ContainerDetailModel.BATCH_STATUS,
  143. null=True,
  144. blank=True,
  145. verbose_name='原状态'
  146. )
  147. # 新值
  148. new_goods_qty = models.IntegerField(verbose_name='新数量', null=True, blank=True)
  149. new_goods_out_qty = models.IntegerField(verbose_name='新出库数量', null=True, blank=True)
  150. new_status = models.IntegerField(
  151. choices=ContainerDetailModel.BATCH_STATUS,
  152. null=True,
  153. blank=True,
  154. verbose_name='新状态'
  155. )
  156. # 元信息
  157. creater = models.CharField(max_length=50, verbose_name='操作人')
  158. create_time = models.DateTimeField(auto_now_add=True, verbose_name='操作时间')
  159. class Meta:
  160. db_table = 'container_detail_log'
  161. verbose_name = '托盘明细变更日志'
  162. verbose_name_plural = "托盘明细变更日志"
  163. ordering = ['-create_time']
  164. def __str__(self):
  165. return f"{self.container_detail} - {self.get_log_type_display()} - {self.create_time.strftime('%Y-%m-%d %H:%M:%S')}"
  166. @receiver(pre_save, sender=ContainerDetailModel)
  167. def container_detail_pre_save(sender, instance, **kwargs):
  168. """在托盘明细保存前记录变更"""
  169. # 检查是否是新建对象
  170. if instance.pk:
  171. try:
  172. # 获取数据库中的当前对象
  173. old_instance = ContainerDetailModel.objects.get(pk=instance.pk)
  174. except ContainerDetailModel.DoesNotExist:
  175. return
  176. # 创建日志记录
  177. log = ContainerDetailLogModel(container_detail=instance, creater=instance.creater)
  178. # 检查数量变化
  179. if old_instance.goods_qty != instance.goods_qty:
  180. log.old_goods_qty = old_instance.goods_qty
  181. log.new_goods_qty = instance.goods_qty
  182. log.log_type = 'update'
  183. log.save()
  184. # 检查出库数量变化
  185. if old_instance.goods_out_qty != instance.goods_out_qty:
  186. log.old_goods_out_qty = old_instance.goods_out_qty
  187. log.new_goods_out_qty = instance.goods_out_qty
  188. log.log_type = 'out'
  189. log.save()
  190. # # 检查状态变化
  191. # if old_instance.status != instance.status:
  192. # log.old_status = old_instance.status
  193. # log.new_status = instance.status
  194. # log.log_type = 'status_change'
  195. # # 保存日志记录
  196. # if hasattr(log, 'log_type'):
  197. # log.save()
  198. @receiver(post_save, sender=ContainerDetailModel)
  199. def container_detail_post_save(sender, instance, created, **kwargs):
  200. """在托盘明细保存后记录创建"""
  201. if created:
  202. # 为新建的对象创建日志
  203. ContainerDetailLogModel.objects.create(
  204. container_detail=instance,
  205. log_type='create',
  206. creater=instance.creater,
  207. new_goods_qty=instance.goods_qty,
  208. new_status=instance.status
  209. )
  210. # 明细表:操作记录 记录每次出入库的记录,使用goods来进行盘点,使用托盘码来进行托盘的操作记录
  211. class ContainerOperationModel(models.Model):
  212. OPERATION_TYPES = (
  213. ('container','组盘'),
  214. ('inbound', '入库'),
  215. ('outbound', '出库'),
  216. ('adjust', '调整'),
  217. )
  218. month = models.IntegerField(verbose_name='月份')
  219. container = models.ForeignKey(ContainerListModel, on_delete=models.CASCADE, related_name='operations')
  220. operation_type = models.CharField(max_length=20, choices=OPERATION_TYPES, verbose_name='操作类型')
  221. bound_id = models.IntegerField(verbose_name='出库申请', null=True, blank=True)
  222. batch = models.ForeignKey(BoundBatchModel, on_delete=models.CASCADE, verbose_name='批次',null=True, blank=True)
  223. goods_code = models.CharField(max_length=50, verbose_name='货品编码')
  224. goods_desc = models.CharField(max_length=100, verbose_name='货品描述')
  225. goods_qty = models.IntegerField(verbose_name='数量')
  226. goods_weight = models.DecimalField(max_digits=10, decimal_places=3, verbose_name='重量')
  227. operator = models.CharField(max_length=50, verbose_name='操作人')
  228. timestamp = models.DateTimeField(auto_now_add=True, verbose_name='操作时间')
  229. from_location = models.CharField(max_length=50, null=True, verbose_name='原库位')
  230. to_location = models.CharField(max_length=50, null=True, verbose_name='目标库位')
  231. memo = models.TextField(null=True, verbose_name='备注')
  232. is_delete = models.BooleanField(default=False, verbose_name='是否删除')
  233. class Meta:
  234. db_table = 'container_operation'
  235. verbose_name = 'ContainerOperation'
  236. verbose_name_plural = "ContainerOperation"
  237. ordering = ['-timestamp']
  238. class ContainerWCSModel(models.Model):
  239. TASK_STATUS = (
  240. (100, '等待中'),
  241. (101, '处理中'),
  242. (102, '已暂停'),
  243. (103, '入库中'),
  244. (200, '已发送'),
  245. (300, '已完成'),
  246. (400, '失败'),
  247. )
  248. taskid = models.CharField(max_length=50, verbose_name='任务ID')
  249. batch = models.ForeignKey(BoundBatchModel, on_delete=models.CASCADE, verbose_name='关联批次',null=True, blank=True )
  250. batch_out = models.ForeignKey(OutBatchModel, on_delete=models.CASCADE, verbose_name='出库批次' , null=True, blank=True )
  251. bound_list = models.ForeignKey(BoundListModel, on_delete=models.CASCADE, verbose_name='关联出库单',null=True, blank=True )
  252. batch_number = models.CharField(max_length=50, verbose_name='批次号',null=True, blank=True )
  253. sequence = models.BigIntegerField(verbose_name='任务顺序')
  254. priority = models.IntegerField(default=100, verbose_name='优先级')
  255. month = models.IntegerField(verbose_name='月份')
  256. tasktype = models.CharField(max_length=50, verbose_name='任务类型')
  257. tasknumber = models.IntegerField(verbose_name='任务号',unique=True)
  258. order_number = models.IntegerField(verbose_name='c_number')
  259. container = models.CharField(max_length=50, verbose_name='托盘号')
  260. current_location = models.CharField(max_length=50, verbose_name='当前库位')
  261. target_location = models.CharField(max_length=50, verbose_name='目标库位')
  262. message = models.TextField(verbose_name='消息')
  263. working = models.IntegerField(default = 1,verbose_name='工作状态')
  264. status = models.IntegerField(choices=TASK_STATUS, default=100, verbose_name='状态')
  265. create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
  266. is_delete = models.BooleanField(default=False, verbose_name='是否删除')
  267. class Meta:
  268. db_table = 'container_wcs'
  269. verbose_name = 'ContainerWCS'
  270. verbose_name_plural = "ContainerWCS"
  271. ordering = ['-create_time']
  272. def to_dict(self):
  273. return {
  274. 'container': self.container,
  275. 'current_location': self.current_location,
  276. 'month' : self.month,
  277. 'target_location': self.target_location,
  278. 'tasktype': self.tasktype,
  279. 'taskid': self.taskid,
  280. 'taskNumber': self.tasknumber-20000000000,
  281. 'message': self.message,
  282. 'container': self.container,
  283. 'status': self.status
  284. }
  285. def __str__(self):
  286. return f"{self.taskid} - {self.get_status_display()}"
  287. # 这里的批次详情是主入库申请单下的子批次
  288. class TaskModel(models.Model):
  289. task_wcs = models.ForeignKey(ContainerWCSModel, on_delete=models.CASCADE, related_name='tasks')
  290. batch_detail = models.ForeignKey(BoundDetailModel, on_delete=models.CASCADE, verbose_name='批次详情')
  291. container_detail = models.ForeignKey(ContainerDetailModel, on_delete=models.CASCADE, verbose_name='托盘明细')
  292. class Meta:
  293. db_table = 'task'
  294. verbose_name = 'Task'
  295. verbose_name_plural = "Task"
  296. ordering = ['-id']
  297. class out_batch_detail(models.Model):
  298. out_bound = models.ForeignKey(BoundListModel, on_delete=models.CASCADE, related_name='out_batch_details')
  299. container = models.ForeignKey(ContainerListModel, on_delete=models.CASCADE, related_name='out_batch_details')
  300. container_detail = models.ForeignKey(ContainerDetailModel, on_delete=models.CASCADE, verbose_name='托盘明细')
  301. out_goods_qty = models.IntegerField(verbose_name='数量')
  302. last_out_goods_qty = models.IntegerField(verbose_name='上次出库数量', default=0)
  303. working = models.IntegerField(default = 1,verbose_name='工作状态')
  304. is_delete = models.BooleanField(default=False, verbose_name='是否删除')
  305. class Meta:
  306. db_table = 'out_batch_detail'
  307. verbose_name = 'OutBatchDetail'
  308. verbose_name_plural = "OutBatchDetail"
  309. ordering = ['container']