262 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			262 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
| from django.db import models
 | |
| from apps.system.models import CommonAModel, Dictionary, CommonBModel, CommonADModel, File, BaseModel
 | |
| from rest_framework.exceptions import ParseError
 | |
| from apps.utils.models import CommonBDModel
 | |
| 
 | |
| class Process(CommonBModel):
 | |
|     """
 | |
|     工序
 | |
|     """
 | |
|     PRO_PROD = 10
 | |
|     PRO_TEST = 20
 | |
| 
 | |
|     
 | |
|     PRO_NORMAL = 10
 | |
|     PRO_DIV = 20
 | |
|     PRO_MERGE = 30
 | |
|     name = models.CharField('工序名称', max_length=100)
 | |
|     type = models.PositiveSmallIntegerField("工序类型", default=PRO_PROD, choices=((PRO_PROD, '生产工序'), (PRO_TEST, '检验工序')))
 | |
|     mtype = models.PositiveSmallIntegerField("工序生产类型", default=PRO_NORMAL, choices=((PRO_NORMAL, '常规'), (PRO_DIV, '切分'), (PRO_MERGE, '合并')))
 | |
|     cate = models.CharField('大类', max_length=10, default='')
 | |
|     sort = models.PositiveSmallIntegerField('排序', default=1)
 | |
|     instruction = models.ForeignKey(
 | |
|         File, verbose_name='指导书', on_delete=models.SET_NULL, null=True, blank=True)
 | |
|     instruction_content = models.TextField('指导书内容', null=True, blank=True)
 | |
|     into_wm_mgroup = models.BooleanField('交接到工段', default=False)
 | |
|     store_notok = models.BooleanField('不合格品是否入库', default=False)
 | |
|     batch_append_equip = models.BooleanField('批号追加设备', default=False)
 | |
|     mlog_need_ticket = models.BooleanField('日志提交是否需要审批', default=False)
 | |
|     mstate_json = models.JSONField('中间状态', default=list, blank=True)
 | |
| 
 | |
|     class Meta:
 | |
|         verbose_name = '工序'
 | |
|         ordering = ['sort', 'create_time']
 | |
| 
 | |
| 
 | |
| # Create your models here.
 | |
| class Material(CommonAModel):
 | |
|     MA_TYPE_BASE = 0
 | |
|     MA_TYPE_GOOD = 10
 | |
|     MA_TYPE_HALFGOOD = 20
 | |
|     MA_TYPE_MAINSO = 30
 | |
|     MA_TYPE_HELPSO = 40
 | |
|     MA_TYPE_TOOL = 50
 | |
|     MA_TYPE_HELPTOOL = 60
 | |
|     MA_TYPE_OFFICE = 70
 | |
| 
 | |
|     type_choices = (
 | |
|         (MA_TYPE_BASE, '电/水/气'),
 | |
|         (MA_TYPE_GOOD, '成品'),
 | |
|         (MA_TYPE_HALFGOOD, '半成品'),
 | |
|         (MA_TYPE_MAINSO, '主要原料'),
 | |
|         (MA_TYPE_HELPSO, '辅助材料'),
 | |
|         (MA_TYPE_TOOL, '加工工具'),
 | |
|         (MA_TYPE_HELPTOOL, '辅助工装'),
 | |
|         (MA_TYPE_OFFICE, '办公用品')
 | |
|     )
 | |
| 
 | |
|     MA_TRACKING_BATCH = 10
 | |
|     MA_TRACKING_SINGLE = 20
 | |
| 
 | |
|     name = models.CharField('名称', max_length=50)
 | |
|     cate = models.CharField('大类', max_length=20, default='', blank=True)
 | |
|     number = models.CharField('编号', max_length=100, null=True, blank=True)
 | |
|     model = models.CharField(
 | |
|         '型号', max_length=100, null=True, blank=True)
 | |
|     specification = models.CharField(
 | |
|         '规格', max_length=100, null=True, blank=True)
 | |
|     code = models.CharField('标识', max_length=50, null=True, blank=True)
 | |
