Compare commits

..

2 Commits

143 changed files with 2416 additions and 7363 deletions

View File

@@ -1,2 +0,0 @@
# -*- coding: utf-8 -*-
from . import models

View File

@@ -1,18 +0,0 @@
# -*- coding: utf-8 -*-
{
'name': '机企猫 需求计划排程队列',
'version': '1.0',
'summary': """ 使用队列进行排程 """,
'author': 'fox',
'website': '',
'category': '',
'depends': ['queue_job_batch', 'sf_demand_plan'],
'data': [
],
'application': True,
'installable': True,
'auto_install': False,
'license': 'LGPL-3',
}

View File

@@ -1,2 +0,0 @@
# -*- coding: utf-8 -*-
from . import production_demand_plan

View File

@@ -1,20 +0,0 @@
from odoo import models, fields
class ProductionDemandPlan(models.Model):
_inherit = 'sf.production.demand.plan'
def _do_production_schedule(self, pro_plan_list):
"""使用队列进行排程"""
batch_size = 10
current_time = fields.Datetime.now().strftime('%Y%m%d%H%M%S')
index = 1
for i in range(0, len(pro_plan_list), batch_size):
batch = self.env['queue.job.batch'].get_new_batch('plan-%s-%s' % (current_time, index))
pro_plans = pro_plan_list[i:i+batch_size]
pro_plans.with_context(
job_batch=batch
).with_delay().do_production_schedule()
index += 1
batch.enqueue()

View File

@@ -119,24 +119,13 @@ patch(ListRenderer.prototype, 'jikimo_frontend.ListRenderer', {
this.listherHeaderBodyNum()
})
const treeModifiers = this.getFieldModifiers(this.props.archInfo.__rawArch);
if(treeModifiers) {
if(treeModifiers.merge_fields) {
this.props.merge_key = treeModifiers.merge_key;
this.props.merge_fields = treeModifiers.merge_fields.split(',');
const data = this.setColumns(this.props.merge_key);
owl.onMounted(() => {
this.mergeColumns(this.props.merge_fields, data)
})
}
if(treeModifiers.pacthResize) {
owl.onPatched(() => {
this.columnWidths = null;
this.freezeColumnWidths();
})
}
this.props.merge_key = treeModifiers.merge_key;
this.props.merge_fields = treeModifiers.merge_fields.split(',');
const data = this.setColumns(this.props.merge_key);
owl.onMounted(() => {
this.mergeColumns(this.props.merge_fields, data)
})
}
return this._super(...arguments);
},

View File

@@ -521,6 +521,11 @@ div:has(.o_required_modifier) > label::before {
}
}
// 设置表格横向滚动
.o_list_renderer.o_renderer {
max-width: 100%;
overflow-x: auto;
}
// 设置表单页面label文本不换行
.o_form_view .o_group .o_wrap_label .o_form_label {

View File

@@ -8,22 +8,18 @@
'category': 'purchase',
'depends': ['sf_manufacturing', 'purchase_request'],
'data': [
'security/ir.model.access.csv',
'views/sale_order_view.xml',
'views/purchase_order.xml',
'views/mrp_production.xml',
'views/purchase_request_view.xml',
'wizard/purchase_request_line_make_purchase_order_view.xml',
'views/purchase_request_line_view.xml',
'views/stock_picking_views.xml',
'wizard/purchase_request_wizard_views.xml',
'views/purchase_request_menu_views.xml',
],
'assets': {
'web.assets_backend': [
'jikimo_purchase_request/static/src/**/*'
],
},
'web.assets_backend': [
'jikimo_purchase_request/static/src/**/*'
],
},
'application': True,
'installable': True,
'auto_install': False,

View File

@@ -410,7 +410,7 @@ msgstr "显示名称"
#: model_terms:ir.ui.view,arch_db:purchase_request.view_purchase_request_form
#: model_terms:ir.ui.view,arch_db:purchase_request.view_purchase_request_search
msgid "Done"
msgstr "关闭"
msgstr "完成"
#. module: purchase_request
#: model:ir.model.fields,field_description:purchase_request.field_purchase_request_line__move_dest_ids

View File

@@ -6,4 +6,3 @@ from . import mrp_production
from . import purchase_order
from . import stock_rule
from . import stock_picking
from . import product_product

View File

@@ -9,11 +9,19 @@ class MrpProduction(models.Model):
@api.depends('state')
def _compute_pr_mp_count(self):
for item in self:
if item.product_id.is_customer_provided:
item.pr_mp_count = 0
else:
pr_ids = item._get_purchase_request()
item.pr_mp_count = len(pr_ids)
# if item.product_id.product_tmpl_id.single_manufacturing == True and not item.is_remanufacture:
# first_order = self.env['mrp.production'].search(
# [('origin', '=', item.origin), ('product_id', '=', item.product_id.id)], limit=1, order='id asc')
# pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', first_order.name)])
# item.pr_mp_count = len(pr_ids)
# else:
# pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', item.name)])
# item.pr_mp_count = len(pr_ids)
# 由于采购申请合并了所有销售订单行的采购,所以不区分产品
first_mp = self.env['mrp.production'].search(
[('origin', '=', item.origin)], limit=1, order='id asc')
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', first_mp.name)])
item.pr_mp_count = len(pr_ids)
# pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', item.name), ('is_subcontract', '!=', 'True')])
def action_view_pr_mp(self):
@@ -21,9 +29,18 @@ class MrpProduction(models.Model):
采购请求
"""
self.ensure_one()
# pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', self.name),('is_subcontract', '!=', True)])
# if self.product_id.product_tmpl_id.single_manufacturing == True and not self.is_remanufacture:
# first_order = self.env['mrp.production'].search(
# [('origin', '=', self.origin), ('product_id', '=', self.product_id.id)], limit=1, order='id asc')
# pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', first_order.name)])
# else:
# pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', self.name)])
# 由于采购申请合并了所有销售订单行的采购,所以不区分产品
pr_ids = self._get_purchase_request()
first_mp = self.env['mrp.production'].search(
[('origin', '=', self.origin)], limit=1, order='id asc')
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', first_mp.name)])
action = {
'res_model': 'purchase.request',
@@ -37,16 +54,7 @@ class MrpProduction(models.Model):
else:
action.update({
'name': _("%s生成采购请求单", self.name),
'domain': [('id', 'in', pr_ids.ids)],
'domain': [('id', 'in', pr_ids)],
'view_mode': 'tree,form',
})
return action
def _get_purchase_request(self):
"""获取跟制造订单相关的采购申请单(根据采购申请单行项目的产品匹配)"""
mrp_names = self.env['mrp.production'].search([('origin', '=', self.origin)]).mapped('name')
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'in', mrp_names)])
product_list = self.product_id._get_product_include_bom()
pr_line_ids = pr_ids.line_ids.filtered(lambda l: l.product_id in product_list)
return pr_line_ids.mapped('request_id')

View File

@@ -1,17 +0,0 @@
from odoo import models
class ProductProduct(models.Model):
_inherit = 'product.product'
def _get_product_include_bom(self):
"""获取产品列表包括所有bom"""
self.ensure_one()
product_list = [self]
bom_ids = self.bom_ids
while (bom_ids):
bom_product_ids = bom_ids.bom_line_ids.mapped('product_id')
product_list.append(bom_product_ids)
bom_ids = bom_product_ids.bom_ids
return product_list

View File

@@ -1,5 +1,4 @@
from odoo import api, fields, models, _
from odoo.tools import float_compare
class PurchaseOrder(models.Model):
@@ -14,114 +13,4 @@ class PurchaseOrder(models.Model):
('done', '完成'),
('cancel', '取消'),
('rejected', '已驳回')
], string='Status', readonly=True, index=True, copy=False, default='draft', tracking=True)
# 成品采购订单对应的坯料采购申请单和采购订单数量
purchase_request_count = fields.Integer('子·采购申请数量', compute='_compute_purchase_request')
purchase_order_count = fields.Integer('子·采购订单数量', compute='_compute_purchase_request')
@api.depends('state')
def _compute_purchase_request(self):
for record in self:
purchase_request_ids, purchase_order_ids = record.get_purchase_request_order()
record.purchase_request_count = len(purchase_request_ids)
record.purchase_order_count = len(purchase_order_ids)
def action_view_preform_body_purchase_request(self):
self.ensure_one()
name_list = self._get_pinking_name()
purchase_request_ids = self.env['purchase.request'].search([('origin', 'in', name_list)])
action = {
'res_model': 'purchase.request',
'type': 'ir.actions.act_window',
}
if len(purchase_request_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': purchase_request_ids[0].id,
})
else:
action.update({
'name': _("子·采购申请"),
'domain': [('id', 'in', purchase_request_ids.ids)],
'view_mode': 'tree,form',
})
return action
def action_view_preform_body_purchase_order(self):
self.ensure_one()
name_list = self._get_pinking_name()
purchase_order_ids = self.env['purchase.order'].search([('origin', 'in', name_list)])
action = {
'res_model': 'purchase.order',
'type': 'ir.actions.act_window',
}
if len(purchase_order_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': purchase_order_ids[0].id,
})
else:
action.update({
'name': _("子·采购订单"),
'domain': [('id', 'in', purchase_order_ids.ids)],
'view_mode': 'tree,form',
})
return action
def get_purchase_request_order(self):
name_list = self._get_pinking_name()
purchase_request_ids = self.env['purchase.request'].search([('origin', 'in', name_list)])
purchase_order_ids = self.env['purchase.order'].search([('origin', 'in', name_list)])
return purchase_request_ids, purchase_order_ids
def _get_pinking_name(self):
return [picking_id.name for picking_id in self.picking_ids if picking_id.name]
def button_confirm(self):
res = super(PurchaseOrder, self).button_confirm()
# 取消反向调拨单
reverse_move_ids = self.env['stock.move'].search([
('origin', '=', self.name),
('purchase_line_id', '=', False),
('state', '!=', 'done')
])
if reverse_move_ids:
reverse_move_ids.picking_id.action_cancel()
return res
def button_cancel(self):
"""
1. 先将采购订单行与目标库存移动断开链接避免采购单取消后调拨单被调整为mts的问题
2. 取消采购订单
3. 将采购订单行与目标库存移动重新建立链接
"""
created_purchase_request_line_ids = {}
if self.order_line.move_dest_ids.created_purchase_request_line_id:
move_ids = self.order_line.move_dest_ids.filtered(lambda move: move.state != 'done' and not move.scrapped)
created_purchase_request_line_ids = {move.id: move.created_purchase_request_line_id for move in move_ids}
self.order_line.write({'move_dest_ids': [(5, 0, 0)]})
res =super(PurchaseOrder, self).button_cancel()
for move_id, created_purchase_request_line_id in created_purchase_request_line_ids.items():
self.env['stock.move'].browse(move_id).created_purchase_request_line_id = created_purchase_request_line_id
# if move_ids.mapped('created_purchase_request_line_id'):
# move_ids.write({'state': 'waiting', 'is_done': False})
return res
def write(self, vals):
res = super(PurchaseOrder, self).write(vals)
if 'state' in vals and vals['state'] == 'purchase':
purchase_request = self.order_line.purchase_request_lines.request_id
if purchase_request:
finished = True
# 判断该采购申请所有明细行是否都完成
for purchase_request_line in purchase_request.line_ids:
finished_qty = sum(purchase_request_line.purchase_lines.filtered(lambda line: line.state == 'purchase').mapped('product_qty'))
if float_compare(finished_qty ,purchase_request_line.product_qty, precision_rounding=purchase_request_line.product_id.uom_id.rounding) < 0:
finished = False
break
if finished:
purchase_request.button_done()
return res
], string='Status', readonly=True, index=True, copy=False, default='draft', tracking=True)

View File

@@ -1,22 +1,19 @@
import re
import ast
from odoo import models, fields, api, _
from itertools import groupby
from odoo.tools import float_compare
from odoo import models, fields, api
class PurchaseRequest(models.Model):
_inherit = 'purchase.request'
_description = '采购申请'
# 为state添加取消状态
# 为state添加取消状态
state = fields.Selection(
selection_add=[('cancel', '已取消')],
ondelete={'cancel': 'set default'} # 添加 ondelete 策略
)
rule_new_add = fields.Boolean('采购请求为规则创建', default=False, compute='_compute_state', store=True)
rule_purchase_to_request = fields.Boolean('采购单根据规则创建坯料采购申请', default=False)
@api.depends('state')
def _compute_state(self):
@@ -32,57 +29,6 @@ class PurchaseRequest(models.Model):
action['context'] = origin_context
return action
def button_done(self):
product_qty_map = {key: sum(line.product_qty for line in group) for key, group in
groupby(self.line_ids, key=lambda x: x.product_id.id)}
lines = self.mapped("line_ids.purchase_lines.order_id")
# 采购单产品和数量
product_summary = {}
product_rounding = {}
if lines:
for line in lines:
for line_item in line.order_line:
if line_item.state == 'purchase':
product_id = line_item.product_id.id
qty = line_item.product_qty
product_rounding[product_id] = line_item.product_id.uom_id.rounding
if product_id in product_summary:
product_summary[product_id] += qty
else:
product_summary[product_id] = qty
# 校验产品数量
discrepancies = []
for product_id, qty in product_qty_map.items():
if product_id in product_summary:
if float_compare(product_summary[product_id], qty, precision_rounding=product_rounding[product_id]) < 0:
discrepancies.append((product_id, qty, product_summary[product_id]))
else:
discrepancies.append((product_id, qty, 0))
if discrepancies:
# 弹出提示框
message = "产品与采购数量不一致:\n"
for product_id, required_qty, order_qty in discrepancies:
product_name = self.env['product.product'].browse(product_id).display_name # 获取产品名称
message += f"产品 {product_name},需求数量 {required_qty},关联采购订单确认的数量 {order_qty}\n"
# 添加确认框
message += "确认关闭?"
return {
'name': _('采购申请'),
'type': 'ir.actions.act_window',
'views': [(self.env.ref(
'jikimo_purchase_request.purchase_request_wizard_wizard_form_view').id,
'form')],
'res_model': 'purchase.request.wizard',
'target': 'new',
'context': {
'default_purchase_request_id': self.id,
'default_message': message,
}}
return super(PurchaseRequest, self).button_done()
class PurchaseRequestLine(models.Model):
_inherit = 'purchase.request.line'
_description = '采购申请明细'
@@ -101,8 +47,7 @@ class PurchaseRequestLine(models.Model):
('outsourcing', "委外加工"),
], string='供货方式', compute='_compute_supply_method', store=True)
purchase_request_count = fields.Integer(string='采购申请数量', compute='_compute_purchase_request_count',
readonly=True)
purchase_request_count = fields.Integer(string='采购申请数量', compute='_compute_purchase_request_count', readonly=True)
purchase_count = fields.Integer(string="采购订单数量", compute="_compute_purchase_count", readonly=True)
@api.depends("purchase_lines")
@@ -147,12 +92,10 @@ class PurchaseRequestLine(models.Model):
continue
if record.product_id.categ_id.name == '坯料':
product_name = ''
match = re.search(r'(S\d{5}-\d+)', record.product_id.name)
match = re.search(r'(S\d{5}-\d)', record.product_id.name)
# 如果匹配成功,提取结果
if match:
product_name = match.group(0)
else:
product_name = record.product_id.name
sale_order_name = ''
match_sale = re.search(r'S(\d+)', record.product_id.name)
if match_sale:

View File

@@ -44,7 +44,7 @@ class StatusChange(models.Model):
else:
action.update({
'name': _("%s生成采购请求单", self.name),
'domain': [('id', 'in', pr_ids.ids)],
'domain': [('id', 'in', pr_ids)],
'view_mode': 'tree,form',
})
return action

View File

@@ -33,28 +33,3 @@ class StockPicking(models.Model):
'view_mode': 'tree,form',
})
return action
def _action_done(self):
res = super(StockPicking, self)._action_done()
# 将新产生的backorder对应上原来的采购申请明细行
backorder_ids = self.backorder_ids
if backorder_ids:
purchase_request_lines = self.move_ids.move_orig_ids.purchase_line_id.purchase_request_lines
if purchase_request_lines:
purchase_request_lines.move_dest_ids = [
(4, x.id) for x in backorder_ids.move_ids if
x.product_id.id in purchase_request_lines.mapped('product_id.id') and \
not x.created_purchase_request_line_id
]
return res
def _subcontracted_produce(self, subcontract_details):
super()._subcontracted_produce(subcontract_details)
# 判断是否根据规则生成新的采购申请单据,如果生成则修改状态为 approved
if self:
pr_ids = self.env["purchase.request"].sudo().search(
[('origin', 'like', self.name), ('rule_purchase_to_request', '=', True), ('state', '=', 'draft')])
if pr_ids:
pr_ids.write({'need_validation': False})
pr_ids.write({"state": "approved", 'need_validation': True, 'rule_new_add': False})

View File

@@ -26,7 +26,7 @@ class StockRule(models.Model):
request_data = rule._prepare_purchase_request(
procurement.origin, procurement.values
)
request_data = self._update_request_data(procurement, request_data)
request_data.update({'rule_new_add': True})
pr = purchase_request_model.create(request_data)
cache[domain] = pr
elif (
@@ -44,18 +44,6 @@ class StockRule(models.Model):
request_line_data.update({'origin': procurement.origin})
purchase_request_line_model.create(request_line_data)
def _update_request_data(self, procurement, request_data):
sp = self.env['stock.picking'].sudo().search([('name', '=', procurement.origin)])
if len(sp) == 1:
po = self.env['purchase.order'].sudo().search(
[('name', '=', sp.origin), ('purchase_type', '=', 'outsourcing')])
if po:
request_data.update({'rule_purchase_to_request': True})
else:
request_data.update({'rule_new_add': True})
return request_data
def _run_buy(self, procurements):
# 如果补货组相同,并且产品相同,则合并
procurements_dict = defaultdict()
@@ -91,4 +79,12 @@ class StockRule(models.Model):
)
res = super(StockRule, self)._run_buy(new_procurements)
# 判断是否根据规则生成新的采购申请单据,如果生成则修改状态为 approved
origins = list(set([procurement[0].origin for procurement in procurements]))
for origin in origins:
pr_ids = self.env["purchase.request"].sudo().search(
[('origin', 'like', origin), ('rule_new_add', '=', True), ('state', '=', 'draft')])
if pr_ids:
pr_ids.write({'need_validation': False})
pr_ids.write({"state": "approved", 'need_validation': True, 'rule_new_add': False})
return res

View File

@@ -1,2 +0,0 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_purchase_request_wizard_group_user,purchase.request.wizard,model_purchase_request_wizard,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_purchase_request_wizard_group_user purchase.request.wizard model_purchase_request_wizard base.group_user 1 1 1 1

View File

@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="purchase_order_form_jikimo_purchase_request" model="ir.ui.view">
<field name="name">purchase.order.inherited.form.jikimo.purchase.request</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="mrp_subcontracting_purchase.purchase_order_form_mrp_subcontracting_purchase"/>
<field name="arch" type="xml">
<xpath expr="//div[hasclass('oe_button_box')]/button[@name='action_view_subcontracting_resupply']" position="before">
<button
class="oe_stat_button" name="action_view_preform_body_purchase_order"
type="object" icon="fa-truck" attrs="{'invisible': [('purchase_order_count', '=', 0)]}" groups="stock.group_stock_user">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value"><field name="purchase_order_count"/></span>
<span class="o_stat_text">子·采购订单</span>
</div>
</button>
<button
class="oe_stat_button" name="action_view_preform_body_purchase_request"
type="object" icon="fa-truck" attrs="{'invisible': [('purchase_request_count', '=', 0)]}" groups="stock.group_stock_user">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value"><field name="purchase_request_count"/></span>
<span class="o_stat_text">子·采购申请</span>
</div>
</button>
</xpath>
</field>
</record>
</odoo>

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="menu_purhcase_request" model="ir.ui.menu">
<field name="name">采购申请</field>
<field name="parent_id" ref="purchase.menu_purchase_root" />
<field name="sequence">2</field>
</record>
<record id="purchase_request.menu_purchase_request_pro_mgt" model="ir.ui.menu">
<field name="sequence">1</field>
<field name="parent_id" ref="jikimo_purchase_request.menu_purhcase_request"/>
</record>
<record id="purchase_request.menu_purchase_request_line" model="ir.ui.menu">
<field name="sequence">10</field>
<field name="parent_id" ref="jikimo_purchase_request.menu_purhcase_request"/>
</record>
</data>
</odoo>

View File

@@ -15,26 +15,6 @@
<field name="part_number"/>
<field name="part_name"/>
</xpath>
<xpath expr="//button[@name='button_done']" position="attributes">
<attribute name="class"/>
</xpath>
<xpath expr="//button[@name='button_in_progress']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//button[@name='%(purchase_request.action_purchase_request_line_make_purchase_order)d']" position="attributes">
<attribute name="class">oe_highlight</attribute>
</xpath>
</field>
</record>
<record id="view_purchase_request_tree_sf" model="ir.ui.view">
<field name="name">purchase.request.sf.tree</field>
<field name="model">purchase.request</field>
<field name="inherit_id" ref="purchase_request.view_purchase_request_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='activity_ids']" position="attributes">
<attribute name="optional">hide</attribute>
</xpath>
</field>
</record>
@@ -67,16 +47,6 @@
<field name="part_number"/>
<field name="part_name" invisible="1"/>
</xpath>
<xpath expr="//tree" position="inside">
<header>
<button
name="%(purchase_request.action_purchase_request_line_make_purchase_order)d"
string="创建询价单"
type="action"
class="btn-primary"
/>
</header>
</xpath>
</field>
</record>
@@ -93,9 +63,4 @@
</xpath>
</field>
</record>
<record model="ir.actions.act_window" id="purchase_request.purchase_request_form_action">
<field name="name">Purchase Requests</field>
<field name="context"></field>
</record>
</odoo>

View File

@@ -1,4 +1,3 @@
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
from . import purchase_request_line_make_purchase_order
from . import purchase_request_wizard

View File

@@ -1,12 +0,0 @@
from odoo import models, fields, api
class PurchaseRequestWizard(models.TransientModel):
_name = 'purchase.request.wizard'
_description = '采购申请向导'
purchase_request_id = fields.Many2one('purchase.request', string='采购申请')
message = fields.Char(string='提示', readonly=True)
def confirm(self):
return self.purchase_request_id.write({"state": "done"})

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record model="ir.ui.view" id="purchase_request_wizard_wizard_form_view">
<field name="name">purchase.request.wizard.form.view</field>
<field name="model">purchase.request.wizard</field>
<field name="arch" type="xml">
<form>
<sheet>
<div>
<div style="white-space: pre-wrap;">
<field name="message"/>
</div>
</div>
<footer>
<button string="确认" name="confirm" type="object" class="oe_highlight"/>
<button string="取消" class="btn btn-secondary" special="cancel"/>
</footer>
</sheet>
</form>
</field>
</record>
</odoo>

View File

@@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
{
'name': "机企猫 采购申请审批流程",
'name': "机企猫 采购审批流程",
'summary': """
采购申请审批流程""",
Short (1 phrase/line) summary of the module's purpose, used as
subtitle on modules listing or apps.openerp.com""",
'description': """
Long description of module's purpose

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
from . import models
from . import stock_rule
from . import models

View File

@@ -22,9 +22,3 @@ class PurchaseRequest(models.Model):
self.state = 'approved'
return res
@api.model
def _get_under_validation_exceptions(self):
res = super(PurchaseRequest, self)._get_under_validation_exceptions()
res.append("state")
return res

View File

@@ -1,17 +0,0 @@
from odoo import models, api
class StockRule(models.Model):
_inherit = 'stock.rule'
def _run_buy(self, procurements):
# 判断是否根据规则生成新的采购申请单据,如果生成则修改状态为 approved
origins = list(set([procurement[0].origin for procurement in procurements]))
res = super(StockRule, self)._run_buy(procurements)
# origins = list(set([procurement[0].origin for procurement in procurements]))
for origin in origins:
pr_ids = self.env["purchase.request"].sudo().search(
[('origin', 'like', origin), ('rule_new_add', '=', True), ('state', '=', 'draft')])
if pr_ids:
pr_ids.write({'need_validation': False})
pr_ids.write({"state": "approved", 'need_validation': True, 'rule_new_add': False})
return res

View File

@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*-
{
'name': "机企猫 采购审批流程",
'name': "机企猫 采购申请审批流程",
'summary': """
采购审批流程""",
采购申请审批流程""",
'description': """
采购审批流程""",
采购申请审批流程""",
'author': "My Company",
'website': "https://www.yourcompany.com",

View File

@@ -1,2 +0,0 @@
# -*- coding: utf-8 -*-
from . import models

View File

@@ -1,18 +0,0 @@
# -*- coding: utf-8 -*-
{
'name': 'Jikimo_test_generate_product_name',
'version': '',
'summary': """ Jikimo_test_generate_product_name Summary """,
'author': '',
'website': '',
'category': '',
'depends': ['sf_manufacturing'],
'data': [
],
'application': True,
'installable': True,
'auto_install': False,
'license': 'LGPL-3',
}

View File

@@ -1,2 +0,0 @@
# -*- coding: utf-8 -*-
from . import product_template

View File

@@ -1,21 +0,0 @@
from odoo import models
class ProductTemplate(models.Model):
_inherit = 'product.template'
def generate_product_name(self, order_id, item, i):
"""生成成品名称"""
# 3D文件名去掉后缀截取前40个字符+“-”+模型ID
product_name = '%s-%s' % ('.'.join(item['model_name'].split('.')[:-1])[:40], item['model_id'])
return product_name
def generate_embryo_name(self, order_id, item, materials_id, materials_type_id, embryo_redundancy_id, i):
"""生成坯料名称"""
embryo_name = '%s-%s[%s * %s * %s]%s' % (materials_id.name, materials_type_id.name,
self.format_float(item['model_long'] + embryo_redundancy_id.long),
self.format_float(item['model_width'] + embryo_redundancy_id.width),
self.format_float(item['model_height'] + embryo_redundancy_id.height),
item['model_id'])
return embryo_name

View File

@@ -4,7 +4,6 @@ import json
import logging
from odoo.addons.sf_mrs_connect.controllers.controllers import Sf_Mrs_Connect
from odoo.addons.sf_manufacturing.controllers.controllers import Manufacturing_Connect
from odoo.addons.sf_base.decorators.api_log import api_log
from datetime import datetime
_logger = logging.getLogger(__name__)
@@ -13,7 +12,6 @@ class WorkorderExceptionConroller(http.Controller):
@http.route('/AutoDeviceApi/BillError', type='json', auth='public', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('工单对接错误', requester='中控系统')
def workder_exception(self, **kw):
"""
记录工单异常

View File

