Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/新增工艺退回调整

# Conflicts:
#	sf_manufacturing/models/mrp_production.py
#	sf_manufacturing/views/mrp_production_addional_change.xml
This commit is contained in:
jinling.yang
2024-11-15 18:08:31 +08:00
25 changed files with 473 additions and 107 deletions

View File

@@ -12,6 +12,7 @@
'website': 'https://www.sf.jikimo.com',
'depends': ['sf_base', 'sf_maintenance', 'web_widget_model_viewer', 'sf_warehouse','jikimo_attachment_viewer', 'jikimo_sale_multiple_supply_methods'],
'data': [
'data/cron_data.xml',
'data/stock_data.xml',
'data/empty_racks_data.xml',
'data/panel_data.xml',

View File

@@ -0,0 +1,29 @@
<odoo>
<data noupdate="1">
<record model="ir.cron" id="ir_cron_update_construction_period_status">
<field name="name">工期状态变更</field>
<field name="model_id" ref="model_mrp_workorder"/>
<field name="state">code</field>
<field name="code">model._corn_update_construction_period_status()</field>
<field name="interval_number">12</field>
<field name="interval_type">hours</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
<field name="user_id" ref="base.user_root"/>
<field name="active" eval="True"/>
</record>
<record model="ir.cron" id="ir_cron_update_delivery_status">
<field name="name">交期状态变更</field>
<field name="model_id" ref="model_mrp_production"/>
<field name="state">code</field>
<field name="code">model._corn_update_delivery_status()</field>
<field name="interval_number">12</field>
<field name="interval_type">hours</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
<field name="user_id" ref="base.user_root"/>
<field name="active" eval="True"/>
</record>
</data>
</odoo>

View File

@@ -8,17 +8,20 @@ class ModelType(models.Model):
name = fields.Char('名称')
embryo_tolerance = fields.Integer('坯料容余')
product_routing_tmpl_ids = fields.One2many('sf.product.model.type.routing.sort', 'product_model_type_id',
'成品工序模板')
'成品工序模板(自动化产线加工')
embryo_routing_tmpl_ids = fields.One2many('sf.embryo.model.type.routing.sort', 'embryo_model_type_id',
'坯料工序模板')
'坯料工序模板(人工线下加工)')
surface_technics_routing_tmpl_ids = fields.One2many('sf.surface_technics.model.type.routing.sort',
'surface_technics_model_type_id',
'表面工艺工序模板')
manual_product_routing_tmpl_ids = fields.One2many('sf.manual.product.model.type.routing.sort',
'manual_product_model_type_id',
'成品工序模板(人工线下加工)')
class ProductModelTypeRoutingSort(models.Model):
_name = 'sf.product.model.type.routing.sort'
_description = '成品工序排序'
_description = '成品工序排序(自动化产线加工)'
sequence = fields.Integer('Sequence')
route_workcenter_id = fields.Many2one('mrp.routing.workcenter',
@@ -65,3 +68,18 @@ class SurfaceTechnicsModelTypeRoutingSort(models.Model):
'route_model_type_uniq', 'unique (route_workcenter_id,surface_technics_model_type_id)',
'表面工艺工序不能重复!')
]
class ManualProductModelTypeRoutingSort(models.Model):
_name = 'sf.manual.product.model.type.routing.sort'
_description = '成品工序排序(人工线下加工)'
sequence = fields.Integer('Sequence')
route_workcenter_id = fields.Many2one('mrp.routing.workcenter')
is_repeat = fields.Boolean('重复', related='route_workcenter_id.is_repeat')
routing_type = fields.Selection(string="工序类型", related='route_workcenter_id.routing_type')
workcenter_ids = fields.Many2many('mrp.workcenter', required=False, related='route_workcenter_id.workcenter_ids')
manual_product_model_type_id = fields.Many2one('sf.model.type')
_sql_constraints = [
('route_model_type_uniq', 'unique (route_workcenter_id,manual_product_model_type_id)', '成品工序不能重复!')
]

View File