|     type = models.PositiveSmallIntegerField(
 | |
|         '物料类型', choices=type_choices, default=1, help_text=str(type_choices))
 | |
|     testitems = models.JSONField('检测项目', default=list, blank=True)
 | |
|     sort = models.FloatField('排序', default=1)
 | |
|     unit = models.CharField('基准计量单位', default='个', max_length=10)
 | |
|     tracking = models.PositiveSmallIntegerField("追踪方式", default=10, 
 | |
|                                                 choices=((MA_TRACKING_BATCH, '批次'), 
 | |
|                                                          (MA_TRACKING_SINGLE, '单件')))
 | |
|     count = models.DecimalField('总库存', max_digits=14, decimal_places=3, default=0)
 | |
|     count_mb = models.DecimalField('仓库库存', max_digits=14, decimal_places=3, default=0)
 | |
|     count_wm = models.DecimalField('车间库存', max_digits=14, decimal_places=3, default=0)
 | |
|     count_safe = models.DecimalField('安全库存数', max_digits=14, decimal_places=3, null=True, blank=True)
 | |
|     week_esitimate_consume = models.DecimalField('周消耗预估', max_digits=14, decimal_places=3, null=True, blank=True)
 | |
|     process = models.ForeignKey(
 | |
|         Process, verbose_name='所用工序', on_delete=models.CASCADE, null=True, blank=True)
 | |
|     parent = models.ForeignKey(
 | |
|         'self', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='父物料')
 | |
|     is_hidden = models.BooleanField('是否隐藏', default=False)
 | |
|     is_assemb = models.BooleanField('是否组合件', default=False)
 | |
|     need_route = models.BooleanField('是否需要定义工艺路线', default=False)
 | |
|     components = models.JSONField('组件', default=dict, null=False, blank=True)
 | |
|     brothers = models.JSONField('兄弟件', default=list, null=False, blank=True)
 | |
|     unit_price = models.DecimalField('单价', max_digits=14, decimal_places=2, null=True, blank=True)
 | |
|     into_wm = models.BooleanField('是否进入车间库存', default=True)
 | |
| 
 | |
|     class Meta:
 | |
|         verbose_name = '物料表'
 | |
|         ordering = ['sort', '-create_time']
 | |
| 
 | |
|     def __str__(self):
 | |
|         return f'{self.name}|{self.specification if self.specification else ""}|{self.model if self.model else ""}|{self.process.name if self.process else ""}'
 | |
| 
 | |
| 
 | |
| class Shift(CommonBModel):
 | |
|     """班次
 | |
|     """
 | |
|     name = models.CharField('名称', max_length=50)
 | |
|     rule = models.CharField('所属规则', max_length=10, default='默认')
 | |
|     start_time_o = models.TimeField('开始时间')
 | |
|     end_time_o = models.TimeField('结束时间')
 | |
|     sort = models.PositiveSmallIntegerField('排序', default=1)
 | |
| 
 | |
|     class Meta:
 | |
|         verbose_name = '班次'
 | |
| 
 | |
| class Srule(CommonBDModel):
 | |
|     """
 | |
|     班组规则
 | |
|     """
 | |
|     rule = models.JSONField('排班规则', default=list, blank=True)
 | |
|     
 | |
| 
 | |
| class Team(CommonBModel):
 | |
|     """班组, belong_dept为所属车间
 | |
|     """
 | |
|     name = models.CharField('名称', max_length=50)
 | |
|     leader = models.ForeignKey(
 | |
|         'system.user', verbose_name='班长', on_delete=models.CASCADE)
 | |
| 
 | |
| 
 | |
| class Mgroup(CommonBModel):
 | |
|     """测点集
 | |
|     """
 | |
| 
 | |
|     name = models.CharField('名称', max_length=50)
 | |
|     code = models.CharField('标识', max_length=50, null=True, blank=True)
 | |
|     cate = models.CharField(
 | |
|         '分类', max_length=50, default='section', help_text='section/other')  # section是工段
 | |
