保留装夹测量数据
diff --git a/sf_manufacturing/wizard/sale_order_cancel.py b/sf_manufacturing/wizard/sale_order_cancel.py
new file mode 100644
index 00000000..984c811b
--- /dev/null
+++ b/sf_manufacturing/wizard/sale_order_cancel.py
@@ -0,0 +1,394 @@
+from odoo import models, fields, api
+
+
+class SFSaleOrderCancelWizard(models.TransientModel):
+ _name = 'sf.sale.order.cancel.wizard'
+ _description = '销售订单取消向导'
+
+ order_id = fields.Many2one('sale.order', string='销售订单')
+ related_docs = fields.One2many('sf.sale.order.cancel.line', 'wizard_id', string='相关单据')
+ has_movement = fields.Boolean(compute='_compute_has_movement', string='是否有异动')
+ display_message = fields.Char(compute='_compute_display_message', string='显示消息')
+
+ @api.model
+ def default_get(self, fields_list):
+ defaults = super().default_get(fields_list)
+ if self._context.get('active_id'):
+ order = self.env['sale.order'].browse(self._context.get('active_id'))
+ defaults['order_id'] = order.id
+ # 创建向导时自动创建关联单据行
+ wizard = self.create(defaults)
+ self.env['sf.sale.order.cancel.line'].create_from_order(wizard.id, order)
+ defaults['related_docs'] = wizard.related_docs.ids
+ return defaults
+
+ @api.depends('related_docs.cancel_reason')
+ def _compute_has_movement(self):
+ for wizard in self:
+ docs_has_movement = any(doc.cancel_reason for doc in wizard.related_docs)
+ order_canceled = wizard.order_id.state == 'cancel'
+ wizard.has_movement = docs_has_movement or order_canceled
+
+ @api.depends('has_movement', 'related_docs', 'related_docs.doc_state')
+ def _compute_display_message(self):
+ for wizard in self:
+ # 如果没有相关记录,显示为空
+ if not wizard.related_docs:
+ wizard.display_message = '无下游单据'
+ continue
+
+ # 检查是否所有记录都是已取消状态
+ all_canceled = all(doc.doc_state == '已取消' for doc in wizard.related_docs)
+ if all_canceled:
+ wizard.display_message = '取消的下游单据如下:'
+ else:
+ wizard.display_message = '部分或全部下游单据存在异动,无法取消,详情如下:' if wizard.has_movement else '确认所有下游单据全部取消?'
+
+ def action_confirm_cancel(self):
+ self.ensure_one()
+
+ # 取消销售订单关联的采购单
+ purchase_orders = self.env['purchase.order'].search([
+ ('origin', '=', self.order_id.name)
+ ])
+ if purchase_orders:
+ purchase_orders.write({'state': 'cancel'})
+
+ # 取消销售订单
+ result = self.order_id.action_cancel()
+
+ # 取消关联的制造订单及其采购单
+ manufacturing_orders = self.env['mrp.production'].search([
+ ('origin', '=', self.order_id.name)
+ ])
+ for mo in manufacturing_orders:
+ # 取消制造订单关联的采购单,但保持关联关系
+ mo_purchase_orders = self.env['purchase.order'].search([
+ ('origin', '=', mo.name)
+ ])
+ if mo_purchase_orders:
+ mo_purchase_orders.write({'state': 'cancel'})
+
+ # 取消制造订单的质检单
+ mo_quality_checks = self.env['quality.check'].search([
+ ('production_id', '=', mo.id)
+ ])
+ if mo_quality_checks:
+ mo_quality_checks.write({'quality_state': 'cancel'})
+
+ # 取消制造订单
+ mo.action_cancel()
+
+ # 取消制造订单关联的编程单
+ mo._change_programming_state()
+
+ # 取消组件的制造单关联的采购单
+ for comp_mo in self.env['mrp.production'].search([
+ ('origin', '=', mo.name)
+ ]):
+ comp_purchase_orders = self.env['purchase.order'].search([
+ ('origin', '=', comp_mo.name)
+ ])
+ if comp_purchase_orders:
+ comp_purchase_orders.button_cancel()
+
+ return result
+
+
+class SFSaleOrderCancelLine(models.TransientModel):
+ _name = 'sf.sale.order.cancel.line'
+ _description = '销售订单取消行'
+
+ wizard_id = fields.Many2one('sf.sale.order.cancel.wizard')
+ sequence = fields.Integer('序号')
+ category = fields.Char('大类')
+ doc_name = fields.Char('单据名称')
+ operation_type = fields.Char('作业类型')
+ doc_number = fields.Char('单据编号')
+ line_number = fields.Char('行号')
+ product_name = fields.Char('产品名称')
+ quantity = fields.Float('数量')
+ doc_state = fields.Char('单据状态')
+ cancel_reason = fields.Char('禁止取消原因')
+
+ @api.model
+ def create_from_order(self, wizard_id, order):
+ sequence = 1
+ lines = []
+ map_dict = {
+ 'waiting': '等待其他作业',
+ 'to approve': '待批准',
+ 'technology_to_confirmed': '待工艺确认',
+ 'confirmed': '已确认',
+ 'pending': '等待其他工单',
+ 'none': '待处理',
+ 'draft': '询价',
+ 'cancel': '已取消',
+ 'pass': '通过的',
+ 'fail': '失败的',
+ 'done': '已完成',
+ 'rework': '返工',
+ 'purchase': '采购订单',
+ 'ready': '就绪',
+ 'approved': '已批准',
+ 'pending_cam': '待加工',
+ 'progress': '加工中',
+ 'assigned': '就绪'
+ }
+
+ # 检查销售订单
+ if order.invoice_ids:
+ a = 0
+ for invoice in order.invoice_ids:
+ a += 1
+ vals = {
+ 'wizard_id': wizard_id,
+ 'sequence': sequence,
+ 'category': '销售',
+ 'doc_name': '销售订单',
+ 'operation_type': '',
+ 'doc_number': invoice.name,
+ 'line_number': a,
+ 'product_name': invoice.product_id.name,
+ 'quantity': invoice.quantity,
+ 'doc_state': invoice.state,
+ 'cancel_reason': '已有异动' if invoice.state != 'draft' else ''
+ }
+ lines.append(self.create(vals))
+ sequence += 1
+
+ # 检查交货单
+ if order.picking_ids:
+ b = 0
+ for picking in order.picking_ids:
+ b += 1
+ vals = {
+ 'wizard_id': wizard_id,
+ 'sequence': sequence,
+ 'category': '库存',
+ 'doc_name': '交货单',
+ 'operation_type': '调拨',
+ 'doc_number': picking.name,
+ 'line_number': b,
+ 'product_name': picking.product_id.name if picking.product_id else '',
+ # 'quantity': picking.product_qty if hasattr(picking, 'product_qty') else 0,
+ 'quantity': sum(picking.move_ids.mapped('product_uom_qty') or [0]),
+ 'doc_state': map_dict.get(picking.state, picking.state),
+ 'cancel_reason': '已有异动' if picking.state not in ['draft', 'cancel', 'waiting'] else ''
+ }
+ lines.append(self.create(vals))
+ sequence += 1
+
+ # 检查销售订单直接关联的采购单
+ purchase_orders = self.env['purchase.order'].search([
+ ('origin', '=', order.name)
+ ])
+ if purchase_orders:
+ c = 0
+ for po in purchase_orders:
+ c += 1
+ vals = {
+ 'wizard_id': wizard_id,
+ 'sequence': sequence,
+ 'category': '采购',
+ 'doc_name': '询价单',
+ 'operation_type': po.picking_type_id.name,
+ 'doc_number': po.name,
+ 'line_number': c,
+ 'product_name': po.order_line[0].product_id.name if po.order_line else '',
+ 'quantity': po.order_line[0].product_qty if po.order_line else 0,
+ 'doc_state': map_dict.get(po.state, po.state),
+ 'cancel_reason': '已有异动' if po.state not in ['draft', 'cancel'] else ''
+ }
+ lines.append(self.create(vals))
+ sequence += 1
+
+ # 检查制造订单
+ manufacturing_orders = self.env['mrp.production'].search([
+ ('origin', '=', order.name)
+ ])
+ d = 0
+ for mo in manufacturing_orders:
+ # 添加制造订单本身
+ d += 1
+ vals = {
+ 'wizard_id': wizard_id,
+ 'sequence': sequence,
+ 'category': '制造',
+ 'doc_name': '制造订单',
+ 'doc_number': mo.name,
+ 'operation_type': '',
+ 'line_number': d,
+ 'product_name': mo.product_id.name,
+ 'quantity': mo.product_qty,
+ 'doc_state': map_dict.get(mo.state, mo.state),
+ 'cancel_reason': '已有异动' if mo.state not in ['technology_to_confirmed', 'cancel'] else ''
+ }
+ lines.append(self.create(vals))
+ sequence += 1
+
+ # 检查制造订单关联的采购单
+ purchase_orders = self.env['purchase.order'].search([
+ ('origin', '=', mo.name)
+ ])
+ if purchase_orders:
+ e = 0
+ for po in purchase_orders:
+ e += 1
+ vals = {
+ 'wizard_id': wizard_id,
+ 'sequence': sequence,
+ 'category': '制造',
+ 'doc_name': '询价单',
+ 'doc_number': po.name,
+ 'line_number': e,
+ 'operation_type': po.picking_type_id.name,
+ 'product_name': po.order_line[0].product_id.name if po.order_line else '',
+ 'quantity': po.order_line[0].product_qty if po.order_line else 0,
+ 'doc_state': map_dict.get(po.state, po.state),
+ 'cancel_reason': '已有异动' if po.state not in ['draft', 'cancel'] else ''
+ }
+ lines.append(self.create(vals))
+ sequence += 1
+
+ # 检查制造订单的领料单
+ if mo.picking_ids:
+ f = 0
+ for picking in mo.picking_ids:
+ f += 1
+ vals = {
+ 'wizard_id': wizard_id,
+ 'sequence': sequence,
+ 'category': '制造',
+ 'doc_name': '库存移动',
+ 'doc_number': picking.name,
+ 'line_number': f,
+ 'operation_type': picking.picking_type_id.name,
+ 'product_name': picking.product_id.name if picking.product_id else '',
+ 'quantity': sum(picking.move_ids.mapped('product_uom_qty') or [0]),
+ 'doc_state': map_dict.get(picking.state, picking.state),
+ 'cancel_reason': '已有异动' if picking.state not in ['draft', 'cancel', 'waiting'] else ''
+ }
+ lines.append(self.create(vals))
+ sequence += 1
+
+ # 检查制造订单的工单
+ if mo.workorder_ids:
+ g = 0
+ for workorder in mo.workorder_ids:
+ g += 1
+ vals = {
+ 'wizard_id': wizard_id,
+ 'sequence': sequence,
+ 'category': '制造',
+ 'doc_name': '工单',
+ 'doc_number': workorder.name,
+ 'line_number': g,
+ 'operation_type': workorder.workcenter_id.name,
+ 'product_name': mo.product_id.name,
+ 'quantity': workorder.qty_production,
+ 'doc_state': map_dict.get(workorder.state, workorder.state),
+ 'cancel_reason': '已有异动' if workorder.state not in ['draft', 'cancel', 'pending',
+ 'waiting'] else ''
+ }
+ lines.append(self.create(vals))
+ sequence += 1
+
+ # 检查制造订单组件的采购单和制造单
+ for move in mo.move_raw_ids:
+ # # 检查组件的采购单
+ # component_pos = self.env['purchase.order'].search([
+ # ('origin', '=', mo.name),
+ # ('order_line.product_id', '=', move.product_id.id)
+ # ])
+ # for po in component_pos:
+ # vals = {
+ # 'wizard_id': wizard_id,
+ # 'sequence': sequence,
+ # 'category': '制造',
+ # 'doc_name': '组件采购单',
+ # 'operation_type': '组件采购',
+ # 'doc_number': po.name,
+ # 'product_name': move.product_id.name,
+ # 'quantity': po.order_line[0].product_qty if po.order_line else 0,
+ # 'doc_state': po.state,
+ # 'cancel_reason': '已有异动' if po.state not in ['draft', 'cancel'] else ''
+ # }
+ # lines.append(self.create(vals))
+ # sequence += 1
+
+ # 检查组件的制造单
+ component_mos = self.env['mrp.production'].search([
+ ('origin', '=', mo.name),
+ ('product_id', '=', move.product_id.id)
+ ])
+ h = 0
+ for comp_mo in component_mos:
+ h += 1
+ vals = {
+ 'wizard_id': wizard_id,
+ 'sequence': sequence,
+ 'category': '制造',
+ 'doc_name': '组件制造单',
+ 'operation_type': '',
+ 'doc_number': comp_mo.name,
+ 'line_number': h,
+ 'product_name': move.product_id.name,
+ 'quantity': comp_mo.product_qty,
+ 'doc_state': map_dict.get(comp_mo.state, comp_mo.state),
+ 'cancel_reason': '已有异动' if comp_mo.state not in ['technology_to_confirmed'] else ''
+ }
+ lines.append(self.create(vals))
+ sequence += 1
+
+ # 检查制造订单的质检单
+ quality_checks = self.env['quality.check'].search([
+ ('production_id', '=', mo.id)
+ ])
+ if quality_checks:
+ i = 0
+ for check in quality_checks:
+ i += 1
+ vals = {
+ 'wizard_id': wizard_id,
+ 'sequence': sequence,
+ 'category': '制造',
+ 'doc_name': '质检单',
+ 'operation_type': '',
+ 'doc_number': check.name,
+ 'line_number': i,
+ 'product_name': check.product_id.name,
+ 'quantity': 1,
+ 'doc_state': map_dict.get(check.quality_state, check.quality_state),
+ 'cancel_reason': '已有异动' if check.quality_state not in ['none', 'cancel'] else ''
+ }
+ lines.append(self.create(vals))
+ sequence += 1
+
+ # 检查制造订单的编程单
+ cloud_programming = mo._cron_get_programming_state()
+ if cloud_programming:
+ vals = {
+ 'wizard_id': wizard_id,
+ 'sequence': sequence,
+ 'category': '编程',
+ 'doc_name': '编程单',
+ 'operation_type': '',
+ 'doc_number': cloud_programming['programming_no'],
+ 'line_number': 1,
+ 'product_name': cloud_programming['production_order_no'],
+ 'quantity': 1,
+ 'doc_state': cloud_programming['programming_state'],
+ 'cancel_reason': ''
+ }
+ lines.append(self.create(vals))
+ sequence += 1
+
+ unique_lines = {}
+ for line in lines:
+ doc_number = line.doc_number
+ if doc_number not in unique_lines:
+ unique_lines[doc_number] = line
+
+ # 返回去重后的记录列表
+ return list(unique_lines.values())
diff --git a/sf_manufacturing/wizard/sale_order_cancel_views.xml b/sf_manufacturing/wizard/sale_order_cancel_views.xml
new file mode 100644
index 00000000..6c07e31a
--- /dev/null
+++ b/sf_manufacturing/wizard/sale_order_cancel_views.xml
@@ -0,0 +1,42 @@
+
+
+
+ sf.sale.order.cancel.wizard.form
+ sf.sale.order.cancel.wizard
+
+
+
+
+
\ No newline at end of file
diff --git a/sf_manufacturing/wizard/workpiece_delivery_wizard.py b/sf_manufacturing/wizard/workpiece_delivery_wizard.py
index 6a13fc08..bc4b8210 100644
--- a/sf_manufacturing/wizard/workpiece_delivery_wizard.py
+++ b/sf_manufacturing/wizard/workpiece_delivery_wizard.py
@@ -54,10 +54,7 @@ class WorkpieceDeliveryWizard(models.TransientModel):
}
}
- def _get_agv_route_type_selection(self):
- return self.env['sf.agv.task.route'].fields_get(['route_type'])['route_type']['selection']
-
- delivery_type = fields.Selection(selection=_get_agv_route_type_selection, string='类型')
+ delivery_type = fields.Selection(related='route_id.route_type', string='类型')
def dispatch_confirm(self):
if len(self.workorder_ids) < 4:
diff --git a/sf_message/data/bussiness_node.xml b/sf_message/data/bussiness_node.xml
index b429d766..14115e27 100644
--- a/sf_message/data/bussiness_node.xml
+++ b/sf_message/data/bussiness_node.xml
@@ -68,6 +68,11 @@
stock.picking
+
+ 调拨单质检完成提醒
+ stock.picking
+
+
装夹预调工单逾期预警
mrp.workorder
@@ -156,4 +161,12 @@
product.product
+
+
+
+ 计划数据异常跟踪
+ mrp.workorder
+
+
+
\ No newline at end of file
diff --git a/sf_message/data/template_data.xml b/sf_message/data/template_data.xml
index 11f05a3c..4d580127 100644
--- a/sf_message/data/template_data.xml
+++ b/sf_message/data/template_data.xml
@@ -252,6 +252,18 @@
### 订单发货提醒:
单号:发料出库单[{{name}}]({{request_url}})
事项:销售订单{{sale_order_name}}已全部产出并入库,请及时发货
+
+
+
+ 调拨单质检完成提醒
+
+ stock.picking
+
+ markdown
+ normal
+ ### {{picking_type_name}}待处理提醒:
+单号:{{name}}
+事项:质量检查已完成
@@ -402,4 +414,19 @@
事项:有{{num}}个质检单需要处理。
+
+
+
+ 计划数据异常跟踪
+
+ mrp.workorder
+
+ markdown
+ normal
+ ### 工单计划数据异常删除:
+工单号:{{name}}
+异动时间:{{write_date}}
+
+
+
\ No newline at end of file
diff --git a/sf_message/models/sf_message_product.py b/sf_message/models/sf_message_product.py
index 0f2cfd7e..302aa5a0 100644
--- a/sf_message/models/sf_message_product.py
+++ b/sf_message/models/sf_message_product.py
@@ -16,6 +16,7 @@ class SFMessageProduct(models.Model):
mrp_production_list = self.env['mrp.production'].sudo().search(
[('product_id', '=', product_product.id)])
production_num = 0
+ routing_type = None
for mrp_production_info in mrp_production_list:
routing_type = '人工线下加工' if mrp_production_info.production_type == '人工线下加工' else '装夹预调'
mrp_production_ready = mrp_production_info.workorder_ids.filtered(
@@ -23,7 +24,7 @@ class SFMessageProduct(models.Model):
if mrp_production_ready:
production_num += 1
if production_num >= 1:
- url = self.get_request_url()
+ url = self.get_request_url(routing_type)
content = content.replace('{{product_id}}', product_product.name).replace(
'{{number}}', str(production_num)).replace(
'{{request_url}}', url)
@@ -42,11 +43,15 @@ class SFMessageProduct(models.Model):
contents.append(content)
return contents, message_queue_ids
- def get_request_url(self):
+ def get_request_url(self, routing_type):
url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
action_id = self.env.ref('sf_message.mrp_workorder_issued_action').id
menu_id = self.env.ref('mrp.menu_mrp_root').id
- active_id = self.env['mrp.workcenter'].sudo().search([('name', '=', '工件装夹中心')]).id
+ if routing_type == '人工线下加工':
+ routing_name = '线下工作中心'
+ else:
+ routing_name = '工件装夹中心'
+ active_id = self.env['mrp.workcenter'].sudo().search([('name', '=', routing_name)]).id
# 查询参数
params = {'menu_id': menu_id, 'action': action_id, 'model': 'mrp.workorder',
'view_type': 'list', 'active_id': active_id}
diff --git a/sf_message/models/sf_message_stock_picking.py b/sf_message/models/sf_message_stock_picking.py
index a2c17407..10304cf3 100644
--- a/sf_message/models/sf_message_stock_picking.py
+++ b/sf_message/models/sf_message_stock_picking.py
@@ -9,6 +9,8 @@ class SFMessageStockPicking(models.Model):
_description = "库存调拨"
_inherit = ['stock.picking', 'jikimo.message.dispatch']
+ quality_check_ids = fields.One2many('quality.check', 'picking_id', '质量检测单')
+
@api.model_create_multi
def create(self, vals):
result = super(SFMessageStockPicking, self).create(vals)
@@ -20,7 +22,8 @@ class SFMessageStockPicking(models.Model):
logging.info('add_queue调拨入库 error:%s' % e)
return result
- @api.depends('move_type', 'immediate_transfer', 'move_ids.state', 'move_ids.picking_id')
+ @api.depends('move_type', 'immediate_transfer', 'move_ids.state', 'move_ids.picking_id',
+ 'quality_check_ids.quality_state')
def _compute_state(self):
super(SFMessageStockPicking, self)._compute_state()
try:
@@ -48,6 +51,8 @@ class SFMessageStockPicking(models.Model):
all_ready_or_done = all(picking.state in ['assigned', 'done'] for picking in stock_picking_list)
if all_ready_or_done:
mrp_production.add_queue('工序外协发料通知')
+ if all(qc.quality_state in ['pass', 'fail'] for qc in record.quality_check_ids):
+ record.add_queue('调拨单质检完成提醒')
except Exception as e:
logging.info('add_queue_compute_state error:%s' % e)
@@ -83,6 +88,11 @@ class SFMessageStockPicking(models.Model):
content = self.deal_stock_picking_sfp(message_queue_id)
if content:
contents.append(content)
+ elif message_queue_id.message_template_id.name == '调拨单质检完成提醒':
+ content = message_queue_id.message_template_id.content
+ content = content.replace('{{picking_type_name}}', self.picking_type_id.name).replace(
+ '{{name}}', self.name)
+ contents.append(content)
return contents, message_queue_ids
def get_special_url(self, id, tmplate_name, special_name, model_id):
diff --git a/sf_message/models/sf_message_workorder.py b/sf_message/models/sf_message_workorder.py
index 653562e1..42b03139 100644
--- a/sf_message/models/sf_message_workorder.py
+++ b/sf_message/models/sf_message_workorder.py
@@ -188,3 +188,10 @@ class SFMessageWork(models.Model):
])
if message_queue_ids:
message_queue_ids.write({'message_status': 'cancel'})
+
+ def write(self, vals):
+ res = super(SFMessageWork, self).write(vals)
+ if ('leave_id' in vals and vals['leave_id'] is False or 'date_planned_start' in vals and vals['date_planned_start'] is False) \
+ and self.schedule_state != '未排':
+ self.add_queue('计划数据异常跟踪')
+ return res
diff --git a/sf_mrs_connect/controllers/controllers.py b/sf_mrs_connect/controllers/controllers.py
index 8c2c7be9..7599ed6c 100644
--- a/sf_mrs_connect/controllers/controllers.py
+++ b/sf_mrs_connect/controllers/controllers.py
@@ -61,7 +61,7 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
for panel in ret['processing_panel'].split(','):
# 查询状态为进行中且工序类型为CNC加工的工单
cnc_workorder_has = production.workorder_ids.filtered(
- lambda ach: ach.routing_type == 'CNC加工' and ach.state not in ['progress', 'done',
+ lambda ach: ach.routing_type in ['CNC加工', '人工线下加工'] and ach.state not in ['progress', 'done',
'rework',
'cancel'] and ach.processing_panel == panel)
if cnc_workorder_has:
@@ -76,7 +76,7 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
for panel in ret['processing_panel'].split(','):
# 查询状态为进行中且工序类型为CNC加工的工单
cnc_workorder = productions.workorder_ids.filtered(
- lambda ac: ac.routing_type == 'CNC加工' and ac.state not in ['progress', 'done', 'rework'
+ lambda ac: ac.routing_type in ['CNC加工', '人工线下加工'] and ac.state not in ['progress', 'done', 'rework'
'cancel'] and ac.processing_panel == panel)
if cnc_workorder:
# program_path_tmp_panel = os.path.join('C://Users//43484//Desktop//fsdownload//test',
@@ -91,19 +91,21 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
logging.info('panel_file_path:%s' % panel_file_path)
cnc_workorder.write({'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())})
pre_workorder = productions.workorder_ids.filtered(
- lambda ap: ap.routing_type == '装夹预调' and ap.state not in ['done', 'rework'
+ lambda ap: ap.routing_type in ['装夹预调', '人工线下加工'] and ap.state not in ['done', 'rework'
'cancel'] and ap.processing_panel == panel)
if pre_workorder:
pre_workorder.write(
{'processing_drawing': base64.b64encode(open(panel_file_path, 'rb').read())})
productions.write({'programming_state': '已编程', 'work_state': '已编程'})
+ productions.filtered(lambda p: p.production_type == '人工线下加工').write({'manual_quotation': True})
logging.info('已更新制造订单编程状态:%s' % productions.ids)
# 对制造订单所有面的cnc工单的程序用刀进行校验
try:
logging.info(f'已更新制造订单:{productions}')
re_tool_chekout = False
- re_tool_chekout = productions.production_cnc_tool_checkout()
+ productions_temp = productions.filtered(lambda p: p.production_type == '自动化产线加工')
+ re_tool_chekout = productions_temp.production_cnc_tool_checkout()
if re_tool_chekout:
return json.JSONEncoder().encode({'status': -3, 'message': '对cnc工单的程序用刀进行校验失败'})
except Exception as e:
@@ -200,6 +202,17 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
'send_time': ret['send_time'],
})
logging.info('已创建无效功能刀具的编程记录:%s' % production.name)
+ elif ret['reprogramming_reason']:
+ production.programming_record_ids.create({
+ 'number': len(production.programming_record_ids) + 1,
+ 'production_id': production.id,
+ 'reason': ret['reprogramming_reason'],
+ 'programming_method': ret['programme_way'],
+ 'current_programming_count': ret['reprogramming_num'],
+ 'target_production_id': productions_reprogram,
+ 'apply_time': ret['trigger_time'],
+ 'send_time': ret['send_time'],
+ })
else:
logging.info('无对应状态,不需更新编程记录')
diff --git a/sf_mrs_connect/models/sync_common.py b/sf_mrs_connect/models/sync_common.py
index d4aa3f36..d6f6dbbe 100644
--- a/sf_mrs_connect/models/sync_common.py
+++ b/sf_mrs_connect/models/sync_common.py
@@ -1988,6 +1988,9 @@ class CuttingSpeed(models.Model):
})
else:
cutting_speed.write({
+ 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search(
+ [('code', '=', item['standard_library_code'].replace("JKM", result[
+ 'factory_short_name']))]).id,
'execution_standard_id': self.env['sf.international.standards'].search(
[('code', '=', item['execution_standard_code'])]).id,
'material_name_id': self.env['sf.materials.model'].search(
@@ -2040,6 +2043,9 @@ class CuttingSpeed(models.Model):
})
else:
cutting_speed.write({
+ 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search(
+ [('code', '=', item['standard_library_code'].replace("JKM", result[
+ 'factory_short_name']))]).id,
'execution_standard_id': self.env['sf.international.standards'].search(
[('code', '=', item['execution_standard_code'])]).id,
'material_name_id': self.env['sf.materials.model'].search(
@@ -2130,6 +2136,9 @@ class CuttingSpeed(models.Model):
})
else:
feed_per_tooth.write({
+ 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search(
+ [('code', '=', item['standard_library_code'].replace("JKM", result[
+ 'factory_short_name']))]).id,
'materials_type_id': self.env['sf.materials.model'].search(
[('materials_no', '=', item['materials_type_code'])]).id,
'cutting_width_depth_id': self.env['sf.cutting.width.depth'].search(
@@ -2168,6 +2177,9 @@ class CuttingSpeed(models.Model):
})
else:
feed_per_tooth.write({
+ 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search(
+ [('code', '=', item['standard_library_code'].replace("JKM", result[
+ 'factory_short_name']))]).id,
'materials_type_id': self.env['sf.materials.model'].search(
[('materials_no', '=', item['materials_type_code'])]).id,
'cutting_width_depth_id': self.env['sf.cutting.width.depth'].search(
@@ -2454,6 +2466,11 @@ class CuttingToolBasicParameters(models.Model):
else:
self.search([('code', '=', integral_tool_item['code'])]).write({
'name': integral_tool_item['name'],
+ 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search(
+ [(
+ 'code', '=',
+ integral_tool_item['standard_library_code'].replace("JKM", result[
+ 'factory_short_name']))]).id,
'total_length': integral_tool_item['total_length'],
'blade_diameter': integral_tool_item['blade_diameter'],
'blade_length': integral_tool_item['blade_length'],
@@ -2516,6 +2533,9 @@ class CuttingToolBasicParameters(models.Model):
else:
self.search([('code', '=', blade_item['code'])]).write({
'name': blade_item['name'],
+ 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search(
+ [('code', '=', blade_item['standard_library_code'].replace("JKM", result[
+ 'factory_short_name']))]).id,
'length': blade_item['length'],
'thickness': blade_item['thickness'],
'cutting_blade_length': blade_item['cutting_blade_length'],
@@ -2573,6 +2593,9 @@ class CuttingToolBasicParameters(models.Model):
else:
self.search([('code', '=', chuck_item['code'])]).write({
'name': chuck_item['name'],
+ 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search(
+ [('code', '=', chuck_item['standard_library_code'].replace("JKM", result[
+ 'factory_short_name']))]).id,
'er_size_model': chuck_item['size_model'],
'min_clamping_diameter': chuck_item['clamping_diameter_min'],
'max_clamping_diameter': chuck_item['clamping_diameter_max'],
@@ -2632,6 +2655,9 @@ class CuttingToolBasicParameters(models.Model):
else:
self.search([('code', '=', cutter_arbor_item['code'])]).write({
'name': cutter_arbor_item['name'],
+ 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search(
+ [('code', '=', cutter_arbor_item['standard_library_code'].replace("JKM", result[
+ 'factory_short_name']))]).id,
'height': cutter_arbor_item['height'],
'width': cutter_arbor_item['width'],
'total_length': cutter_arbor_item['total_length'],
@@ -2697,6 +2723,9 @@ class CuttingToolBasicParameters(models.Model):
else:
self.search([('code', '=', cutter_head_item['code'])]).write({
'name': cutter_head_item['name'],
+ 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search(
+ [('code', '=', cutter_head_item['standard_library_code'].replace("JKM", result[
+ 'factory_short_name']))]).id,
'install_blade_tip_num': cutter_head_item['number_blade_installed'],
'blade_diameter': cutter_head_item['blade_diameter'],
'cutter_head_diameter': cutter_head_item['cutter_diameter'],
@@ -2727,6 +2756,9 @@ class CuttingToolBasicParameters(models.Model):
[('code', '=', knife_handle_item['code']), ('active', 'in', [True, False])])
val = {
'name': knife_handle_item['name'],
+ 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search(
+ [('code', '=', knife_handle_item['standard_library_code'].replace("JKM", result[
+ 'factory_short_name']))]).id,
'taper_shank_model': knife_handle_item['taper_shank_model'],
'total_length': knife_handle_item['total_length'],
'flange_shank_length': knife_handle_item['flange_length'],
@@ -2751,9 +2783,6 @@ class CuttingToolBasicParameters(models.Model):
if not knife_handle:
val['code'] = knife_handle_item['code']
val['cutting_tool_type'] = '刀柄'
- val['standard_library_id'] = self.env['sf.cutting_tool.standard.library'].search(
- [('code', '=', knife_handle_item['standard_library_code'].replace("JKM", result[
- 'factory_short_name']))]).id
self.create(val)
else:
self.search([('code', '=', knife_handle_item['code'])]).write(val)
@@ -2809,6 +2838,11 @@ class CuttingToolBasicParameters(models.Model):
else:
integral_tool.write({
'name': integral_tool_item['name'],
+ 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search(
+ [(
+ 'code', '=',
+ integral_tool_item['standard_library_code'].replace("JKM", result[
+ 'factory_short_name']))]).id,
'total_length': integral_tool_item['total_length'],
'blade_diameter': integral_tool_item['blade_diameter'],
'blade_length': integral_tool_item['blade_length'],
@@ -2871,6 +2905,9 @@ class CuttingToolBasicParameters(models.Model):
else:
blade.write({
'name': blade_item['name'],
+ 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search(
+ [('code', '=', blade_item['standard_library_code'].replace("JKM", result[
+ 'factory_short_name']))]).id,
'length': blade_item['length'],
'thickness': blade_item['thickness'],
'cutting_blade_length': blade_item['cutting_blade_length'],
@@ -2928,6 +2965,9 @@ class CuttingToolBasicParameters(models.Model):
else:
chuck.write({
'name': chuck_item['name'],
+ 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search(
+ [('code', '=', chuck_item['standard_library_code'].replace("JKM", result[
+ 'factory_short_name']))]).id,
'er_size_model': chuck_item['size_model'],
'min_clamping_diameter': chuck_item['clamping_diameter_min'],
'max_clamping_diameter': chuck_item['clamping_diameter_max'],
@@ -2987,6 +3027,9 @@ class CuttingToolBasicParameters(models.Model):
else:
cutter_arbor.write({
'name': cutter_arbor_item['name'],
+ 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search(
+ [('code', '=', cutter_arbor_item['standard_library_code'].replace("JKM", result[
+ 'factory_short_name']))]).id,
'height': cutter_arbor_item['height'],
'width': cutter_arbor_item['width'],
'total_length': cutter_arbor_item['total_length'],
@@ -3053,6 +3096,9 @@ class CuttingToolBasicParameters(models.Model):
else:
cutter_head.write({
'name': cutter_head_item['name'],
+ 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search(
+ [('code', '=', cutter_head_item['standard_library_code'].replace("JKM", result[
+ 'factory_short_name']))]).id,
'install_blade_tip_num': cutter_head_item['number_blade_installed'],
'blade_diameter': cutter_head_item['blade_diameter'],
'cutter_head_diameter': cutter_head_item['cutter_diameter'],
@@ -3114,6 +3160,9 @@ class CuttingToolBasicParameters(models.Model):
else:
knife_handle.write({
'name': knife_handle_item['name'],
+ 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search(
+ [('code', '=', knife_handle_item['standard_library_code'].replace("JKM", result[
+ 'factory_short_name']))]).id,
'total_length': knife_handle_item['total_length'],
'taper_shank_model': knife_handle_item['taper_shank_model'],
'flange_shank_length': knife_handle_item['flange_length'],
diff --git a/sf_quality/models/quality.py b/sf_quality/models/quality.py
index 201103f1..fb8518e2 100644
--- a/sf_quality/models/quality.py
+++ b/sf_quality/models/quality.py
@@ -14,7 +14,8 @@ class QualityCheck(models.Model):
('waiting', '等待'),
('none', '待处理'),
('pass', '通过的'),
- ('fail', '失败的')], string='状态', tracking=True, store=True,
+ ('fail', '失败的'),
+ ('cancel', '已取消'), ], string='状态', tracking=True, store=True,
default='none', copy=False, compute='_compute_quality_state')
individuation_page_PTD = fields.Boolean('个性化记录(是否显示后置三元检测[PTD]页签)', related='workorder_id.individuation_page_PTD')
@@ -84,6 +85,17 @@ class QualityCheck(models.Model):
raise ValidationError('请填写【判定结果】里的信息')
if self.test_results == '合格':
raise ValidationError('请重新选择【判定结果】-【检测结果】')
+ if self.workorder_id.routing_type != 'CNC加工' and self.workorder_id.individuation_page_PTD is False:
+ self.workorder_id.production_id.write({'detection_result_ids': [(0, 0, {
+ 'rework_reason': self.reason,
+ 'detailed_reason': self.detailed_reason,
+ 'processing_panel': self.workorder_id.processing_panel,
+ 'routing_type': self.workorder_id.routing_type,
+ 'handle_result': '待处理',
+ 'test_results': self.test_results,
+ 'test_report': self.workorder_id.detection_report})],
+ 'is_scrap': True if self.test_results == '报废' else False
+ })
if self.workorder_id.state not in ['done']:
self.workorder_id.write(
{'test_results': self.test_results, 'reason': self.reason, 'detailed_reason': self.detailed_reason})
@@ -110,4 +122,3 @@ class QualityCheck(models.Model):
return "零件特采发送成功"
else:
raise ValidationError("零件特采发送失败")
-
diff --git a/sf_quality/views/quality_check_view.xml b/sf_quality/views/quality_check_view.xml
index 61309737..00b139de 100644
--- a/sf_quality/views/quality_check_view.xml
+++ b/sf_quality/views/quality_check_view.xml
@@ -74,6 +74,7 @@
+
@@ -89,8 +90,15 @@
-
+
+ {
+ 'is_web_request': True,
+ 'search_default_progress': 1,
+ 'search_default_passed': 1,
+ 'search_default_failed': 1,
+ }
+
\ No newline at end of file
diff --git a/sf_sale/__manifest__.py b/sf_sale/__manifest__.py
index e824240e..c12f94fd 100644
--- a/sf_sale/__manifest__.py
+++ b/sf_sale/__manifest__.py
@@ -28,6 +28,7 @@
'web.assets_backend': [
'sf_sale/static/js/setTableWidth.js',
'sf_sale/static/src/css/purchase_list.css',
+ 'sf_sale/static/lib/*',
]
},
'demo': [
diff --git a/sf_sale/models/sale_order.py b/sf_sale/models/sale_order.py
index 31681932..66a32f20 100644
--- a/sf_sale/models/sale_order.py
+++ b/sf_sale/models/sale_order.py
@@ -132,7 +132,7 @@ class ReSaleOrder(models.Model):
'name': '%s/%s/%s/%s/%s/%s' % (
self.format_float(product.model_long),
self.format_float(product.model_width),
- self.format_float(product.model_height),
+ self.format_float(product.model_height),
self.format_float(product.model_volume),
machining_accuracy_name,
product.materials_id.name),
@@ -517,9 +517,10 @@ class ResUserToSale(models.Model):
@api.model
def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
+ domain = []
if self._context.get('is_sale'):
if self.env.user.has_group('sf_base.group_sale_director'):
- domain = []
+ pass
elif self.env.user.has_group('sf_base.group_sale_salemanager'):
if self.id != self.env.user.id:
domain = [('id', '=', self.id)]
@@ -528,7 +529,7 @@ class ResUserToSale(models.Model):
return self._search(domain, limit=limit, access_rights_uid=name_get_uid)
elif self._context.get('supplier_rank'):
if self.env.user.has_group('sf_base.group_purchase_director'):
- domain = []
+ pass
elif self.env.user.has_group('sf_base.group_purchase'):
if self.id != self.env.user.id:
domain = [('id', '=', self.id)]
diff --git a/sf_sale/static/lib/merge_field.js b/sf_sale/static/lib/merge_field.js
new file mode 100644
index 00000000..6278c36e
--- /dev/null
+++ b/sf_sale/static/lib/merge_field.js
@@ -0,0 +1,18 @@
+/** @odoo-module */
+
+import { Component } from "@odoo/owl";
+import { registry } from "@web/core/registry";
+
+
+export class MergeField extends Component {
+ get mergeValue() {
+ const data = this.props.record.data;
+
+ const v = data?.product_uom_qty
+ const unit = data?.product_uom[1]
+ return `${v} ${unit}`
+ }
+}
+MergeField.template = "jikimo_sf.MergeField";
+
+registry.category("fields").add("merge_field", MergeField);
diff --git a/sf_sale/static/lib/merge_field.xml b/sf_sale/static/lib/merge_field.xml
new file mode 100644
index 00000000..59103fa9
--- /dev/null
+++ b/sf_sale/static/lib/merge_field.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sf_sale/static/src/css/purchase_list.css b/sf_sale/static/src/css/purchase_list.css
index 2deacb9c..17df6065 100644
--- a/sf_sale/static/src/css/purchase_list.css
+++ b/sf_sale/static/src/css/purchase_list.css
@@ -1,3 +1,11 @@
.purchase_order_list_name {
min-width: 62px !important;
+}
+
+.o_list_renderer .o_list_table .o_data_row td.o_data_cell.o_field_cell.o_list_char.section_and_note_text, .section_and_note_text span{
+ white-space: wrap!important;
+ overflow: auto!important;
+ text-overflow: unset!important;
+ word-wrap: break-word;
+ word-break: break-all;
}
\ No newline at end of file
diff --git a/sf_sale/views/purchase_order_view.xml b/sf_sale/views/purchase_order_view.xml
index ecc061fa..209fd4fa 100644
--- a/sf_sale/views/purchase_order_view.xml
+++ b/sf_sale/views/purchase_order_view.xml
@@ -139,7 +139,7 @@
-
+
{'readonly': [('state', 'in', ['purchase'])]}
diff --git a/sf_sale/views/sale_order_view.xml b/sf_sale/views/sale_order_view.xml
index 1c96bf59..185050c0 100644
--- a/sf_sale/views/sale_order_view.xml
+++ b/sf_sale/views/sale_order_view.xml
@@ -102,7 +102,7 @@
+ string="模型文件" attrs="{'readonly': [('state', 'in', ['draft'])], 'isInList': True}"/>
@@ -112,6 +112,7 @@
{'no_create': True}
{'is_sale_order_line': True }
+ section_and_note_text
{'readonly': [('state', 'in', ['cancel','sale'])]}
@@ -122,6 +123,13 @@
+
+
+
+
+
+ hide
+
{'readonly': [('state', 'in', ['cancel','sale'])]}
@@ -228,6 +236,18 @@
+
+ sale.order.line.tree.sale.stock.qty.sf
+
+ sale.order
+
+
+
+
+
+
+
+
sale.order.quotation.tree.inherit.sf
sale.order
diff --git a/sf_stock/models/stock_picking.py b/sf_stock/models/stock_picking.py
index f8105097..7b73485b 100644
--- a/sf_stock/models/stock_picking.py
+++ b/sf_stock/models/stock_picking.py
@@ -18,7 +18,7 @@ class StockPicking(models.Model):
@api.depends('name')
def _compute_pro_purchase_count(self):
for sp in self:
- if sp:
+ if sp.name and sp.name != '/':
po_ids = self.env['purchase.order'].sudo().search([
('origin', 'like', sp.name), ('purchase_type', '=', 'standard')])
if po_ids:
@@ -52,7 +52,7 @@ class StockPicking(models.Model):
@api.depends('name')
def _compute_pro_out_purchase_count(self):
for sp in self:
- if sp:
+ if sp.name and sp.name != '/':
po_ids = self.env['purchase.order'].sudo().search([
('origin', 'like', sp.name), ('purchase_type', '=', 'outsourcing')])
if po_ids:
diff --git a/sf_warehouse/models/model.py b/sf_warehouse/models/model.py
index fb80618a..581ef114 100644
--- a/sf_warehouse/models/model.py
+++ b/sf_warehouse/models/model.py
@@ -935,11 +935,28 @@ class SfStockPicking(models.Model):
_inherit = 'stock.picking'
check_in = fields.Char(string='查询是否为入库单', compute='_check_is_in')
+ product_uom_qty_sp = fields.Float('需求数量', compute='_compute_product_uom_qty_sp', store=True)
+
+ @api.depends('move_ids_without_package', 'move_ids_without_package.product_uom_qty')
+ def _compute_product_uom_qty_sp(self):
+ for sp in self:
+ if sp.move_ids_without_package:
+ sp.product_uom_qty_sp = 0
+ for move_id in sp.move_ids_without_package:
+ sp.product_uom_qty_sp += move_id.product_uom_qty
+ else:
+ sp.product_uom_qty_sp = 0
def batch_stock_move(self):
"""
批量调拨,非就绪状态的会被忽略,完成后有通知提示
"""
+ # 对所以调拨单的质检单进行是否完成校验
+ sp_ids = [sp.id for sp in self]
+ qc_ids = self.env['quality.check'].sudo().search(
+ [('picking_id', 'in', sp_ids), ('quality_state', 'in', ['waiting', 'none'])])
+ if qc_ids:
+ raise ValidationError(f'单据{[qc.picking_id.name for qc in qc_ids]}未完成质量检查,完成后再试。')
for record in self:
if record.state != 'assigned':
continue
diff --git a/sf_warehouse/views/change_stock_move_views.xml b/sf_warehouse/views/change_stock_move_views.xml
index 9e1073c9..8cde3ca3 100644
--- a/sf_warehouse/views/change_stock_move_views.xml
+++ b/sf_warehouse/views/change_stock_move_views.xml
@@ -156,6 +156,9 @@
+
+
+
diff --git a/web_widget_model_viewer/static/src/js/3d_viewer.css b/web_widget_model_viewer/static/src/js/3d_viewer.css
new file mode 100644
index 00000000..238f793d
--- /dev/null
+++ b/web_widget_model_viewer/static/src/js/3d_viewer.css
@@ -0,0 +1,3 @@
+.model-viewer-in-list {
+ width: 150px;
+}
\ No newline at end of file
diff --git a/web_widget_model_viewer/static/src/js/3d_viewer.js b/web_widget_model_viewer/static/src/js/3d_viewer.js
index 4ed9dcc1..6e4b969c 100644
--- a/web_widget_model_viewer/static/src/js/3d_viewer.js
+++ b/web_widget_model_viewer/static/src/js/3d_viewer.js
@@ -63,11 +63,16 @@ StepViewer.supportedTypes = ["binary"];
StepViewer.props = {
...standardFieldProps,
url: {type: String, optional: true},
+ isInList: {type: Boolean, optional: true},
};
StepViewer.extractProps = ({attrs}) => {
+ const modifiedAttrs = JSON.parse(attrs.modifiers || '{}');
+
+
return {
url: attrs.options.url,
+ isInList: modifiedAttrs.isInList,
};
};
diff --git a/web_widget_model_viewer/static/src/js/3d_viewer.xml b/web_widget_model_viewer/static/src/js/3d_viewer.xml
index 73f142c9..9053d184 100644
--- a/web_widget_model_viewer/static/src/js/3d_viewer.xml
+++ b/web_widget_model_viewer/static/src/js/3d_viewer.xml
@@ -5,6 +5,7 @@