diff --git a/sf_demand_plan/__init__.py b/sf_demand_plan/__init__.py
new file mode 100644
index 00000000..cec04a5b
--- /dev/null
+++ b/sf_demand_plan/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+from . import controllers
+from . import models
+from . import wizard
diff --git a/sf_demand_plan/__manifest__.py b/sf_demand_plan/__manifest__.py
new file mode 100644
index 00000000..932685e0
--- /dev/null
+++ b/sf_demand_plan/__manifest__.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+{
+ 'name': '机企猫智能工厂 需求计划',
+ 'version': '1.0',
+ 'summary': '智能工厂计划管理',
+ 'sequence': 1,
+ 'description': """
+在本模块,支持齐套检查与下达生产
+ """,
+ 'category': 'sf',
+ 'website': 'https://www.sf.jikimo.com',
+ 'depends': ['sf_plan', 'jikimo_printing'],
+ 'data': [
+ 'security/ir.model.access.csv',
+ 'views/demand_plan.xml',
+ 'wizard/sf_demand_plan_print_wizard_view.xml',
+ ],
+ 'demo': [
+ ],
+ 'assets': {
+ 'web.assets_qweb': [
+ ],
+ 'web.assets_backend': [
+ 'sf_demand_plan/static/src/scss/style.css',
+ ]
+ },
+ 'license': 'LGPL-3',
+ 'installable': True,
+ 'application': False,
+ 'auto_install': False,
+}
diff --git a/sf_demand_plan/controllers/__init__.py b/sf_demand_plan/controllers/__init__.py
new file mode 100644
index 00000000..e046e49f
--- /dev/null
+++ b/sf_demand_plan/controllers/__init__.py
@@ -0,0 +1 @@
+from . import controllers
diff --git a/sf_demand_plan/controllers/controllers.py b/sf_demand_plan/controllers/controllers.py
new file mode 100644
index 00000000..5db1ebe6
--- /dev/null
+++ b/sf_demand_plan/controllers/controllers.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+import logging
+import json
+from odoo import http, fields, models
+from odoo.http import request
+from odoo.addons.sf_base.controllers.controllers import MultiInheritController
+
+
+class SfPlanMrsConnect(http.Controller, MultiInheritController):
+
+ @http.route('/api/demand_plan/update_processing_time', type='json', auth='sf_token', methods=['GET', 'POST'],
+ csrf=False,
+ cors="*")
+ def update_processing_time(self, **kw):
+ """
+ 根据模型id修改程序工时
+ :param kw:
+ :return:
+ """
+ try:
+ res = {'status': 1, 'message': '成功'}
+ datas = request.httprequest.data
+ ret = json.loads(datas)
+ ret = json.loads(ret['result'])
+ logging.info('根据模型id修改程序工时:%s' % ret)
+ demand_plan = request.env['sf.production.demand.plan'].sudo().search(
+ [('model_id', '=', ret['model_id'])])
+ if demand_plan:
+ demand_plan.write(
+ {'processing_time': ret['total_estimated_time']})
+ else:
+ res = {'status': 0, 'message': '未查到该需求计划'}
+ except Exception as e:
+ logging.info('update_demand_paln error:%s' % e)
+ res['status'] = -1
+ res['message'] = '系统解析错误!'
+ return json.JSONEncoder().encode(res)
diff --git a/sf_demand_plan/models/__init__.py b/sf_demand_plan/models/__init__.py
new file mode 100644
index 00000000..a0554c11
--- /dev/null
+++ b/sf_demand_plan/models/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+
+from . import sf_production_demand_plan
+from . import sale_order
diff --git a/sf_demand_plan/models/sale_order.py b/sf_demand_plan/models/sale_order.py
new file mode 100644
index 00000000..e1168115
--- /dev/null
+++ b/sf_demand_plan/models/sale_order.py
@@ -0,0 +1,23 @@
+from odoo import models, fields, api, _
+
+
+class ReSaleOrder(models.Model):
+ _inherit = 'sale.order'
+
+ def sale_order_create_line(self, product, item):
+ ret = super(ReSaleOrder, self).sale_order_create_line(product, item)
+ vals = {
+ 'sale_order_id': ret.order_id.id,
+ 'sale_order_line_id': ret.id,
+ }
+ demand_plan = self.env['sf.production.demand.plan'].sudo().create(vals)
+ if demand_plan.product_id.machining_drawings_name:
+ filename_url = demand_plan.product_id.machining_drawings_name.rsplit('.', 1)[0]
+ wizard_vals = {
+ 'demand_plan_id': demand_plan.id,
+ 'model_id': demand_plan.model_id,
+ 'filename_url': filename_url,
+ 'type': '1',
+ }
+ self.env['sf.demand.plan.print.wizard'].sudo().create(wizard_vals)
+ return ret
diff --git a/sf_demand_plan/models/sf_production_demand_plan.py b/sf_demand_plan/models/sf_production_demand_plan.py
new file mode 100644
index 00000000..086412fa
--- /dev/null
+++ b/sf_demand_plan/models/sf_production_demand_plan.py
@@ -0,0 +1,558 @@
+# -*- coding: utf-8 -*-
+import ast
+import json
+from odoo import models, fields, api, _
+from odoo.exceptions import ValidationError
+from odoo.tools import float_compare
+from datetime import datetime, timedelta
+from odoo.exceptions import UserError
+
+
+class SfProductionDemandPlan(models.Model):
+ _name = 'sf.production.demand.plan'
+ _description = 'sf_production_demand_plan'
+
+ def _get_machining_precision(self):
+ machinings = self.env['sf.machining.accuracy'].sudo().search([])
+
+ list = [(m.sync_id, m.name) for m in machinings]
+ return list
+
+ priority = fields.Selection([
+ ('1', '紧急'),
+ ('2', '高'),
+ ('3', '中'),
+ ('4', '低'),
+ ], string='优先级', default='3')
+ status = fields.Selection([
+ ('10', '草稿'),
+ ('20', '待确认'),
+ ('30', '需求确认'),
+ ('50', '待下达生产'),
+ ('60', '已下达'),
+ ('100', '取消'),
+ ], string='状态', compute='_compute_status', store=True)
+ sale_order_id = fields.Many2one(comodel_name="sale.order",
+ string="销售订单", readonly=True)
+ sale_order_line_id = fields.Many2one(comodel_name="sale.order.line",
+ string="销售订单行", readonly=True)
+ company_id = fields.Many2one(
+ related='sale_order_id.company_id',
+ store=True, index=True, precompute=True)
+ partner_id = fields.Many2one(
+ comodel_name='res.partner',
+ related='sale_order_line_id.order_partner_id',
+ string="客户",
+ store=True, index=True)
+ order_remark = fields.Text(related='sale_order_id.remark',
+ string="订单备注", store=True)
+ glb_url = fields.Char(related='sale_order_line_id.glb_url', string='glb文件地址')
+ product_id = fields.Many2one(
+ comodel_name='product.product',
+ related='sale_order_line_id.product_id',
+ string='产品', store=True, index=True)
+ model_id = fields.Char('模型ID', related='product_id.model_id')
+ part_name = fields.Char('零件名称', related='product_id.part_name')
+ part_number = fields.Char('零件图号', compute='_compute_part_number', store=True)
+ is_incoming_material = fields.Boolean('客供料', related='sale_order_line_id.is_incoming_material', store=True)
+ supply_method = fields.Selection([
+ ('automation', "自动化产线加工"),
+ ('manual', "人工线下加工"),
+ ('purchase', "外购"),
+ ('outsourcing', "委外加工"),
+ ], string='供货方式', related='sale_order_line_id.supply_method', store=True)
+ product_uom_qty = fields.Float(
+ string="需求数量",
+ related='sale_order_line_id.product_uom_qty', store=True)
+ deadline_of_delivery = fields.Date('客户交期', related='sale_order_id.deadline_of_delivery', store=True)
+ inventory_quantity_auto_apply = fields.Float(
+ string="成品库存",
+ compute='_compute_inventory_quantity_auto_apply'
+ )
+ qty_delivered = fields.Float(
+ "交货数量", related='sale_order_line_id.qty_delivered')
+ qty_to_deliver = fields.Float(
+ "待交货数量", related='sale_order_line_id.qty_to_deliver')
+ model_long = fields.Char('尺寸', compute='_compute_model_long')
+ materials_id = fields.Char('材料', compute='_compute_materials_id', store=True)
+ model_machining_precision = fields.Selection(selection=_get_machining_precision, string='精度',
+ related='product_id.model_machining_precision')
+ model_process_parameters_ids = fields.Many2many('sf.production.process.parameter',
+ 'plan_process_parameter_rel',
+ string='表面工艺',
+ compute='_compute_model_process_parameters_ids'
+ , store=True
+ )
+ product_remark = fields.Char("产品备注", related='product_id.model_remark')
+ order_code = fields.Char('E-SHOP订单号', related='sale_order_id.order_code')
+ order_state = fields.Selection(
+ string='订单状态',
+ related='sale_order_line_id.state')
+ route_id = fields.Many2one('stock.route', string='路线', related='sale_order_line_id.route_id', store=True)
+ contract_date = fields.Date('合同日期')
+ date_order = fields.Datetime('下单日期', related='sale_order_id.date_order')
+ contract_code = fields.Char('合同号')
+ plan_remark = fields.Text("计划备注")
+ material_check = fields.Selection([
+ ('0', "未齐套"),
+ ('1', "已齐套"),
+ ], string='投料齐套检查', compute='_compute_material_check', store=True)
+ processing_time = fields.Char('程序工时', readonly=True)
+ planned_start_date = fields.Date('计划开工日期')
+ actual_start_date = fields.Date('实际开工日期', compute='_compute_actual_start_date', store=True)
+ actual_end_date = fields.Date('实际完工日期', compute='_compute_actual_end_date', store=True)
+ print_count = fields.Char('打印次数', default='T0C0', readonly=True)
+ sequence = fields.Integer('序号')
+
+ hide_action_open_mrp_production = fields.Boolean(
+ string='显示待工艺确认按钮',
+ compute='_compute_hid_button',
+ default=False
+ )
+
+ hide_action_purchase_orders = fields.Boolean(
+ string='显示采购按钮',
+ compute='_compute_hide_action_purchase_orders',
+ default=False
+ )
+
+ hide_action_stock_picking = fields.Boolean(
+ string='显示调拨单按钮',
+ compute='_compute_hide_action_stock_picking',
+ default=False
+ )
+
+ hide_action_outsourcing_stock_picking = fields.Boolean(
+ string='委外显示调拨单按钮',
+ compute='_compute_hide_action_stock_picking',
+ default=False
+ )
+
+ hide_action_view_programming = fields.Boolean(
+ string='显示编程单按钮',
+ compute='_compute_hid_button',
+ default=False
+ )
+
+ outsourcing_purchase_request = fields.Char('委外采购申请单')
+
+ @api.depends('sale_order_id.state', 'sale_order_id.mrp_production_ids.schedule_state', 'sale_order_id.order_line',
+ 'sale_order_id.mrp_production_ids.state')
+ def _compute_status(self):
+ for record in self:
+ if record.sale_order_id:
+ sale_order_state = record.sale_order_id.state
+ if sale_order_state in ('draft', 'sent', 'supply method'):
+ record.status = '20' # 待确认
+ if record.supply_method in ('purchase', 'outsourcing') and sale_order_state in (
+ 'sale', 'processing', 'physical_distribution', 'delivered',
+ 'done') and sale_order_state != 'cancel':
+ record.status = '60' # 已下达
+ if record.supply_method in ('automation', 'manual'):
+ if sale_order_state in (
+ 'sale', 'processing', 'physical_distribution', 'delivered',
+ 'done') and sale_order_state != 'cancel':
+ record.status = '30' # 需求确认
+ # 检查所有制造订单的排程单状态,有一个为待排程状态,就为待下达生产
+ pending_productions = record.sale_order_id.mrp_production_ids.filtered(
+ lambda p: p.state == 'confirmed' and p.product_id.id == record.product_id.id
+ )
+ if pending_productions:
+ record.status = '50' # 待下达生产
+ # 检查所有制造订单的排程单状态
+ if record.sale_order_id.mrp_production_ids and all(
+ order.product_id == record.product_id and order.schedule_state != '未排' for order in
+ record.sale_order_id.mrp_production_ids):
+ record.status = '60' # 已下达
+ if sale_order_state == 'cancel' or not record.sale_order_line_id:
+ record.status = '100' # 取消
+
+ @api.depends('product_id.part_number', 'product_id.model_name')
+ def _compute_part_number(self):
+ for line in self:
+ if line.product_id:
+ if line.product_id.part_number:
+ line.part_number = line.product_id.part_number
+ else:
+ if line.product_id.model_name:
+ line.part_number = line.product_id.model_name.rsplit('.', 1)[0]
+ else:
+ line.part_number = None
+
+ @api.depends('product_id.length', 'product_id.width', 'product_id.height')
+ def _compute_model_long(self):
+ for line in self:
+ if line.product_id:
+ line.model_long = f"{line.product_id.length}*{line.product_id.width}*{line.product_id.height}"
+ else:
+ line.model_long = None
+
+ @api.depends('product_id.materials_id')
+ def _compute_materials_id(self):
+ for line in self:
+ if line.product_id:
+ line.materials_id = f"{line.product_id.materials_id.name}*{line.product_id.materials_type_id.name}"
+ else:
+ line.materials_id = None
+
+ @api.depends('product_id.model_process_parameters_ids')
+ def _compute_model_process_parameters_ids(self):
+ for line in self:
+ if line.product_id and line.product_id.model_process_parameters_ids:
+ line.model_process_parameters_ids = [(6, 0, line.product_id.model_process_parameters_ids.ids)]
+ else:
+ line.model_process_parameters_ids = [(5, 0, 0)]
+
+ def _compute_inventory_quantity_auto_apply(self):
+ location_id = self.env['stock.location'].search([('name', '=', '成品存货区')], limit=1).id
+ product_ids = self.mapped('product_id').ids
+ if product_ids:
+ quant_data = self.env['stock.quant'].read_group(
+ domain=[
+ ('product_id', 'in', product_ids),
+ ('location_id', '=', location_id)
+ ],
+ fields=['product_id', 'inventory_quantity_auto_apply'],
+ groupby=['product_id']
+ )
+ quantity_map = {item['product_id'][0]: item['inventory_quantity_auto_apply'] for item in quant_data}
+ else:
+ quantity_map = {}
+ for line in self:
+ if line.product_id:
+ line.inventory_quantity_auto_apply = quantity_map.get(line.product_id.id, 0.0)
+ else:
+ line.inventory_quantity_auto_apply = 0.0
+
+ @api.depends('sale_order_id.mrp_production_ids.workorder_ids.date_start')
+ def _compute_actual_start_date(self):
+ for record in self:
+ if record.sale_order_id and record.sale_order_id.mrp_production_ids:
+ manufacturing_orders = record.sale_order_id.mrp_production_ids.filtered(
+ lambda mo: mo.product_id == record.product_id)
+ if manufacturing_orders:
+ start_dates = [
+ workorder.date_start.date() for mo in manufacturing_orders
+ for workorder in mo.workorder_ids if workorder.date_start
+ ]
+ record.actual_start_date = min(start_dates) if start_dates else None
+ else:
+ record.actual_start_date = None
+ else:
+ record.actual_start_date = None
+
+ @api.depends('sale_order_id.mrp_production_ids.workorder_ids.state',
+ 'sale_order_id.mrp_production_ids.workorder_ids.date_finished')
+ def _compute_actual_end_date(self):
+ for record in self:
+ if record.sale_order_id and record.sale_order_id.mrp_production_ids:
+ manufacturing_orders = record.sale_order_id.mrp_production_ids.filtered(
+ lambda mo: mo.product_id == record.product_id)
+ finished_orders = manufacturing_orders.filtered(lambda mo: mo.state == 'done')
+ if len(finished_orders) >= record.product_uom_qty:
+ end_dates = [
+ workorder.date_finished.date() for mo in finished_orders
+ for workorder in mo.workorder_ids if workorder.date_finished
+ ]
+ record.actual_end_date = max(end_dates) if end_dates else None
+ else:
+ record.actual_end_date = None
+ else:
+ record.actual_end_date = None
+
+ @api.depends('sale_order_id.mrp_production_ids.move_raw_ids.forecast_availability',
+ 'sale_order_id.mrp_production_ids.move_raw_ids.quantity_done')
+ def _compute_material_check(self):
+ for record in self:
+ if record.sale_order_id and record.sale_order_id.mrp_production_ids:
+ manufacturing_orders = record.sale_order_id.mrp_production_ids.filtered(
+ lambda mo: mo.product_id == record.product_id)
+ if manufacturing_orders and manufacturing_orders.move_raw_ids:
+ total_forecast_availability = sum(manufacturing_orders.mapped('move_raw_ids.forecast_availability'))
+ total_quantity_done = sum(manufacturing_orders.mapped('move_raw_ids.quantity_done'))
+ total_sum = total_forecast_availability + total_quantity_done
+ if float_compare(total_sum, record.product_uom_qty,
+ precision_rounding=record.product_id.uom_id.rounding) >= 0:
+ record.material_check = '1' # 已齐套
+ else:
+ record.material_check = '0' # 未齐套
+ else:
+ record.material_check = None
+ else:
+ record.material_check = None
+
+ @api.constrains('planned_start_date')
+ def _check_planned_start_date(self):
+ for record in self:
+ if record.planned_start_date and record.planned_start_date < fields.Date.today():
+ raise ValidationError("计划开工日期必须大于或等于今天。")
+
+ def release_production_order(self):
+ pro_plan_list = self.env['sf.production.plan'].search(
+ [('product_id', '=', self.product_id.id), ('state', '=', 'draft')])
+ sf_production_line = self.env['sf.production.line'].sudo().search(
+ [('name', '=', '1#CNC自动生产线')], limit=1)
+ current_datetime = datetime.now() + timedelta(minutes=3)
+ current_hour = current_datetime.hour + current_datetime.minute / 60
+ date_planned_start = None
+ production_lines = sf_production_line.mrp_workcenter_ids.filtered(lambda b: "自动生产线" in b.name)
+ if production_lines:
+ if not production_lines.deal_with_workcenter_calendar(current_datetime):
+ attendance_list = production_lines.resource_calendar_id.attendance_ids
+ # 获取所有工作日规则并按星期几分组
+ attendance_by_day = {}
+ for attendance in attendance_list:
+ if attendance.dayofweek not in attendance_by_day:
+ attendance_by_day[attendance.dayofweek] = []
+ attendance_by_day[attendance.dayofweek].append(attendance)
+
+ for day_offset in range(0, 8):
+ check_date = current_datetime + timedelta(days=day_offset)
+ # 日期为星期几
+ check_day = production_lines.get_current_day_of_week(check_date)
+ if check_day in attendance_by_day:
+ day_attendances = attendance_by_day[check_day]
+ if day_offset == 0:
+ for attendance in day_attendances:
+ if current_hour < attendance.hour_to:
+ # 找到下一个有效时间段
+ if current_hour < attendance.hour_from:
+ # 使用开始时间
+ date_planned_start = check_date.replace(
+ hour=int(attendance.hour_from),
+ minute=int((attendance.hour_from % 1) * 60),
+ second=0,
+ microsecond=0
+ )
+ else:
+ continue
+ break
+ else:
+ # 不是今天,使用第一个工作时间段
+ attendance = day_attendances[0]
+ date_planned_start = check_date.replace(
+ hour=int(attendance.hour_from),
+ minute=int((attendance.hour_from % 1) * 60),
+ second=0,
+ microsecond=0
+ )
+
+ if date_planned_start:
+ break
+ else:
+ date_planned_start = current_datetime
+
+ if date_planned_start:
+ pro_plan_list.production_line_id = sf_production_line.id
+ pro_plan_list.date_planned_start = date_planned_start
+ for pro_plan in pro_plan_list:
+ pro_plan.do_production_schedule()
+
+ def button_action_print(self):
+ return {
+ 'res_model': 'sf.demand.plan.print.wizard',
+ 'type': 'ir.actions.act_window',
+ 'name': _("打印"),
+ 'domain': [('demand_plan_id', 'in', self.ids)],
+ 'views': [[self.env.ref('sf_demand_plan.action_plan_print_tree').id, 'list']],
+ 'target': 'new',
+ }
+
+ @api.depends('sale_order_id.mrp_production_ids.state', 'sale_order_id.mrp_production_ids.programming_state')
+ def _compute_hid_button(self):
+ for record in self:
+ mrp_production_ids = record.sale_order_id.mrp_production_ids.filtered(
+ lambda p: p.state == 'technology_to_confirmed' and p.product_id.id == record.product_id.id
+ )
+ record.hide_action_open_mrp_production = bool(mrp_production_ids) and record.supply_method in (
+ 'automation', 'manual')
+ programming_mrp_production_ids = record.sale_order_id.mrp_production_ids.filtered(
+ lambda p: p.programming_state == '编程中' and p.product_id.id == record.product_id.id
+ )
+ record.hide_action_view_programming = bool(programming_mrp_production_ids)
+
+ def _compute_hide_action_purchase_orders(self):
+ for record in self:
+ record.hide_action_purchase_orders = False
+ outsourcing_purchase_request = []
+ if record.supply_method in ('automation',
+ 'manual') and record.material_check == '0' and not record.sale_order_line_id.is_incoming_material:
+ mrp_production = record.sale_order_id.mrp_production_ids.filtered(
+ lambda p: p.product_id.id == record.product_id.id
+ ).sorted(key=lambda p: p.id)
+ if mrp_production:
+ raw_materials = mrp_production.mapped('move_raw_ids.product_id')
+ if raw_materials:
+ purchase_orders = self.env['purchase.order'].sudo().search([
+ ('state', '=', 'purchase'),
+ ('order_line.product_id', 'in', raw_materials.ids)
+ ])
+ total_purchase_quantity = sum(
+ sum(
+ order.order_line.filtered(
+ lambda line: line.product_id in raw_materials
+ ).mapped('product_qty')
+ )
+ for order in purchase_orders
+ )
+ if total_purchase_quantity < record.product_uom_qty:
+ pr_ids = self.env['purchase.request'].sudo().search(
+ [('origin', 'like', mrp_production[0].name), ('state', '!=', 'done')])
+ outsourcing_purchase_request.extend(pr_ids.ids)
+ elif record.supply_method in ('purchase', 'outsourcing'):
+ pr_ids = None
+ purchase_orders = self.env['purchase.order'].sudo().search([
+ ('state', 'in', ('purchase', 'done')),
+ ('order_line.product_id', '=', record.product_id.id)
+ ])
+ total_purchase_quantity = sum(
+ sum(
+ order.order_line.filtered(
+ lambda line: line.product_id in record.product_id
+ ).mapped('product_qty')
+ )
+ for order in purchase_orders
+ )
+ if total_purchase_quantity < record.product_uom_qty:
+ pr_ids = self.env['purchase.request'].sudo().search(
+ [('origin', 'like', record.sale_order_id.name), ('state', '!=', 'done')])
+ outsourcing_purchase_request.extend(pr_ids.ids)
+ if record.supply_method == 'outsourcing' and not record.sale_order_line_id.is_incoming_material:
+ bom_line_ids = record.product_id.bom_ids.bom_line_ids
+ # BOM_数量
+ total_product_qty = sum(line.product_qty for line in bom_line_ids)
+ bom_product_ids = bom_line_ids.mapped('product_id')
+ product_purchase_orders = self.env['purchase.order'].sudo().search([
+ ('state', 'in', ('purchase', 'done')),
+ ('order_line.product_id', 'in', bom_product_ids.ids)
+ ])
+ # 购订单_数量
+ total_outsourcing_purchase_quantity = sum(
+ sum(
+ order.order_line.filtered(
+ lambda line: line.product_id in bom_product_ids
+ ).mapped('product_qty')
+ )
+ for order in product_purchase_orders
+ )
+ if total_outsourcing_purchase_quantity / total_product_qty < record.product_uom_qty:
+ purchase_request = self.env['purchase.request'].sudo().search(
+ [('line_ids.product_id', 'in', bom_product_ids.ids),
+ ('line_ids.purchase_state', 'not in', ('purchase', 'done')), ('state', '!=', 'done')])
+ outsourcing_purchase_request.extend(purchase_request.ids)
+ record.outsourcing_purchase_request = json.dumps(outsourcing_purchase_request)
+ if outsourcing_purchase_request:
+ record.hide_action_purchase_orders = True
+
+ @api.depends('sale_order_id.mrp_production_ids.picking_ids.state', 'sale_order_id.picking_ids.state')
+ def _compute_hide_action_stock_picking(self):
+ for record in self:
+ record.hide_action_stock_picking = False
+ record.hide_action_outsourcing_stock_picking = False
+ if record.supply_method in ('automation', 'manual'):
+ manufacturing_orders = record.sale_order_id.mrp_production_ids
+ record.hide_action_stock_picking = bool(manufacturing_orders.mapped('picking_ids').filtered(
+ lambda p: p.state == 'assigned'))
+ elif record.supply_method in ('purchase', 'outsourcing'):
+ assigned_picking_ids = record.sale_order_id.picking_ids.filtered(
+ lambda
+ p: p.state == 'assigned' and p.picking_type_id.name != '发料出库' and p.move_line_ids.product_id in record.product_id)
+ if record.supply_method == 'outsourcing':
+ outsourcing_assigned_picking_ids = record.get_outsourcing_picking_ids()
+ record.hide_action_outsourcing_stock_picking = outsourcing_assigned_picking_ids
+ record.hide_action_stock_picking = assigned_picking_ids or outsourcing_assigned_picking_ids
+ else:
+ record.hide_action_stock_picking = assigned_picking_ids
+
+ def get_outsourcing_picking_ids(self):
+ order_ids = self.env['purchase.order'].sudo().search(
+ [('order_line.product_id', 'in', self.product_id.ids),
+ ('purchase_type', '=', 'outsourcing')])
+ outsourcing_picking_ids = order_ids._get_subcontracting_resupplies()
+ outsourcing_assigned_picking_ids = outsourcing_picking_ids.filtered(lambda p: p.state == 'assigned')
+ return outsourcing_assigned_picking_ids
+
+ def action_open_sale_order(self):
+ self.ensure_one()
+ return {
+ 'type': 'ir.actions.act_window',
+ 'res_model': 'sale.order',
+ 'res_id': self.sale_order_id.id,
+ 'view_mode': 'form',
+ }
+
+ def action_open_mrp_production(self):
+ self.ensure_one()
+ mrp_production_ids = self.sale_order_id.mrp_production_ids.filtered(
+ lambda p: p.state == 'technology_to_confirmed' and p.product_id.id == self.product_id.id
+ )
+ action = {
+ 'res_model': 'mrp.production',
+ 'type': 'ir.actions.act_window',
+ }
+ if len(mrp_production_ids) == 1:
+ action.update({
+ 'view_mode': 'form',
+ 'res_id': mrp_production_ids.id,
+ })
+ else:
+ action.update({
+ 'name': _("制造订单列表"),
+ 'domain': [('id', 'in', mrp_production_ids.ids)],
+ 'view_mode': 'tree,form',
+ })
+ return action
+
+ def action_view_purchase_request(self):
+ self.ensure_one()
+ pr_ids = self.env['purchase.request'].sudo().search(
+ [('id', 'in', ast.literal_eval(self.outsourcing_purchase_request))])
+ action = {
+ 'res_model': 'purchase.request',
+ 'type': 'ir.actions.act_window',
+ }
+ if len(pr_ids) == 1:
+ action.update({
+ 'view_mode': 'form',
+ 'res_id': pr_ids[0].id,
+ })
+ else:
+ action.update({
+ 'name': _("采购申请"),
+ 'domain': [('id', 'in', pr_ids.ids)],
+ 'view_mode': 'tree,form',
+ })
+ return action
+
+ def action_view_stock_picking(self):
+ self.ensure_one()
+ action = self.env["ir.actions.actions"]._for_xml_id("stock.action_picking_tree_all")
+ picking_ids = None
+ if self.supply_method in ('automation', 'manual'):
+ picking_ids = self.sale_order_id.mrp_production_ids.mapped('picking_ids').filtered(
+ lambda p: p.state == 'assigned')
+ elif self.supply_method in ('purchase', 'outsourcing'):
+ picking_ids = self.sale_order_id.picking_ids.filtered(
+ lambda
+ p: p.state == 'assigned' and p.picking_type_id.name != '发料出库' and p.move_line_ids.product_id in self.product_id)
+ if self.supply_method == 'outsourcing' and self.hide_action_outsourcing_stock_picking:
+ picking_ids = picking_ids.union(self.get_outsourcing_picking_ids())
+ if picking_ids:
+ if len(picking_ids) > 1:
+ action['domain'] = [('id', 'in', picking_ids.ids)]
+ elif picking_ids:
+ action['res_id'] = picking_ids.id
+ action['views'] = [(self.env.ref('stock.view_picking_form').id, 'form')]
+ if 'views' in action:
+ action['views'] += [(state, view) for state, view in action['views'] if view != 'form']
+ return action
+
+ def action_view_programming(self):
+ self.ensure_one()
+ programming_mrp_production_ids = self.sale_order_id.mrp_production_ids.filtered(
+ lambda p: p.programming_state == '编程中' and p.product_id.id == self.product_id.id
+ ).mapped('programming_no')
+ if programming_mrp_production_ids:
+ programming_no = list(set(programming_mrp_production_ids))
+ numbers_str = "、".join(programming_no)
+ raise ValidationError(f"编程单号:{numbers_str},请去云平台处理")
diff --git a/sf_demand_plan/security/ir.model.access.csv b/sf_demand_plan/security/ir.model.access.csv
new file mode 100644
index 00000000..56e8e247
--- /dev/null
+++ b/sf_demand_plan/security/ir.model.access.csv
@@ -0,0 +1,6 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_sf_production_demand_plan,sf.production.demand.plan,model_sf_production_demand_plan,base.group_user,1,0,0,0
+access_sf_production_demand_plan_for_dispatch,sf.production.demand.plan for dispatch,model_sf_production_demand_plan,sf_base.group_plan_dispatch,1,1,0,0
+
+access_sf_demand_plan_print_wizard,sf.demand.plan.print.wizard,model_sf_demand_plan_print_wizard,base.group_user,1,0,0,0
+access_sf_demand_plan_print_wizard_for_dispatch,sf.demand.plan.print.wizard for dispatch,model_sf_demand_plan_print_wizard,sf_base.group_plan_dispatch,1,1,0,0
diff --git a/sf_demand_plan/static/src/scss/style.css b/sf_demand_plan/static/src/scss/style.css
new file mode 100644
index 00000000..f5b687fb
--- /dev/null
+++ b/sf_demand_plan/static/src/scss/style.css
@@ -0,0 +1,11 @@
+.demand_plan_tree .o_list_table_ungrouped th:not(.o_list_record_selector,.row_no,[data-name=sequence]) {
+ min-width: 98px !important;
+}
+
+.demand_plan_tree .o_list_table_grouped th:not(.o_list_record_selector,.row_no,[data-name=sequence]) {
+ width: 98px !important;
+}
+
+.demand_plan_tree .o_list_table_ungrouped {
+ min-width: 1900px;
+}
diff --git a/sf_demand_plan/views/demand_plan.xml b/sf_demand_plan/views/demand_plan.xml
new file mode 100644
index 00000000..587bb2ce
--- /dev/null
+++ b/sf_demand_plan/views/demand_plan.xml
@@ -0,0 +1,119 @@
+
+
+ sf.production.demand.plan.tree
+ sf.production.demand.plan
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ sf.production.demand.plan.search
+ sf.production.demand.plan
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 需求计划
+ ir.actions.act_window
+ sf.production.demand.plan
+ tree
+
+
+
+
+
\ No newline at end of file
diff --git a/sf_demand_plan/wizard/__init__.py b/sf_demand_plan/wizard/__init__.py
new file mode 100644
index 00000000..170f9e85
--- /dev/null
+++ b/sf_demand_plan/wizard/__init__.py
@@ -0,0 +1 @@
+from . import sf_demand_plan_print_wizard
diff --git a/sf_demand_plan/wizard/sf_demand_plan_print_wizard.py b/sf_demand_plan/wizard/sf_demand_plan_print_wizard.py
new file mode 100644
index 00000000..871d7e5c
--- /dev/null
+++ b/sf_demand_plan/wizard/sf_demand_plan_print_wizard.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+import logging
+from odoo import models, fields, api, _
+
+_logger = logging.getLogger(__name__)
+
+
+class SfDemandPlanPrintWizard(models.TransientModel):
+ _name = 'sf.demand.plan.print.wizard'
+ _description = u'打印向导'
+
+ demand_plan_id = fields.Many2one('sf.production.demand.plan', string='需求计划ID')
+ product_id = fields.Many2one(
+ comodel_name='product.product',
+ related='demand_plan_id.product_id',
+ string='产品', store=True, index=True)
+ model_id = fields.Char('模型ID')
+ filename_url = fields.Char('文件名/URL')
+ type = fields.Selection([
+ ('1', '图纸'),
+ ('2', '程序单'),
+ ], string='类型')
+ status = fields.Selection([
+ ('not_start', '未开始'),
+ ('success', '成功'),
+ ('fail', '失败'),
+ ], string='状态', default='not_start')
+ machining_drawings = fields.Binary('2D加工图纸', related='product_id.machining_drawings', store=True)
+
+ workorder_id = fields.Many2one('mrp.workorder', string='工单')
+ cnc_worksheet = fields.Binary('程序单')
+
+ def demand_plan_print(self):
+ for record in self:
+ pdf_data = record.machining_drawings if record.type == '1' else record.cnc_worksheet
+ if pdf_data:
+ try:
+ # 执行打印
+ self.env['jikimo.printing'].sudo().print_pdf(pdf_data)
+ record.status = 'success'
+ t_part, c_part = record.demand_plan_id.print_count.split('C')
+ t_num = int(t_part[1:])
+ c_num = int(c_part)
+ if record.type == '1':
+ t_num += 1
+ elif record.type == '2':
+ c_num += 1
+ record.demand_plan_id.print_count = f"T{t_num}C{c_num}"
+ except Exception as e:
+ record.status = 'fail'
+ _logger.error(f"文件{record.filename_url}打印失败: {str(e)}")
+
+
+class MrpWorkorder(models.Model):
+ _inherit = 'mrp.workorder'
+
+ def write(self, vals):
+ res = super(MrpWorkorder, self).write(vals)
+ for record in self:
+ if 'cnc_worksheet' in vals:
+ demand_plan_print = self.env['sf.demand.plan.print.wizard'].sudo().search(
+ [('workorder_id', '=', record.id)])
+ if demand_plan_print:
+ self.env['sf.demand.plan.print.wizard'].sudo().write(
+ {'cnc_worksheet': res.cnc_worksheet, 'filename_url': record.cnc_worksheet_name})
+ else:
+ demand_plan = self.env['sf.production.demand.plan'].sudo().search(
+ [('product_id', '=', record.product_id.id)])
+ if demand_plan:
+ wizard_vals = {
+ 'demand_plan_id': demand_plan.id,
+ 'model_id': demand_plan.model_id,
+ 'type': '2',
+ 'workorder_id': record.id,
+ 'cnc_worksheet': record.cnc_worksheet,
+ 'filename_url': record.cnc_worksheet_name
+ }
+ self.env['sf.demand.plan.print.wizard'].sudo().create(wizard_vals)
+ return res
diff --git a/sf_demand_plan/wizard/sf_demand_plan_print_wizard_view.xml b/sf_demand_plan/wizard/sf_demand_plan_print_wizard_view.xml
new file mode 100644
index 00000000..311e3ae8
--- /dev/null
+++ b/sf_demand_plan/wizard/sf_demand_plan_print_wizard_view.xml
@@ -0,0 +1,17 @@
+
+
+
+ sf.demand.plan.print.wizard.tree
+ sf.demand.plan.print.wizard
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sf_manufacturing/models/mrp_workorder.py b/sf_manufacturing/models/mrp_workorder.py
index 832af867..06f23cd2 100644
--- a/sf_manufacturing/models/mrp_workorder.py
+++ b/sf_manufacturing/models/mrp_workorder.py
@@ -334,6 +334,7 @@ class ResMrpWorkOrder(models.Model):
tag_type = fields.Selection([("重新加工", "重新加工")], string="标签", tracking=True)
technology_design_id = fields.Many2one('sf.technology.design')
+ cnc_worksheet_name = fields.Char('工作指令文件名', readonly=True)
def _compute_default_construction_period_status(self):
need_list = ['pending', 'waiting', 'ready', 'progress', 'to be detected', 'done']
diff --git a/sf_mrs_connect/controllers/controllers.py b/sf_mrs_connect/controllers/controllers.py
index 7a72c7ed..1091f905 100644
--- a/sf_mrs_connect/controllers/controllers.py
+++ b/sf_mrs_connect/controllers/controllers.py
@@ -91,12 +91,15 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
program_path_tmp_panel = os.path.join('/tmp', ret['folder_name'], 'return', panel)
files_panel = os.listdir(program_path_tmp_panel)
panel_file_path = ''
+ panel_file_name = ''
if files_panel:
for file in files_panel:
file_extension = os.path.splitext(file)[1]
if file_extension.lower() == '.pdf':
panel_file_path = os.path.join(program_path_tmp_panel, file)
+ panel_file_name = os.path.splitext(file)[0]
logging.info('panel_file_path:%s' % panel_file_path)
+ logging.info('panel_file_name:%s' % panel_file_name)
# 向编程单中添加二维码
request.env['printing.utils'].add_qr_code_to_pdf(
@@ -105,7 +108,8 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
"模型ID:%s" % model_id,
"零件图号:%s" % part_number if part_number else None
)
- cnc_workorder.write({'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())})
+ cnc_workorder.write({'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read()),
+ 'cnc_worksheet_name': panel_file_name})
pre_workorder = productions.workorder_ids.filtered(
lambda ap: ap.routing_type in ['装夹预调', '人工线下加工'] and ap.state not in ['done', 'rework'
'cancel'] and ap.processing_panel == panel)