@@ -133,7 +133,6 @@ class QualityCheck(models.Model):
part_name = fields.Char('零件名称', related='product_id.part_name', readonly=False, store=True)
part_number = fields.Char('零件图号', related='product_id.part_number', readonly=False, store=True)
material_name = fields.Char('材料名称', compute='_compute_material_name')
model_id = fields.Char('模型ID', related='product_id.model_id')
# # 总数量值为调拨单_产品明细_数量
# total_qty = fields.Float('总数量', compute='_compute_total_qty', readonly=True)
@@ -339,7 +338,7 @@ class QualityCheck(models.Model):
# 4. 获取报告动作并生成PDF此时二维码将包含正确的文档ID
report_action = self.env.ref('sf_quality.action_report_quality_inspection')
pdf_content, v = report_action._render_qweb_pdf(
pdf_content, _ = report_action._render_qweb_pdf(
report_ref=report_action.report_name,
res_ids=self.ids
)

View File

@@ -493,9 +493,6 @@
<field name="picking_id"/>
<field name="lot_id"/>
<field name="team_id"/>
<field name="part_number"/>
<field name="part_name"/>
<field name="model_id"/>
<filter string="In Progress" name="progress" domain="[('quality_state', '=', 'none')]"/>
<filter string="Passed" name="passed" domain="[('quality_state', '=', 'pass')]"/>
<filter string="Failed" name="failed" domain="[('quality_state', '=', 'fail')]"/>

View File

@@ -103,19 +103,12 @@ class PrintingUtils(models.AbstractModel):
self.send_to_printer(host, port, zpl_code)
def add_qr_code_to_pdf(
self,
pdf_path:str,
content:str,
qr_code_buttom_text:Optional[str]=False,
buttom_text:Optional[str]=False,
):
def add_qr_code_to_pdf(self, pdf_path:str, content:str, buttom_text:Optional[str]=False):
"""
在PDF文件中添加二维码
:param pdf_path: PDF文件路径
:param content: 二维码内容
:param qr_code_buttom_text: 二维码下方文字
:param buttom_text: 正文下方文字
:param buttom_text: 二维码下方文字
:return: 是否成功
"""
if not os.path.exists(pdf_path):
@@ -163,9 +156,8 @@ class PrintingUtils(models.AbstractModel):
existing_pdf = PdfFileReader(original_file)
output = PdfFileWriter()
# 处理最后一页
last_page = existing_pdf.getNumPages() - 1
page = existing_pdf.getPage(last_page)
# 处理一页
page = existing_pdf.getPage(0)
# 获取页面尺寸
page_width = float(page.mediaBox.getWidth())
page_height = float(page.mediaBox.getHeight())
@@ -187,29 +179,13 @@ class PrintingUtils(models.AbstractModel):
qr_y = margin + 20 # 将二维码向上移动一点,为文字留出空间
c.drawImage(qr_temp_path, page_width - qr_size - margin, qr_y, width=qr_size, height=qr_size)
if qr_code_buttom_text:
if buttom_text:
# 在二维码下方绘制文字
text = qr_code_buttom_text
text = buttom_text
text_width = c.stringWidth(text, "SimSun" if font_found else "Helvetica", 10) # 准确计算文字宽度
text_x = page_width - qr_size - margin + (qr_size - text_width) / 2 # 文字居中对齐
text_y = margin + 20 # 文字位置靠近底部
c.drawString(text_x, text_y, text)
# 设置字体
if font_found:
c.setFont('SimSun', 12) # 增大字体大小到14pt
else:
# 如果没有找到中文字体,使用默认字体
c.setFont('Helvetica', 120)
logging.warning("未找到中文字体,将使用默认字体")
if buttom_text:
# 在下方中间添加文字
text = buttom_text
text_width = c.stringWidth(text, "SimSun" if font_found else "Helvetica", 12) # 准确计算文字宽度
text_x = (page_width - text_width) / 2 # 文字居中对齐
text_y = margin + 20 # 文字位置靠近底部
c.drawString(text_x, text_y, text)
c.save()
@@ -220,12 +196,11 @@ class PrintingUtils(models.AbstractModel):
# 合并原始页面和二维码页面
page.mergePage(qr_page)
output.addPage(page)
# 添加剩余的页面
for i in range(0, last_page):
for i in range(1, existing_pdf.getNumPages()):
output.addPage(existing_pdf.getPage(i))
output.addPage(page)
# 保存最终的PDF到一个临时文件
final_temp_path = pdf_path + '.tmp'

View File

@@ -4,7 +4,6 @@ import json
import logging
from odoo import http
from odoo.http import request
from odoo.addons.sf_base.decorators.api_log import api_log
_logger = logging.getLogger(__name__)
@@ -12,7 +11,6 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/MachineToolGroup', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('机床刀具组', requester='中控系统')
def get_maintenance_tool_groups_Info(self, **kw):
"""
机床刀具组接口

View File

@@ -27,27 +27,21 @@ def api_log(name=None, requester=None):
# 执行原始函数
result = func(*args, **kwargs)
origin_result = result
if isinstance(result, str):
result = json.loads(result)
# 计算响应时间
end_time = datetime.now()
response_time = (end_time - start_time).total_seconds()
# 获取响应状态
status = result.get('code') if 'code' in result else result.get('ErrorCode') if 'ErrorCode' in result else 200
# 创建日志记录
log_vals = {
'name': name or func.__name__,
'path': path,
'method': method.upper(),
'method': method,
'request_data': json.dumps(request_data, ensure_ascii=False),
'response_data': json.dumps(result, ensure_ascii=False),
'remote_addr': remote_addr,
'response_time': response_time,
'status': 200 if status == 0 else status,
'status': result.get('code', 500),
'requester': requester,
'responser': '智能工厂'
}
@@ -55,7 +49,7 @@ def api_log(name=None, requester=None):
# 异步创建日志记录
request.env['api.request.log'].sudo().with_context(tracking_disable=True).create(log_vals)
return origin_result
return result
except Exception as e:
_logger.error(f"API日志记录失败: {str(e)}")

View File

@@ -1,9 +1,4 @@
from odoo import models, fields, api
import json, ast
import logging
import requests
_logger = logging.getLogger(__name__)
class ApiRequestLog(models.Model):
@@ -21,52 +16,3 @@ class ApiRequestLog(models.Model):
status = fields.Integer('状态码')
requester = fields.Char('请求方')
responser = fields.Char('响应方')
@api.model
def log_request(self, method, url, name=None, responser=None, **kwargs):
# Log the request
request_headers = kwargs.get('headers', {})
request_body = kwargs.get('json') or kwargs.get('params') or {}
_logger.info(f"Request: {method} {url} Headers: {request_headers} Body: {request_body}")
# Make the actual request
response = requests.request(method, url, **kwargs)
# Log the response
response_status = response.status_code
response_headers = response.headers
response_body = response.text
response_time = response.elapsed.total_seconds()
_logger.info(f"Response: Status: {response_status} Headers: {response_headers} Body: {response_body}")
try:
# 如果是字符串,先尝试用 ast.literal_eval 安全地转换成 Python 对象
if isinstance(response_body, str):
response_body_obj = json.loads(response_body)
else:
response_body_obj = response_body
# 再使用 json.dumps 转换成标准的 JSON 字符串
response_body = json.dumps(response_body_obj, ensure_ascii=False)
except Exception as e:
_logger.warning(f"转换 response_body 到标准 JSON 失败: {str(e)}")
# 如果转换失败,保持原样
# Save to database
self.sudo().create({
'name': name,
'path': url,
'method': method.upper(),
'request_data': request_body,
'response_data': response_body,
'remote_addr': None,
'response_time': response_time,
'status': response_status,
'requester': '智能工厂',
'responser': responser
})
return response

View File

@@ -5,15 +5,13 @@
<field name="model">api.request.log</field>
<field name="arch" type="xml">
<tree>
<field name="create_date"/>
<field name="name"/>
<field name="path"/>
<field name="method"/>
<field name="remote_addr"/>
<field name="response_time" sum="0"/>
<field name="requester"/>
<field name="responser"/>
<field name="create_date" string="请求时间"/>
<field name="status" sum="0"/>
<field name="response_time"/>
<field name="status"/>
</tree>
</field>
</record>
@@ -34,8 +32,6 @@
<group>
<field name="response_time"/>
<field name="status"/>
<field name="requester"/>
<field name="responser"/>
<field name="create_date" string="请求时间"/>
</group>
</group>
@@ -52,23 +48,6 @@
</field>
</record>
<record model="ir.ui.view" id="view_api_request_log_search">
<field name="name">api.request.log.search</field>
<field name="model">api.request.log</field>
<field name="arch" type="xml">
<search string="API请求日志">
<field name="name"/>
<field name="requester"/>
<field name="responser"/>
<group>
<filter name="name" context="{'group_by':'name'}"/>
<filter name="requester" context="{'group_by':'requester'}"/>
<filter name="responser" context="{'group_by':'responser'}"/>
</group>
</search>
</field>
</record>
<record id="action_api_request_log" model="ir.actions.act_window">
<field name="name">API请求日志</field>
<field name="res_model">api.request.log</field>

View File

@@ -2,7 +2,7 @@
import logging
from datetime import datetime, timedelta
import hashlib
from odoo import models, SUPERUSER_ID
from odoo import models
from odoo.http import request
__author__ = 'jinling.yang'
@@ -48,7 +48,5 @@ class Http(models.AbstractModel):
_logger.info('sf_secret_key:%s' % factory_secret.sf_secret_key)
if check_sf_str != datas['HTTP_CHECKSTR']:
raise AuthenticationError('数据校验不通过')
# 设置管理员用户
request.update_env(user=SUPERUSER_ID)
else:
raise AuthenticationError('请求参数中无token')

View File

@@ -1,3 +0,0 @@
# -*- coding: utf-8 -*-
from . import models
from . import wizard

View File

@@ -1,41 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': '机企猫智能工厂 需求计划',
'version': '1.1',
'summary': '智能工厂计划管理',
'sequence': 1,
'description': """
在本模块,支持齐套检查与下达生产
""",
'category': 'sf',
'website': 'https://www.sf.jikimo.com',
'depends': ['sf_plan', 'jikimo_printing', 'purchase_request', 'purchase_request_tier_validation'],
'data': [
'security/ir.model.access.csv',
'data/stock_route_group.xml',
'views/demand_plan_info.xml',
'views/demand_plan.xml',
'views/stock_route.xml',
'views/sale_order_views.xml',
'wizard/sf_demand_plan_print_wizard_view.xml',
'wizard/sf_release_plan_wizard_views.xml',
'wizard/sf_demand_plan_detail_wizard_view.xml',
'views/menu_view.xml',
],
'demo': [
],
'assets': {
'web.assets_qweb': [
],
'web.assets_backend': [
'sf_demand_plan/static/src/scss/style.css',
'sf_demand_plan/static/src/js/print_demand.js',
'sf_demand_plan/static/src/js/custom_button.js',
]
},
'license': 'LGPL-3',
'installable': True,
'application': False,
'auto_install': False,
}

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data noupdate="0">
<record id="stock_route_group_automation_sf" model="stock.route.group">
<field name="name">自动化产线加工</field>
<field name="code">automation</field>
</record>
<record id="stock_route_group_manual_sf" model="stock.route.group">
<field name="name">人工线下加工</field>
<field name="code">manual</field>
</record>
<record id="stock_route_group_purchase_sf" model="stock.route.group">
<field name="name">外购</field>
<field name="code">purchase</field>
</record>
<record id="stock_route_group_outsourcing_sf" model="stock.route.group">
<field name="name">委外加工</field>
<field name="code">outsourcing</field>
</record>
</data>
</odoo>

View File

@@ -1,25 +0,0 @@
# migrations/1.1.0/post-migrate.py
import os
import csv
import logging
from odoo import api, SUPERUSER_ID
_logger = logging.getLogger(__name__)
def migrate(cr, version):
# 获取环境
env = api.Environment(cr, SUPERUSER_ID, {})
ProductionLine = env['sf.production.demand.plan']
DemandPlan = env['sf.demand.plan']
lines = ProductionLine.search([('demand_plan_id', '=', False)])
for line in lines:
vals = {
'sale_order_id': line.sale_order_id.id,
'sale_order_line_id': line.sale_order_line_id.id,
'line_ids': line.ids
}
new_plan = DemandPlan.create(vals)
line.write({'demand_plan_id': new_plan.id})

View File

@@ -1,13 +0,0 @@
# -*- coding: utf-8 -*-
from . import sf_demand_plan
from . import sf_production_demand_plan
from . import sale_order
from . import stock_route
from . import mrp_bom
from . import mrp_production
from . import stock_rule
from . import purchase_request
from . import purchase_order
from . import stock_move
from . import stock_picking

View File

@@ -1,29 +0,0 @@
from odoo import models, fields
class MrpBom(models.Model):
_inherit = 'mrp.bom'
# 业务平台分配工厂后在智能工厂先创建销售订单再创建该产品后再次进行创建bom
def bom_create(self, product, bom_type, product_type, code=None):
bom_id = self.env['mrp.bom'].create({
'product_tmpl_id': product.product_tmpl_id.id,
'type': bom_type,
# 'subcontractor_id': '' or subcontract.partner_id.id,
'product_qty': 1,
'product_uom_id': 1,
'code': code
})
if bom_type == 'subcontract' and product_type is not False:
subcontract = self.get_supplier(product.materials_type_id)
bom_id.subcontractor_id = subcontract.partner_id.id
return bom_id
def name_get(self):
"""重写name_get方法只显示BOM编码"""
result = []
for record in self:
# 只显示BOM编码如果编码为空则显示产品名称
display_name = record.code or record.product_tmpl_id.name or f'BOM-{record.id}'
result.append((record.id, display_name))
return result

View File

@@ -1,43 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, api
class MrpProduction(models.Model):
_inherit = 'mrp.production'
demand_plan_line_id = fields.Many2one(comodel_name="sf.production.demand.plan",
string="需求计划明细", readonly=True)
@api.depends('demand_plan_line_id')
def _compute_production_type(self):
for production in self:
if production.demand_plan_line_id.supply_method == 'automation':
production.production_type = '自动化产线加工'
elif production.demand_plan_line_id.supply_method == 'manual':
production.production_type = '人工线下加工'
else:
production.production_type = None
def _get_purchase_request(self):
"""获取跟制造订单相关的采购申请单(根据采购申请单行项目的产品匹配)"""
pr_ids = self.env['purchase.request'].sudo().search(
[('line_ids.demand_plan_line_id', 'in', self.demand_plan_line_id.ids)])
return pr_ids
@api.depends('procurement_group_id', 'procurement_group_id.stock_move_ids.group_id')
def _compute_picking_ids(self):
for order in self:
if order.product_id.product_tmpl_id.single_manufacturing == True and not order.is_remanufacture:
first_order = self.env['mrp.production'].search(
[('demand_plan_line_id', '=', order.demand_plan_line_id.id), ('product_id', '=', order.product_id.id)], limit=1, order='id asc')
order.picking_ids = self.env['stock.picking'].search([
('group_id', '=', first_order.procurement_group_id.id), ('group_id', '!=', False),
])
order.delivery_count = len(first_order.picking_ids)
else:
order.picking_ids = self.env['stock.picking'].search([
('group_id', '=', order.procurement_group_id.id), ('group_id', '!=', False),
])
order.delivery_count = len(order.picking_ids)

View File

@@ -1,48 +0,0 @@
from odoo import api, fields, models, _
from odoo.tools import float_compare
class PurchaseOrder(models.Model):
_inherit = 'purchase.order'
def button_confirm(self):
if self.order_line[0].demand_plan_line_id:
self = self.with_context(
demand_plan_line_id=self.order_line[0].demand_plan_line_id.id
)
res = super(PurchaseOrder, self).button_confirm()
return res
@api.depends('origin')
def _compute_purchase_type(self):
for purchase in self:
if purchase.order_line[0].product_id.categ_id.name == '坯料':
if purchase.order_line[0].product_id.materials_type_id.gain_way == '外协':
purchase.purchase_type = 'outsourcing'
else:
if purchase.order_line[0].demand_plan_line_id.supply_method == 'outsourcing':
purchase.purchase_type = 'outsourcing'
elif purchase.order_line[0].demand_plan_line_id.supply_method == 'purchase':
purchase.purchase_type = 'outside'
class PurchaseOrderLine(models.Model):
_inherit = 'purchase.order.line'
demand_plan_line_id = fields.Many2one(comodel_name="sf.production.demand.plan",
string="需求计划明细", readonly=True)
@api.model
def create(self, vals):
res = super(PurchaseOrderLine, self).create(vals)
if not res.demand_plan_line_id and res.order_id.origin:
origin = [origin.replace(' ', '') for origin in res.order_id.origin.split(',')]
if self.env.context.get('demand_plan_line_id'):
res.demand_plan_line_id = self.env.context.get('demand_plan_line_id')
elif 'MO' in res.order_id.origin:
# 原单据是制造订单
mp_ids = self.env['mrp.production'].sudo().search([('name', 'in', origin)])
if mp_ids:
res.demand_plan_line_id = mp_ids[0].demand_plan_line_id.id
return res

View File

@@ -1,35 +0,0 @@
from odoo import models, fields, api, _
from odoo.exceptions import UserError, ValidationError
class PurchaseRequestLine(models.Model):
_inherit = 'purchase.request.line'
_description = '采购申请明细'
supply_method = fields.Selection([
('automation', "自动化产线加工"),
('manual', "人工线下加工"),
('purchase', "外购"),
('outsourcing', "委外加工"),
], string='供货方式', readonly=True)
demand_plan_line_id = fields.Many2one(comodel_name="sf.production.demand.plan",
string="需求计划明细", readonly=True)
@api.depends('demand_plan_line_id')
def _compute_supply_method(self):
for prl in self:
if prl.demand_plan_line_id:
prl.supply_method = prl.demand_plan_line_id.supply_method
else:
prl.supply_method = None
class PurchaseRequestLineMakePurchaseOrder(models.TransientModel):
_inherit = "purchase.request.line.make.purchase.order"
@api.model
def _prepare_purchase_order_line(self, po, item):
ret = super(PurchaseRequestLineMakePurchaseOrder, self)._prepare_purchase_order_line(po, item)
ret['demand_plan_line_id'] = item.line_id.demand_plan_line_id.id
return ret

View File

@@ -1,74 +0,0 @@
from odoo import models, fields, api, _
class ReSaleOrder(models.Model):
_inherit = 'sale.order'
mrp_production_ids = fields.Many2many(
'mrp.production',
compute='_compute_mrp_production_ids',
string='与此销售订单相关联的制造订单',
groups='mrp.group_mrp_user', store=True)
demand_plan_ids = fields.Many2many(comodel_name="sf.demand.plan",
string="需求计划", readonly=True)
demand_plan_count = fields.Integer(
string="需求计划生成计数",
compute='_compute_demand_plan_count'
)
@api.depends('demand_plan_ids.line_ids.status')
def _compute_purchase_request_count(self):
for so in self:
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', so.name)])
if pr_ids:
so.purchase_request_purchase_order_count = len(pr_ids)
else:
so.purchase_request_purchase_order_count = 0
@api.depends('demand_plan_ids.line_ids')
def _compute_demand_plan_count(self):
for line in self:
demand_plan = self.env['sf.production.demand.plan'].sudo().search([('sale_order_id', '=', line.id)])
line.demand_plan_count = len(demand_plan)
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_info = self.env['sf.demand.plan'].sudo().create(vals)
vals.update({'demand_plan_id': demand_plan_info.id, 'plan_uom_qty': ret.product_uom_qty,
'new_supply_method': 'custom_made', 'custom_made_type': 'manual'})
demand_plan = self.env['sf.production.demand.plan'].sudo().create(vals)
demand_plan_info.write({'line_ids': demand_plan.ids})
if demand_plan.product_id.machining_drawings_name:
filename_url = demand_plan.product_id.machining_drawings_name.rsplit('.', 1)[0]
wizard_vals = {
'model_id': demand_plan.model_id,
'filename_url': filename_url,
'machining_drawings': product.machining_drawings,
'type': '1',
}
self.env['sf.demand.plan.print.wizard'].sudo().create(wizard_vals)
ret.order_id.demand_plan_ids = [(4, demand_plan_info.id)]
return ret
def confirm_to_supply_method(self):
self.state = 'sale'
for line in self.order_line:
if line.product_id.auto_machining:
line.supply_method = 'automation'
def action_view_demand_plan(self):
self.ensure_one()
demand_plan_ids = self.env['sf.production.demand.plan'].sudo().search([('sale_order_id', '=', self.id)]).ids
return {
'res_model': 'sf.production.demand.plan',
'type': 'ir.actions.act_window',
'name': _("需求计划"),
'domain': [('id', 'in', demand_plan_ids)],
'view_mode': 'tree',
}

View File

@@ -1,260 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.tools import float_compare
from odoo.exceptions import ValidationError
import re
class SfDemandPlan(models.Model):
_name = 'sf.demand.plan'
_description = 'sf_demand_plan'
state = fields.Selection([
('10', '待工艺设计'),
('30', '部分下达'),
('40', '已下达'),
('50', '取消'),
], string='状态', default='10', compute='_compute_state', store=True)
line_ids = fields.One2many(comodel_name='sf.production.demand.plan',
inverse_name='demand_plan_id', string="需求计划", copy=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)
product_id = fields.Many2one(
comodel_name='product.product',
related='sale_order_line_id.product_id',
string='产品', store=True, index=True)
part_name = fields.Char('零件名称', related='product_id.part_name')
part_number = fields.Char('零件图号', compute='_compute_part_number', store=True)
materials_id = fields.Char('材料', compute='_compute_materials_id', store=True)
blank_type = fields.Selection([('圆料', '圆料'), ('方料', '方料')], string='坯料分类',
related='product_id.blank_type')
blank_precision = fields.Selection([('精坯', '精坯'), ('粗坯', '粗坯')], string='坯料类型',
related='product_id.blank_precision')
manual_quotation = fields.Boolean('人工编程', related='product_id.manual_quotation', default=False)
embryo_long = fields.Char('坯料尺寸(mm)', compute='_compute_embryo_long', store=True)
is_incoming_material = fields.Boolean('客供料', related='sale_order_line_id.is_incoming_material', store=True)
pending_qty = fields.Float(
string="待计划",
compute='_compute_pending_qty', store=True)
planned_qty = fields.Float(
string="已计划",
compute='_compute_planned_qty', store=True)
model_id = fields.Char('模型ID', related='product_id.model_id')
customer_name = fields.Char('客户', related='sale_order_id.customer_name')
product_uom_qty = fields.Float(
string="需求数量",
related='sale_order_line_id.product_uom_qty', store=True)
deadline_of_delivery = fields.Date('客户交期', related='sale_order_line_id.delivery_end_date', store=True)
contract_date = fields.Date('合同日期', related='sale_order_id.contract_date')
contract_code = fields.Char('合同号', related='sale_order_id.contract_code', store=True)
model_process_parameters_ids = fields.Many2many('sf.production.process.parameter',
'demand_plan_process_parameter_rel',
string='表面工艺',
compute='_compute_model_process_parameters_ids'
, store=True
)
model_machining_precision = fields.Selection(related='product_id.model_machining_precision', string='精度')
inventory_quantity_auto_apply = fields.Float(
string="成品库存",
compute='_compute_inventory_quantity_auto_apply'
)
priority = fields.Selection([
('1', '紧急'),
('2', ''),
('3', ''),
('4', ''),
], string='优先级', default='3')
overdelivery_allowed = fields.Boolean('可超量发货', default=False)
hide_button_release_plan = fields.Boolean(
string='显示下达计划按钮',
compute='_compute_hide_button_release_plan',
default=False
)
readonly_custom_made_type = fields.Boolean(
string='字段自制类型只读',
compute='_compute_readonly_custom_made_type',
default=False
)
demand_plan_number = fields.Char('需求计划号', compute='_compute_demand_plan_number', readonly=True, store=True)
origin = fields.Char('来源', related='sale_order_id.name', readonly=True, store=True)
@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.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_long', 'product_id.model_width', 'product_id.model_height')
def _compute_embryo_long(self):
for line in self:
if line.product_id:
if line.product_id.blank_type == '圆料':
line.embryo_long = f"Ø{round(line.product_id.model_width, 3)}*{round(line.product_id.model_long, 3)}"
else:
line.embryo_long = f"{round(line.product_id.model_long, 3)}*{round(line.product_id.model_width, 3)}*{round(line.product_id.model_height, 3)}"
else:
line.embryo_long = 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('product_uom_qty', 'line_ids.plan_uom_qty')
def _compute_pending_qty(self):
for line in self:
sum_plan_uom_qty = sum(line.line_ids.mapped('plan_uom_qty'))
pending_qty = line.product_uom_qty - sum_plan_uom_qty
if float_compare(pending_qty, 0,
precision_rounding=line.product_id.uom_id.rounding) == -1:
line.pending_qty = 0
else:
line.pending_qty = pending_qty
@api.depends('line_ids.plan_uom_qty')
def _compute_planned_qty(self):
for line in self:
line.planned_qty = sum(line.line_ids.mapped('plan_uom_qty'))
@api.depends('line_ids.status')
def _compute_hide_button_release_plan(self):
for line in self:
line.hide_button_release_plan = bool(line.line_ids.filtered(
lambda p: p.status == '30'))
@api.depends('line_ids.status', 'sale_order_id.state')
def _compute_state(self):
for line in self:
status_line = line.line_ids.filtered(lambda p: p.status == '60')
if not line.line_ids:
line.state = '10'
elif line.sale_order_id.state == 'cancel':
line.state = '50'
line.line_ids.status = '100'
elif len(line.line_ids) == len(status_line):
line.state = '40'
elif bool(status_line):
line.state = '30'
else:
line.state = '10'
@api.depends('line_ids.status')
def _compute_readonly_custom_made_type(self):
for line in self:
production_demand_plan = line.line_ids.filtered(
lambda p: p.status in ('50', '60') and p.new_supply_method == 'custom_made')
line.readonly_custom_made_type = bool(production_demand_plan)
@api.constrains('line_ids')
def check_line_ids(self):
for item in self:
if not item.line_ids:
raise ValidationError('计划不能为空!')
def write(self, vals):
res = super(SfDemandPlan, self).write(vals)
if 'line_ids' in vals:
for line in self.line_ids:
if not line.sale_order_id:
line.sale_order_id = self.sale_order_id
if not line.sale_order_line_id:
line.sale_order_line_id = self.sale_order_line_id
return res
def name_get(self):
result = []
for plan in self:
result.append((plan.id, plan.demand_plan_number))
return result
def button_production_release_plan(self):
line_ids = self.line_ids.filtered(lambda p: p.status == '30')
sum_product_uom_qty = sum(line_ids.mapped('plan_uom_qty'))
customer_location_id = self.env['ir.model.data']._xmlid_to_res_id('stock.stock_location_customers')
if not self.overdelivery_allowed and line_ids.filtered(lambda p: p.location_id.id == customer_location_id):
if float_compare(sum_product_uom_qty, self.product_uom_qty,
precision_rounding=self.product_id.uom_id.rounding) == 1:
raise ValidationError(f"已禁止向合作伙伴/客户超量发货,请更换“补货原因”或将“可超量发货”设置为“是”。")
elif float_compare(sum_product_uom_qty, self.product_uom_qty,
precision_rounding=self.product_id.uom_id.rounding) == 1:
return {
'name': _('需求计划'),
'type': 'ir.actions.act_window',
'views': [(self.env.ref(
'sf_demand_plan.sf_release_plan_wizard_form').id,
'form')],
'res_model': 'sf.release.plan.wizard',
'target': 'new',
'context': {
'default_demand_plan_line_id': line_ids.ids,
'default_release_message': f"您正在下达计划量 {sum_product_uom_qty},需求数量为 {self.product_uom_qty},已超过需求数量,是否继续?",
}}
else:
for demand_plan_line_id in line_ids:
demand_plan_line_id.action_confirm()
# 需求要求取值格式是来源+来源明细行ID,但是来源明细行ID取得就是product_id.name得最后一位所以这里也直接截取product_id.name
@api.depends('product_id.name')
def _compute_demand_plan_number(self):
for line in self:
product_name = line.product_id.name or ''
plan_no = None
if line.product_id:
# 使用正则表达式匹配P-后面的所有字符
match = re.search(r'P-(.*)', product_name)
if match:
plan_no = match.group(1)
line.demand_plan_number = plan_no
else:
line.demand_plan_number = None

View File

@@ -1,971 +0,0 @@
# -*- 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
import re
class SfProductionDemandPlan(models.Model):
_name = 'sf.production.demand.plan'
_description = 'sf_production_demand_plan'
def get_location_id(self):
customer_location_id = self.env['ir.model.data']._xmlid_to_res_id('stock.stock_location_customers')
return customer_location_id
priority = fields.Selection(related='demand_plan_id.priority', string='优先级', store=True)
status = fields.Selection([
('10', '草稿'),
('20', '待确认'),
('30', '待工艺设计'),
('50', '待下达生产'),
('60', '已下达'),
('100', '取消'),
], string='状态', default='30', readonly=True)
demand_plan_id = fields.Many2one(comodel_name="sf.demand.plan",
string="物料需求", readonly=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)
sale_order_line_number = fields.Char(string='销售订单行', compute='_compute_sale_order_line_number', store=True)
company_id = fields.Many2one(
related='sale_order_id.company_id',
store=True, index=True, precompute=True)
customer_name = fields.Char('客户', related='sale_order_id.customer_name', store=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('零件图号', related='demand_plan_id.part_number')
is_incoming_material = fields.Boolean('客供料', related='sale_order_line_id.is_incoming_material', store=True)
new_supply_method = fields.Selection([
('custom_made', "自制"),
('purchase', "外购"),
('outsourcing', "委外加工"),
], string='供货方式', required=True)
custom_made_type = fields.Selection([
('automation', "自动化产线加工"),
('manual', "人工线下加工"),
], string='产线类型', compute='_compute_custom_made_type', store=True)
supply_method = fields.Selection([
('automation', "自动化产线加工"),
('manual', "人工线下加工"),
('purchase', "外购"),
('outsourcing', "委外加工"),
], string='供货方式', compute='_compute_supply_method', store=True, readonly=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_line_id.delivery_end_date', store=True)
inventory_quantity_auto_apply = fields.Float(
string="成品库存",
related='demand_plan_id.inventory_quantity_auto_apply'
)
qty_delivered = fields.Float(
"交货数量", related='sale_order_line_id.qty_delivered')
qty_to_deliver = fields.Float(
"待交货数量", compute='_compute_qty_to_deliver', store=True)
model_long = fields.Char('尺寸(mm)', compute='_compute_model_long')
blank_type = fields.Selection([('圆料', '圆料'), ('方料', '方料')], string='坯料分类',
related='product_id.blank_type')
blank_precision = fields.Selection([('精坯', '精坯'), ('粗坯', '粗坯')], string='坯料类型',
related='product_id.blank_precision')
unit_number = fields.Float('单件用量', digits=(16, 3), related='product_id.unit_number')
embryo_long = fields.Char('坯料尺寸(mm)', related='demand_plan_id.embryo_long')
materials_id = fields.Char('材料', related='demand_plan_id.materials_id', store=True)
model_machining_precision = fields.Selection(related='product_id.model_machining_precision', string='精度')
model_process_parameters_ids = fields.Many2many(related='demand_plan_id.model_process_parameters_ids',
string='表面工艺', )
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_ids = fields.Many2many('stock.route', 'stock_route_demand_plan', 'demand_plan_id', 'route_id', '库存路线',
domain=[('demand_plan_selectable', '=', True)], compute='_compute_route_ids',
store=True)
contract_date = fields.Date('合同日期', related='sale_order_id.contract_date')
date_order = fields.Datetime('下单日期', related='sale_order_id.date_order')
contract_code = fields.Char('合同号', related='sale_order_id.contract_code', store=True)
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.Datetime('实际开工日期', compute='_compute_actual_start_date', store=True)
actual_end_date = fields.Datetime('实际完工日期', compute='_compute_actual_end_date', store=True)
print_count = fields.Char('打印次数', default='T0C0', readonly=True)
sequence = fields.Integer('序号')
mrp_production_ids = fields.Many2many(
'mrp.production',
compute='_compute_mrp_production_ids',
string='与此相需求计划关联的制造订单',
store=True)
hide_release_production_order = fields.Boolean(
string='显示下发生产按钮',
compute='_compute_hide_release_production_order',
default=False
)
readonly_custom_made_type = fields.Boolean(
related='demand_plan_id.readonly_custom_made_type',
string='字段自制类型只读'
)
is_processing = fields.Boolean(
string='正在处理中',
default=False,
help='用于防止重复点击按钮'
)
# 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('委外采购申请单')
plan_uom_qty = fields.Float(string="计划量", required=True)
procurement_reason = fields.Selection([
('销售订单', "销售订单"),
('需求预测', "需求预测"),
('生产报废', "生产报废"),
], string='补货原因', default='销售订单', readonly=True)
blank_arrival_date = fields.Date('采购计划到货(坯料)')
finished_product_arrival_date = fields.Date('采购计划到货(成品)')
bom_id = fields.Many2one('mrp.bom', string="BOM", readonly=True)
location_id = fields.Many2one('stock.location', string='需求位置', default=get_location_id, readonly=True)
manual_quotation = fields.Boolean('人工编程', related='product_id.manual_quotation', default=False)
@api.constrains('plan_uom_qty')
def _check_plan_uom_qty(self):
line_ids = self.filtered(lambda p: p.plan_uom_qty == 0 or p.plan_uom_qty < 0)
if line_ids:
raise ValidationError(_("计划量不能小于等于0"))
@api.constrains('supply_method')
def _check_supply_method(self):
product_name = []
product = []
for line in self:
if line.supply_method == 'purchase' and line.is_incoming_material:
product_name.append(line.product_id.display_name)
if line.supply_method == 'automation' and line.manual_quotation:
product.append(line.product_id.display_name)
if product_name:
unique_product_names = list(set(product_name))
raise UserError('当前(%s)产品为客供料,不能选择外购' % ','.join(unique_product_names))
if product:
unique_product = list(set(product))
raise UserError('当前(%s)产品为人工编程,不能选择自动化产线加工' % ','.join(unique_product))
@api.depends('new_supply_method')
def _compute_custom_made_type(self):
DemandPlan = self.env['sf.production.demand.plan'].sudo()
for line in self:
if line.new_supply_method != "custom_made":
line.custom_made_type = False
else:
demand_plan_id = line.demand_plan_id._origin.id
demand_plan = DemandPlan.search([
('demand_plan_id', '=', demand_plan_id),
('new_supply_method', '=', 'custom_made'),
('status', 'in', ('50', '60'))
], limit=1)
if demand_plan:
line.custom_made_type = demand_plan.custom_made_type
@api.depends('new_supply_method', 'custom_made_type')
def _compute_supply_method(self):
for line in self:
if line.new_supply_method == 'custom_made':
line.supply_method = line.custom_made_type
else:
line.supply_method = line.new_supply_method
@api.depends('sale_order_line_id.qty_to_deliver')
def _compute_qty_to_deliver(self):
for line in self:
if float_compare(line.sale_order_line_id.qty_to_deliver, 0,
precision_rounding=line.product_id.uom_id.rounding) == -1:
line.qty_to_deliver = 0
else:
line.qty_to_deliver = line.sale_order_line_id.qty_to_deliver
@api.depends('supply_method')
def _compute_route_ids(self):
for pdp in self:
if pdp.supply_method:
group_id = self.env['stock.route.group'].sudo().search([('code', '=', pdp.supply_method)])
route_ids = self.env['stock.route'].sudo().search(
[('demand_plan_selectable', '=', True), ('stock_route_group_ids', '=', group_id.id)])
if route_ids:
pdp.route_ids = route_ids.ids
continue
pdp.route_ids = None
@api.depends('sale_order_line_id.product_id.name')
def _compute_sale_order_line_number(self):
for line in self:
if line.product_id:
line.sale_order_line_number = line.sale_order_line_id.product_id.name[-1]
else:
line.sale_order_line_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('mrp_production_ids.workorder_ids.date_start')
def _compute_actual_start_date(self):
for record in self:
if record.mrp_production_ids:
start_dates = [
workorder.date_start for mo in record.mrp_production_ids
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
@api.depends('mrp_production_ids.workorder_ids.state',
'mrp_production_ids.workorder_ids.date_finished')
def _compute_actual_end_date(self):
for record in self:
if record.mrp_production_ids:
finished_orders = record.mrp_production_ids.filtered(lambda mo: mo.state == 'done')
sum_product_qty = sum(finished_orders.mapped('product_qty'))
if finished_orders and float_compare(sum_product_qty, record.plan_uom_qty,
precision_rounding=record.product_id.uom_id.rounding) >= 0:
end_dates = [
workorder.date_finished 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('mrp_production_ids.move_raw_ids.reserved_availability')
def _compute_material_check(self):
for record in self:
if record.mrp_production_ids and record.mrp_production_ids.move_raw_ids:
# 获取完成的制造订单
done_manufacturing = record.mrp_production_ids.filtered(lambda mo: mo.state == 'done')
product_qty = sum(done_manufacturing.mapped('product_qty'))
# 需求数量-完成数量
product_uom_qty = record.plan_uom_qty - product_qty
total_reserved_availability = sum(
record.mrp_production_ids.mapped('move_raw_ids.reserved_availability'))
if float_compare(total_reserved_availability, 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
@api.depends('status')
def _compute_mrp_production_ids(self):
for record in self:
if record.status in ('50', '60'):
record.mrp_production_ids = self.env['mrp.production'].sudo().search(
[('demand_plan_line_id', '=', record.id)]).ids
else:
record.mrp_production_ids = None
@api.depends('mrp_production_ids.state')
def _compute_hide_release_production_order(self):
for record in self:
# 检查这条需求计划所有制造订单的排程单状态,有一个为待排程状态,就显示改按钮
record.hide_release_production_order = bool(record.mrp_production_ids.filtered(
lambda p: p.state == 'confirmed'
))
@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 button_release_production(self):
self.ensure_one()
if not self.planned_start_date:
raise ValidationError("请先填写计划开工日期")
pro_plan_list = self.env['sf.production.plan'].sudo().search(
[('production_id', 'in', self.mrp_production_ids.ids), ('state', '=', 'draft')])
sf_production_line = self.env['sf.production.line'].sudo().search(
[('name', '=', '1#CNC自动生产线')], limit=1)
if sf_production_line:
now = datetime.now()
time_part = (now + timedelta(hours=2)).time()
date_part = fields.Date.from_string(self.planned_start_date)
date_planned_start = datetime.combine(date_part, time_part)
pro_plan_list.production_line_id = sf_production_line.id
pro_plan_list.date_planned_start = date_planned_start
self._do_production_schedule(pro_plan_list)
self.status = '60'
self.update_sale_order_state()
def _do_production_schedule(self, pro_plan_list):
for pro_plan in pro_plan_list:
pro_plan.do_production_schedule()
def update_sale_order_state(self):
# demand_plan = self.env['sf.demand.plan'].sudo().search([('sale_order_id', '=', self.sale_order_id.id)])
# demand_plan_state = demand_plan.filtered(lambda line: line.state != '40')
production_demand_plan = self.env['sf.production.demand.plan'].sudo().search(
[('sale_order_id', '=', self.sale_order_id.id)])
production_demand_plan_state = production_demand_plan.filtered(lambda line: line.status in ('10', '20', '30'))
if not production_demand_plan_state:
# 修改销售订单为加工中
self.sale_order_id.state = 'processing'
def edit_button(self):
self.ensure_one()
action = {
'name': _("需求计划"),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'views': [[self.env.ref('sf_demand_plan.view_sf_demand_plan_form').id, 'form']],
'res_model': 'sf.demand.plan',
'res_id': self.demand_plan_id.id,
}
if self.demand_plan_id.state == '40':
action.update({'flags': {'mode': 'readonly'}})
return action
def button_action_print(self):
model_id = self.mapped('model_id')
return {
'res_model': 'sf.demand.plan.print.wizard',
'type': 'ir.actions.act_window',
'name': _("打印"),
'domain': [('model_id', 'in', model_id)],
'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 float_compare(total_purchase_quantity, record.product_uom_qty,
# precision_rounding=record.product_id.uom_id.rounding) == -1:
# pr_ids = self.env['purchase.request'].sudo().search(
# [('line_ids.product_id', 'in', raw_materials.ids), ('state', '!=', 'done')])
# outsourcing_purchase_request.extend(pr_ids.ids)
# elif record.supply_method in ('purchase', 'outsourcing'):
# 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 float_compare(total_purchase_quantity, record.product_uom_qty,
# precision_rounding=record.product_id.uom_id.rounding) == -1:
# 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
# if 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
# )
# quantity = total_outsourcing_purchase_quantity / total_product_qty
# if float_compare(quantity, record.product_uom_qty,
# precision_rounding=record.product_id.uom_id.rounding) == -1:
# 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.filtered(
# lambda p: p.product_id.id == record.product_id.id
# )
# 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'):
# mrp_production_ids = self.sale_order_id.mrp_production_ids.filtered(
# lambda p: p.product_id.id == self.product_id.id
# )
# picking_ids = 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},请去云平台处理")
@api.model
def unlink(self):
for item in self:
if item.status not in ('10', '20', '30'):
raise ValidationError(u'只能删除状态为【草稿,待确认,待工艺设计】的需求计划。')
else:
super(SfProductionDemandPlan, item).unlink()
def button_batch_release_plan(self):
filtered_plan = self.filtered(lambda mo: mo.status == '30')
if not filtered_plan:
raise UserError(_("没有需要下达的计划!"))
check_overdelivery_allowed = False
if not self.demand_plan_id.overdelivery_allowed:
customer_location_id = self.env['ir.model.data']._xmlid_to_res_id('stock.stock_location_customers')
if self.location_id.id == customer_location_id:
check_overdelivery_allowed = True
# 按产品分组并计算总数
product_data = {}
for plan in filtered_plan:
if plan.product_id not in product_data:
# 初始化产品数据,从产品上获取需求量
product_data[plan.product_id] = {
'plan_uom_qty': 0.0,
'product_uom_qty': plan.product_uom_qty
}
# 累加计划数量
product_data[plan.product_id]['plan_uom_qty'] += plan.plan_uom_qty
# 检查需求超过计划数量的产品
warning_messages = []
for product, data in product_data.items():
if float_compare(data['plan_uom_qty'], data['product_uom_qty'],
precision_rounding=product.uom_id.rounding) == 1:
warning_messages.append(
_("您正在下达的产品 %s,计划量%s,需求数量为%s,已超过需求数量") %
(product.display_name, data['plan_uom_qty'], data['product_uom_qty'])
)
if warning_messages and check_overdelivery_allowed:
raise ValidationError(f"已禁止向合作伙伴/客户超量发货,请更换“补货原因”或将“可超量发货”设置为“是”。")
elif warning_messages:
warning_message = "\n".join(warning_messages)
return {
'name': _('需求计划'),
'type': 'ir.actions.act_window',
'views': [(self.env.ref(
'sf_demand_plan.sf_release_plan_wizard_form').id,
'form')],
'res_model': 'sf.release.plan.wizard',
'target': 'new',
'context': {
'default_demand_plan_line_id': self.ids,
'default_release_message': warning_message,
}}
def button_release_plan(self):
self.ensure_one()
if self.is_processing:
return
self.is_processing = True
check_overdelivery_allowed = False
if not self.demand_plan_id.overdelivery_allowed:
customer_location_id = self.env['ir.model.data']._xmlid_to_res_id('stock.stock_location_customers')
if self.location_id.id == customer_location_id:
check_overdelivery_allowed = True
if check_overdelivery_allowed:
if float_compare(self.plan_uom_qty, self.product_uom_qty,
precision_rounding=self.product_id.uom_id.rounding) == 1:
raise ValidationError(f"已禁止向合作伙伴/客户超量发货,请更换“补货原因”或将“可超量发货”设置为“是”。")
elif float_compare(self.plan_uom_qty, self.product_uom_qty,
precision_rounding=self.product_id.uom_id.rounding) == 1:
return {
'name': _('需求计划'),
'type': 'ir.actions.act_window',
'views': [(self.env.ref(
'sf_demand_plan.sf_release_plan_wizard_form').id,
'form')],
'res_model': 'sf.release.plan.wizard',
'target': 'new',
'context': {
'default_demand_plan_line_id': self.ids,
'default_release_message': f"您正在下达计划量 {self.plan_uom_qty},需求数量为 {self.product_uom_qty},已超过需求数量,是否继续?",
}}
self.action_confirm()
def action_confirm(self):
self = self.with_context(
demand_plan_line_id=self.id
)
self.mrp_bom_create()
self._action_launch_stock_rule()
if self.supply_method in ('automation', 'manual'):
self.write({'status': '50'})
self.update_sale_order_state()
else:
self.write({'status': '60'})
self.update_sale_order_state()
def mrp_bom_create(self):
bom_type = ''
# 根据供货方式修改成品模板
if self.supply_method == 'automation':
bom_type = 'normal'
product_template_id = self.env.ref('sf_dlm.product_template_sf').sudo().product_tmpl_id
elif self.supply_method == 'outsourcing':
bom_type = 'subcontract'
product_template_id = self.env.ref(
'jikimo_sale_multiple_supply_methods.product_template_outsourcing').sudo()
elif self.supply_method == 'purchase':
product_template_id = self.env.ref(
'jikimo_sale_multiple_supply_methods.product_template_purchase').sudo()
elif self.supply_method == 'manual':
bom_type = 'normal'
product_template_id = self.env.ref(
'jikimo_sale_multiple_supply_methods.product_template_manual_processing').sudo()
# 复制成品模板上的属性
self.product_id.product_tmpl_id.copy_template(product_template_id)
if self.supply_method in ('automation', 'manual'):
line_ids = self.demand_plan_id.line_ids.filtered(
lambda p: p.supply_method in ('automation', 'manual') and p.status in ('50', '60'))
if line_ids:
self.bom_id = line_ids[0].bom_id.id
return
elif self.supply_method == 'outsourcing':
line_ids = self.demand_plan_id.line_ids.filtered(
lambda p: p.supply_method == 'outsourcing' and p.status == '60')
if line_ids:
self.bom_id = line_ids[0].bom_id.id
return
future_time = datetime.now() + timedelta(hours=8)
# 生成BOM单据编码
code = f"{self.product_id.default_code}-{bom_type}-{future_time.strftime('%Y%m%d%H%M%S')}"
order_id = self.sale_order_id
product = self.product_id
# 拼接方法需要的item结构成品的模型数据信息就是坯料的数据信息
item = {
'texture_code': product.materials_id.materials_no,
'texture_type_code': product.materials_type_id.materials_no,
'model_long': product.length,
'model_width': product.width,
'model_height': product.height,
'blank_volume': product.model_volume,
'blank_area': product.model_area,
'price': product.list_price,
'embryo_redundancy_id': self.sale_order_line_id.embryo_redundancy_id,
'model_id': self.model_id
}
product_name = ''
match = re.search(r'(S\d{5}-\d+)', product.name)
product_seria = 0
# 如果匹配成功,提取结果
if match:
product_name = match.group(0)
# 获取成品名结尾-n的n
product_seria = int(product_name.split('-')[-1])
# 成品供货方式为采购则不生成bom
if self.supply_method != 'purchase':
# 当成品上带有客供料选项时,生成坯料时选择“客供料”路线
if self.sale_order_line_id.embryo_redundancy_id:
# 将成品模板的内容复制到成品上
customer_provided_embryo = self.env.ref(
'jikimo_sale_multiple_supply_methods.product_template_embryo_customer_provided').sudo()
# 创建坯料客供料的批量不需要创建bom
material_customer_provided_embryo = self.env['product.template'].sudo().no_bom_product_create(
customer_provided_embryo.with_context(active_test=False).product_variant_id,
item,
order_id, 'material_customer_provided', product_seria, product)
# 成品配置bom
product_bom_material_customer_provided = self.env['mrp.bom'].with_user(
self.env.ref("base.user_admin")).bom_create(
product, bom_type, 'product', code)
product_bom_material_customer_provided.with_user(
self.env.ref("base.user_admin")).bom_create_line_has(
material_customer_provided_embryo)
self.bom_id = product_bom_material_customer_provided.id
elif self.product_id.materials_type_id.gain_way == '自加工':
self_machining_id = self.env.ref('sf_dlm.product_embryo_sf_self_machining').sudo()
# 创建坯料
self_machining_embryo = self.env['product.template'].sudo().no_bom_product_create(
self_machining_id,
item,
order_id, 'self_machining', product_seria, product)
# 创建坯料的bom
self_machining_bom = self.env['mrp.bom'].with_user(
self.env.ref("base.user_admin")).bom_create(
self_machining_embryo, 'normal', False)
# 创建坯料里bom的组件
self_machining_bom_line = self_machining_bom.with_user(
self.env.ref("base.user_admin")).bom_create_line(
self_machining_embryo)
if not self_machining_bom_line:
raise UserError('该订单模型的材料型号暂未有原材料,请先配置再进行分配')
# 产品配置bom
product_bom_self_machining = self.env['mrp.bom'].with_user(
self.env.ref("base.user_admin")).bom_create(
product, bom_type, 'product', code)
product_bom_self_machining.with_user(self.env.ref("base.user_admin")).bom_create_line_has(
self_machining_embryo)
self.bom_id = product_bom_self_machining.id
elif self.product_id.materials_type_id.gain_way == '外协':
outsource_id = self.env.ref('sf_dlm.product_embryo_sf_outsource').sudo()
# 创建坯料
outsource_embryo = self.env['product.template'].sudo().no_bom_product_create(outsource_id,
item,
order_id,
'subcontract',
product_seria,
product)
if outsource_embryo == -3:
raise UserError('该订单模型的材料型号暂未设置获取方式和供应商,请先配置再进行分配')
# 创建坯料的bom
outsource_bom = self.env['mrp.bom'].with_user(self.env.ref("base.user_admin")).bom_create(
outsource_embryo,
'subcontract', True)
# 创建坯料的bom的组件
outsource_bom_line = outsource_bom.with_user(
self.env.ref("base.user_admin")).bom_create_line(outsource_embryo)
if not outsource_bom_line:
raise UserError('该订单模型的材料型号暂未有原材料,请先配置再进行分配')
# 产品配置bom
product_bom_outsource = self.env['mrp.bom'].with_user(
self.env.ref("base.user_admin")).bom_create(product, bom_type, 'product', code)
product_bom_outsource.with_user(self.env.ref("base.user_admin")).bom_create_line_has(
outsource_embryo)
self.bom_id = product_bom_outsource.id
elif self.product_id.materials_type_id.gain_way == '采购':
purchase_id = self.env.ref('sf_dlm.product_embryo_sf_purchase').sudo()
purchase_embryo = self.env['product.template'].sudo().no_bom_product_create(purchase_id,
item,
order_id,
'purchase',
product_seria,
product)
if purchase_embryo and purchase_embryo == -3:
raise UserError('该订单模型的材料型号暂未设置获取方式和供应商,请先配置再进行分配')
else:
# 产品配置bom
product_bom_purchase = self.env['mrp.bom'].with_user(
self.env.ref("base.user_admin")).bom_create(product, bom_type, 'product', code)
product_bom_purchase.with_user(self.env.ref("base.user_admin")).bom_create_line_has(
purchase_embryo)
self.bom_id = product_bom_purchase.id
def _action_launch_stock_rule(self):
procurements = []
group_id = self.sale_order_id.procurement_group_id
if not group_id:
group_id = self.env['procurement.group'].create(self._prepare_procurement_group_vals())
self.sale_order_id.procurement_group_id = group_id
else:
updated_vals = {}
if group_id.partner_id != self.sale_order_id.partner_shipping_id:
updated_vals.update({'partner_id': self.sale_order_id.partner_shipping_id.id})
if group_id.move_type != self.sale_order_id.picking_policy:
updated_vals.update({'move_type': self.sale_order_id.picking_policy})
if updated_vals:
group_id.write(updated_vals)
values = self._prepare_procurement_values(group_id=group_id)
line_uom = self.sale_order_line_id.product_uom
quant_uom = self.product_id.uom_id
plan_uom_qty, procurement_uom = line_uom._adjust_uom_quantities(self.plan_uom_qty, quant_uom)
procurements.append(self.env['procurement.group'].Procurement(
self.product_id, plan_uom_qty, procurement_uom,
self.sale_order_id.partner_shipping_id.property_stock_customer,
self.product_id.display_name, self.sale_order_id.name, self.sale_order_id.company_id, values))
if procurements:
procurement_group = self.env['procurement.group']
if self.env.context.get('import_file'):
procurement_group = procurement_group.with_context(import_file=False)
procurement_group.run(procurements)
orders = self.mapped('sale_order_id')
for order in orders:
pickings_to_confirm = order.picking_ids.filtered(lambda p: p.state not in ['cancel', 'done'])
if pickings_to_confirm:
pickings_to_confirm.action_confirm()
return True
def _prepare_procurement_group_vals(self):
return {
'name': self.sale_order_id.name,
'move_type': self.sale_order_id.picking_policy,
'sale_id': self.sale_order_id.id,
'partner_id': self.sale_order_id.partner_shipping_id.id,
}
def _prepare_procurement_values(self, group_id=False):
self.ensure_one()
date_deadline = self.sale_order_id.commitment_date or (
self.sale_order_id.date_order + timedelta(days=self.sale_order_line_id.customer_lead or 0.0))
date_planned = date_deadline - timedelta(days=self.sale_order_id.company_id.security_lead)
values = {
'group_id': group_id,
'sale_line_id': self.sale_order_line_id.id,
'date_planned': date_planned,
'date_deadline': date_deadline,
'route_ids': self.route_ids,
'warehouse_id': self.sale_order_id.warehouse_id or False,
'partner_id': self.sale_order_id.partner_shipping_id.id,
'product_description_variants': self.sale_order_line_id.with_context(
lang=self.sale_order_id.partner_id.lang)._get_sale_order_line_multiline_description_variants(),
'company_id': self.sale_order_id.company_id,
'product_packaging_id': self.sale_order_line_id.product_packaging_id,
'sequence': self.sale_order_line_id.sequence,
'demand_plan_line_id': self.id
}
return values
def button_plan_detail(self):
self.ensure_one()
purchase_request_ids = self.env['purchase.request'].sudo().search(
[('line_ids.demand_plan_line_id', 'in', self.ids)])
purchase_order_ids = self.env['purchase.order'].sudo().search(
[('order_line.demand_plan_line_id', 'in', self.ids)])
picking_ids = self.env['stock.picking'].sudo().search(
[('demand_plan_line_ids', 'in', self.ids)])
return {
'name': _('详情'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'sf.demand.plan.detail.wizard',
'target': 'new',
'views': [(False, 'form')],
'context': {
'default_mrp_production_ids': self.mrp_production_ids.ids,
'default_purchase_request_ids': purchase_request_ids.ids,
'default_standard_purchase_order_ids': purchase_order_ids.filtered(
lambda o: o.purchase_type == 'standard'
).ids,
'default_consignment_purchase_order_ids': purchase_order_ids.filtered(
lambda o: o.purchase_type == 'consignment'
).ids,
'default_outsourcing_purchase_order_ids': purchase_order_ids.filtered(
lambda o: o.purchase_type == 'outsourcing'
).ids,
'default_outside_purchase_order_ids': purchase_order_ids.filtered(
lambda o: o.purchase_type == 'outside'
).ids,
'default_in_stock_picking_ids': picking_ids.filtered(
lambda o: o.picking_type_sequence_code == 'IN'
).ids,
'default_dl_stock_picking_ids': picking_ids.filtered(
lambda o: o.picking_type_sequence_code == 'DL'
).ids,
'default_int_stock_picking_ids': picking_ids.filtered(
lambda o: o.picking_type_sequence_code == 'INT'
).ids,
'default_pc_stock_picking_ids': picking_ids.filtered(
lambda o: o.picking_type_sequence_code == 'PC'
).ids,
'default_sfp_stock_picking_ids': picking_ids.filtered(
lambda o: o.picking_type_sequence_code == 'SFP'
).ids,
'default_onin_stock_picking_ids': picking_ids.filtered(
lambda o: o.picking_type_sequence_code == 'OCIN'
).ids,
'default_ocout_stock_picking_ids': picking_ids.filtered(
lambda o: o.picking_type_sequence_code == 'OCOUT'
).ids,
'default_out_stock_picking_ids': picking_ids.filtered(
lambda o: o.picking_type_sequence_code == 'OUT'
).ids,
'default_res_stock_picking_ids': picking_ids.filtered(
lambda o: o.picking_type_sequence_code == 'RES'
).ids,
}}

View File

@@ -1,37 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class StockMove(models.Model):
_inherit = 'stock.move'
demand_plan_line_ids = fields.Many2many(comodel_name="sf.production.demand.plan",
string="需求计划明细", compute='_compute_demand_plan_line_ids', store=True)
@api.depends('origin', 'move_orig_ids')
def _compute_demand_plan_line_ids(self):
sorted_records = self.sorted(key=lambda r: -r.id)
for line in sorted_records:
origin = [origin.replace(' ', '') for origin in line.origin.split(',')]
if line.created_purchase_request_line_id:
line.demand_plan_line_ids = line.created_purchase_request_line_id.demand_plan_line_id.ids
elif line.origin and 'MO' in line.origin:
mp_ids = self.env['mrp.production'].sudo().search([('name', '=', origin[0])])
line.demand_plan_line_ids = mp_ids.demand_plan_line_id.ids if mp_ids else []
elif line.origin and 'P' in line.origin:
purchase_order_ids = self.env['purchase.order'].sudo().search([('name', '=', origin[0])])
if purchase_order_ids.order_line:
line.demand_plan_line_ids = purchase_order_ids.order_line.demand_plan_line_id.ids
elif line.move_orig_ids:
demand_plan_lines = self.env['sf.production.demand.plan']
for move_orig in line.move_orig_ids:
demand_plan_lines |= move_orig.demand_plan_line_ids
line.demand_plan_line_ids = demand_plan_lines.ids
else:
line.demand_plan_line_ids = []
if not line.demand_plan_line_ids and self.env.context.get('demand_plan_line_id'):
plan_ids = self.env.context['demand_plan_line_id']
line.demand_plan_line_ids = [plan_ids]

View File

@@ -1,16 +0,0 @@
from odoo import fields, api, models, _
class StockPicking(models.Model):
_inherit = "stock.picking"
demand_plan_line_ids = fields.Many2many(comodel_name="sf.production.demand.plan",
string="需求计划明细", compute='_compute_demand_plan_line_ids', store=True)
@api.depends('move_ids_without_package')
def _compute_demand_plan_line_ids(self):
for line in self:
demand_plan_lines = self.env['sf.production.demand.plan']
if line.move_ids_without_package and line.move_ids_without_package.demand_plan_line_ids:
demand_plan_lines |= line.move_ids_without_package.demand_plan_line_ids
line.demand_plan_line_ids = demand_plan_lines.ids

View File

@@ -1,49 +0,0 @@
from odoo import models, fields, api, _
class SfStockRoute(models.Model):
_inherit = 'stock.route'
demand_plan_selectable = fields.Boolean("需求计划行")
stock_route_group_ids = fields.Many2many('stock.route.group', 'route_to_group', string='路线组')
demand_plan_ids = fields.Many2many('sf.production.demand.plan', 'stock_route_demand_plan', 'route_id',
'demand_plan_id', '需求计划', copy=False, compute='_compute_demand_plan_ids',
store=True)
@api.depends('demand_plan_selectable', 'stock_route_group_ids')
def _compute_demand_plan_ids(self):
for sr in self:
if sr.demand_plan_selectable:
stock_route_group = [srg.code for srg in sr.stock_route_group_ids]
demand_plan_ids = self.env['sf.production.demand.plan'].sudo().search(
[('supply_method', 'in', stock_route_group)])
if demand_plan_ids:
sr.demand_plan_ids = demand_plan_ids.ids
continue
sr.demand_plan_ids = None
# def name_get(self):
# res = super().name_get()
# if self.env.context.get('demand_plan_search_stock_route_id'):
# demand_plan_id = self.env['sf.production.demand.plan'].sudo().browse(
# int(self.env.context.get('demand_plan_search_stock_route_id')))
# if demand_plan_id and demand_plan_id.supply_method:
# supply_method = self._set_supply_method(demand_plan_id.supply_method)
# res = [(item[0], f'{item[1]}-{supply_method}') for item in res if len(item) == 2]
# return res
#
# def _set_supply_method(self, supply_method):
# return {
# 'automation': "自动化产线加工",
# 'manual': "人工线下加工",
# 'purchase': "外购",
# 'outsourcing': "委外加工"
# }.get(supply_method)
class SfStockRouteGroup(models.Model):
_name = 'stock.route.group'
_description = '路线组'
name = fields.Char('名称')
code = fields.Char('编码')

View File

@@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class StockRule(models.Model):
_inherit = 'stock.rule'
def _prepare_mo_vals(self, product_id, product_qty, product_uom, location_id, name, origin, company_id, values,
bom):
res = super()._prepare_mo_vals(product_id, product_qty, product_uom, location_id, name, origin, company_id,
values, bom)
if self.env.context.get('demand_plan_line_id'):
res['demand_plan_line_id'] = self.env.context.get('demand_plan_line_id')
return res
@api.model
def _prepare_purchase_request_line(self, request_id, procurement):
res = super()._prepare_purchase_request_line(request_id, procurement)
if self.env.context.get('demand_plan_line_id'):
res['demand_plan_line_id'] = self.env.context.get('demand_plan_line_id')
return res

View File

@@ -1,19 +0,0 @@
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,1,1
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
access_sf_demand_plan,sf.demand.plan,model_sf_demand_plan,base.group_user,1,0,0,0
access_sf_demand_plan_for_dispatch,sf.demand.plan for dispatch,model_sf_demand_plan,sf_base.group_plan_dispatch,1,1,0,0
access_stock_route_group,stock.route.group,model_stock_route_group,base.group_user,1,0,0,0
access_stock_route_group_dispatch,stock.route.group.dispatch,model_stock_route_group,sf_base.group_plan_dispatch,1,1,0,0
access_sf_release_plan_wizard,sf.release.plan.wizard,model_sf_release_plan_wizard,base.group_user,1,0,0,0
access_sf_release_plan_wizard_for_dispatch,sf.release.plan.wizard for dispatch,model_sf_release_plan_wizard,sf_base.group_plan_dispatch,1,1,1,1
access_sf_demand_plan_detail_wizard,sf.demand.plan.detail.wizard,model_sf_demand_plan_detail_wizard,base.group_user,1,0,0,0
access_sf_demand_plan_detail_wizard_for_dispatch,sf.demand.plan.detail.wizard for dispatch,model_sf_demand_plan_detail_wizard,sf_base.group_plan_dispatch,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_sf_production_demand_plan sf.production.demand.plan model_sf_production_demand_plan base.group_user 1 0 0 0
3 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 1 1
4 access_sf_demand_plan_print_wizard sf.demand.plan.print.wizard model_sf_demand_plan_print_wizard base.group_user 1 0 0 0
5 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
6 access_sf_demand_plan sf.demand.plan model_sf_demand_plan base.group_user 1 0 0 0
7 access_sf_demand_plan_for_dispatch sf.demand.plan for dispatch model_sf_demand_plan sf_base.group_plan_dispatch 1 1 0 0
8 access_stock_route_group stock.route.group model_stock_route_group base.group_user 1 0 0 0
9 access_stock_route_group_dispatch stock.route.group.dispatch model_stock_route_group sf_base.group_plan_dispatch 1 1 0 0
10 access_sf_release_plan_wizard sf.release.plan.wizard model_sf_release_plan_wizard base.group_user 1 0 0 0
11 access_sf_release_plan_wizard_for_dispatch sf.release.plan.wizard for dispatch model_sf_release_plan_wizard sf_base.group_plan_dispatch 1 1 1 1
12 access_sf_demand_plan_detail_wizard sf.demand.plan.detail.wizard model_sf_demand_plan_detail_wizard base.group_user 1 0 0 0
13 access_sf_demand_plan_detail_wizard_for_dispatch sf.demand.plan.detail.wizard for dispatch model_sf_demand_plan_detail_wizard sf_base.group_plan_dispatch 1 1 1 1

View File

@@ -1,58 +0,0 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { ListRenderer } from "@web/views/list/list_renderer";
import { useService } from "@web/core/utils/hooks";
import { useEffect } from "@odoo/owl";
export class CustomDemandPlanListRenderer extends ListRenderer {
setup() {
super.setup();
this.orm = useService("orm");
this.notification = useService("notification");
console.log('setup', this.props);
// 监听selection属性的变化
useEffect(() => {
this.updateButtonState();
}, () => [this.props.list.selection]);
}
/**
* 更新按钮状态
*/
async updateButtonState() {
const selectedRecords = this.props.list.selection;
const isStatus30 = selectedRecords.some(record => record.data.status != "30");
const button = $(this.__owl__.parent.bdom.parentEl).find('button[name="button_batch_release_plan"]');
if (isStatus30) {
// 禁用按钮
button.attr('disabled', true);
} else {
button.attr('disabled', false);
}
}
}
// 使用setTimeout延迟注册避免在模块加载时立即执行
setTimeout(() => {
const registerCustomRenderer = () => {
try {
const listView = registry.category("views").get("list");
if (listView) {
registry.category("views").add("custom_demand_plan_list", {
...listView,
Renderer: CustomDemandPlanListRenderer,
});
console.log("Custom demand plan list renderer registered successfully");
} else {
console.warn("List view not found, retrying...");
// 如果还没找到,再等一段时间
setTimeout(registerCustomRenderer, 1000);
}
} catch (error) {
console.error("Error registering custom renderer:", error);
}
};
registerCustomRenderer();
}, 1000);

View File

@@ -1,256 +0,0 @@
odoo.define('sf_demand.print_demand', function (require) {
"use strict";
var ListController = require('web.ListController');
var ListRenderer = require('web.ListRenderer');
var ListView = require('web.ListView');
var viewRegistry = require('web.view_registry');
var { url } = require("@web/core/utils/urls")
var CustomListRenderer = ListRenderer.extend({
_render: function () {
var self = this;
this.getParent()?.$buttons.hide();
return this._super.apply(this, arguments).then(function () {
if(!self.state.data || !self.state.data.length) return
// 添加图片预览容器到页面左侧
if (!$('.table-image-preview-container').length) {
self.$el.parent().addClass('custom-table-image-container')
self.$el.before(
`<div class="custom-preview-container">
<img class="table-image-preview-container" src="" />
<iframe class="table-image-preview-container" src=""/>
</div>`
);
}
if(!$('.denmand_set').length) {
const checked = self.getParent().radioCheck || 'all'
self.$el.prepend(`
<form class="denmand_set">
<span>更多设置:</span>
<input type="radio" id="male" name="set" value="图纸">
<label for="male">图纸</label>
<input type="radio" id="female" name="set" value="程序单">
<label for="female">程序单</label>
<input type="radio" id="other" name="set" value="all" >
<label for="other">图纸/程序单</label>
</form>
`)
setTimeout(() => {
$(`input[name=set][value=${checked}]`).prop('checked', true)
$('.denmand_set').trigger('click')
}, 100);
self.$el.prepend(`
<div class="print-button-container" style="margin-bottom:10px;">
<button class="btn btn-primary o_print_custom">
<i class="fa fa-print"></i> 打印
</button>
<button class="btn btn-secondary o_cancel_custom">
取消
</button>
</div>
`);
}
});
},
start: function() {
setTimeout(() => {
this.$el.find('.o_data_row').eq(0).trigger('click')
this.getParent().$el?.find('.o_cp_top_right,.o_cp_bottom').hide()
}, 500);
return this._super();
},
events: _.extend({}, ListRenderer.prototype.events, {
'click .o_data_row': '_onCustomRowClick',
'click .o_print_custom': '_onPrintClick',
'click .o_cancel_custom': '_onCancelClick',
'click .denmand_set': '_onDenmandChange',
}),
_onCancelClick() {
this.getParent()?.getParent()?.dialogs.closeAll()
},
_onCustomRowClick: async function (ev) {
var self = this;
var $row = $(ev.currentTarget);
var index = $row.index();
var data = this.state.data[index];
if(data.fileData?.fileUrl) {
} else {
data.fileData = { }
if(data.res_id) {
// 正确获取 ORM 服务的方式
// var orm = this.getParent().getParent().env.services
const key = data.data.type == 1 ? 'machining_drawings' : 'cnc_worksheet'
const attachment = await this._rpc({
model: 'ir.binary',
method: 'attachment_info',
args: [
data.model,
data.res_id,
key
],
})
if (attachment) {
Object.assign(data.fileData, attachment)
this._getTypeInfo(attachment.mimetype, data.fileData)
}
const fileUrl = this.getFileUrl(data.fileData.attachment_type, data, key)
data.fileData.fileUrl = fileUrl
}
}
$('.table-image-preview-container').hide()
if(data.fileData.attachment_type == 'iframe') {
$('iframe.table-image-preview-container').attr('src', decodeURIComponent(data.fileData.fileUrl) ).show()
} else {
$('img.table-image-preview-container').attr('src', data.fileData.fileUrl).show()
}
},
getSelectedIds: function() {
return this.state.data.filter(_ => !_.hide).map(_ => {
return _.data.id
})
},
_onPrintClick(e) {
var print_ids = this.getSelectedIds();
this._rpc({
model: 'sf.demand.plan.print.wizard',
method: 'demand_plan_print',
args: [ print_ids ] ,
// context: this.state.getContext()
}).then((e) => {
this.getParent()?.getParent()?.env.services?.notification.notify( {
type: 'info',
message: e.message,
})
// self.do_notify("成功", "打印任务已发送到打印机");
}).catch(function(error) {
console.error("打印错误:", error);
// self.do_warn("打印失败", error.data.message || "未知错误");
}).finally(e => {
this.getParent().reload()
})
},
_onDenmandChange(e) {
const isChecked = $(e.currentTarget).find('input:checked').val()
this.getParent().radioCheck = isChecked
this.$el.find('tbody').find('.o_data_row').show()
this.state.data.forEach(_ => {
_.hide = false
})
const self = this
if(!isChecked || isChecked == 'all') return
this.$el.find('tbody').children('.o_data_row').each(function() {
if($(this).find('td[name=type]').text() != isChecked) {
const i = $(this).index()
self.state.data[i].hide = true
$(this).hide()
}
})
},
getFileUrl(attachment_type, data, key) {
let fileUrl
switch (attachment_type) {
case 'image':
const timer = +new Date()
fileUrl = url("/web/image", {
model: data.model,
id: data.res_id,
field: key,
unique: '',
timer
});
break;
case 'iframe':
const iframe_file_url = encodeURIComponent(
url("/web/content", {
model: data.model,
id: data.res_id,
field: key,
})
);
fileUrl = `${this.state.attachment_base}?file=${iframe_file_url}`;
case 'unknown':
fileUrl = encodeURIComponent(
url("/web/content", {
model: data.model,
id: data.res_id,
field: key,
})
);
}
return fileUrl
},
_getTypeInfo(type, data) {
switch (type) {
case 'application/pdf':
data.attachment_base = `/web/static/lib/pdfjs/web/viewer.html`;
data.attachment_type = 'iframe'
break;
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
data.attachment_base = `/jikimo_attachment_viewer/static/lib/docxjs/viewer.html`;
data.attachment_type = 'iframe'
break;
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
data.attachment_base = `/jikimo_attachment_viewer/static/lib/exceljs/viewer.html`;
data.attachment_type = 'iframe'
break;
case 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
data.attachment_base = '';
data.attachment_type = 'unknown'
break;
case 'image/png':
case 'image/jpeg':
case 'image/jpg':
data.attachment_base = ''
data.attachment_type = 'image'
break;
default:
data.attachment_base = ''
data.attachment_type = 'unknown'
break;
}
}
});
var CustomListController = ListController.extend({
// 可以保留或移除,根据是否需要处理自定义事件
// 处理打印操作
// rpc.query({
// model: 'sf.demand.plan.print.wizard',
// method: 'demand_plan_print',
// args: [recordIds]
// }).then(function(result) {
// self.do_notify("成功", "打印任务已发送到打印机");
// // 刷新视图显示最新状态
// self.reload();
// }).catch(function(error) {
// self.do_warn("打印失败", error.data.message || "发生未知错误");
// });
});
var PrintDemand = ListView.extend({
config: _.extend({}, ListView.prototype.config, {
Renderer: CustomListRenderer,
Controller: CustomListController,
}),
});
viewRegistry.add('print_demand', PrintDemand);
return PrintDemand;
});

View File

@@ -1,89 +0,0 @@
.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;
}
.o_selected_row {
background-color: #e6f7ff !important;
font-weight: bold;
}
.custom-table-image-container {
display: flex;
height: calc(95vh - 254px);
gap: 20px;
position: relative;
th.o_list_record_selector, td.o_list_record_selector{
display: none;
}
.custom-preview-container, .print_demand {
flex: 1;
max-width: 49%;
tbody {
tr:not(.o_data_row) {
display: none;
}
}
tfoot {
display: none;
}
}
.print_demand {
.table-responsive {
width: 100%;
overflow-x: auto!important;
}
}
.custom-preview-container {
background-color: #dadce0;
padding: 20px;
img {
max-width: 100%;
max-height: 100%;
}
iframe {
width: 100%;
height: 100%;
}
}
.o_print_custom, .o_cancel_custom {
position: absolute;
bottom: 20px;
right: 20px;
}
.o_print_custom {
right: 66px;
}
}
.denmand_set {
display: flex;
align-items: center;
height: 50px;
> span {
font-weight: bold;
}
input {
margin-left: 30px;
}
label {
margin-left: 5px;
}
input,label {
cursor: pointer;
}
}
/*.demand_plan_tree th[data-name=planned_start_date] + th::before{*/
/* content: '待执行单据';*/
/* line-height: 38px;*/
/*}*/

View File

@@ -1,145 +0,0 @@
<odoo>
<record id="view_sf_production_demand_plan_tree" model="ir.ui.view">
<field name="name">sf.production.demand.plan.tree</field>
<field name="model">sf.production.demand.plan</field>
<field name="arch" type="xml">
<tree string="需求计划" default_order="sequence desc,id desc" editable="bottom"
class="demand_plan_tree freeze-columns-before-part_number" create="false" delete="false"
js_class="custom_demand_plan_list">
<header>
<button string="打印" name="button_action_print" type="object"
class="btn-primary"/>
<button string="下达计划" name="button_batch_release_plan" type="object"
class="btn-primary"
/>
</header>
<field name="sequence" widget="handle"/>
<field name="id" optional="hide"/>
<field name="status"/>
<field name="customer_name"/>
<field name="order_remark"/>
<field name="glb_url" optional="hide"/>
<field name="product_id"/>
<field name="model_id" optional="hide"/>
<field name="part_name"/>
<field name="part_number"/>
<field name="manual_quotation" optional="hide"/>
<field name="is_incoming_material"/>
<field name="new_supply_method" attrs="{'readonly': [('status', '!=', '30')]}"/>
<field name="readonly_custom_made_type" invisible="1"/>
<field name="custom_made_type"
attrs="{'readonly': ['|',('status', '!=', '30'),('readonly_custom_made_type', '=', True)],
'required': [('new_supply_method', '=', 'custom_made')]}"/>
<field name="product_uom_qty"/>
<field name="plan_uom_qty" attrs="{'readonly': [('status', '!=', '30')]}"/>
<field name="deadline_of_delivery"/>
<field name="inventory_quantity_auto_apply" optional="hide"/>
<field name="qty_delivered" optional="hide"/>
<field name="qty_to_deliver" optional="hide"/>
<field name="model_long"/>
<field name="blank_type" optional="hide"/>
<field name="blank_precision"/>
<field name="embryo_long"/>
<field name="unit_number" optional="hide"/>
<field name="materials_id"/>
<field name="model_machining_precision"/>
<field name="model_process_parameters_ids" widget="many2many_tags"/>
<field name="product_remark" optional="hide"/>
<field name="order_code" optional="hide"/>
<field name="sale_order_id" optional="hide"/>
<field name="sale_order_line_number" optional="hide"/>
<field name="order_state"/>
<field name="route_ids" widget="many2many_tags" optional="hide"
context="{'demand_plan_search_stock_route_id': id}"/>
<field name="contract_date"/>
<field name="date_order"/>
<field name="contract_code"/>
<field name="plan_remark" attrs="{'readonly': [('status', 'in', ('60','100'))]}"/>
<field name="priority" decoration-danger="priority == '1'"
decoration-warning="priority == '2'"
decoration-info="priority == '3'"
decoration-success="priority == '4'"/>
<field name="material_check" optional="hide"/>
<!-- <field name="hide_action_open_mrp_production" invisible="1"/>-->
<!-- <field name="hide_action_purchase_orders" invisible="1"/>-->
<!-- <field name="hide_action_stock_picking" invisible="1"/>-->
<!-- <field name="hide_action_view_programming" invisible="1"/>-->
<!-- <button name="action_open_sale_order" type="object" string="供货方式待确认" class="btn-secondary"-->
<!-- attrs="{'invisible': [('supply_method', '!=', False)]}"/>-->
<!-- <button name="action_open_mrp_production" type="object" string="待工艺确认" class="btn-secondary"-->
<!-- attrs="{'invisible': [('hide_action_open_mrp_production', '=', False)]}"/>-->
<!-- <button name="action_view_purchase_request" type="object" string="采购申请" class="btn-secondary"-->
<!-- attrs="{'invisible': [('hide_action_purchase_orders', '=', False)]}"/>-->
<!-- <button name="action_view_stock_picking" type="object" string="调拨单" class="btn-secondary"-->
<!-- attrs="{'invisible': [('hide_action_stock_picking', '=', False)]}"/>-->
<!-- <button name="action_view_programming" type="object" string="编程单" class="btn-secondary"-->
<!-- attrs="{'invisible': [('hide_action_view_programming', '=', False)]}"/>-->
<field name="planned_start_date" attrs="{'readonly': [('status', 'in', ('60','100'))]}"/>
<field name="actual_start_date"/>
<field name="actual_end_date"/>
<field name="processing_time"/>
<field name="create_date" optional="hide" string="创建时间"/>
<field name="create_uid" optional="hide" string="创建人"/>
<field name="write_date" string="更新时间"/>
<field name="write_uid" optional="hide" string="更新人"/>
<field name="print_count"/>
<field name="hide_release_production_order" invisible="1"/>
<button string="下达计划" name="button_release_plan" type="object"
class="btn-primary"
attrs="{'invisible': [('status', 'in', ('50','60','100'))]}"
/>
<button name="button_release_production" type="object" string="下发生产" class="btn-primary"
attrs="{'invisible': [('hide_release_production_order', '=', False)]}"
/>
<button name="edit_button" type="object" string="拆分" class="btn-primary"/>
<button string="详情" name="button_plan_detail" type="object"
class="btn-primary"
attrs="{'invisible': [('status', 'not in', ('50','60'))]}"
/>
</tree>
</field>
</record>
<record id="view_sf_production_demand_plan_search" model="ir.ui.view">
<field name="name">sf.production.demand.plan.search</field>
<field name="model">sf.production.demand.plan</field>
<field name="arch" type="xml">
<search>
<field name="order_remark"/>
<field name="product_id"/>
<field name="part_name"/>
<field name="part_number"/>
<field name="customer_name"/>
<field name="supply_method"/>
<field name="materials_id"/>
<field name="model_process_parameters_ids"/>
<field name="plan_remark"/>
<field name="contract_code"/>
<group expand="0" string="Group By">
<filter name="group_by_priority" string="优先级" domain="[]" context="{'group_by': 'priority'}"/>
<filter name="group_by_status" string="状态" domain="[]" context="{'group_by': 'status'}"/>
<filter name="group_by_customer_name" string="客户" domain="[]"
context="{'group_by': 'customer_name'}"/>
<filter name="group_by_is_incoming_material" string="客供料" domain="[]"
context="{'group_by': 'is_incoming_material'}"/>
<filter name="group_by_supply_method" string="供货方式" domain="[]"
context="{'group_by': 'supply_method'}"/>
<filter name="group_by_deadline_of_delivery" string="客户交期" domain="[]"
context="{'group_by': 'deadline_of_delivery'}"/>
<filter name="group_by_materials_id" string="材料" domain="[]"
context="{'group_by': 'materials_id'}"/>
<filter name="group_by_contract_code" string="合同号" domain="[]"
context="{'group_by': 'contract_code'}"/>
</group>
</search>
</field>
</record>
<record id="sf_production_demand_plan_action" model="ir.actions.act_window">
<field name="name">需求计划</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">sf.production.demand.plan</field>
<field name="view_mode">tree</field>
</record>
</odoo>

View File

@@ -1,124 +0,0 @@
<odoo>
<record id="view_sf_demand_plan_form" model="ir.ui.view">
<field name="name">sf.demand.plan.form</field>
<field name="model">sf.demand.plan</field>
<field name="arch" type="xml">
<form>
<header>
<field name="state" widget="statusbar"/>
<field name="hide_button_release_plan" invisible="1"/>
<button string="下达计划" name="button_production_release_plan" type="object"
class="btn-primary"
attrs="{'invisible': [('hide_button_release_plan', '=', False)]}"/>
</header>
<sheet>
<group>
<group>
<field name="demand_plan_number"/>
<field name="product_id"/>
<field name="part_name"/>
<field name="part_number"/>
<field name="materials_id"/>
<field name="blank_type"/>
<field name="blank_precision"/>
<field name="embryo_long"/>
<field name="manual_quotation"/>
<field name="is_incoming_material"/>
<field name="pending_qty"/>
<field name="planned_qty"/>
<field name="model_id"/>
</group>
<group>
<field name="customer_name"/>
<field name="product_uom_qty"/>
<field name="deadline_of_delivery"/>
<field name="contract_date"/>
<field name="contract_code"/>
<field name="model_process_parameters_ids" widget="many2many_tags"/>
<field name="model_machining_precision"/>
<field name="inventory_quantity_auto_apply"/>
<field name="priority" attrs="{'readonly': [('state', 'in', ('40','50'))]}"/>
<field name="overdelivery_allowed"/>
<field name="origin"/>
</group>
</group>
<notebook>
<page string="计划">
<field name="line_ids" attrs="{'invisible': [('state', 'in', ('40','50'))]}">
<tree editable="bottom" create="false" delete="false">
<field name="status"/>
<field name="readonly_custom_made_type" invisible="1"/>
<field name="new_supply_method" attrs="{'readonly': [('status', '!=', '30')]}"/>
<field name="custom_made_type"
attrs="{
'readonly': ['|', '|', ('new_supply_method', '!=', 'custom_made'), ('status', '!=', '30'), ('readonly_custom_made_type', '=', True)],
'required': [('new_supply_method', '=', 'custom_made')]}"/>
<field name="route_ids" widget="many2many_tags" optional="hide"/>
<field name="location_id" optional="hide"/>
<field name="bom_id" optional="hide"/>
<field name="processing_time" optional="hide"/>
<field name="plan_uom_qty" attrs="{'readonly': [('status', '!=', '30')]}"/>
<field name="blank_arrival_date"/>
<field name="finished_product_arrival_date"/>
<field name="planned_start_date"/>
<field name="actual_start_date"/>
<field name="actual_end_date"/>
<field name="plan_remark"/>
<field name="procurement_reason"/>
<field name="write_date" string="更新时间"/>
<field name="hide_release_production_order" invisible="1"/>
<button string="下达计划" name="button_release_plan" type="object"
class="btn-primary"
attrs="{'invisible': [('status', 'in', ('50','60','100'))]}"
/>
<button name="button_release_production" type="object" string="下发生产"
class="btn-primary"
attrs="{'invisible': [('hide_release_production_order', '=', False)]}"
/>
</tree>
</field>
<field name="line_ids" attrs="{'invisible': [('state', 'not in', ('40','50'))]}">
<tree editable="bottom">
<field name="status"/>
<field name="readonly_custom_made_type" invisible="1"/>
<field name="new_supply_method" attrs="{'readonly': [('status', '!=', '30')]}"/>
<field name="custom_made_type"
attrs="{
'readonly': ['|', '|', ('new_supply_method', '!=', 'custom_made'), ('status', '!=', '30'), ('readonly_custom_made_type', '=', True)],
'required': [('new_supply_method', '=', 'custom_made')]}"/>
<field name="route_ids" widget="many2many_tags" optional="hide"/>
<field name="location_id" optional="hide"/>
<field name="bom_id" optional="hide" readonly="1" options="{'no_create': True}"/>
<field name="processing_time" optional="hide"/>
<field name="plan_uom_qty" attrs="{'readonly': [('status', '!=', '30')]}"/>
<field name="blank_arrival_date"/>
<field name="finished_product_arrival_date"/>
<field name="planned_start_date"/>
<field name="actual_start_date"/>
<field name="actual_end_date"/>
<field name="plan_remark"/>
<field name="procurement_reason"/>
<field name="write_date" string="更新时间"/>
<field name="hide_release_production_order" invisible="1"/>
<button string="下达计划" name="button_release_plan" type="object"
class="btn-primary"
attrs="{'invisible': [('status', 'in', ('50','60','100'))]}"
/>
<button name="button_release_production" type="object" string="下发生产"
class="btn-primary"
attrs="{'invisible': [('hide_release_production_order', '=', False)]}"
/>
<button string="详情" name="button_plan_detail" type="object"
class="btn-primary"
attrs="{'invisible': [('status', 'not in', ('50','60'))]}"
/>
</tree>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
</odoo>

View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<menuitem
id="demand_plan_menu"
name="需求计划"
sequence="140"
action="sf_demand_plan.sf_production_demand_plan_action"
parent="sf_plan.sf_production_plan_menu"
/>
<!-- 调拨动作中屏蔽验证-->
<record id="stock.action_validate_picking" model="ir.actions.server">
<field name="binding_model_id" eval="False"/>
</record>
</data>
</odoo>

View File

@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_order_form_inherit_plan" model="ir.ui.view">
<field name="name">view.sale.order.form.inherit.plan</field>
<field name="inherit_id" ref="sf_manufacturing.view_order_form_inherit_supply_method"/>
<field name="model">sale.order</field>
<field name="arch" type="xml">
<xpath expr="//header/button[@name='action_confirm'][last()]" position="attributes">
<attribute name="invisible">True</attribute>
</xpath>
<xpath expr="//page/field[@name='order_line']/tree/field[@name='supply_method']" position="attributes">
<attribute name="invisible">True</attribute>
</xpath>
<xpath expr="//div[@name='button_box']" position="inside">
<button class="oe_stat_button" name="action_view_demand_plan" type="object" icon="fa-pencil-square-o"
attrs="{'invisible': [('demand_plan_count', '=', 0)]}">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="demand_plan_count"/>
</span>
<span class="o_stat_text">需求计划</span>
</div>
</button>
</xpath>
</field>
</record>
</odoo>

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="sf_stock_location_route_form_view" model="ir.ui.view">
<field name="name">stock.route.form</field>
<field name="model">stock.route</field>
<field name="inherit_id" ref="stock.stock_location_route_form_view"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='packaging_selectable']" position="after">
<field name="demand_plan_selectable"/>
</xpath>
<xpath expr="//group[@name='route_selector']" position="after">
<group name="group_category" string="组类">
<group>
<field name="stock_route_group_ids" options="{'no_create': True}" widget="many2many_tags"/>
<field name="demand_plan_ids" invisible="1" options="{'no_create': True}" widget="many2many_tags"/>
</group>
</group>
</xpath>
</field>
</record>
</odoo>

View File

@@ -1,3 +0,0 @@
from . import sf_demand_plan_print_wizard
from . import sf_release_plan_wizard
from . import sf_demand_plan_detail_wizard

View File

@@ -1,74 +0,0 @@
# -*- coding: utf-8 -*-
import logging
from odoo import models, fields, api, _
_logger = logging.getLogger(__name__)
class SfDemandPlanDetailWizard(models.TransientModel):
_name = 'sf.demand.plan.detail.wizard'
_description = u'需求计划详情向导'
mrp_production_ids = fields.Many2many('mrp.production', string='关联制造订单', readonly=True)
purchase_request_ids = fields.Many2many('purchase.request', string='关联采购申请', readonly=True)
standard_purchase_order_ids = fields.Many2many('purchase.order', string='关联标准采购',
relation='standard_purchase_order_rel',
column1='plan_detail_wizard_id', column2='purchase_order_id',
readonly=True)
consignment_purchase_order_ids = fields.Many2many('purchase.order', string='关联工序外协采购',
relation='consignment_purchase_order_rel',
column1='plan_detail_wizard_id', column2='purchase_order_id',
readonly=True)
outsourcing_purchase_order_ids = fields.Many2many('purchase.order', string='关联委外加工采购',
relation='outsourcing_purchase_order_rel',
column1='plan_detail_wizard_id', column2='purchase_order_id',
readonly=True)
outside_purchase_order_ids = fields.Many2many('purchase.order', string='关联外购订单采购',
relation='outside_purchase_order_rel',
column1='plan_detail_wizard_id', column2='purchase_order_id',
readonly=True)
in_stock_picking_ids = fields.Many2many('stock.picking', string='关联收料入库调拨单',
relation='in_stock_picking_rel',
column1='plan_detail_wizard_id', column2='stock_picking_id',
readonly=True)
dl_stock_picking_ids = fields.Many2many('stock.picking', string='关联客供料入库调拨单',
relation='dl_stock_picking_rel',
column1='plan_detail_wizard_id', column2='stock_picking_id',
readonly=True)
int_stock_picking_ids = fields.Many2many('stock.picking', string='关联内部调拨',
relation='int_stock_picking_rel',
column1='plan_detail_wizard_id', column2='stock_picking_id',
readonly=True)
pc_stock_picking_ids = fields.Many2many('stock.picking', string='关联生产发料调拨单',
relation='pc_stock_picking_rel',
column1='plan_detail_wizard_id', column2='stock_picking_id',
readonly=True)
sfp_stock_picking_ids = fields.Many2many('stock.picking', string='关联生产入库调拨单',
relation='sfp_stock_picking_rel',
column1='plan_detail_wizard_id', column2='stock_picking_id',
readonly=True)
onin_stock_picking_ids = fields.Many2many('stock.picking', string='关联外协入库调拨单',
relation='onin_stock_picking_rel',
column1='plan_detail_wizard_id', column2='stock_picking_id',
readonly=True)
ocout_stock_picking_ids = fields.Many2many('stock.picking', string='关联外协出库调拨单',
relation='ocout_stock_picking_rel',
column1='plan_detail_wizard_id', column2='stock_picking_id',
readonly=True)
out_stock_picking_ids = fields.Many2many('stock.picking', string='关联发料出库调拨单',
relation='out_stock_picking_rel',
column1='plan_detail_wizard_id', column2='stock_picking_id',
readonly=True)
res_stock_picking_ids = fields.Many2many('stock.picking', string='关联委外发料调拨单',
relation='res_stock_picking_rel',
column1='plan_detail_wizard_id', column2='stock_picking_id',
readonly=True)

View File

@@ -1,58 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="action_plan_detail_form" model="ir.ui.view">
<field name="name">sf.demand.plan.detail.wizard.form</field>
<field name="model">sf.demand.plan.detail.wizard</field>
<field name="arch" type="xml">
<form string="详情">
<notebook>
<page string="采购申请" attrs="{'invisible': [('purchase_request_ids', '=', [])]}">
<field name="purchase_request_ids" widget="many2many"/>
</page>
<page string="制造订单" attrs="{'invisible': [('mrp_production_ids', '=', [])]}">
<field name="mrp_production_ids" widget="many2many"/>
</page>
<page string="标准采购" attrs="{'invisible': [('standard_purchase_order_ids', '=', [])]}">
<field name="standard_purchase_order_ids" widget="many2many"/>
</page>
<page string="工序外协采购" attrs="{'invisible': [('consignment_purchase_order_ids', '=', [])]}">
<field name="consignment_purchase_order_ids" widget="many2many"/>
</page>
<page string="委外加工采购" attrs="{'invisible': [('outsourcing_purchase_order_ids', '=', [])]}">
<field name="outsourcing_purchase_order_ids" widget="many2many"/>
</page>
<page string="外购订单采购" attrs="{'invisible': [('outside_purchase_order_ids', '=', [])]}">
<field name="outside_purchase_order_ids" widget="many2many"/>
</page>
<page string="收料入库" attrs="{'invisible': [('in_stock_picking_ids', '=', [])]}">
<field name="in_stock_picking_ids" widget="many2many"/>
</page>
<page string="客供料入库" attrs="{'invisible': [('dl_stock_picking_ids', '=', [])]}">
<field name="dl_stock_picking_ids" widget="many2many"/>
</page>
<page string="内部调拨" attrs="{'invisible': [('int_stock_picking_ids', '=', [])]}">
<field name="int_stock_picking_ids" widget="many2many"/>
</page>
<page string="生产发料" attrs="{'invisible': [('pc_stock_picking_ids', '=', [])]}">
<field name="pc_stock_picking_ids" widget="many2many"/>
</page>
<page string="生产入库" attrs="{'invisible': [('sfp_stock_picking_ids', '=', [])]}">
<field name="sfp_stock_picking_ids" widget="many2many"/>
</page>
<page string="外协入库" attrs="{'invisible': [('onin_stock_picking_ids', '=', [])]}">
<field name="onin_stock_picking_ids" widget="many2many"/>
</page>
<page string="外协出库" attrs="{'invisible': [('ocout_stock_picking_ids', '=', [])]}">
<field name="ocout_stock_picking_ids" widget="many2many"/>
</page>
<page string="发料出库" attrs="{'invisible': [('out_stock_picking_ids', '=', [])]}">
<field name="out_stock_picking_ids" widget="many2many"/>
</page>
<page string="委外发料" attrs="{'invisible': [('res_stock_picking_ids', '=', [])]}">
<field name="res_stock_picking_ids" widget="many2many"/>
</page>
</notebook>
</form>
</field>
</record>
</odoo>

View File

@@ -1,89 +0,0 @@
# -*- coding: utf-8 -*-
import logging
from odoo import models, fields, api, _
_logger = logging.getLogger(__name__)
class SfDemandPlanPrintWizard(models.Model):
_name = 'sf.demand.plan.print.wizard'
_description = u'打印向导'
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加工图纸')
cnc_worksheet = fields.Binary('程序单')
@api.model
def demand_plan_print(self, print_ids):
plan_print_ids = self.env['sf.demand.plan.print.wizard'].sudo().search(
[('id', 'in', print_ids)])
if not plan_print_ids:
return {'message': '记录不存在'}
success_records = []
failed_records = []
for record in plan_print_ids:
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'
production_demand_plan_id = self.env['sf.production.demand.plan'].sudo().search(
[('model_id', '=', record.model_id)])
for production_demand_plan in production_demand_plan_id:
t_part, c_part = production_demand_plan.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
production_demand_plan.print_count = f"T{t_num}C{c_num}"
success_records.append({
'filename_url': record.filename_url,
})
except Exception as e:
record.status = 'fail'
_logger.error(f"文件{record.filename_url}打印失败: {str(e)}")
failed_records.append({
'filename_url': record.filename_url,
})
if failed_records:
message = f"成功打印 {len(success_records)} 个文件,失败 {len(failed_records)}"
else:
message = f"所有 {len(success_records)} 个文件打印成功"
return {'message': message}
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(
[('model_id', '=', record.model_id), ('type', '=', '2')])
if demand_plan_print:
demand_plan_print.write(
{'cnc_worksheet': record.cnc_worksheet, 'filename_url': record.cnc_worksheet_name})
else:
wizard_vals = {
'model_id': record.model_id,
'type': '2',
'cnc_worksheet': record.cnc_worksheet,
'filename_url': record.cnc_worksheet_name
}
self.env['sf.demand.plan.print.wizard'].sudo().create(wizard_vals)
return res

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="action_plan_print_tree" model="ir.ui.view">
<field name="name">sf.demand.plan.print.wizard.tree</field>
<field name="model">sf.demand.plan.print.wizard</field>
<field name="arch" type="xml">
<tree string="打印" class="print_demand" js_class="print_demand">
<field name="model_id"/>
<field name="filename_url"/>
<field name="type"/>
<field name="machining_drawings" attrs="{'column_invisible': True }"/>
<field name="cnc_worksheet" attrs="{'column_invisible': True }"/>
<field name="status"/>
</tree>
</field>
</record>
</odoo>

View File

@@ -1,27 +0,0 @@
# -*- coding: utf-8 -*-
import logging
from odoo import models, fields, api, _
from werkzeug.exceptions import InternalServerError
_logger = logging.getLogger(__name__)
class SfReleasePlanWizard(models.TransientModel):
_name = 'sf.release.plan.wizard'
_description = u'下达计划向导'
demand_plan_line_id = fields.Many2many(comodel_name="sf.production.demand.plan",
string="需求计划明细", readonly=True)
release_message = fields.Char(string='提示', readonly=True)
def confirm(self):
if self.demand_plan_line_id:
for demand_plan_line_id in self.demand_plan_line_id:
try:
demand_plan_line_id.action_confirm()
except Exception as e:
self.env.cr.rollback()
demand_plan_line_id.write({'is_processing': False})
self.env.cr.commit()
raise InternalServerError('操作失败', e)

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record model="ir.ui.view" id="sf_release_plan_wizard_form">
<field name="name">sf.release.plan.wizard.form</field>
<field name="model">sf.release.plan.wizard</field>
<field name="arch" type="xml">
<form>
<sheet>
<div>
<div style="white-space: pre-wrap;">
<field name="release_message"/>
</div>
</div>
<footer>
<button string="确认" name="confirm" type="object" class="oe_highlight"/>
<button string="取消" class="btn btn-secondary" special="cancel"/>
</footer>
</sheet>
</form>
</field>
</record>
</odoo>

View File

@@ -3,7 +3,6 @@ import logging
import re
from odoo import models, fields, api
from odoo.exceptions import ValidationError
class ResProductCategory(models.Model):
@@ -18,7 +17,7 @@ class ResProductCategory(models.Model):
class ResProductProduct(models.Model):
_inherit = 'product.product'
# single_manufacturing = fields.Boolean(string="单个制造")
single_manufacturing = fields.Boolean(string="单个制造")
is_bfm = fields.Boolean('业务平台是否自动创建', default=False)
@@ -48,14 +47,11 @@ class ResMrpBomMo(models.Model):
item.subcontractor_name = ''
def bom_create_line_has(self, embryo):
product = self.product_tmpl_id
if product.unit_number in (0, None, False):
raise ValidationError(f'产品{product.name}单件用量的值不能为{product.unit_number}')
vals = {
'bom_id': self.id,
'product_id': embryo.id,
'product_tmpl_id': embryo.product_tmpl_id.id,
'product_qty': product.unit_number,
'product_qty': 1,
'product_uom_id': 1
}
return self.env['mrp.bom.line'].sudo().create(vals)
@@ -126,7 +122,7 @@ class ResMrpBomMo(models.Model):
# 查bom的原材料
def get_raw_bom(self, product):
raw_bom = self.env['product.product'].search(
[('categ_id.type', '=', '原材料'), ('materials_type_id', '=', product.materials_type_id.id)], limit=1)
[('categ_id.type', '=', '原材料'), ('materials_type_id', '=', product.materials_type_id.id)],limit=1)
return raw_bom

View File

@@ -10,7 +10,6 @@ class ResProductTemplate(models.Model):
model_name = fields.Char('模型名称')
categ_type = fields.Selection(
[("成品", "成品"), ("胚料", "胚料"), ("原材料", "原材料")], string='产品的类别', related='categ_id.type', store=True)
blank_type = fields.Selection([('圆料', '圆料'), ('方料', '方料')], string='坯料分类')
model_long = fields.Float('模型长[mm]', digits=(16, 3))
model_width = fields.Float('模型宽[mm]', digits=(16, 3))
model_height = fields.Float('模型高[mm]', digits=(16, 3))
@@ -73,20 +72,14 @@ class ResProductTemplate(models.Model):
copy_product_id.product_tmpl_id.active = True
model_type = self.env['sf.model.type'].search([], limit=1)
attachment = self.attachment_create(item['model_name'], item['model_data'])
# 判断参数中是否包含 坯料尺寸(长、宽、高)
blank_bool = any(value is not None and value != 0 for value in (
item.get('blank_length'), item.get('blank_width'), item.get('blank_height'))) if all(
key in item for key in ('blank_length', 'blank_width', 'blank_height')) else False
vals = {
'name': '%s-%s-%s' % ('P', order_id.name, i),
'blank_type': item.get('blank_type'),
'model_long': item.get('blank_length') if blank_bool else item['model_long'] + model_type.embryo_tolerance,
'model_width': item.get('blank_width') if blank_bool else item['model_width'] + model_type.embryo_tolerance,
'model_height': item.get('blank_height') if blank_bool else item['model_height'] + model_type.embryo_tolerance,
'model_volume': ((item['model_long'] + model_type.embryo_tolerance) *
(item['model_width'] + model_type.embryo_tolerance) *
(item['model_height'] + model_type.embryo_tolerance)) if not blank_bool else (
item.get('blank_length') * item.get('blank_width') * item.get('blank_height')),
'model_long': item['model_long'] + model_type.embryo_tolerance,
'model_width': item['model_width'] + model_type.embryo_tolerance,
'model_height': item['model_height'] + model_type.embryo_tolerance,
'model_volume': (item['model_long'] + model_type.embryo_tolerance) * (
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_machining_precision': item['model_machining_precision'],

View File

@@ -6,50 +6,50 @@ from odoo import models, fields, api, _
class StockRuleInherit(models.Model):
_inherit = 'stock.rule'
# @api.model
# def _run_buy(self, procurements):
# # 判断补货组的采购类型
# procurements_group = {'standard': [], 'outsourcing': []}
# for procurement, rule in procurements:
# is_outsourcing = False
# product = procurement.product_id
# # 获取主 BOM
# bom = self.env['mrp.bom'].search([('product_tmpl_id', '=', product.product_tmpl_id.id)], limit=1)
#
# if bom:
# # 遍历 BOM 中的组件(即坯料等)
# for line in bom.bom_line_ids:
# raw_material = line.product_id
# # 检查路线
# for route in raw_material.route_ids:
# # print('route.name:', route.name)
# if route.name == '按订单补给外包商':
# is_outsourcing = True
#
# if is_outsourcing:
# procurements_group['outsourcing'].append((procurement, rule))
# else:
# procurements_group['standard'].append((procurement, rule))
#
# for key, value in procurements_group.items():
# super(StockRuleInherit, self)._run_buy(value)
#
# if key == 'outsourcing':
# for procurement, rule in value:
# supplier = procurement.values.get('supplier')
# if supplier:
# domain = rule._make_po_get_domain(procurement.company_id, procurement.values,
# supplier.partner_id)
# logging.info("domain=============: %s", domain)
# po = self.env['purchase.order'].sudo().search([
# ('partner_id', '=', supplier.partner_id.id),
# ('company_id', '=', procurement.company_id.id), # 保证公司一致
# ('origin', 'like', procurement.origin), # 根据来源匹配
# ('state', '=', 'draft') # 状态为草稿
# ], limit=1)
# logging.info("po=: %s", po)
# if po:
# po.write({'purchase_type': 'outsourcing'})
@api.model
def _run_buy(self, procurements):
# 判断补货组的采购类型
procurements_group = {'standard': [], 'outsourcing': []}
for procurement, rule in procurements:
is_outsourcing = False
product = procurement.product_id
# 获取主 BOM
bom = self.env['mrp.bom'].search([('product_tmpl_id', '=', product.product_tmpl_id.id)], limit=1)
if bom:
# 遍历 BOM 中的组件(即坯料等)
for line in bom.bom_line_ids:
raw_material = line.product_id
# 检查路线
for route in raw_material.route_ids:
# print('route.name:', route.name)
if route.name == '按订单补给外包商':
is_outsourcing = True
if is_outsourcing:
procurements_group['outsourcing'].append((procurement, rule))
else:
procurements_group['standard'].append((procurement, rule))
for key, value in procurements_group.items():
super(StockRuleInherit, self)._run_buy(value)
if key == 'outsourcing':
for procurement, rule in value:
supplier = procurement.values.get('supplier')
if supplier:
domain = rule._make_po_get_domain(procurement.company_id, procurement.values,
supplier.partner_id)
logging.info("domain=============: %s", domain)
po = self.env['purchase.order'].sudo().search([
('partner_id', '=', supplier.partner_id.id),
('company_id', '=', procurement.company_id.id), # 保证公司一致
('origin', 'like', procurement.origin), # 根据来源匹配
('state', '=', 'draft') # 状态为草稿
], limit=1)
logging.info("po=: %s", po)
if po:
po.write({'purchase_type': 'outsourcing'})
# # 首先调用父类的 _run_buy 方法,以保留原有逻辑
# super(StockRuleInherit, self)._run_buy(procurements)

View File

@@ -2,8 +2,8 @@
# from odoo import fields, models, api
# from odoo.exceptions import UserError
# from odoo.tools import str2bool
#
#
# class ResMrpRoutingWorkcenter(models.Model):
# _inherit = 'mrp.routing.workcenter'
# def init(self):

View File

@@ -3,12 +3,12 @@
# from odoo import fields, models, api
# from odoo.exceptions import UserError
# from odoo.tools import str2bool
#
#
# class SfProductionProcessParameter(models.Model):
# _inherit = 'sf.production.process.parameter'
#
#
# @api.model
# def create(self, vals):
# # if vals.get('code', '/') == '/' or vals.get('code', '/') is False:
@@ -26,7 +26,7 @@
# def create_service_product(self):
# service_categ = self.env.ref(
# 'sf_dlm.product_category_surface_technics_sf').sudo()
#
# product_name = f"{self.process_id.name}_{self.name}"
# product_id = self.env['product.template'].search(
# [("name", '=', product_name)])
@@ -48,7 +48,7 @@
# 'partner_id': res_partner.id,
# 'price': 1, })],
# })
#
# def create_work_center(self):
# production_process_parameter = self
# if not production_process_parameter.process_id:
@@ -70,7 +70,7 @@
# production_process_parameter.routing_id = routing_id.id
# else:
# production_process_parameter.routing_id = workcenter_id.id
#
# def init(self):
# super(SfProductionProcessParameter, self).init()
# # 在模块初始化时触发计算字段的更新

View File

@@ -95,9 +95,7 @@
<page string="加工参数">
<group>
<group string="模型">
<field name="blank_type" readonly="1"/>
<field name="blank_precision" readonly="1"/>
<label for="model_long" string="坯料尺寸[mm]"/>
<label for="model_long" string="尺寸[mm]"/>
<div class="o_address_format">
<label for="model_long" string="长"/>
<field name="model_long" class="o_address_zip"/>
@@ -106,7 +104,6 @@
<label for="model_height" string="高"/>
<field name="model_height" class="o_address_zip"/>
</div>
<field name="unit_number" readonly="1"/>
<field name="model_volume" string="体积[mm³]"/>
<field name="product_model_type_id" string="模型类型"/>
<field name="model_processing_panel" placeholder="例如R,U" string="加工面板"

View File

@@ -377,11 +377,7 @@ class Sf_Dashboard_Connect(http.Controller):
line_list_obj = request.env['sf.production.line'].sudo().search([('name', 'ilike', 'CNC')])
line_list = list(map(lambda x: x.name, line_list_obj))
# print('line_list: %s' % line_list)
res['LineList'] = ['业绩总览']
res['LineList'] += line_list
res['LineList'].append('人工线下加工中心')
# 增加“业绩总览”与“人工线下加工中心”
res['LineList'] = line_list
except Exception as e:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
@@ -405,55 +401,37 @@ class Sf_Dashboard_Connect(http.Controller):
try:
plan_obj = request.env['sf.production.plan'].sudo()
# production_obj = request.env['mrp.production'].sudo()
production_obj = request.env['mrp.production'].sudo()
work_order_obj = request.env['mrp.workorder'].sudo()
line_list = ast.literal_eval(kw['line_list'])
line_list_obj = request.env['sf.production.line'].sudo().search([('name', 'ilike', 'CNC')])
cnc_line_list = list(map(lambda x: x.name, line_list_obj))
# print('line_list: %s' % line_list)
for line in line_list:
if line == '业绩总览':
work_order_domain = [('routing_type', 'in', ['人工线下加工', 'CNC加工'])]
plan_domain = []
elif line == '人工线下加工中心':
work_order_domain = [('routing_type', '=', '人工线下加工')]
plan_domain = [('production_type', '=', '人工线下加工')]
else:
work_order_domain = [
('production_line_id.name', '=', line),
('routing_type', '=', 'CNC加工')
]
plan_domain = [('production_line_id.name', '=', line)]
# # 工单计划量
# plan_data_total_counts = production_obj.search_count(
# [('production_line_id.name', '=', line), ('state', 'not in', ['cancel']),
# ('active', '=', True)])
# 工单计划量切换为CNC工单
plan_data_total = work_order_obj.search(work_order_domain + [
('id', '!=', 8061),
('state', 'in', ['ready', 'progress', 'done'])
])
plan_data_total_counts = sum(plan_data_total.mapped('qty_production'))
plan_data_total_counts = work_order_obj.search_count(
[('production_line_id.name', '=', line), ('id', '!=', 8061),
('state', 'in', ['ready', 'progress', 'done']), ('routing_type', '=', 'CNC加工')])
# # 工单完成量
# plan_data_finish_counts = plan_obj.search_count(
# [('production_line_id.name', '=', line), ('state', 'in', ['finished'])])
# 工单完成量切换为CNC工单
plan_data_finish = work_order_obj.search(work_order_domain + [
('state', 'in', ['done'])
])
plan_data_finish_counts = sum(plan_data_finish.mapped('qty_produced'))
plan_data_finish_counts = work_order_obj.search_count(
[('production_line_id.name', '=', line),
('state', 'in', ['done']), ('routing_type', '=', 'CNC加工')])
# 超期完成量
# 搜索所有已经完成的工单
plan_data_overtime = work_order_obj.search(work_order_domain + [
('state', 'in', ['done'])
plan_data_overtime = work_order_obj.search([
('production_line_id.name', '=', line),
('state', 'in', ['done']),
('routing_type', '=', 'CNC加工')
])
# 使用 filtered 进行字段比较
@@ -462,38 +440,36 @@ class Sf_Dashboard_Connect(http.Controller):
)
# 获取数量
# plan_data_overtime_counts = len(plan_data_overtime_counts)
plan_data_overtime_counts = sum(plan_data_overtime_counts.mapped('qty_produced'))
plan_data_overtime_counts = len(plan_data_overtime_counts)
# 查找符合条件的生产计划记录
# plan_data = plan_obj.search(plan_domain)
plan_data = plan_obj.search([
('production_line_id.name', '=', line),
])
# 过滤出那些检测结果状态为 '返工' 或 '报废' 的记录
# faulty_plans = plan_data.filtered(lambda p: any(
# result.test_results in ['返工', '报废'] for result in p.production_id.detection_result_ids
# ))
faulty_plans = work_order_obj.search(work_order_domain + [
('state', 'in', ['scrap', 'rework'])
faulty_plans = request.env['quality.check'].sudo().search([
('operation_id.name', '=', 'CNC加工'),
('quality_state', 'in', ['fail'])
])
# 查找制造订单取消与归档的数量
# cancel_order_count = production_obj.search_count(
# [('production_line_id.name', '=', line), ('state', 'in', ['cancel']),
# ('active', '=', False)])
cancel_order_count = production_obj.search_count(
[('production_line_id.name', '=', line), ('state', 'in', ['cancel']),
('active', '=', False)])
# 计算符合条件的记录数量
# plan_data_fault_counts = len(faulty_plans) + cancel_order_count
# plan_data_fault_counts = len(faulty_plans)
plan_data_fault_counts = sum(faulty_plans.mapped('qty_produced'))
plan_data_fault_counts = len(faulty_plans)
# 工单返工数量
plan_data_rework = work_order_obj.search(work_order_domain + [
('state', 'in', ['rework'])
])
plan_data_rework_counts = sum(plan_data_rework.mapped('qty_produced'))
plan_data_rework_counts = plan_obj.search_count(
[('production_line_id.name', '=', line), ('production_id.state', 'in', ['rework'])])
# 工单完成率
finishe_rate = round(
@@ -503,9 +479,8 @@ class Sf_Dashboard_Connect(http.Controller):
plan_data_progress_deviation = plan_data_total_counts - plan_data_finish_counts - plan_data_fault_counts
# 完成记录
plan_data_finish_orders = plan_obj.search(plan_domain + [
('state', 'in', ['finished'])
])
plan_data_finish_orders = plan_obj.search(
[('production_line_id.name', '=', line), ('state', 'in', ['finished'])])
# # 检测量
# detection_nums = 0
@@ -559,25 +534,25 @@ class Sf_Dashboard_Connect(http.Controller):
delay_rate = round((delay_num / plan_data_finish_counts if plan_data_finish_counts > 0 else 0), 3)
on_time_rate = 1 - delay_rate
# if plan_data:
data = {
'plan_data_total_counts': plan_data_total_counts,
'plan_data_finish_counts': plan_data_finish_counts,
'plan_data_plan_counts': plan_data_total_counts,
'plan_data_fault_counts': plan_data_fault_counts,
'nopass_orders_counts': detection_data - len(pass_nums),
'finishe_rate': finishe_rate,
'plan_data_progress_deviation': plan_data_progress_deviation,
'plan_data_rework_counts': plan_data_rework_counts,
'on_time_rate': on_time_rate,
# 'detection_data': detection_data,
'detection_data': plan_data_finish_counts,
'pass_rate': (plan_data_finish_counts - plan_data_fault_counts) / plan_data_finish_counts,
'plan_data_overtime_counts': plan_data_overtime_counts,
'overtime_rate': plan_data_overtime_counts / plan_data_finish_counts
if plan_data_finish_counts > 0 else 0,
}
res['data'][line] = data
if plan_data:
data = {
'plan_data_total_counts': plan_data_total_counts,
'plan_data_finish_counts': plan_data_finish_counts,
'plan_data_plan_counts': plan_data_total_counts,
'plan_data_fault_counts': plan_data_fault_counts,
'nopass_orders_counts': detection_data - len(pass_nums),
'finishe_rate': finishe_rate,
'plan_data_progress_deviation': plan_data_progress_deviation,
'plan_data_rework_counts': plan_data_rework_counts,
'on_time_rate': on_time_rate,
# 'detection_data': detection_data,
'detection_data': plan_data_finish_counts,
'pass_rate': (plan_data_finish_counts - plan_data_fault_counts) / plan_data_finish_counts,
'plan_data_overtime_counts': plan_data_overtime_counts,
'overtime_rate': plan_data_overtime_counts / plan_data_finish_counts
if plan_data_finish_counts > 0 else 0,
}
res['data'][line] = data
return json.dumps(res) # 注意使用 json.dumps 而不是直接用 json.JSONEncoder().encode()
@@ -601,11 +576,8 @@ class Sf_Dashboard_Connect(http.Controller):
line_list = ast.literal_eval(kw['line_list'])
begin_time_str = kw['begin_time'].strip('"')
end_time_str = kw['end_time'].strip('"')
# 将时间减去8小时UTC+8转UTC
begin_time = (datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S') - timedelta(hours=8))
end_time = (datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S') - timedelta(hours=8))
# begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
# end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
# print('line_list: %s' % line_list)
print('kw', kw)
time_unit = kw.get('time_unit', 'day').strip('"') # 默认单位为天
@@ -636,43 +608,16 @@ class Sf_Dashboard_Connect(http.Controller):
date_list.append(current_date)
current_date += timedelta(days=1)
return date_list
if time_unit == 'hour':
for line in line_list:
date_field_name = 'date_finished' # 替换为你模型中的实际字段名
order_counts = []
# 计划量目前只能从mail.message中筛选出
plan_order_messages = request.env['mail.message'].sudo().search([
('model', '=', 'mrp.workorder'),
('create_date', '>=', begin_time.strftime('%Y-%m-%d %H:%M:%S')),
('create_date', '<=', end_time.strftime('%Y-%m-%d %H:%M:%S')),
('tracking_value_ids.field_desc', '=', '状态'),
('tracking_value_ids.new_value_char', '=', '就绪')
])
for line in line_list:
date_field_name = 'date_finished' # 替换为你模型中的实际字段名
order_counts = []
if line == '业绩总览':
work_order_domain = [('routing_type', 'in', ['人工线下加工', 'CNC加工'])]
elif line == '人工线下加工中心':
work_order_domain = [('routing_type', '=', '人工线下加工')]
else:
work_order_domain = [
('production_line_id.name', '=', line),
('routing_type', '=', 'CNC加工')
]
if time_unit == 'hour':
time_intervals = get_time_intervals(begin_time, end_time, time_unit)
print('============================= %s' % time_intervals)
time_count_dict = {}
plan_count_dict = {}
orders = request.env['mrp.workorder'].sudo().search(work_order_domain + [
('state', 'in', ['done']),
(date_field_name, '>=', begin_time.strftime('%Y-%m-%d %H:%M:%S')),
(date_field_name, '<=', end_time.strftime('%Y-%m-%d %H:%M:%S'))
])
for time_interval in time_intervals:
start_time, end_time = time_interval
@@ -684,106 +629,66 @@ class Sf_Dashboard_Connect(http.Controller):
# (date_field_name, '<=', end_time.strftime('%Y-%m-%d %H:%M:%S')) # 包括结束时间
# ])
interval_orders = orders.filtered(
lambda o: o[date_field_name] >= start_time
and o[date_field_name] <= end_time
)
orders = request.env['mrp.workorder'].sudo().search([
('routing_type', '=', 'CNC加工'), # 将第一个条件合并进来
('production_line_id.name', '=', line),
('state', 'in', ['done']),
(date_field_name, '>=', start_time.strftime('%Y-%m-%d %H:%M:%S')),
(date_field_name, '<=', end_time.strftime('%Y-%m-%d %H:%M:%S'))
])
# 使用小时和分钟作为键,确保每个小时的数据有独立的键
key = (start_time + timedelta(hours=8)).strftime('%H:%M:%S') # 只取小时:分钟:秒作为键
# time_count_dict[key] = len(orders)
time_count_dict[key] = sum(interval_orders.mapped('qty_produced'))
for time_interval in time_intervals:
start_time, end_time = time_interval
# orders = plan_obj.search([
# ('production_line_id.name', '=', line),
# ('state', 'in', ['done']),
# (date_field_name, '>=', start_time.strftime('%Y-%m-%d %H:%M:%S')),
# (date_field_name, '<=', end_time.strftime('%Y-%m-%d %H:%M:%S')) # 包括结束时间
# ])
interval_plan_orders = plan_order_messages.filtered(
lambda o: o.create_date >= start_time
and o.create_date <= end_time
)
interval_order_ids = set(interval_plan_orders.mapped('res_id'))
interval_orders = request.env['mrp.workorder'].sudo().browse(interval_order_ids)
if line == '业绩总览':
interval_orders = interval_orders.filtered(lambda o: o.routing_type in ['人工线下加工', 'CNC加工'])
elif line == '人工线下加工中心':
interval_orders = interval_orders.filtered(lambda o: o.routing_type == '人工线下加工')
else:
interval_orders = interval_orders.filtered(lambda o: o.routing_type == 'CNC加工' and o.production_line_id.name == line)
# 使用小时和分钟作为键,确保每个小时的数据有独立的键
key = (start_time + timedelta(hours=8)).strftime('%H:%M:%S') # 只取小时:分钟:秒作为键
# time_count_dict[key] = len(orders)
plan_count_dict[key] = sum(interval_orders.mapped('qty_production'))
key = start_time.strftime('%H:%M:%S') # 只取小时:分钟:秒作为键
time_count_dict[key] = len(orders)
# order_counts.append()
res['data'][line] = {
'finish_order_nums': time_count_dict,
'plan_order_nums': plan_count_dict
'plan_order_nums': 28
}
else:
return json.dumps(res)
for line in line_list:
date_field_name = 'date_finished' # 替换为你模型中的实际字段名
order_counts = []
date_list = get_date_list(begin_time, end_time)
if line == '业绩总览':
work_order_domain = [('routing_type', 'in', ['人工线下加工', 'CNC加工'])]
elif line == '人工线下加工中心':
work_order_domain = [('routing_type', '=', '人工线下加工')]
else:
work_order_domain = [
('production_line_id.name', '=', line),
('routing_type', '=', 'CNC加工')
]
for date in date_list:
next_day = date + timedelta(days=1)
orders = request.env['mrp.workorder'].sudo().search(
[('production_id.production_line_id.name', '=', line), ('state', 'in', ['done']),
('routing_type', '=', 'CNC加工'),
(date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')),
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00'))
])
date_list = get_date_list(begin_time, end_time)
rework_orders = request.env['mrp.workorder'].sudo().search(
[('production_id.production_line_id.name', '=', line), ('state', 'in', ['rework']),
('routing_type', '=', 'CNC加工'),
(date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')),
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00'))
])
not_passed_orders = request.env['mrp.workorder'].sudo().search(
[('production_id.production_line_id.name', '=', line), ('state', 'in', ['scrap', 'cancel']),
('routing_type', '=', 'CNC加工'),
(date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')),
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00'))
])
order_counts.append({
'date': date.strftime('%Y-%m-%d'),
'order_count': len(orders),
'rework_orders': len(rework_orders),
'not_passed_orders': len(not_passed_orders)
})
# 外面包一层没什么是包一层不能解决的包一层就能区分了类似于包一层div
# 外面包一层的好处是,可以把多个数据结构打包在一起,方便前端处理
for date in date_list:
next_day = date + timedelta(days=1)
orders = request.env['mrp.workorder'].sudo().search(work_order_domain + [
('state', 'in', ['done']),
(date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')),
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00'))
])
# date_list_dict = {line: order_counts}
rework_orders = request.env['mrp.workorder'].sudo().search(work_order_domain + [
('state', 'in', ['rework']),
(date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')),
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00'))
])
not_passed_orders = request.env['mrp.workorder'].sudo().search(work_order_domain + [
('state', 'in', ['scrap', 'cancel']),
(date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')),
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00'))
])
order_counts.append({
'date': date.strftime('%Y-%m-%d'),
'order_count': sum(orders.mapped('qty_produced')),
'rework_orders': sum(rework_orders.mapped('qty_produced')),
'not_passed_orders': sum(not_passed_orders.mapped('qty_produced'))
})
# 外面包一层没什么是包一层不能解决的包一层就能区分了类似于包一层div
# 外面包一层的好处是,可以把多个数据结构打包在一起,方便前端处理
# date_list_dict = {line: order_counts}
res['data'][line] = order_counts
res['data'][line] = order_counts
return json.dumps(res)
# 实时产量
@http.route('/api/RealTimeProduct', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
def RealTimeProduct(self, **kw):
"""
获取实时产量(作废)
获取实时产量
:param kw:
:return:
"""
@@ -806,21 +711,6 @@ class Sf_Dashboard_Connect(http.Controller):
# 当班计划量
for line in line_list:
if line == '业绩总览':
work_order_domain = [('routing_type', 'in', ['人工线下加工', 'CNC加工'])]
plan_domain = []
elif line == '人工线下加工中心':
work_order_domain = [('routing_type', '=', '人工线下加工')]
plan_domain = [('production_type', '=', '人工线下加工')]
else:
work_order_domain = [
('production_line_id.name', '=', line),
('routing_type', '=', 'CNC加工')
]
plan_domain = [('production_line_id.name', '=', line)]
plan_order_nums = plan_obj.search_count(
[('production_line_id.name', '=', line), ('state', 'not in', ['draft']),
('date_planned_start', '>=', begin_time),
@@ -862,72 +752,42 @@ class Sf_Dashboard_Connect(http.Controller):
:param kw:
:return:
"""
# res = {'status': 1, 'message': '成功', 'not_done_data': [], 'done_data': []}
res = {'status': 1, 'message': '成功', 'data': {}}
# 解决产品名称取到英文的问题
request.update_context(lang='zh_CN')
plan_obj = request.env['sf.production.plan'].sudo()
work_order_obj = request.env['mrp.workorder'].sudo()
# 获取mrp.workorder的state字段的selection内容
state_dict = dict(request.env['mrp.workorder'].sudo()._fields['state'].selection)
line_list = ast.literal_eval(kw['line_list'])
begin_time_str = kw['begin_time'].strip('"')
end_time_str = kw['end_time'].strip('"')
begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
# print('line_list: %s' % line_list)
not_done_data = []
done_data = []
final_data = {}
# 获取当前时间并计算24小时前的时间
current_time = datetime.now()
time_48_hours_ago = current_time - timedelta(hours=48)
# # 计划量目前只能从mail.message中筛选出
# plan_order_messages = request.env['mail.message'].sudo().search([
# ('model', '=', 'mrp.workorder'),
# ('create_date', '>=', time_48_hours_ago.strftime('%Y-%m-%d %H:%M:%S')),
# ('tracking_value_ids.field_desc', '=', '状态'),
# ('tracking_value_ids.new_value_char', 'in', ['就绪', '生产中'])
# ])
for line in line_list:
not_done_data = []
done_data = []
not_done_index = 1
done_index = 1
if line == '业绩总览':
work_order_domain = [('routing_type', 'in', ['人工线下加工', 'CNC加工'])]
elif line == '人工线下加工中心':
work_order_domain = [('routing_type', '=', '人工线下加工')]
else:
work_order_domain = [
('production_line_id.name', '=', line),
('routing_type', '=', 'CNC加工')
]
# 未完成订单
# not_done_orders = plan_obj.search(
# [('production_line_id.name', '=', line), ('state', 'not in', ['finished']),
# ('production_id.state', 'not in', ['cancel', 'done']), ('active', '=', True)
# ])
not_done_orders = work_order_obj.search(work_order_domain + [
('state', 'in', ['ready', 'progress']),
('date_planned_start', '>=', time_48_hours_ago),
('date_planned_start', '<=', current_time)
], order='id asc'
)
not_done_orders = request.env['mrp.workorder'].sudo().search(
[('production_line_id.name', '=', line), ('state', 'in', ['ready', 'progress']),
('routing_type', '=', 'CNC加工')
])
# 完成订单
# 获取当前时间并计算24小时前的时间
# current_time = datetime.now()
# time_24_hours_ago = current_time - timedelta(hours=24)
current_time = datetime.now()
time_24_hours_ago = current_time - timedelta(hours=24)
finish_orders = work_order_obj.search(work_order_domain + [
('state', 'in', ['done']),
('production_id.state', 'not in', ['cancel']),
('date_finished', '>=', time_48_hours_ago)
], order='id asc')
# logging.info('完成订单: %s' % finish_orders)
finish_orders = plan_obj.search([
('production_line_id.name', '=', line), ('state', 'in', ['finished']),
('production_id.state', 'not in', ['cancel']), ('active', '=', True),
('actual_end_time', '>=', time_24_hours_ago)
])
# print(finish_orders)
# 获取所有未完成订单的ID列表
order_ids = [order.id for order in not_done_orders]
@@ -935,14 +795,14 @@ class Sf_Dashboard_Connect(http.Controller):
finish_order_ids = [order.id for order in finish_orders]
# 对ID进行排序
# sorted_order_ids = sorted(order_ids)
sorted_order_ids = sorted(order_ids)
# finish_sorted_order_ids = sorted(finish_order_ids)
finish_sorted_order_ids = sorted(finish_order_ids)
# 创建ID与序号的对应关系
# id_to_sequence = {order_id: index + 1 for index, order_id in enumerate(sorted_order_ids)}
id_to_sequence = {order_id: index + 1 for index, order_id in enumerate(sorted_order_ids)}
# finish_id_to_sequence = {order_id: index + 1 for index, order_id in enumerate(finish_sorted_order_ids)}
finish_id_to_sequence = {order_id: index + 1 for index, order_id in enumerate(finish_sorted_order_ids)}
# # 输出结果或进一步处理
# for order_id, sequence in id_to_sequence.items():
@@ -963,21 +823,30 @@ class Sf_Dashboard_Connect(http.Controller):
material_match = re.search(material_pattern, blank_name)
material = material_match.group(1) if material_match else 'No match found'
state_dict = {
'draft': '待排程',
'done': '已排程',
'processing': '生产中',
'finished': '已完成',
'ready': '待加工',
'progress': '生产中',
}
line_dict = {
'sequence': not_done_index,
'sequence': id_to_sequence[order.id],
'workorder_name': order.production_id.name,
'blank_name': blank_name,
'material': material,
'dimensions': dimensions,
'order_qty': order.qty_production,
'order_qty': 1,
'state': state_dict[order.state],
}
not_done_data.append(line_dict)
not_done_index += 1
for finish_order in finish_orders:
if not finish_order.actual_end_time:
continue
blank_name = ''
try:
blank_name = finish_order.production_id.move_raw_ids[0].product_id.name
@@ -992,18 +861,17 @@ class Sf_Dashboard_Connect(http.Controller):
material = material_match.group(1) if material_match else 'No match found'
line_dict = {
'sequence': done_index,
'workorder_name': finish_order.production_id.name,
'sequence': finish_id_to_sequence[finish_order.id],
'workorder_name': finish_order.name,
'blank_name': blank_name,
'material': material,
'dimensions': dimensions,
'order_qty': finish_order.qty_produced,
'finish_time': finish_order.date_finished.strftime(
'%Y-%m-%d %H:%M:%S') if finish_order.date_finished else ' '
'order_qty': finish_order.product_qty,
'finish_time': finish_order.actual_end_time.strftime(
'%Y-%m-%d %H:%M:%S') if finish_order.actual_end_time else ' '
}
done_data.append(line_dict)
done_index += 1
# 开始包一层
res['data'][line] = {'not_done_data': not_done_data, 'done_data': done_data}

View File

@@ -48,7 +48,6 @@
'views/mrp_workorder_batch_replan.xml',
'views/purchase_order_view.xml',
'views/product_template_views.xml',
# 'views/stock_warehouse_orderpoint.xml',
],
'assets': {

View File

@@ -6,14 +6,12 @@ from datetime import datetime
from odoo.addons.sf_manufacturing.models.agv_scheduling import RepeatTaskException
from odoo import http
from odoo.http import request
from odoo.addons.sf_base.decorators.api_log import api_log
class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/GetWoInfo', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('获取工单', requester='中控系统')
def get_Work_Info(self, **kw):
"""
自动化传递工单号获取工单信息
@@ -56,7 +54,6 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/GetShiftPlan', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('获取日计划', requester='中控系统')
def get_ShiftPlan(self, **kw):
"""
自动化每天获取机台日计划
@@ -110,7 +107,6 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/QcCheck', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('工件预调(前置三元检测)', requester='中控系统')
def get_qcCheck(self, **kw):
"""
工件预调(前置三元检测)
@@ -153,7 +149,6 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/FeedBackStart', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('工单开始', requester='中控系统')
def button_Work_START(self, **kw):
"""
工单任务开始
@@ -203,7 +198,6 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/FeedBackEnd', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('工单结束', requester='中控系统')
def button_Work_End(self, **kw):
"""
工单任务结束
@@ -255,7 +249,6 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/PartQualityInspect', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('零件检测(后置三元检测)', requester='中控系统')
def PartQualityInspect(self, **kw):
"""
零件质检
@@ -302,7 +295,6 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/CMMProgDolod', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('CMM测量程序下载', requester='中控系统')
def CMMProgDolod(self, **kw):
"""
中控系统传递RFID编号给MES获取测量程序文件。Ftp下载文件
@@ -343,7 +335,6 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/NCProgDolod', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('CAM加工程序下载', requester='中控系统')
def NCProgDolod(self, **kw):
"""
中控系统传递RFID编号给MES获取程序单及程序文件。Ftp下载文件
@@ -385,7 +376,6 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/LocationChange', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('库位变更', requester='中控系统')
def LocationChange(self, **kw):
"""
库位变更
@@ -445,7 +435,32 @@ class Manufacturing_Connect(http.Controller):
shelfinfo = list(filter(lambda x: x.get('DeviceId') == DeciveId,
request.env['sf.shelf.location'].sudo().get_sf_shelf_location_info(
DeciveId)))
request.env['sf.shelf.location.datasync'].sudo().set_shelf_location(shelfinfo)
total_data = request.env['sf.shelf.location.datasync'].sudo().get_total_data()
for item in shelfinfo:
logging.info('货架已获取信息:%s' % item)
shelf_barcode = request.env['sf.shelf.location.datasync'].sudo().find_our_code(
total_data, item['Postion'])
location_id = request.env['sf.shelf.location'].sudo().search(
[('barcode', '=', shelf_barcode)],
limit=1)
if location_id:
# 如果是线边刀库信息,则对功能刀具移动生成记录
if 'Tool' in item['Postion']:
tool = request.env['sf.functional.cutting.tool.entity'].sudo().search(
[('rfid', '=', item['RfidCode']), ('functional_tool_status', '!=', '已拆除')])
tool.sudo().tool_in_out_stock_location(location_id)
if tool:
location_id.product_sn_id = tool.barcode_id.id
# 修改功能刀具状态
if item.get('State') == '报警':
if tool.functional_tool_status != item.get('State'):
tool.write({
'functional_tool_status': item['State']
})
else:
location_id.product_sn_id = False
if item['RfidCode']:
logging.info('Rfid为【%s】的功能刀具在系统中不存在!' % item['RfidCode'])
else:
equipment_id = request.env['maintenance.equipment'].sudo().search([('name', '=', DeciveId)])
if equipment_id:
@@ -465,7 +480,6 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/AGVToProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('AGV运送上产线', requester='中控系统')
def AGVToProduct(self, **kw):
"""
AGV运送上产线完成
@@ -538,7 +552,6 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/AGVDownProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('AGV运送下产线', requester='中控系统')
def AGVDownProduct(self, **kw):
"""
MES调度AGV搬运零件AGV托盘到产线接驳站。

View File

@@ -27,7 +27,7 @@ class JikimoSaleRoutePicking(Sf_Bf_Connect):
bfm_process_order_list = json.loads(kw['bfm_process_order_list'])
order_id = request.env['sale.order'].with_user(request.env.ref("base.user_admin")).sale_order_create(
company_id, kw['delivery_name'], kw['delivery_telephone'], kw['delivery_address'],
kw['delivery_end_date'], kw['payments_way'], kw['pay_way'], kw['order_number'], kw['remark'], state='draft',
kw['delivery_end_date'], kw['payments_way'], kw['pay_way'], kw['order_number'], state='draft',
model_display_version=kw.get('model_display_version'))
i = 1
# 给sale_order的default_code字段赋值
@@ -45,8 +45,6 @@ class JikimoSaleRoutePicking(Sf_Bf_Connect):
product.product_tmpl_id.is_customer_provided = True if item['embryo_redundancy_id'] else False
order_id.with_user(request.env.ref("base.user_admin")).sale_order_create_line(product, item)
i += 1
# BFM 内部下单 新增合同等内容补充
order_id.write_sale_documents(kw)
res['factory_order_no'] = order_id.name
order_id.confirm_to_supply_method()
except Exception as e:

View File

@@ -18,5 +18,3 @@ from . import quick_easy_order
from . import purchase_order
from . import quality_check
from . import purchase_request_line
from . import bom
# from . import stock_warehouse_orderpoint

View File

@@ -1,16 +0,0 @@
from odoo import models
from odoo.osv.expression import AND
class MrpBom(models.Model):
_inherit = 'mrp.bom'
def _bom_subcontract_find(self, product, picking_type=None, company_id=False, bom_type='subcontract', subcontractor=False):
domain = self._bom_find_domain(product, picking_type=picking_type, company_id=company_id, bom_type=bom_type)
if self.env.context.get('stock_picking') == 'outsourcing':
return self.search(domain, order='sequence, product_id, id', limit=1)
if subcontractor:
domain = AND([domain, [('subcontractor_ids', 'parent_of', subcontractor.ids)]])
return self.search(domain, order='sequence, product_id, id', limit=1)
else:
return self.env['mrp.bom']

View File

@@ -279,12 +279,10 @@ class MrpProduction(models.Model):
production_id.part_name = production_id.product_id.part_name
elif production_id.product_id.categ_id.type == '坯料':
product_name = ''
match = re.search(r'(S\d{5}-\d+)', production_id.product_id.name)
match = re.search(r'(S\d{5}-\d)', production_id.product_id.name)
# 如果匹配成功,提取结果
if match:
product_name = match.group(0)
else:
product_name = production_id.product_id.name
if production_id.sale_order_id:
sale_order = production_id.sale_order_id
else:
@@ -638,17 +636,13 @@ class MrpProduction(models.Model):
# 增加触发时间参数
def update_programming_state(self, trigger_time=None, reprogramming_reason=None):
try:
reason = ""
manufacturing_type = None
if self.is_scrap:
manufacturing_type = 'scrap'
reason = "报废"
elif self.tool_state == '2':
manufacturing_type = 'invalid_tool_rework'
reason = "无效功能刀具"
elif self.is_rework:
manufacturing_type = 'rework'
reason = "返工"
res = {'programming_no': self.programming_no,
'manufacturing_type': manufacturing_type,
'trigger_time': trigger_time,
@@ -663,16 +657,6 @@ class MrpProduction(models.Model):
result = json.loads(ret['result'])
logging.info('update_programming_state-ret:%s' % result)
if result['status'] == 1:
self.programming_record_ids.create({
'number': len(self.programming_record_ids) + 1,
'production_id': self.id,
'reason': reason,
'programming_method': False,
'current_programming_count': False,
'target_production_id': False,
'apply_time': fields.Datetime.now(),
'send_time': False,
})
self.write({'is_rework': True})
else:
raise UserError(ret['message'])
@@ -803,17 +787,6 @@ class MrpProduction(models.Model):
if ret['status'] == 1:
self.write(
{'programming_no': ret['programming_no'], 'programming_state': '编程中', 'work_state': '编程中'})
# 生成编程记录
self.programming_record_ids.create({
'number': len(self.programming_record_ids) + 1,
'production_id': self.id,
'reason': '首次下发',
'programming_method': False,
'current_programming_count': False,
'target_production_id': False,
'apply_time': fields.Datetime.now(),
'send_time': False,
})
else:
raise UserError(ret['message'])
except Exception as e:
@@ -955,13 +928,12 @@ class MrpProduction(models.Model):
# 'sf_stock.stock_route_process_outsourcing').id)]
# for product_id, request_line_list in grouped_purchase_request_line_sorted_list.items():
# cur_request_line = request_line_list[0]
# # cur_request_line['product_qty'] = cur_request_line['product_qty']
# cur_request_line['product_qty'] = len(request_line_list)
# cur_request_line['request_id'] = pr.id
# cur_request_line['origin'] = ", ".join({item['production_name'] for item in request_line_list if item.get('production_name')})
# cur_request_line.pop('group_id', None)
# cur_request_line.pop('production_name', None)
# self.env["purchase.request.line"].create(cur_request_line)
# pr.button_approved()
# 外协出入库单处理
def get_subcontract_pick_purchase(self):
@@ -1709,7 +1681,6 @@ class MrpProduction(models.Model):
vals['procurement_group_id'] = product_group_id[product_id.id]
else:
vals['procurement_group_id'] = is_custemer_group_id[key]
return super(MrpProduction, self).create(vals_list)
@api.depends('procurement_group_id.stock_move_ids.created_purchase_line_id.order_id',
@@ -1800,6 +1771,7 @@ class MrpProduction(models.Model):
"""
检查前置条件:制造订单【状态】=“待排程、待加工”,制造订单的【编程状态】=“已编程”。
"""
print('申请编程')
if len(self) > 1:
raise UserError('仅支持选择单个制造订单进行编程申请,请重新选择')
for production in self:
@@ -1868,7 +1840,6 @@ class sf_programming_record(models.Model):
target_production_id = fields.Char('目标制造单号')
apply_time = fields.Datetime('申请时间')
send_time = fields.Datetime('下发时间')
apply_uid = fields.Many2one('res.users', '申请人', default=lambda self: self.env.user)
class sf_detection_result(models.Model):

View File

@@ -71,7 +71,7 @@ class ResMrpWorkOrder(models.Model):
tracking=True)
back_button_display = fields.Boolean(default=False, compute='_compute_back_button_display', store=True)
# pr_mp_count = fields.Integer('采购申请单数量', compute='_compute_pr_mp_count', store=True)
#
# @api.depends('state')
# def _compute_pr_mp_count(self):
# for item in self:
@@ -85,7 +85,6 @@ class ResMrpWorkOrder(models.Model):
# item.pr_mp_count = len(pr_ids)
# else:
# item.pr_mp_count = 0
@api.depends('state')
def _compute_back_button_display(self):
for record in self:
@@ -130,14 +129,8 @@ class ResMrpWorkOrder(models.Model):
record.back_button_display = False
else:
next_workorder = sorted_workorders[position + 1]
# 持续获取下一个工单,直到找到一个不是返工的工单
while next_workorder and next_workorder.state == 'rework':
position += 1
if position + 1 < len(sorted_workorders):
next_workorder = sorted_workorders[position + 1]
else:
next_workorder = None
if next_workorder and (next_workorder.state == 'ready' or (
next_state = next_workorder.state
if (next_state == 'ready' or (
next_workorder.state == 'waiting' and next_workorder.is_subcontract)) and cur_workorder.state == 'done':
record.back_button_display = True
else:
@@ -227,30 +220,22 @@ class ResMrpWorkOrder(models.Model):
# finish_move.move_dest_ids.move_line_ids.reserved_uom_qty = 0
else:
next_workorder = sorted_workorders[position + 1]
# 持续获取下一个工单,直到找到一个不是返工的工单
while next_workorder and next_workorder.state == 'rework':
position += 1
if position + 1 < len(sorted_workorders):
next_workorder = sorted_workorders[position + 1]
next_state = next_workorder.state
if next_state not in ['pending', 'waiting', 'ready']:
raise UserError('下工序已经开始,无法回退')
if next_workorder.is_subcontract:
next_workorder.picking_ids.write({'state': 'waiting'})
next_workorder.state = 'pending'
self.time_ids.date_end = None
cur_workorder.state = 'progress'
cur_workorder.production_id.state = 'progress'
quality_check = self.env['quality.check'].search(
[('workorder_id', '=', self.id)])
for check_order in quality_check:
if check_order.point_id.is_inspect:
check_order.quality_state = 'waiting'
else:
next_workorder = None
if next_workorder:
next_state = next_workorder.state
if next_state not in ['pending', 'waiting', 'ready']:
raise UserError('下工序已经开始,无法回退')
if next_workorder.is_subcontract:
next_workorder.picking_ids.write({'state': 'waiting'})
next_workorder.state = 'pending'
self.time_ids.date_end = None
cur_workorder.state = 'progress'
cur_workorder.production_id.state = 'progress'
quality_check = self.env['quality.check'].search(
[('workorder_id', '=', self.id)])
for check_order in quality_check:
if check_order.point_id.is_inspect:
check_order.quality_state = 'waiting'
else:
check_order.quality_state = 'none'
check_order.quality_state = 'none'
def _compute_working_users(self):
super()._compute_working_users()
@@ -334,7 +319,6 @@ 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']
@@ -455,6 +439,7 @@ class ResMrpWorkOrder(models.Model):
action['context'] = dict(self._context, default_origin=self.name)
return action
@api.depends('state', 'production_id.name')
def _compute_surface_technics_purchase_ids(self):
for order in self:
if order.routing_type == '表面工艺' and order.state not in ['cancel']:
@@ -500,7 +485,6 @@ class ResMrpWorkOrder(models.Model):
# 'view_mode': 'tree,form',
# })
# return action
def action_view_surface_technics_purchase(self):
self.ensure_one()
# if self.routing_type == '表面工艺':
@@ -529,8 +513,7 @@ class ResMrpWorkOrder(models.Model):
return result
def _get_surface_technics_purchase_ids(self):
domain = [('origin', 'like', '%' + self.production_id.name + '%'), ('purchase_type', '=', 'consignment'),
('state', '!=', 'cancel')]
domain = [('origin', 'like', '%' + self.production_id.name + '%'), ('purchase_type', '=', 'consignment'), ('state', '!=', 'cancel')]
# domain = [('origin', 'like', '%' + self.production_id.name + '%'), ('purchase_type', '=', 'consignment')]
# domain = [('group_id', '=', self.production_id.procurement_group_id.id), ('purchase_type', '=', 'consignment')]
purchase_orders = self.env['purchase.order'].search(domain, order='id desc')
@@ -753,25 +736,21 @@ class ResMrpWorkOrder(models.Model):
# self.workpiece_delivery_ids[0].write({'rfid_code': self.rfid_code})
def get_plan_workorder(self, production_line):
tomorrow = (date.today() + timedelta(days=1)).strftime("%Y-%m-%d")
tomorrow_start = f"{tomorrow} 00:00:00"
tomorrow_end = f"{tomorrow} 23:59:59"
tomorrow = (date.today() + timedelta(days=+1)).strftime("%Y-%m-%d")
tomorrow_start = tomorrow + ' 00:00:00'
tomorrow_end = tomorrow + ' 23:59:59'
logging.info('tomorrow:%s' % tomorrow)
sql = """
SELECT *
FROM mrp_workorder
WHERE state!='rework'
AND (date_planned_start + interval '8 hours') >= %s::timestamp
AND (date_planned_finished + interval '8 hours') <= %s::timestamp
to_char(date_planned_start::timestamp + '8 hour','YYYY-MM-DD HH:mm:SS')>= %s
AND to_char(date_planned_finished::timestamp + '8 hour','YYYY-MM-DD HH:mm:SS')<= %s
"""
params = [tomorrow_start, tomorrow_end]
if production_line:
line = self.env['sf.production.line'].search(
[('name', '=', production_line)], limit=1)
if not line:
raise ValueError(f"生产线'{production_line}'不存在")
sql += "AND production_line_id = %s"
params.append(line.id)
params.append(production_line)
self.env.cr.execute(sql, params)
ids = [t[0] for t in self.env.cr.fetchall()]
return [('id', 'in', ids)]
@@ -1264,13 +1243,6 @@ class ResMrpWorkOrder(models.Model):
}]
return workorders_values_str
# def check_lot_exists(self, picking_id, lot_id):
# return bool(
# picking_id.move_ids.move_line_ids.filtered(
# lambda line: line.lot_id.id == lot_id
# )
# )
def _process_compute_state(self):
sorted_workorders = sorted(self, key=lambda x: x.sequence)
for workorder in sorted_workorders:
@@ -1292,17 +1264,10 @@ class ResMrpWorkOrder(models.Model):
workorder.state = 'pending'
continue
# ================= 如果制造订单制造类型为【人工线下加工】==========================
# lot_id = workorder.production_id.move_raw_ids.move_line_ids.lot_id
# picking_ids = workorder.production_id.picking_ids.filtered(
# lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前')
# exists = any(
# move_line.lot_id == lot_id
# for picking in picking_ids
# for move in picking.move_ids
# for move_line in move.move_line_ids
# )
if (workorder.production_id.production_type == '人工线下加工'
and workorder.production_id.schedule_state == '已排'):
and workorder.production_id.schedule_state == '已排'
and len(workorder.production_id.picking_ids.filtered(
lambda w: w.state not in ['done', 'cancel'])) == 0):
# and workorder.production_id.programming_state == '已编程'
if workorder.is_subcontract is True:
if workorder.production_id.state == 'rework':
@@ -1311,9 +1276,6 @@ class ResMrpWorkOrder(models.Model):
purchase_orders_id = self._get_surface_technics_purchase_ids()
if purchase_orders_id.state == 'purchase':
workorder.state = 'ready'
# picking_id = workorder.production_id.picking_ids.filtered(
# lambda wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区')
# move_out = picking_id.move_ids
move_out = workorder.move_subcontract_workorder_ids[1]
for mo in move_out:
if mo.state != 'done':
@@ -1354,10 +1316,6 @@ class ResMrpWorkOrder(models.Model):
if purchase_orders_id.state == 'purchase':
workorder.state = 'ready'
move_out = workorder.move_subcontract_workorder_ids[1]
# picking_id = workorder.production_id.picking_ids.filtered(
# lambda
# wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区')
# move_out = picking_id.move_ids
for mo in move_out:
if mo.state != 'done':
mo.write({'state': 'assigned', 'production_id': False})
@@ -1367,6 +1325,7 @@ class ResMrpWorkOrder(models.Model):
else:
workorder.state = 'waiting'
@api.depends('production_availability', 'blocked_by_workorder_ids', 'blocked_by_workorder_ids.state',
'production_id.tool_state', 'production_id.schedule_state', 'sequence',
'production_id.programming_state')
@@ -1398,8 +1357,7 @@ class ResMrpWorkOrder(models.Model):
# 判断是否有坯料的序列号信息
boolean = False
if self.production_id.move_raw_ids:
if self.production_id.move_raw_ids[0].product_id.categ_type == '坯料' and \
self.production_id.move_raw_ids[0].product_id.tracking == 'serial':
if self.production_id.move_raw_ids[0].product_id.categ_type == '坯料' and self.production_id.move_raw_ids[0].product_id.tracking == 'serial':
if self.production_id.move_raw_ids[0].move_line_ids:
if self.production_id.move_raw_ids[0].move_line_ids[0].lot_id.name:
boolean = True
@@ -1432,10 +1390,6 @@ class ResMrpWorkOrder(models.Model):
if self.routing_type == '表面工艺':
if self.is_subcontract is True:
move_out = self.move_subcontract_workorder_ids[1]
# picking_id = self.production_id.picking_ids.filtered(
# lambda wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区')
# move_out = picking_id.move_ids
# move_out = self.move_subcontract_workorder_ids[1]
# move_out = self.env['stock.move'].search(
# [('location_id', '=', self.env['stock.location'].search(
# [('barcode', 'ilike', 'WH-PREPRODUCTION')]).id),
@@ -1601,17 +1555,25 @@ class ResMrpWorkOrder(models.Model):
len(done_workorder) == len(
record.production_id.workorder_ids.filtered(lambda wo: wo.state != 'cancel'))):
is_production_id = True
if record.routing_type in ['解除装夹'] or (
record.is_rework is True and record.routing_type in ['装夹预调']):
for workorder in record.production_id.workorder_ids:
if workorder.processing_panel == record.processing_panel:
rfid_code = workorder.rfid_code
if record.is_rework is not True:
workorder.write({'rfid_code_old': rfid_code, 'rfid_code': False})
elif workorder.routing_type != '装夹预调' and workorder.state != 'rework':
workorder.write({'rfid_code_old': False, 'rfid_code': False})
elif workorder.routing_type == '装夹预调' and workorder.state != 'rework':
workorder.write({'rfid_code_old': rfid_code, 'rfid_code': False})
self.env['stock.lot'].sudo().search([('rfid', '=', rfid_code)]).write(
{'tool_material_status': '可用'})
if workorder.rfid_code:
raise ValidationError(f'{workorder.name}】工单解绑失败,请重新点击完成按钮!!!')
# workorder.rfid_code_old = rfid_code
# workorder.rfid_code = False
logging.info('workorder.rfid_code:%s' % workorder.rfid_code)
if record.routing_type in ['解除装夹']:
rfid_code = record.rfid_code
work_ids = record.production_id.workorder_ids.filtered(
lambda wo: wo.processing_panel == record.processing_panel and wo.state != 'rework')
work_ids.write({'rfid_code_old': rfid_code, 'rfid_code': False})
self.env['stock.lot'].sudo().search([('rfid', '=', rfid_code)]).write(
{'tool_material_status': '可用'})
if any(wo.rfid_code for wo in work_ids):
raise ValidationError(f'{record.name}】工单解绑失败,请重新点击完成按钮!!!')
logging.info('work_ids.rfid_code:%s' % [wo.rfid_code for wo in work_ids])
if is_production_id is True:
logging.info('product_qty:%s' % record.production_id.product_qty)
for move_raw_id in record.production_id.move_raw_ids:
@@ -1846,7 +1808,7 @@ class ResMrpWorkOrder(models.Model):
orderby=orderby,
lazy=lazy
)
model_id = fields.Char('模型ID', related='production_id.model_id')

View File

@@ -10,8 +10,8 @@ from odoo.exceptions import ValidationError, UserError
from odoo.modules import get_resource_path
# from OCC.Extend.DataExchange import read_step_file
# from OCC.Extend.DataExchange import write_stl_file
from OCC.Extend.DataExchange import read_step_file
from OCC.Extend.DataExchange import write_stl_file
class ResProductMo(models.Model):
@@ -26,16 +26,13 @@ class ResProductMo(models.Model):
model_file = fields.Binary('模型文件')
categ_type = fields.Selection(string='产品的类别', related='categ_id.type', store=True)
model_name = fields.Char('模型名称')
blank_type = fields.Selection([('圆料', '圆料'), ('方料', '方料')], string='坯料分类')
blank_precision = fields.Selection([('精坯', '精坯'), ('粗坯', '粗坯')], string='坯料类型')
model_long = fields.Float('模型长(mm)', digits=(16, 3))
model_width = fields.Float('模型宽(mm)', digits=(16, 3))
model_height = fields.Float('模型高(mm)', digits=(16, 3))
unit_number = fields.Float('单件用量', digits=(16, 3), default=1)
model_volume = fields.Float('模型体积(m³)')
model_area = fields.Float('模型表面积(m²)')
model_machining_precision = fields.Selection(selection=_get_machining_precision, string='加工精度')
model_processing_panel = fields.Char('模型加工面板', default='')
model_processing_panel = fields.Char('模型加工面板')
model_remark = fields.Char('模型备注说明')
length = fields.Float('长(mm)', digits=(16, 3))
width = fields.Float('宽(mm)', digits=(16, 3))
@@ -798,12 +795,10 @@ class ResProductMo(models.Model):
for record in self:
if record.categ_id.name == '坯料':
product_name = ''
match = re.search(r'(S\d{5}-\d+)', record.name)
match = re.search(r'(S\d{5}-\d)', record.name)
# 如果匹配成功,提取结果
if match:
product_name = match.group(0)
else:
product_name = record.name
sale_order_name = ''
match_sale = re.search(r'S(\d+)', record.name)
if match_sale:
@@ -895,25 +890,16 @@ class ResProductMo(models.Model):
embryo_redundancy_id = item.get('embryo_redundancy')
if not embryo_redundancy_id:
raise UserError('请先配置模型类型内的坯料冗余')
product_name = self.generate_product_name(order_id, item, i)
# 判断参数中是否包含 坯料尺寸(长、宽、高)
blank_bool = any(value is not None and value != 0 for value in (
item.get('blank_length'), item.get('blank_width'), item.get('blank_height'))) if all(
key in item for key in ('blank_length', 'blank_width', 'blank_height')) else False
vals = {
'name': product_name,
'blank_type': item.get('blank_type'),
'blank_precision': item.get('blank_precision'),
'model_long': item.get('blank_length') if blank_bool else self.format_float(item['model_long'] + embryo_redundancy_id.long),
'model_width': item.get('blank_width') if blank_bool else self.format_float(item['model_width'] + embryo_redundancy_id.width),
'model_height': item.get('blank_height') if blank_bool else self.format_float(item['model_height'] + embryo_redundancy_id.height),
'unit_number': item.get('unit_number'),
'model_volume': self.format_float(((item['model_long'] + embryo_redundancy_id.long) *
(item['model_width'] + embryo_redundancy_id.width) *
(item['model_height'] + embryo_redundancy_id.height))) if not blank_bool else (
item.get('blank_length') * item.get('blank_width') * item.get('blank_height')),
'name': '%s-%s-%s' % ('P', order_id.name, i),
'model_long': self.format_float(item['model_long'] + embryo_redundancy_id.long),
'model_width': self.format_float(item['model_width'] + embryo_redundancy_id.width),
'model_height': self.format_float(item['model_height'] + embryo_redundancy_id.height),
'model_volume': self.format_float((item['model_long'] + embryo_redundancy_id.long) * (
item['model_width'] + embryo_redundancy_id.width) * (
item['model_height'] + embryo_redundancy_id.height)),
'product_model_type_id': model_type.id,
'model_processing_panel': item['processing_panel_detail'] if item['processing_panel_detail'] else '',
'model_processing_panel': item['processing_panel_detail'],
'model_machining_precision': item['model_machining_precision'],
'model_code': item['barcode'],
'length': item['model_long'],
@@ -961,7 +947,7 @@ class ResProductMo(models.Model):
self.attachment_update(item['quality_standard_name'], copy_product_id.product_tmpl_id.id,
'quality_standard', item['quality_standard_mimetype'])
return copy_product_id
def format_float(self, value):
# 将浮点数转换为字符串
value_str = str(value)
@@ -1026,9 +1012,12 @@ class ResProductMo(models.Model):
if not embryo_redundancy_id:
raise UserError('请先配置模型类型内的坯料冗余')
logging.info('no_bom_copy_product_supplier-vals:%s' % supplier)
embryo_name = self.generate_embryo_name(order_id, item, materials_id, materials_type_id, embryo_redundancy_id, i)
vals = {
'name': embryo_name,
'name': '%s-%s-%s [%s %s-%s * %s * %s]' % ('R',
order_id.name, i, materials_id.name, materials_type_id.name,
self.format_float(item['model_long'] + embryo_redundancy_id.long),
self.format_float(item['model_width'] + embryo_redundancy_id.width),
self.format_float(item['model_height'] + embryo_redundancy_id.height)),
'length': self.format_float(item['model_long'] + embryo_redundancy_id.long),
'width': self.format_float(item['model_width'] + embryo_redundancy_id.width),
'height': self.format_float(item['model_height'] + embryo_redundancy_id.height),
@@ -1130,19 +1119,7 @@ class ResProductMo(models.Model):
# 增加产品表面积
def generate_product_name(self, order_id, item, i):
"""生成成品名称"""
product_name = '%s-%s-%s' % ('P', order_id.name, i)
return product_name
def generate_embryo_name(self, order_id, item, materials_id, materials_type_id, embryo_redundancy_id, i):
"""生成坯料名称"""
embryo_name = '%s-%s-%s [%s %s-%s * %s * %s]' % ('R',
order_id.name, i, materials_id.name, materials_type_id.name,
self.format_float(item['model_long'] + embryo_redundancy_id.long),
self.format_float(item['model_width'] + embryo_redundancy_id.width),
self.format_float(item['model_height'] + embryo_redundancy_id.height))
return embryo_name
class ResProductFixture(models.Model):
_inherit = 'product.template'

View File

@@ -59,86 +59,6 @@ class PurchaseOrder(models.Model):
production_id = self.env['mrp.production'].search([('origin', 'in', origins)])
purchase.production_count = len(production_id)
# def process_replenish(self,production,total_qty):
# record = self
# bom_line_id = production.bom_id.bom_line_ids
# replenish = self.env['stock.warehouse.orderpoint'].search([
# ('product_id', '=', bom_line_id.product_id.id),
# (
# 'location_id', '=', self.env.ref('sf_stock.stock_location_outsourcing_material_receiving_area').id),
# # ('state', 'in', ['draft', 'confirmed'])
# ], limit=1)
# if not replenish:
# replenish_model = self.env['stock.warehouse.orderpoint']
# replenish = replenish_model.create({
# 'product_id': bom_line_id.product_id.id,
# 'location_id': self.env.ref(
# 'sf_stock.stock_location_outsourcing_material_receiving_area').id,
# 'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id,
# 'group_id': record.group_id.id,
# 'qty_to_order': total_qty,
# 'origin': record.name,
# })
# else:
# replenish.write({
# 'product_id': bom_line_id.product_id.id,
# 'location_id': self.env.ref(
# 'sf_stock.stock_location_outsourcing_material_receiving_area').id,
# 'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id,
# 'group_id': record.group_id.id,
# 'qty_to_order': total_qty + replenish.qty_to_order,
# 'origin': record.name + ',' + replenish.origin,
# })
# replenish.action_replenish()
# def outsourcing_service_replenishment(self):
# record = self
# if record.purchase_type != 'consignment':
# return
# grouped_lines = {}
# for line in record.order_line:
# if line.related_product.id not in grouped_lines:
# grouped_lines[line.related_product.id] = []
# grouped_lines[line.related_product.id].append(line)
# for product_id,lines in grouped_lines.items():
# production = self.env['mrp.production'].search([('product_id', '=', product_id)], limit=1)
# if not production:
# continue
# total_qty = sum(line.product_qty for line in lines)
# record.process_replenish(production,total_qty)
# for product_id,lines in grouped_lines.items():
# productions = self.env['mrp.production'].search([('product_id', '=', product_id)], limit=1)
# if not productions:
# continue
# # production.bom_id.bom_line_ids.product_id
# location_id = self.env['stock.location'].search([('name', '=', '制造前')])
# quants = self.env['stock.quant'].search([
# ('product_id', '=', productions.bom_id.bom_line_ids.product_id.id),
# ('location_id', '=', location_id.id)
# ])
# total_qty = sum(quants.mapped('quantity')) # 计算该位置的总库存量
# is_available = total_qty > 0
# if not is_available:
# raise UserError('请先完成坯料入库')
# for production_id in productions:
# work_ids = production_id.workorder_ids.filtered(
# lambda wk: wk.state not in ['done', 'rework', 'cancel'])
# if not work_ids:
# continue
# min_sequence_wk = min(work_ids, key=lambda wk: wk.sequence)
# if min_sequence_wk.is_subcontract:
# picking_id = production_id.picking_ids.filtered(
# lambda wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区')
# move_out = picking_id.move_ids
# for mo in move_out:
# if mo.state != 'done':
# mo.write({'state': 'assigned', 'production_id': False})
# if not mo.move_line_ids:
# self.env['stock.move.line'].create(
# mo.get_move_line(production_id, min_sequence_wk))
# product = self.env['mrp.production'].search([('product_id', '=', product_id)], limit=1)
# match = re.search(r'(S\d{5}-\d)',product.name)
# pass
def button_confirm(self):
for record in self:
for line in record.order_line:
@@ -146,10 +66,37 @@ class PurchaseOrder(models.Model):
raise UserError('请对【产品】中的【数量】进行输入')
if line.price_unit <= 0:
raise UserError('请对【产品】中的【单价】进行输入')
# record.outsourcing_service_replenishment()
# if record.purchase_type == 'consignment':
# bom_line_id = record.order_line[0].purchase_request_lines.request_id.bom_id.bom_line_ids
# replenish = self.env['stock.warehouse.orderpoint'].search([
# ('product_id', '=', bom_line_id.product_id.id),
# (
# 'location_id', '=', self.env.ref('sf_stock.stock_location_outsourcing_material_receiving_area').id),
# # ('state', 'in', ['draft', 'confirmed'])
# ], limit=1)
# if not replenish:
# replenish_model = self.env['stock.warehouse.orderpoint']
# replenish = replenish_model.create({
# 'product_id': bom_line_id.product_id.id,
# 'location_id': self.env.ref(
# 'sf_stock.stock_location_outsourcing_material_receiving_area').id,
# 'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id,
# 'group_id': record.group_id.id,
# 'qty_to_order': 1,
# 'origin': record.name,
# })
# else:
# replenish.write({
# 'product_id': bom_line_id.product_id.id,
# 'location_id': self.env.ref(
# 'sf_stock.stock_location_outsourcing_material_receiving_area').id,
# 'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id,
# 'group_id': record.group_id.id,
# 'qty_to_order': 1 + replenish.qty_to_order,
# 'origin': record.name + ',' + replenish.origin,
# })
# replenish.action_replenish()
res = super(PurchaseOrder, self).button_confirm()
for line in self.order_line:
# 将产品不追踪序列号的行项目设置qty_done
if line.move_ids and line.move_ids[0].product_id.tracking == 'none':
@@ -208,12 +155,10 @@ class PurchaseOrderLine(models.Model):
continue
if record.product_id.categ_id.name == '坯料':
product_name = ''
match = re.search(r'(S\d{5}-\d+)', record.product_id.name)
match = re.search(r'(S\d{5}-\d)', record.product_id.name)
# 如果匹配成功,提取结果
if match:
product_name = match.group(0)
else:
product_name = record.product_id.name
sale_order_name = ''
match_sale = re.search(r'S(\d+)', record.product_id.name)
if match_sale:

View File

@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# # -*- coding: utf-8 -*-
# import base64
# import datetime
# import logging
@@ -7,24 +7,24 @@
# import re
# import traceback
# from operator import itemgetter
#
# import requests
# from itertools import groupby
# from collections import defaultdict, namedtuple
#
# from odoo import api, fields, models, SUPERUSER_ID, _
# from odoo.exceptions import UserError, ValidationError
# from odoo.tools import float_compare, float_round, float_is_zero, format_datetime
#
#
# class PurchaseRequestLine(models.Model):
# _inherit = 'purchase.request'
# is_subcontract = fields.Boolean(string='是否外协',default=False)
# class PurchaseRequestLine(models.Model):
# _inherit = 'purchase.request.line'
# is_subcontract = fields.Boolean(string='是否外协')
#
#
# class PurchaseRequest(models.Model):
# _inherit = 'purchase.request'
# bom_id = fields.Many2one('mrp.bom')

View File

@@ -58,8 +58,8 @@ class SaleOrder(models.Model):
# 复制成品模板上的属性
line.product_id.product_tmpl_id.copy_template(product_template_id)
# 将模板上的single_manufacturing属性复制到成品上
# line.product_id.single_manufacturing = product_template_id.single_manufacturing
# line.product_id.tracking = product_template_id.tracking
line.product_id.single_manufacturing = product_template_id.single_manufacturing
line.product_id.tracking = product_template_id.tracking
order_id = self
product = line.product_id
@@ -74,17 +74,14 @@ class SaleOrder(models.Model):
'blank_area': product.model_area,
'price': product.list_price,
'embryo_redundancy_id': line.embryo_redundancy_id,
'model_id': line.model_id
}
product_name = ''
match = re.search(r'(S\d{5}-\d+)', product.name)
product_seria = 0
match = re.search(r'(S\d{5}-\d)', product.name)
# 如果匹配成功,提取结果
if match:
product_name = match.group(0)
# 获取成品名结尾-n的n
product_seria = int(product_name.split('-')[-1])
# 获取成品名结尾-n的n
product_seria = int(product_name.split('-')[-1])
# 成品供货方式为采购则不生成bom
if line.supply_method != 'purchase':
bom_data = self.env['mrp.bom'].with_user(self.env.ref("base.user_admin")).get_bom(product)
@@ -194,40 +191,6 @@ class SaleOrder(models.Model):
'res_id': wizard.id,
}
def write_sale_documents(self, kw):
"""BFM 内部下单 内容补充 """
val = {}
if kw.get('contract_file_name') and kw.get('contract_file'):
document_id = self.create_sale_documents(kw.get('contract_file_name'), kw.get('contract_file'))
val.update({'contract_document_id': document_id.id})
if kw.get('contract_code') or kw.get('contract_date'):
val.update({'contract_code': kw.get('contract_code'), 'contract_date': kw.get('contract_date')})
if kw.get('customer_name'):
val.update({'customer_name': kw.get('customer_name')})
self.write(val)
def create_sale_documents(self, contract_file_name, contract_file):
# 创建ir.attachment记录
attachment = self.env['ir.attachment'].sudo().create({
'name': contract_file_name,
'type': 'binary',
'datas': contract_file,
'res_model': 'sale.order',
})
# 获取默认的文档文件夹
workspace = self.env.ref('sf_sale.documents_sales_contracts_folder_1').id
# 创建 documents.document 记录
document = self.env['documents.document'].sudo().create({
'name': contract_file_name,
'attachment_id': attachment.id,
'folder_id': workspace,
'res_model': 'sale.order',
'res_id': self.id,
})
return document
class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'

View File

@@ -20,13 +20,13 @@ class SfProductionProcessParameter(models.Model):
# is_product_button = fields.Boolean(compute='_compute_is_product_button',default=False)
# is_delete_button = fields.Boolean(compute='_compute_is_delete_button', default=False)
# routing_id = fields.Many2one('mrp.routing.workcenter', string="工序")
#
# @api.depends('outsourced_service_products')
# def _compute_service_products(self):
# for record in self:
# # 假设取第一条作为主明细
# record.service_products = record.outsourced_service_products.ids if record.outsourced_service_products else False
# record.service_products = record.outsourced_service_products.id if record.outsourced_service_products else False
#
# def _inverse_service_products(self):
# for record in self:
# if record.service_products:
@@ -45,7 +45,7 @@ class SfProductionProcessParameter(models.Model):
# for record in self:
# if len(record.outsourced_service_products) > 1:
# raise ValidationError("工艺参数不能与多个产品关联")
#
# @api.onchange('outsourced_service_products')
# def _onchange_validate_partner_limit(self):
# for record in self:
@@ -58,7 +58,7 @@ class SfProductionProcessParameter(models.Model):
# record.is_product_button = True
# else:
# record.is_product_button = False
#
# def has_wksp_prefix(self):
# """
# 判断字符串是否以WKSP开头不区分大小写

View File

@@ -23,6 +23,7 @@ class sf_technology_design(models.Model):
# def _compute_group_uniq_id(self):
# for record in self:
def json_technology_design_str(self, k, route, i, process_parameter):
workorders_values_str = [0, '', {
'route_id': route.id if route.routing_type in ['表面工艺'] else route.route_workcenter_id.id,
@@ -35,19 +36,11 @@ class sf_technology_design(models.Model):
return workorders_values_str
def write(self, vals):
res = super(sf_technology_design, self).write(vals)
if 'group_uniq_id' in vals or 'process_parameters_id' in vals or 'active' in vals:
if self.production_id:
process_parameters_id = self.production_id.technology_design_ids.mapped('process_parameters_id')
if process_parameters_id.ids:
self.production_id.product_id.model_process_parameters_ids = process_parameters_id.ids
else:
self.production_id.product_id.model_process_parameters_ids = None
return res
return super(sf_technology_design, self).write(vals)
def unlink_technology_design(self):
self.active = False
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
@@ -55,12 +48,10 @@ class sf_technology_design(models.Model):
raise ValidationError(_("工序不能为空"))
result = super(sf_technology_design, self).create(vals_list)
for res in result:
record = self.search([('production_id', '=', res.production_id.id), ('active', 'in', [True, False])],
order='group_uniq_id desc', limit=1)
res.group_uniq_id = record.group_uniq_id + 1
record = self.search([('production_id', '=', res.production_id.id), ('active', 'in', [True, False])], order='group_uniq_id desc', limit=1)
res.group_uniq_id=record.group_uniq_id + 1
return result
def get_duplicates_with_inactive(self, technology_designs):
def get_duplicates_with_inactive(self,technology_designs):
# 统计每个 'sequence' 出现的次数
sequence_count = Counter(technology_design.sequence for technology_design in technology_designs)
@@ -71,7 +62,6 @@ class sf_technology_design(models.Model):
]
return result
# def rearrange_numbering(self,self_technology_designs):
# inactive_designs = self.get_duplicates_with_inactive(self_technology_designs)
# if inactive_designs:
@@ -85,7 +75,7 @@ class sf_technology_design(models.Model):
def get_technology_design(self):
return {
'sequence': self.sequence,
'sequence':self.sequence,
'route_id': self.route_id.id,
'process_parameters_id': self.process_parameters_id.id,
'panel': self.panel,
@@ -93,19 +83,17 @@ class sf_technology_design(models.Model):
'time_cycle_manual': self.time_cycle_manual,
'is_auto': self.is_auto,
'active': self.active,
'group_uniq_id': self.group_uniq_id,
'group_uniq_id':self.group_uniq_id,
}
def sync_technology_designs(self, production_technology_designs, self_technology_designs):
def sync_technology_designs(self,production_technology_designs, self_technology_designs):
production_id = production_technology_designs[0].production_id.id
self_technology_design_dict = {item.group_uniq_id: item for item in self_technology_designs}
production_technology_designs_dict = {item.group_uniq_id: item for item in production_technology_designs}
self_technology_design_dict = {item.group_uniq_id:item for item in self_technology_designs}
production_technology_designs_dict = {item.group_uniq_id:item for item in production_technology_designs}
for technology_design in production_technology_designs:
if not self_technology_design_dict.get(technology_design.group_uniq_id):
technology_design.write({'production_id': False})
else:
technology_design.write(
self_technology_design_dict.get(technology_design.group_uniq_id).get_technology_design())
technology_design.write(self_technology_design_dict.get(technology_design.group_uniq_id).get_technology_design())
for technology_design in self_technology_designs:
if not production_technology_designs_dict.get(technology_design.group_uniq_id):
technology_design = technology_design.get_technology_design()
@@ -113,8 +101,9 @@ class sf_technology_design(models.Model):
technology_design.pop('group_uniq_id')
self.env['sf.technology.design'].create(technology_design)
def unified_procedure_multiple_work_orders(self, self_technology_designs, production_item):
def unified_procedure_multiple_work_orders(self,self_technology_designs,production_item):
technology_designs = self.env['sf.technology.design'].sudo().search(
[('production_id', '=', production_item.id), ('active', 'in', [True, False])])
self.sync_technology_designs(self_technology_designs=self_technology_designs,
production_technology_designs=technology_designs)
self.sync_technology_designs(self_technology_designs=self_technology_designs,production_technology_designs=technology_designs)

View File

@@ -21,7 +21,6 @@ from odoo.addons.sf_base.commons.common import Common
from odoo.exceptions import UserError
from io import BytesIO
from odoo.exceptions import ValidationError
from dateutil.relativedelta import relativedelta
class stockWarehouse(models.Model):
@@ -96,36 +95,35 @@ class StockRule(models.Model):
precision_rounding=proc[
0].product_uom.rounding) > 0)
list2 = []
for procurement, rule in procurements:
num = int(procurement.product_qty)
for item in procurements:
num = int(item[0].product_qty)
warehouse_id = rule.warehouse_id
if not warehouse_id:
warehouse_id = rule.location_dest_id.warehouse_id
manu_rule = rule.route_id.rule_ids.filtered(lambda r: r.action == 'manufacture' and r.warehouse_id == warehouse_id)
if procurement.product_id.product_tmpl_id.single_manufacturing and manu_rule:
product = self.env['product.product'].search(
[("id", '=', item[0].product_id.id)])
product_tmpl = self.env['product.template'].search(
["&", ("id", '=', product.product_tmpl_id.id), ('single_manufacturing', "!=", False)])
if product_tmpl:
if num > 1:
for no in range(1, num + 1):
Procurement = namedtuple('Procurement', ['product_id', 'product_qty',
'product_uom', 'location_id', 'name', 'origin',
'company_id',
'values'])
s = Procurement(product_id=procurement.product_id, product_qty=1.0, product_uom=procurement.product_uom,
location_id=procurement.location_id,
name=procurement.name,
origin=procurement.origin,
company_id=procurement.company_id,
values=procurement.values,
s = Procurement(product_id=item[0].product_id, product_qty=1.0, product_uom=item[0].product_uom,
location_id=item[0].location_id,
name=item[0].name,
origin=item[0].origin,
company_id=item[0].company_id,
values=item[0].values,
)
# item1 = list(item)
# item1[0] = s
item1 = list(item)
item1[0] = s
list2.append((s, rule))
list2.append(tuple(item1))
else:
list2.append((procurement, rule))
list2.append(item)
else:
list2.append((procurement, rule))
list2.append(item)
for procurement, rule in list2:
procure_method = rule.procure_method
@@ -185,6 +183,18 @@ class StockRule(models.Model):
'''创建制造订单'''
productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create(
productions_values)
# 将这一批制造订单的采购组根据成品设置为不同的采购组
# product_group_id = {}
# for index, production in enumerate(productions):
# if production.product_id.id not in product_group_id.keys():
# product_group_id[production.product_id.id] = production.procurement_group_id.id
# else:
# productions_values[index].update({'name': production.name})
# procurement_group_vals = production._prepare_procurement_group_vals(productions_values[index])
# production.procurement_group_id = self.env["procurement.group"].create(procurement_group_vals).id
# self.env['stock.move'].sudo().create(productions._get_moves_raw_values())
# self.env['stock.move'].sudo().create(productions._get_moves_finished_values())
'''
创建工单
@@ -217,8 +227,7 @@ class StockRule(models.Model):
'''
创建制造订单时生成序列号
'''
if production.product_id.tracking != "none":
production.action_generate_serial()
production.action_generate_serial()
origin_production = production.move_dest_ids and production.move_dest_ids[
0].raw_material_production_id or False
orderpoint = production.orderpoint_id
@@ -443,7 +452,7 @@ class ProductionLot(models.Model):
@api.model
def _get_next_serial(self, company, product):
"""Return the next serial number to be attributed to the product."""
if product.tracking != "none":
if product.tracking == "serial":
last_serial = self.env['stock.lot'].search(
[('company_id', '=', company.id), ('product_id', '=', product.id), ('name', 'ilike', product.name)],
limit=1, order='name desc')
@@ -454,9 +463,7 @@ class ProductionLot(models.Model):
return self.env['stock.lot'].generate_lot_names1(product.name, last_serial.name if (
not move_line_id or
(last_serial and last_serial.name > move_line_id.lot_name)) else move_line_id.lot_name, 2)[1]
else:
return "%s-%03d" % (product.name, 1)
return False
return "%s-%03d" % (product.name, 1)
qr_code_image = fields.Binary(string='二维码', compute='_generate_qr_code')
@@ -557,13 +564,6 @@ class StockPicking(models.Model):
part_numbers = fields.Char(string="零件图号", compute='_compute_part_info', store=True, index=True)
part_names = fields.Char(string="零件名称", compute='_compute_part_info', store=True, index=True)
model_id = fields.Char('模型ID', compute='_compute_model_id', store=True, index=True)
@api.depends('move_ids_without_package.model_id')
def _compute_model_id(self):
for picking in self:
model_id = picking.move_ids_without_package.mapped('model_id')
picking.model_id = ','.join(filter(None, model_id))
@api.depends('move_ids_without_package.part_number', 'move_ids_without_package.part_name')
def _compute_part_info(self):
@@ -630,65 +630,7 @@ class StockPicking(models.Model):
if lot_ids:
move.action_clear_lines_show_details()
move.action_show_details()
# 先进行设置数量
self.action_set_quantities_to_reservation()
res = super().button_validate()
# lot_ids = None
# product_ids = self.move_ids.mapped('product_id')
# if not self.move_ids[0].product_id.single_manufacturing and self.move_ids[0].product_id.tracking == 'none':
# lot_ids = self.move_ids.move_line_ids.mapped('lot_id')
# production_ids = self.sale_order_id.mrp_production_ids if self.sale_order_id else self.env['mrp.production']
# if res and self.location_id.name == '外协收料区' and self.location_dest_id.name == '制造前':
# # 如果是最后一张外协入库单,则设置库存位置的预留数量
# for production_id in production_ids:
# if lot_ids:
# lot_id = production_id.move_raw_ids.move_line_ids.lot_id
# # picking_ids = production_id.picking_ids.filtered(
# # lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前')
# if lot_id in lot_ids:
# workorder_id = production_id.workorder_ids.filtered(
# lambda a: a.state == 'progress' and a.is_subcontract)
# if not workorder_id:
# continue
# workorder_id.button_finish()
# else:
# workorder_id = production_id.workorder_ids.filtered(lambda a: a.state == 'progress' and a.is_subcontract)
# if not workorder_id:
# continue
# workorder_id.button_finish()
# # lot_id = workorder.production_id.move_raw_ids.move_line_ids.lot_id
# # picking_ids = workorder.production_id.picking_ids.filtered(
# # lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前')
# # if move_in:
# # workorder = move_in.subcontract_workorder_id
# # workorders = workorder.production_id.workorder_ids
# # subcontract_workorders = workorders.filtered(
# # lambda wo: wo.is_subcontract == True and wo.state != 'cancel').sorted('sequence')
# # # if workorder == subcontract_workorders[-1]:
# # # self.env['stock.quant']._update_reserved_quantity(
# # # move_in.product_id, move_in.location_dest_id, move_in.product_uom_qty,
# # # lot_id=move_in.move_line_ids.lot_id,
# # # package_id=False, owner_id=False, strict=False
# # # )
# # workorder.button_finish()
# if res and self.location_id.name == '制造前' and self.location_dest_id.name == '外协加工区':
# for production_id in production_ids:
# if lot_ids:
# lot_id = production_id.move_raw_ids.move_line_ids.lot_id
# # picking_ids = production_id.picking_ids.filtered(
# # lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前')
# if lot_id in lot_ids:
# workorder_id = production_id.workorder_ids.filtered(
# lambda a: a.state == 'progress' and a.is_subcontract)
# if not workorder_id:
# continue
# workorder_id.button_finish()
# else:
# workorder_id = production_id.workorder_ids.filtered(lambda a: a.state == 'ready' and a.is_subcontract)
# if not workorder_id:
# continue
# workorder_id.button_start()
picking_type_in = self.env.ref('sf_manufacturing.outcontract_picking_in').id
if res is True and self.picking_type_id.id == picking_type_in:
# 如果是最后一张外协入库单,则设置库存位置的预留数量
@@ -731,33 +673,6 @@ class StockPicking(models.Model):
production.workorder_ids.write({'back_button_display': False})
return res
def _prepare_subcontract_mo_vals(self, subcontract_move, bom):
subcontract_move.ensure_one()
group = self.env['procurement.group'].sudo().search([('name', '=', self.name)])
if not group:
group = self.env['procurement.group'].create({
'name': self.name,
'partner_id': self.partner_id.id,
})
product = subcontract_move.product_id
warehouse = self._get_warehouse(subcontract_move)
vals = {
'company_id': subcontract_move.company_id.id,
'procurement_group_id': group.id,
'subcontractor_id': subcontract_move.picking_id.partner_id.commercial_partner_id.id,
'picking_ids': [subcontract_move.picking_id.id],
'product_id': product.id,
'product_uom_id': subcontract_move.product_uom.id,
'bom_id': bom.id,
'location_src_id': subcontract_move.picking_id.partner_id.with_company(subcontract_move.company_id).property_stock_subcontractor.id,
'location_dest_id': subcontract_move.picking_id.partner_id.with_company(subcontract_move.company_id).property_stock_subcontractor.id,
'product_qty': subcontract_move.product_uom_qty,
'picking_type_id': warehouse.subcontracting_type_id.id,
'date_planned_start': subcontract_move.date - relativedelta(days=product.produce_delay)
}
return vals
# 创建 外协出库入单
def create_outcontract_picking(self, workorders, item, sorted_workorders):
production = workorders[0].production_id
@@ -868,7 +783,6 @@ class ReStockMove(models.Model):
materiel_height = fields.Float(string='物料高度', digits=(16, 4))
part_number = fields.Char(string='零件图号', compute='_compute_part_info', store=True)
part_name = fields.Char(string='零件名称', compute='_compute_part_info', store=True)
model_id = fields.Char('模型ID', related='product_id.model_id')
@api.depends('product_id')
def _compute_part_info(self):
@@ -879,12 +793,10 @@ class ReStockMove(models.Model):
move.part_name = move.product_id.part_name
elif move.product_id.categ_id.type == '坯料':
product_name = ''
match = re.search(r'(S\d{5}-\d+)', move.product_id.name)
match = re.search(r'(S\d{5}-\d)', move.product_id.name)
# 如果匹配成功,提取结果
if match:
product_name = match.group(0)
else:
product_name = move.product_id.name
if move.picking_id.sale_order_id:
sale_order = move.picking_id.sale_order_id
else:
@@ -913,12 +825,10 @@ class ReStockMove(models.Model):
continue
product_name = ''
logging.info('制造订单的产品 %s', production_id.product_id.name)
match = re.search(r'(S\d{5}-\d+)', production_id.product_id.name)
match = re.search(r'(S\d{5}-\d)', production_id.product_id.name)
# 如果匹配成功,提取结果
if match:
product_name = match.group(0)
else:
product_name = production_id.product_id.name
if move.picking_id.sale_order_id:
sale_order = move.picking_id.sale_order_id
else:
@@ -1236,20 +1146,6 @@ class ReStockMove(models.Model):
res['lot_id'] = self.subcontract_workorder_id.production_id.move_raw_ids.move_line_ids[0].lot_id.id
return res
def _get_subcontract_bom(self):
self.ensure_one()
purchase_type = getattr(self.picking_id.purchase_id, 'purchase_type', False)
if purchase_type:
self = self.with_context(stock_picking=purchase_type)
bom = self.env['mrp.bom'].sudo()._bom_subcontract_find(
self.product_id,
picking_type=self.picking_type_id,
company_id=self.company_id.id,
bom_type='subcontract',
subcontractor=self.picking_id.partner_id
)
return bom
class ReStockQuant(models.Model):
_inherit = 'stock.quant'

View File

@@ -1,11 +0,0 @@
# -*- coding: utf-8 -*-
# Part of SmartGo. See LICENSE file for full copyright and licensing details.
import base64
from io import BytesIO
from odoo import api, fields, models, SUPERUSER_ID, _
class StockWarehouseOrderpoint(models.Model):
_inherit = 'stock.warehouse.orderpoint'
origin = fields.Char(string='来源')
_order = 'create_date DESC'

View File

@@ -427,7 +427,6 @@
<field name="programming_method"/>
<field name="current_programming_count"/>
<field name="target_production_id"/>
<field name="apply_uid"/>
<field name="apply_time"/>
<field name="send_time"/>
</tree>
@@ -603,7 +602,6 @@
<field name="part_number"/>
<field name="sale_order_id"/>
<field name="deadline_of_delivery" icon="fa-calendar" enable_counters="1" filter_domain="[('deadline_of_delivery', 'ilike', self)]"/>
<field name="model_id"/>
</xpath>
<xpath expr="//field[@name='product_variant_attributes']" position="attributes">
<attribute name="invisible">1</attribute>

View File

@@ -22,26 +22,26 @@
<field name="is_repeat"/>
<field name="reserved_duration"/>
</field>
<!-- <xpath expr="//notebook/page[1]" position="before">
<page string="可选工艺参数">
<field name="optional_process_parameters">
<tree editable="bottom">
<field name="is_product_button" invisible="1"/>
<field name="is_delete_button" invisible="1"/>
<field name="code" attrs="{'readonly': True}"/>
<field name="name" required="1"/>
<field name="service_products" domain="[('detailed_type', '=', 'service'),('server_product_process_parameters_id', '=', False)]"/> -->
<!-- 按钮列 -->
<!-- <button name="action_create_service_product" string="创建服务产品" type="object"
class="btn-primary"
attrs="{'invisible': [('is_product_button', '=', True)]}" context="{'default_process_parameter_id':id}"/>
<button name="action_hide_service_products" string="删除" type="object"
class="oe_highlight"
attrs="{'invisible': [('is_delete_button', '=', True)]}"/>
</tree>
</field>
</page>
</xpath> -->
<!-- <xpath expr="//notebook/page[1]" position="before">-->
<!-- <page string="可选工艺参数">-->
<!-- <field name="optional_process_parameters">-->
<!-- <tree editable="bottom">-->
<!-- <field name="is_product_button" invisible="1"/>-->
<!-- <field name="is_delete_button" invisible="1"/>-->
<!-- <field name="code" attrs="{'readonly': True}"/>-->
<!-- <field name="name" required="1"/>-->
<!-- <field name="service_products" domain="[('detailed_type', '=', 'service'),('server_product_process_parameters_id', '=', False)]"/>-->
<!-- &lt;!&ndash; 按钮列 &ndash;&gt;-->
<!-- <button name="action_create_service_product" string="创建服务产品" type="object"-->
<!-- class="btn-primary"-->
<!-- attrs="{'invisible': [('is_product_button', '=', True)]}" context="{'default_process_parameter_id':id}"/>-->
<!-- <button name="action_hide_service_products" string="删除" type="object"-->
<!-- class="oe_highlight"-->
<!-- attrs="{'invisible': [('is_delete_button', '=', True)]}"/>-->
<!-- </tree>-->
<!-- </field>-->
<!-- </page>-->
<!-- </xpath>-->
</field>
</record>
</data>

View File

@@ -144,17 +144,17 @@
statusbar_visible="pending,waiting,ready,progress,to be detected,done,rework"/>
</xpath>
<xpath expr="//div[@name='button_box']" position="inside">
<!-- <button type="object" name="action_view_pr_mrp_workorder" class="oe_stat_button"
icon="fa-credit-card"
groups="base.group_user,sf_base.group_sf_order_user"
attrs="{'invisible': [('pr_mp_count', '=', 0)]}">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="pr_mp_count"/>
</span>
<span class="o_stat_text">采购申请</span>
</div>
</button> -->
<!-- <button type="object" name="action_view_pr_mrp_workorder" class="oe_stat_button"-->
<!-- icon="fa-credit-card"-->
<!-- groups="base.group_user,sf_base.group_sf_order_user"-->
<!-- attrs="{'invisible': [('pr_mp_count', '=', 0)]}">-->
<!-- <div class="o_field_widget o_stat_info">-->
<!-- <span class="o_stat_value">-->
<!-- <field name="pr_mp_count"/>-->
<!-- </span>-->
<!-- <span class="o_stat_text">采购申请</span>-->
<!-- </div>-->
<!-- </button>-->
<button type="object" name="action_view_surface_technics_purchase" class="oe_stat_button"
icon="fa-credit-card"
groups="base.group_user,sf_base.group_sf_order_user"
@@ -677,9 +677,8 @@
<field name="inherit_id" ref="mrp.view_mrp_production_work_order_search"/>
<field name="arch" type="xml">
<field name="product_id" position="after">
<field name="part_number" string="零件图号"/>
<field name="part_name" string="零件名称"/>
<field name="model_id" string="模型ID"/>
<field name="part_number" string="成品零件图号"/>
<field name="model_id" string="模型id"/>
</field>
<xpath expr="//filter[@name='progress']" position="after">
<filter string="待检测" name="state" domain="[('state','=','to be detected')]"/>

View File

@@ -12,18 +12,5 @@
</xpath>
</field>
</record>
<record id="product_template_search_inherit_sf_manufacturing" model="ir.ui.view">
<field name="name">product.template.search</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_search_view"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='categ_id']" position="after">
<field name="part_number" string="零件图号"/>
<field name="part_name" string="零件名称"/>
<field name="model_id" string="模型ID"/>
</xpath>
</field>
</record>
</data>
</odoo>

View File

@@ -73,7 +73,6 @@
<xpath expr="//field[@name='picking_type_id']" position="after">
<field name="part_numbers" string="零件图号" filter_domain="[('part_numbers', 'ilike', self)]"/>
<field name="part_names" string="零件名称" filter_domain="[('part_names', 'ilike', self)]"/>
<field name="model_id" string="模型ID" filter_domain="[('model_id', 'ilike', self)]"/>
</xpath>
</field>
</record>

Some files were not shown because too many files have changed in this diff Show More