@@ -34,6 +34,95 @@ class MrpProduction(models.Model):
tool_state_remark = fields.Text(string='功能刀具状态备注(缺刀)', compute='_compute_tool_state_remark', store=True)
tool_state_remark2 = fields.Text(string='功能刀具状态备注(无效刀)', readonly=True)
def _compute_default_delivery_status(self):
try:
if self.state == 'cancel':
return False
if not self.deadline_of_delivery:
return False
hours = self.get_hours_diff()
if hours >= 48:
return '正常'
elif hours > 0 and hours < 48 and self.state != 'done':
return '预警'
elif hours > 0 and hours < 48 and self.state == 'done':
return '正常'
else:
return '已逾期'
except Exception as e:
logging.error("Error processing production ID {}: {}".format(self.id, e))
raise e
@api.depends('state', 'deadline_of_delivery')
def _compute_delivery_status(self):
for production in self:
delivery_status = production._compute_default_delivery_status()
if delivery_status and production.delivery_status != delivery_status:
production.delivery_status = delivery_status
delivery_status = fields.Selection([('正常', '正常'), ('预警', '预警'), ('已逾期', '已逾期')], string='交期状态',
store=True,
compute='_compute_delivery_status',
default=lambda self: self._compute_default_delivery_status())
def get_page_all_records(self, model_name, func, domain, page_size=100):
# 获取模型对象
model = self.env[model_name].sudo()
# 初始化分页参数
page_number = 1
while True:
# 计算偏移量
offset = (page_number - 1) * page_size
# 获取当前页的数据
records = model.search(domain, limit=page_size, offset=offset)
# 如果没有更多记录,退出循环
if not records:
break
# 将当前页的数据添加到结果列表
func(records)
# 增加页码
page_number += 1
def run_compute_delivery_status(self, records):
records._compute_delivery_status()
def _corn_update_delivery_status(self):
need_list = [
'draft',
'technology_to_confirmed',
'confirmed',
'pending_cam',
'progress',
'rework',
'scrap',
'to_close',
]
# previous_workorder = self.env['mrp.production'].search([('state', 'in', need_list)])
self.get_page_all_records('mrp.production', self.run_compute_delivery_status,
[('state', 'in', need_list)], 100)
def get_hours_diff(self):
# 获取当前日期和时间
current_datetime = fields.Datetime.now()
# 将 date_field 转换为 datetime 对象
if self.deadline_of_delivery:
date_obj = fields.Date.from_string(self.deadline_of_delivery)
# 将 date 对象转换为 datetime 对象,设置时间为 00:00:00
date_obj = datetime.datetime.combine(date_obj, datetime.time.min)
# 计算两个日期之间的差值
delta = date_obj - current_datetime
# 返回差值的小时数
return int(delta.total_seconds() / 3600)
else:
return 0.0
@api.depends('procurement_group_id.mrp_production_ids.move_dest_ids.group_id.sale_id')
def _compute_deadline_of_delivery(self):
for production in self:
@@ -612,20 +701,6 @@ class MrpProduction(models.Model):
for workorder in production.workorder_ids:
workorder.duration_expected = workorder._get_duration_expected()
# 生成采购单
def get_subcontract_purchase(self):
production_all = self.sorted(lambda x: x.id)
for production in production_all:
for special in production.technology_design_ids:
if special.process_parameters_id.gain_way == '外协':
product_id_to_production_names = {}
grouped_product_ids = {k: list(g) for k, g in
groupby(production_all, key=lambda x: x.product_id.id)}
for product_id, pd in grouped_product_ids.items():
product_id_to_production_names[product_id] = [p.name for p in pd]
self.env['purchase.order'].get_purchase_order(special.process_parameters_id,
special.production_id,
product_id_to_production_names)
# 外协出入库单处理
def get_subcontract_pick_purchase(self):
@@ -800,6 +875,7 @@ class MrpProduction(models.Model):
if td_ids:
work.sequence = td_ids[0].sequence
def _reset_work_order_sequence_1(self):
"""
工单工序排序方法(旧)

View File

@@ -144,6 +144,88 @@ class ResMrpWorkOrder(models.Model):
tag_type = fields.Selection([("重新加工", "重新加工")], string="标签", tracking=True)
def _compute_default_construction_period_status(self):
need_list=['pending', 'waiting', 'ready', 'progress', 'to be detected','done']
try:
if self.state not in need_list:
return False
if not self.date_planned_finished:
return False
hours = self.get_hours_diff()
if hours >= 12:
return '正常'
elif hours > 0 and hours < 12 and self.state!='done':
return '预警'
elif hours > 0 and hours < 12 and self.state=='done':
return '正常'
else:
return '已逾期'
except Exception as e:
logging.error("Error processing production ID {}: {}".format(self.id, e))
raise e
@api.depends('state', 'date_planned_finished')
def _compute_construction_period_status(self):
for worker in self:
construction_period_status = worker._compute_default_construction_period_status()
if construction_period_status and worker.construction_period_status!=construction_period_status:
worker.construction_period_status = construction_period_status
construction_period_status = fields.Selection([('正常', '正常'), ('预警', '预警'), ('已逾期', '已逾期')],
string='工期状态',
store=True,
compute='_compute_construction_period_status',
default=lambda
self: self._compute_default_construction_period_status())
def get_page_all_records(self, model_name, func, domain, page_size=100):
# 获取模型对象
model = self.env[model_name].sudo()
# 初始化分页参数
page_number = 1
while True:
# 计算偏移量
offset = (page_number - 1) * page_size
# 获取当前页的数据
records = model.search(domain, limit=page_size, offset=offset)
# 如果没有更多记录,退出循环
if not records:
break
# 将当前页的数据添加到结果列表
func(records)
# 增加页码
page_number += 1
def run_compute_construction_period_status(self,records):
records._compute_construction_period_status()
def _corn_update_construction_period_status(self):
need_list=['pending', 'waiting', 'ready', 'progress', 'to be detected']
# need_list = [
# 'progress',
# 'to be detected']
self.get_page_all_records('mrp.workorder',self.run_compute_construction_period_status,[('state', 'in', need_list)],100)
def get_hours_diff(self):
# 获取当前日期和时间
current_datetime = fields.Datetime.now()
# 将 date_field 转换为 datetime 对象
if self.date_planned_finished:
date_obj = fields.Datetime.from_string(self.date_planned_finished)
# 将 date 对象转换为 datetime 对象,设置时间为 00:00:00
# date_obj = datetime.datetime.combine(date_obj, datetime.time.min)
# 计算两个日期之间的差值
delta = date_obj - current_datetime
# 返回差值的小时数
return int(delta.total_seconds() / 3600)
else:
return 0.0
@api.depends('name', 'production_id.name')
def _compute_surface_technics_picking_ids(self):
for workorder in self:

View File

@@ -859,7 +859,7 @@ class ResProductMo(models.Model):
item['model_width'] + model_type.embryo_tolerance) * (
item['model_height'] + model_type.embryo_tolerance),
'product_model_type_id': model_type.id,
# 'model_processing_panel': 'R',
'model_processing_panel': item['processing_panel_detail'],
'model_machining_precision': item['model_machining_precision'],
'model_code': item['barcode'],
'length': item['model_long'],
@@ -934,6 +934,7 @@ class ResProductMo(models.Model):
# if surface_technology:
# no_bom_copy_product_id.route_ids |= surface_technology
no_bom_copy_product_id.product_tmpl_id.active = True
logging.info('no_bom_copy_product_id[is_manual_processing]:%s' % no_bom_copy_product_id.is_manual_processing)
materials_id = self.env['sf.production.materials'].search(
[('materials_no', '=', item['texture_code'])])
materials_type_id = self.env['sf.materials.model'].search(
@@ -959,7 +960,7 @@ class ResProductMo(models.Model):
'materials_type_id': materials_type_id.id,
'single_manufacturing': product_id.single_manufacturing,
'is_bfm': True,
'active': True
'active': True,
}
# 外协和采购生成的坯料需要根据材料型号绑定供应商
if route_type == 'subcontract' or route_type == 'purchase':

View File

@@ -294,10 +294,10 @@ class StockRule(models.Model):
# 为同一个product_id创建一个生产订单名称列表
product_id_to_production_names[product_id] = [production.name for production in all_production]
for production_item in productions:
production_programming = self.env['mrp.production'].search(
[('product_id.id', '=', production_item.product_id.id),
('origin', '=', production_item.origin)],
limit=1, order='id asc')
# production_programming = self.env['mrp.production'].search(
# [('product_id.id', '=', production_item.product_id.id),
# ('origin', '=', production_item.origin)],
# limit=1, order='id asc')
if production_item.product_id.id in product_id_to_production_names:
# 同一个产品多个制造订单对应一个编程单和模型库
# 只调用一次fetchCNC并将所有生产订单的名称作为字符串传递
@@ -455,6 +455,12 @@ class ProductionLot(models.Model):
if product.categ_id.name == '刀具':
return self.env['stock.lot'].get_tool_generate_lot_names1(company, product)
else:
# 对last_serial的name进行检测如果不是以产品名称+数字的形式的就重新搜索
if product.name.split('[')[0] not in last_serial.name:
last_serial = self.env['stock.lot'].search(
[('company_id', '=', company.id), ('product_id', '=', product.id),
('name', 'ilike', product.name.split('[')[0])],
limit=1, order='name desc')
return self.env['stock.lot'].generate_lot_names1(product.name, last_serial.name, 2)[1]
now = datetime.now().strftime("%Y%m%d")
if product.cutting_tool_model_id:
@@ -775,10 +781,11 @@ class ReStockMove(models.Model):
self.next_serial = self._get_tool_next_serial(self.company_id, self.product_id, self.origin)
else:
self.next_serial = self.env['stock.lot']._get_next_serial(self.company_id, self.product_id)
if self.picking_type_id.sequence_code == 'DL' and not self.move_line_nosuggest_ids:
self.action_assign_serial_show_details()
elif self.product_id.tracking == "lot":
self._put_tool_lot(self.company_id, self.product_id, self.origin)
if not self.move_line_nosuggest_ids:
self._generate_serial_numbers()
return {
'name': _('Detailed Operations'),

View File

@@ -175,5 +175,6 @@ access_sf_production_technology_re_adjust_wizard_group_plan_dispatch,sf_producti
access_sf_production_technology_re_adjust_wizard_group_sf_mrp_manager,sf_production_technology_re_adjust_wizard_group_sf_mrp_manager,model_sf_production_technology_re_adjust_wizard,sf_base.group_sf_mrp_manager,1,1,1,0
access_sf_production_technology_re_adjust_wizard_group_production_engineer,sf_production_technology_re_adjust_wizard_group_production_engineer,model_sf_production_technology_re_adjust_wizard,sf_base.group_production_engineer,1,1,1,0
access_sf_manual_product_model_type_routing_sort_group_sf_mrp_user,sf_manual_product_model_type_routing_sort,model_sf_manual_product_model_type_routing_sort,sf_base.group_sf_mrp_user,1,0,0,0
access_sf_manual_product_model_type_routing_sort_manager,sf_manual_product_model_type_routing_sort,model_sf_manual_product_model_type_routing_sort,sf_base.group_sf_mrp_manager,1,1,1,0
access_sf_manual_product_model_type_routing_sort_group_plan_dispatch,sf_manual_product_model_type_routing_sort_group_plan_dispatch,model_sf_manual_product_model_type_routing_sort,sf_base.group_plan_dispatch,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
175
176
177
178
179
180

View File

@@ -44,6 +44,17 @@
</tree>
</field>
</group>
<group>
<field name='manual_product_routing_tmpl_ids'>
<tree editable='bottom'>
<field name="sequence" widget="handle" string="序号"/>
<field name="route_workcenter_id" string="工序" options="{'no_create': True}"/>
<field name="routing_type" string="类型"/>
<field name="is_repeat" string="重复"/>
<field name="workcenter_ids" string="工作中心" widget="many2many_tags"/>
</tree>
</field>
</group>
<group>
<field name='embryo_routing_tmpl_ids'>
<tree editable='bottom'>

View File

@@ -57,6 +57,16 @@
<!-- <button name="action_view_production_schedule" string="生产排程" type="object" attrs="{'invisible': [('state', 'in', ['draft', 'cancel','已排程','progress','done','to_close'])]}"/> -->
<!-- <button name="cancel_plan" string="取消排程" type="object" attrs="{'invisible': [('state', 'in', ['draft', 'cancel','progress','done','to_close','confirmed'])]}"/> -->
<!-- </xpath> -->
<xpath expr="//field[@name='production_real_duration']" position="before">
<field name="delivery_status" optional="show" widget="badge"
decoration-success="delivery_status == '正常'"
decoration-warning="delivery_status == '预警'"
decoration-danger="delivery_status == '已逾期'"/>
</xpath>
<xpath expr="//field[@name='production_real_duration']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
</field>
</record>
@@ -347,8 +357,6 @@
<field name="technology_design_ids" widget="one2many">
<tree editable="bottom">
<field name="sequence" widget="handle"/>
<!-- <field name="sequence"/>-->
<field name="id"/>
<field name="route_id" context="{'production_id': production_id}"
attrs="{'readonly': [('id', '!=', False)]}" options="{'no_create': True}"/>
<field name="process_parameters_id" attrs="{'readonly': [('id', '!=', False)]}"
@@ -552,8 +560,9 @@
<separator/>
</xpath>
<xpath expr="//search" position="inside">
<searchpanel class="account_root">
<searchpanel>
<field name="state" icon="fa-filter" enable_counters="1"/>
<field name="delivery_status" icon="fa-filter" enable_counters="1"/>
</searchpanel>
</xpath>
<filter name='todo' position="replace"/>

View File

@@ -32,8 +32,10 @@
</field>
<xpath expr="//field[@name='qty_remaining']" position="after">
<field name="manual_quotation" optional="show"/>
<field name='tag_type' widget="badge"
decoration-danger="tag_type == '重新加工'"/>
<field name="construction_period_status" optional="show" widget="badge"
decoration-success="construction_period_status == '正常'"
decoration-warning="construction_period_status == '预警'"
decoration-danger="construction_period_status == '已逾期'"/>
</xpath>
<xpath expr="//field[@name='date_planned_start']" position="replace">
<field name="date_planned_start" string="计划开始日期" optional="show"/>
@@ -257,11 +259,12 @@
</xpath>
<xpath expr="//header" position="inside">
<div class="o_statusbar_buttons"><button name="button_change_env"
type="object"
string="演示模式"
class="btn-primary"
groups="sf_manufacturing.group_show_button"/>
<div class="o_statusbar_buttons">
<button name="button_change_env"
type="object"
string="演示模式"
class="btn-primary"
groups="sf_manufacturing.group_show_button"/>
</div>
</xpath>
@@ -482,11 +485,11 @@
</group>
</page>
<page string="2D加工图纸" attrs="{'invisible': [('routing_type','!=','装夹预调')]}">
<page string="2D加工图纸" attrs="{'invisible': [('routing_type','!=','装夹预调')]}">
<field name="machining_drawings" widget="adaptive_viewer"/>
</page>
<page string="质检标准" attrs="{'invisible': [('routing_type','!=','装夹预调')]}">
<page string="质检标准" attrs="{'invisible': [('routing_type','!=','装夹预调')]}">
<field name="quality_standard" widget="adaptive_viewer"/>
</page>
@@ -543,11 +546,11 @@
<!-- attrs='{"invisible": ["|","|",("state","!=","progress"),("user_permissions","=",False),("results","=","合格")]}'/>-->
<!-- </div>-->
</page>
<page string="2D加工图纸" attrs='{"invisible": [("routing_type","!=","CNC加工")]}'>
<page string="2D加工图纸" attrs='{"invisible": [("routing_type","!=","CNC加工")]}'>
<field name="machining_drawings" widget="adaptive_viewer"/>
</page>
<page string="质检标准" attrs='{"invisible": [("routing_type","!=","CNC加工")]}'>
<page string="质检标准" attrs='{"invisible": [("routing_type","!=","CNC加工")]}'>
<field name="quality_standard" widget="adaptive_viewer"/>
</page>
</xpath>