|     shift_rule = models.CharField('班次规则', max_length=10, default='默认')
 | |
|     process = models.ForeignKey(
 | |
|         Process, verbose_name='工序', on_delete=models.SET_NULL, null=True, blank=True)
 | |
|     product = models.ForeignKey(
 | |
|         Material, verbose_name='主要产品', on_delete=models.SET_NULL, null=True, blank=True, related_name='mgroup_product')
 | |
|     product_cost = models.ForeignKey(
 | |
|         Material, verbose_name='主要产品(成本计算)', on_delete=models.SET_NULL, null=True, blank=True, related_name='mgroup_product_cost')
 | |
|     input_materials = models.JSONField(
 | |
|         '直接材料', default=list, blank=True, help_text='material的ID列表')
 | |
|     test_materials = models.JSONField(
 | |
|         '检测材料', default=list, blank=True, help_text='material的ID列表')
 | |
|     sort = models.PositiveSmallIntegerField('排序', default=1)
 | |
|     need_enm = models.BooleanField('是否进行能源监测', default=True)
 | |
|     is_running = models.BooleanField('是否正常运行', default=False)
 | |
| 
 | |
|     class Meta:
 | |
|         verbose_name = '测点集'
 | |
|         ordering = ['sort', '-create_time']
 | |
| 
 | |
|     def __str__(self) -> str:
 | |
|         return self.name
 | |
| 
 | |
| 
 | |
| class TeamMember(BaseModel):
 | |
|     team = models.ForeignKey(Team, verbose_name='关联班组',
 | |
|                              on_delete=models.CASCADE)
 | |
|     user = models.ForeignKey(
 | |
|         'system.user', verbose_name='成员', on_delete=models.CASCADE)
 | |
|     mgroup = models.ForeignKey(
 | |
|         Mgroup, verbose_name='所在工段', on_delete=models.CASCADE)
 | |
|     post = models.ForeignKey('system.post', verbose_name='岗位',
 | |
|                              on_delete=models.SET_NULL, null=True, blank=True)
 | |
| 
 | |
| 
 | |
| class Goal(CommonADModel):
 | |
|     """目标
 | |
|     """
 | |
|     mgroup = models.ForeignKey(
 | |
|         Mgroup, verbose_name='关联工段', on_delete=models.CASCADE, null=True, blank=True)
 | |
|     year = models.PositiveSmallIntegerField('年')
 | |
|     goal_cate = models.ForeignKey(
 | |
|         Dictionary, verbose_name='目标种类', on_delete=models.CASCADE)
 | |
|     goal_val = models.FloatField('全年目标值')
 | |
|     goal_val_1 = models.FloatField('1月份目标值')
 | |
|     goal_val_2 = models.FloatField('2月份目标值')
 | |
|     goal_val_3 = models.FloatField('3月份目标值')
 | |
|     goal_val_4 = models.FloatField('4月份目标值')
 | |
|     goal_val_5 = models.FloatField('5月份目标值')
 | |
|     goal_val_6 = models.FloatField('6月份目标值')
 | |
|     goal_val_7 = models.FloatField('7月份目标值')
 | |
|     goal_val_8 = models.FloatField('8月份目标值')
 | |
|     goal_val_9 = models.FloatField('9月份目标值')
 | |
|     goal_val_10 = models.FloatField('10月份目标值')
 | |
|     goal_val_11 = models.FloatField('11月份目标值')
 | |
|     goal_val_12 = models.FloatField('12月份目标值')
 | |
| 
 | |
|     class Meta:
 | |
|         unique_together = ("mgroup", "year", "goal_cate")
 | |
| 
 | |
| 
 | |
| class RoutePack(CommonADModel):
 | |
|     """
 | |
|     加工工艺
 | |
|     """
 | |
|     RP_S_CREATE = 10
 | |
|     RP_S_AUDIT = 20
 | |
|     RP_S_CONFIRM = 30
 | |
|     RP_STATE = (
 | |
|         (RP_S_CREATE, '创建中'),
 | |
|         (RP_S_AUDIT, '审批中'),
 | |
|         (RP_S_CONFIRM, '已确认'),
 | |
|     )
 | |
|     state = models.PositiveSmallIntegerField('状态', default=10, choices=RP_STATE)
 | |
|     name = models.CharField('名称', max_length=100)
 | |
|     material = models.ForeignKey(
 | |
|         Material, verbose_name='产品', on_delete=models.CASCADE)
 | |
|     ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单',
 | |
|                                on_delete=models.SET_NULL, related_name='routepack_ticket', null=True, blank=True, db_constraint=False)
 | |
|     document = models.ForeignKey("system.file", verbose_name='工艺文件', on_delete=models.SET_NULL, null=True, blank=True, db_constraint=False)
 | |
| 
 | |
| class Route(CommonADModel):
 | |
|     """
 | |
|     加工路线
 | |
|     """
 | |
|     routepack = models.ForeignKey(RoutePack, verbose_name='关联路线包', on_delete=models.CASCADE, null=True, blank=True)
 | |
|     material = models.ForeignKey(
 | |
|         Material, verbose_name='关联产品', on_delete=models.CASCADE, null=True, blank=True)
 | |
|     process = models.ForeignKey(
 | |
|         Process, verbose_name='工序', on_delete=models.CASCADE, null=True, blank=True, related_name="route_p")
 | |
|     mgroup = models.ForeignKey(Mgroup, verbose_name='指定工段', on_delete=models.CASCADE,
 | |
|                                null=True, blank=True, related_name='route_mgroup')
 | |
|     sort = models.PositiveSmallIntegerField('顺序', default=1)
 | |
|     is_autotask = models.BooleanField('是否自动排产', default=False)
 | |
|     material_in = models.ForeignKey(
 | |
|         Material, verbose_name='主要输入物料', on_delete=models.CASCADE, related_name='route_material_in', null=True, blank=True)
 | |
|     material_out = models.ForeignKey(
 | |
|         Material, verbose_name='主要输出物料', on_delete=models.CASCADE, related_name='route_material_out', null=True, blank=True)
 | |
|     is_count_utask = models.BooleanField('是否主任务统计', default=False)
 | |
|     out_rate = models.FloatField('出材率', default=100, blank=True)
 | |
|     div_number = models.PositiveSmallIntegerField('切分数量', default=1, blank=True)
 | |
|     hour_work = models.FloatField('工时', null=True, blank=True)
 | |
|     batch_bind = models.BooleanField('是否绑定批次', default=True)
 | |
|     materials = models.ManyToManyField(Material, verbose_name='关联辅助物料', related_name="route_materials",
 | |
|                                   through="mtm.routemat", blank=True)
 | |
|     parent = models.ForeignKey('self', verbose_name='上级路线', on_delete=models.CASCADE, null=True, blank=True)
 | |
| 
 | |
|     @staticmethod
 | |
|     def get_routes(material: Material):
 | |
|         """
 | |
|         返回工艺路线带车间(不关联工艺包)
 | |
|         """
 | |
|         kwargs = {'material': material, 'routepack__isnull': True}
 | |
|         # 校验工艺路线是否正常
 | |
|         rq = Route.objects.filter(
 | |
|             **kwargs).order_by('sort', 'process__sort', 'create_time')
 | |
|         if not rq.exists():
 | |
|             raise ParseError('未配置工艺路线')
 | |
|         if rq.first().material_in is None or rq.last().material_out is None or rq.last().material_out != rq.last().material:
 | |
|             raise ParseError('首步缺少输入/最后一步缺少输出')
 | |
|         if not rq.filter(is_count_utask=True).exists():
 | |
|             raise ParseError('未指定统计步骤')
 | |
|         return rq
 | |
| 
 | |
| class RouteMat(BaseModel):
 | |
|     route = models.ForeignKey(Route, verbose_name='关联路线', on_delete=models.CASCADE, related_name="routemat_route")
 | |
|     material = models.ForeignKey(Material, verbose_name='辅助物料', on_delete=models.CASCADE) |