Compare commits

..

2 Commits

Author SHA1 Message Date
胡尧
307e860fe0 增加提示模块 2025-06-20 15:16:43 +08:00
胡尧
bd27f288f7 增加提示模块 2025-06-20 15:16:25 +08:00
69 changed files with 863 additions and 2752 deletions

View File

@@ -190,7 +190,7 @@ def _create(self, data_list):
# 如果该用户组被限制创建或更新操作 # 如果该用户组被限制创建或更新操作
if rec['is_create_or_update']: if rec['is_create_or_update']:
raise UserError( raise UserError(
_("您没有执行此操作的权限。请联系管理员")) _("您没有执行此操作%s的权限。请联系管理员" % group_xml_id))
else: else:
# 如果 'access.right' 模型不存在,可以在这里定义备选逻辑 # 如果 'access.right' 模型不存在,可以在这里定义备选逻辑
# 例如,记录日志、发送通知或者简单地跳过这部分逻辑 # 例如,记录日志、发送通知或者简单地跳过这部分逻辑

View File

@@ -10,7 +10,6 @@
'data': [ 'data': [
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'views/sale_order_view.xml', 'views/sale_order_view.xml',
'views/purchase_order.xml',
'views/mrp_production.xml', 'views/mrp_production.xml',
'views/purchase_request_view.xml', 'views/purchase_request_view.xml',
'wizard/purchase_request_line_make_purchase_order_view.xml', 'wizard/purchase_request_line_make_purchase_order_view.xml',

View File

@@ -16,69 +16,6 @@ class PurchaseOrder(models.Model):
('rejected', '已驳回') ('rejected', '已驳回')
], string='Status', readonly=True, index=True, copy=False, default='draft', tracking=True) ], 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): def button_confirm(self):
res = super(PurchaseOrder, self).button_confirm() res = super(PurchaseOrder, self).button_confirm()
@@ -94,20 +31,12 @@ class PurchaseOrder(models.Model):
def button_cancel(self): def button_cancel(self):
""" """
1. 先将采购订单行与目标库存移动断开链接避免采购单取消后调拨单被调整为mts的问题 将取消的采购订单关联的库存移动撤销
2. 取消采购订单
3. 将采购订单行与目标库存移动重新建立链接
""" """
created_purchase_request_line_ids = {} move_ids = self.order_line.move_dest_ids.filtered(lambda move: move.state != 'done' and not move.scrapped)
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() res =super(PurchaseOrder, self).button_cancel()
for move_id, created_purchase_request_line_id in created_purchase_request_line_ids.items(): if move_ids.mapped('created_purchase_request_line_id'):
self.env['stock.move'].browse(move_id).created_purchase_request_line_id = created_purchase_request_line_id move_ids.write({'state': 'waiting', 'is_done': False})
# if move_ids.mapped('created_purchase_request_line_id'):
# move_ids.write({'state': 'waiting', 'is_done': False})
return res return res
def write(self, vals): def write(self, vals):

View File

@@ -16,7 +16,6 @@ class PurchaseRequest(models.Model):
) )
rule_new_add = fields.Boolean('采购请求为规则创建', default=False, compute='_compute_state', store=True) rule_new_add = fields.Boolean('采购请求为规则创建', default=False, compute='_compute_state', store=True)
rule_purchase_to_request = fields.Boolean('采购单根据规则创建坯料采购申请', default=False)
@api.depends('state') @api.depends('state')
def _compute_state(self): def _compute_state(self):

View File

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

View File

@@ -41,20 +41,7 @@ class StockPicking(models.Model):
if backorder_ids: if backorder_ids:
purchase_request_lines = self.move_ids.move_orig_ids.purchase_line_id.purchase_request_lines purchase_request_lines = self.move_ids.move_orig_ids.purchase_line_id.purchase_request_lines
if purchase_request_lines: if purchase_request_lines:
purchase_request_lines.move_dest_ids = [ purchase_request_lines.move_dest_ids = [
(4, x.id) for x in backorder_ids.move_ids if (4, x.id) for x in backorder_ids.move_ids if x.product_id.id in purchase_request_lines.mapped('product_id.id')
x.product_id.id in purchase_request_lines.mapped('product_id.id') and \
not x.created_purchase_request_line_id
] ]
return res 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( request_data = rule._prepare_purchase_request(
procurement.origin, procurement.values 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) pr = purchase_request_model.create(request_data)
cache[domain] = pr cache[domain] = pr
elif ( elif (
@@ -44,18 +44,6 @@ class StockRule(models.Model):
request_line_data.update({'origin': procurement.origin}) request_line_data.update({'origin': procurement.origin})
purchase_request_line_model.create(request_line_data) 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): def _run_buy(self, procurements):
# 如果补货组相同,并且产品相同,则合并 # 如果补货组相同,并且产品相同,则合并
procurements_dict = defaultdict() procurements_dict = defaultdict()

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

@@ -67,16 +67,6 @@
<field name="part_number"/> <field name="part_number"/>
<field name="part_name" invisible="1"/> <field name="part_name" invisible="1"/>
</xpath> </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> </field>
</record> </record>

View File

@@ -4,10 +4,9 @@ class StockRule(models.Model):
_inherit = 'stock.rule' _inherit = 'stock.rule'
def _run_buy(self, procurements): def _run_buy(self, procurements):
res = super(StockRule, self)._run_buy(procurements)
# 判断是否根据规则生成新的采购申请单据,如果生成则修改状态为 approved # 判断是否根据规则生成新的采购申请单据,如果生成则修改状态为 approved
origins = list(set([procurement[0].origin for procurement in procurements])) 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: for origin in origins:
pr_ids = self.env["purchase.request"].sudo().search( pr_ids = self.env["purchase.request"].sudo().search(
[('origin', 'like', origin), ('rule_new_add', '=', True), ('state', '=', 'draft')]) [('origin', 'like', origin), ('rule_new_add', '=', True), ('state', '=', 'draft')])

View File

@@ -4,4 +4,4 @@ wechatpy==1.8.18
pycryptodome==3.22.0 pycryptodome==3.22.0
openupgradelib==3.10.0 openupgradelib==3.10.0
opcua==0.98.13 opcua==0.98.13
openpyxl openpyxl

View File

@@ -35,6 +35,7 @@
], ],
'web.assets_backend': [ 'web.assets_backend': [
'sf_base/static/src/scss/*.scss', 'sf_base/static/src/scss/*.scss',
'sf_base/static/src/js/*.js',
], ],
}, },

View File

@@ -67,52 +67,6 @@ class BasicParametersFixture(models.Model):
mounting_hole_depth = fields.Float('安装孔深度(mm)', digits=(16, 2)) mounting_hole_depth = fields.Float('安装孔深度(mm)', digits=(16, 2))
centering_diameter = fields.Float('定心直径(mm)', digits=(16, 2)) centering_diameter = fields.Float('定心直径(mm)', digits=(16, 2))
# ‘磁吸托盘’ 字段
magnet_tray_length = fields.Float('磁吸托盘长度(mm)', digits=(16, 2))
magnet_tray_width = fields.Float('磁吸托盘宽度(mm)', digits=(16, 2))
magnet_tray_height = fields.Float('磁吸托盘厚度(mm)', digits=(16, 2))
magnet_tray_diameter = fields.Float('磁吸托盘直径(mm)', digits=(16, 2))
magnet_tray_weight = fields.Float('磁吸托盘重量(kg)', digits=(16, 2))
magnet_max_adsorp_length = fields.Float('磁吸托盘最大吸附长度(mm)', digits=(16, 2))
magnet_max_adsorp_width = fields.Float('磁吸托盘最大吸附宽度(mm)', digits=(16, 2))
magnet_max_adsorp_height = fields.Float('磁吸托盘最大吸附厚度(mm)', digits=(16, 2))
magnet_max_adsorp_diameter = fields.Float('磁吸托盘最大吸附直径(mm)', digits=(16, 2))
magnet_max_adsorp_force = fields.Float('磁吸托盘最大吸附力(N)', digits=(16, 2))
magnet_unlocking_method = fields.Selection(
[('手动', '手动'), ('气动', '气动'), ('液压', '液压'), ('电动', '电动'), ('其他', '其他')],
string='磁吸托盘锁紧方式'
)
magnet_flatness = fields.Char('磁吸托盘平面精度(mm)', size=20)
magnet_max_load = fields.Float('磁吸托盘最大负载(kg)', digits=(16, 2))
# ‘气吸托盘’ 字段
air_tray_length = fields.Float('气吸托盘长度(mm)', digits=(16, 2))
air_tray_width = fields.Float('气吸托盘宽度(mm)', digits=(16, 2))
air_tray_height = fields.Float('气吸托盘高度(mm)', digits=(16, 2))
air_tray_diameter = fields.Float('气吸托盘直径(mm)', digits=(16, 2))
air_tray_weight = fields.Float('气吸托盘重量(kg)', digits=(16, 2))
air_max_adsorp_length = fields.Float('气吸托盘最大吸附长度(mm)', digits=(16, 2))
air_max_adsorp_width = fields.Float('气吸托盘最大吸附宽度(mm)', digits=(16, 2))
air_max_adsorp_height = fields.Float('气吸托盘最大吸附厚度(mm)', digits=(16, 2))
air_max_adsorp_diameter = fields.Float('气吸托盘最大吸附直径(mm)', digits=(16, 2))
air_max_adsorp_force = fields.Float('气吸托盘最大吸附力(N)', digits=(16, 2))
air_unlocking_method = fields.Selection(
[('手动', '手动'), ('气动', '气动'), ('液压', '液压'), ('电动', '电动'), ('其他', '其他')],
string='气吸托盘锁紧方式'
)
air_flatness = fields.Char('气吸托盘平面精度(mm)', size=20)
air_max_load = fields.Float('气吸托盘最大负载(kg)', digits=(16, 2))
air_boolean_chip_blowing_function = fields.Boolean('气吸托盘是否有吹屑功能')
air_way_to_install = fields.Selection(
[('接口式', '接口式'), ('螺栓固定', '螺栓固定'), ('磁吸式', '磁吸式'), ('其他', '其他')],
string='气吸托盘安装方式'
)
code = fields.Char('编码') code = fields.Char('编码')
active = fields.Boolean('有效', default=True) active = fields.Boolean('有效', default=True)
@@ -131,10 +85,6 @@ class BasicParametersFixture(models.Model):
return self._json_adapter_board_fixture_param(fixture_materials_data) return self._json_adapter_board_fixture_param(fixture_materials_data)
elif fixture_materials_name == '三爪卡盘': elif fixture_materials_name == '三爪卡盘':
return self._json_scroll_chuck_param(fixture_materials_data) return self._json_scroll_chuck_param(fixture_materials_data)
elif fixture_materials_name == '磁吸托盘':
return self._json_magnet_tray_param(fixture_materials_data)
elif fixture_materials_name == '气吸托盘':
return self._json_air_tray_param(fixture_materials_data)
return {} return {}
def _json_zero_chuck_param(self, obj): def _json_zero_chuck_param(self, obj):
@@ -334,57 +284,3 @@ class BasicParametersFixture(models.Model):
'centering_diameter': obj['centering_diameter'], 'centering_diameter': obj['centering_diameter'],
'type_of_drive': obj['type_of_drive'], 'type_of_drive': obj['type_of_drive'],
'active': obj['active']} 'active': obj['active']}
def _json_magnet_tray_param(self, obj):
"""磁吸托盘将data数据转换成list数据"""
return {
'code': obj['code'],
'fixture_model_id': self.env['sf.fixture.model'].sudo().search(
[('code', '=', obj.get('fixture_model_code'))]).id,
'name': obj['name'],
'length': obj['length'],
'width': obj['width'],
'height': obj['height'],
'diameter': obj['diameter'],
'weight': obj['weight'],
'max_adsorp_length': obj['max_adsorp_length'],
'max_adsorp_width': obj['max_adsorp_width'],
'max_adsorp_height': obj['max_adsorp_height'],
'max_adsorp_diameter': obj.get('max_adsorp_diameter'),
'max_adsorp_force': obj['max_adsorp_force'],
'flatness': obj.get('flatness'),
'max_load': obj.get('max_load'),
'unlocking_method': obj.get('unlocking_method'),
'materials_model_id': self.env['sf.materials.model'].sudo().search(
[('materials_no', '=', obj['materials_model_id']), ('active', '=', True)]
).id if obj.get('materials_model_id') else False,
'active': obj.get('active', True),
}
def _json_air_tray_param(self, obj):
"""气吸托盘将data数据转换成list数据"""
return {
'code': obj['code'],
'fixture_model_id': self.env['sf.fixture.model'].sudo().search(
[('code', '=', obj.get('fixture_model_code'))]).id,
'name': obj['name'],
'length': obj['length'],
'width': obj['width'],
'height': obj['height'],
'diameter': obj['diameter'],
'weight': obj['weight'],
'max_adsorp_length': obj['max_adsorp_length'],
'max_adsorp_width': obj['max_adsorp_width'],
'max_adsorp_height': obj['max_adsorp_height'],
'max_adsorp_diameter': obj.get('max_adsorp_diameter'),
'max_adsorp_force': obj['max_adsorp_force'],
'flatness': obj.get('flatness'),
'max_load': obj.get('max_load'),
'unlocking_method': obj.get('unlocking_method'),
'boolean_chip_blowing_function': obj.get('blowing_function', False),
'way_to_install': obj.get('way_to_install'),
'materials_model_id': self.env['sf.materials.model'].sudo().search(
[('materials_no', '=', obj['materials_model_id']), ('active', '=', True)]
).id if obj.get('materials_model_id') else False,
'active': obj.get('active', True),
}

View File

@@ -42,7 +42,6 @@ class MrsMaterialModel(models.Model):
materials_num = fields.Char("编码号") materials_num = fields.Char("编码号")
name = fields.Char('型号名') name = fields.Char('型号名')
need_h = fields.Boolean("热处理", default="false") need_h = fields.Boolean("热处理", default="false")
need_m = fields.Boolean("是否磁吸", default="false")
mf_materia_post = fields.Char("热处理后密度") mf_materia_post = fields.Char("热处理后密度")
density = fields.Float("密度(kg/m³)") density = fields.Float("密度(kg/m³)")
materials_id = fields.Many2one('sf.production.materials', "材料名") materials_id = fields.Many2one('sf.production.materials', "材料名")

View File

@@ -35,7 +35,6 @@ class FixtureModel(models.Model):
glb_url = fields.Char(string="图片") glb_url = fields.Char(string="图片")
status = fields.Boolean('状态') status = fields.Boolean('状态')
active = fields.Boolean('有效', default=False) active = fields.Boolean('有效', default=False)
code = fields.Char(string='编码', readonly=True)
zero_chuck_ids = fields.One2many('sf.fixture.materials.basic.parameters', 'fixture_model_id', zero_chuck_ids = fields.One2many('sf.fixture.materials.basic.parameters', 'fixture_model_id',
string='零点卡盘基本参数') string='零点卡盘基本参数')
@@ -47,14 +46,11 @@ class FixtureModel(models.Model):
string='虎钳夹具基本参数') string='虎钳夹具基本参数')
magnet_fixture_ids = fields.One2many('sf.fixture.materials.basic.parameters', 'fixture_model_id', magnet_fixture_ids = fields.One2many('sf.fixture.materials.basic.parameters', 'fixture_model_id',
string='磁吸夹具基本参数') string='磁吸夹具基本参数')
magnet_tray_ids = fields.One2many('sf.fixture.materials.basic.parameters', 'fixture_model_id',
string='磁吸托盘基本参数')
adapter_board_fixture_ids = fields.One2many('sf.fixture.materials.basic.parameters', 'fixture_model_id', adapter_board_fixture_ids = fields.One2many('sf.fixture.materials.basic.parameters', 'fixture_model_id',
string='转接板(锁板)夹具基本参数') string='转接板(锁板)夹具基本参数')
scroll_chuck_ids = fields.One2many('sf.fixture.materials.basic.parameters', 'fixture_model_id', scroll_chuck_ids = fields.One2many('sf.fixture.materials.basic.parameters', 'fixture_model_id',
string='三爪卡盘基本参数') string='三爪卡盘基本参数')
air_tray_ids = fields.One2many('sf.fixture.materials.basic.parameters', 'fixture_model_id', code = fields.Char(string='编码', readonly=True)
string='气吸托盘基本参数')
# def _get_code(self, fixture_model_type_code): # def _get_code(self, fixture_model_type_code):
# fixture_model = self.env['sf.fixture.model'].sudo().search( # fixture_model = self.env['sf.fixture.model'].sudo().search(

View File

@@ -0,0 +1,62 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { barcodeGenericHandlers } from '@barcodes/barcode_handlers';
import { patch } from "@web/core/utils/patch";
// 定义新的 clickOnButton 函数
function customClickOnButton(selector) {
console.log("This is the custom clickOnButton function!");
const buttons = document.body.querySelectorAll(selector);
let length = buttons.length;
if (length > 0) {
buttons[length - 1].click();
} else {
console.warn(`Button with selector ${selector} not found`);
}
}
patch(barcodeGenericHandlers, "start", {
start(env, { ui, barcode, notification }) {
// 使用新定义的 clickOnButton 函数
const COMMANDS = {
"O-CMD.EDIT": () => customClickOnButton(".o_form_button_edit"),
"O-CMD.DISCARD": () => customClickOnButton(".o_form_button_cancel"),
"O-CMD.SAVE": () => customClickOnButton(".o_form_button_save"),
"O-CMD.PREV": () => customClickOnButton(".o_pager_previous"),
"O-CMD.NEXT": () => customClickOnButton(".o_pager_next"),
"O-CMD.PAGER-FIRST": () => updatePager("first"),
"O-CMD.PAGER-LAST": () => updatePager("last"),
"O-CMD.CONFIRM": () => customClickOnButton(".jikimo_button_confirm"),
"O-CMD.FLUSHED": () => customClickOnButton(".jikimo_button_flushed"),
};
barcode.bus.addEventListener("barcode_scanned", (ev) => {
const barcode = ev.detail.barcode;
if (barcode.startsWith("O-BTN.")) {
let targets = [];
try {
targets = getVisibleElements(ui.activeElement, `[barcode_trigger=${barcode.slice(6)}]`);
} catch (_e) {
console.warn(`Barcode '${barcode}' is not valid`);
}
for (let elem of targets) {
elem.click();
}
}
if (barcode.startsWith("O-CMD.")) {
const fn = COMMANDS[barcode];
if (fn) {
fn();
} else {
notification.add(env._t("Barcode: ") + `'${barcode}'`, {
title: env._t("Unknown barcode command"),
type: "danger"
});
}
}
});
}
})

View File

@@ -0,0 +1,42 @@
/** @odoo-module **/
import { registry } from '@web/core/registry';
import { formView } from '@web/views/form/form_view';
import { FormController } from '@web/views/form/form_controller';
import { listView } from '@web/views/list/list_view';
import { ListController } from '@web/views/list/list_controller'
import { onRendered, onMounted } from "@odoo/owl";
export class RemoveFocusFormController extends FormController {
setup() {
super.setup();
onMounted(() => {
this.__owl__.bdom.el.querySelectorAll(':focus').forEach(element => element.blur());
})
}
}
registry.category('views').add('remove_focus_form_view', {
...formView,
Controller: RemoveFocusFormController,
});
export class RemoveFocusListController extends ListController {
setup() {
super.setup();
onMounted(() => {
this.__owl__.bdom.el.querySelectorAll(':focus').forEach(element => element.blur());
})
}
}
registry.category('views').add('remove_focus_list_view', {
...listView,
Controller: RemoveFocusListController,
});

View File

@@ -263,7 +263,6 @@
<field name="materials_no" readonly="1" force_save="1"/> <field name="materials_no" readonly="1" force_save="1"/>
<field name="gain_way" required="0"/> <field name="gain_way" required="0"/>
<field name="density" readonly="1" required="1" class="custom_required"/> <field name="density" readonly="1" required="1" class="custom_required"/>
<field name="need_m" default="false" readonly="1"/>
</group> </group>
<group> <group>
<field name="rough_machining" required="1"/> <field name="rough_machining" required="1"/>
@@ -307,7 +306,6 @@
<field name="tensile_strength"/> <field name="tensile_strength"/>
<field name="hardness" optional="show"/> <field name="hardness" optional="show"/>
<field name="need_h"/> <field name="need_h"/>
<field name="need_m"/>
<field name="apply" widget="many2many_tags" optional="show"/> <field name="apply" widget="many2many_tags" optional="show"/>
<field name="density" optional="show"/> <field name="density" optional="show"/>
<field name="rough_machining" optional="hide"/> <field name="rough_machining" optional="hide"/>
@@ -354,7 +352,6 @@
<field name="materials_no"/> <field name="materials_no"/>
<field name="name"/> <field name="name"/>
<field name="need_h"/> <field name="need_h"/>
<field name="need_m"/>
<field name="mf_materia_post"/> <field name="mf_materia_post"/>
<field name="density"/> <field name="density"/>
<field name='materials_id' default="default" invisible="1"/> <field name='materials_id' default="default" invisible="1"/>

View File

@@ -328,52 +328,6 @@
<field name="type_of_drive"/> <field name="type_of_drive"/>
</tree> </tree>
</field> </field>
<field name="air_tray_ids"
attrs="{'invisible': [('fixture_material_type', '!=', '气吸托盘')]}">
<tree editable="bottom" class="center" delete="0">
<field name="code" invisible="1"/>
<field name="name"/>
<field name="length"/>
<field name="width"/>
<field name="height"/>
<field name="diameter"/>
<field name="weight" string="重量(kg)"/>
<field name="max_adsorp_length"/>
<field name="max_adsorp_width"/>
<field name="max_adsorp_height"/>
<field name="max_adsorp_diameter"/>
<field name="max_adsorp_force"/>
<field name="flatness"/>
<field name="max_load"/>
<field name="unlocking_method"/>
<field name="boolean_chip_blowing_function"/>
<field name="way_to_install"/>
<field name="materials_model_id" options="{'no_create': True}" placeholder="请选择"/>
<field name="active" invisible="1"/>
</tree>
</field>
<field name="magnet_tray_ids"
attrs="{'invisible': [('fixture_material_type', '!=', '磁吸托盘')]}">
<tree editable="bottom" class="center" delete="0">
<field name="code" invisible="1"/>
<field name="name"/>
<field name="length"/>
<field name="width"/>
<field name="height"/>
<field name="diameter"/>
<field name="weight" string="重量(kg)"/>
<field name="max_adsorp_length"/>
<field name="max_adsorp_width"/>
<field name="max_adsorp_height"/>
<field name="max_adsorp_diameter"/>
<field name="max_adsorp_force"/>
<field name="flatness"/>
<field name="max_load"/>
<field name="unlocking_method"/>
<field name="materials_model_id" options="{'no_create': True}" placeholder="请选择"/>
<field name="active" invisible="1"/>
</tree>
</field>
<field name="scroll_chuck_ids" <field name="scroll_chuck_ids"
attrs="{'invisible': [('fixture_material_type', '!=', '三爪卡盘')]}"> attrs="{'invisible': [('fixture_material_type', '!=', '三爪卡盘')]}">
<tree editable="bottom" class="center" delete="0"> <tree editable="bottom" class="center" delete="0">

View File

@@ -2,7 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
{ {
'name': '机企猫智能工厂 需求计划', 'name': '机企猫智能工厂 需求计划',
'version': '1.1', 'version': '1.0',
'summary': '智能工厂计划管理', 'summary': '智能工厂计划管理',
'sequence': 1, 'sequence': 1,
'description': """ 'description': """
@@ -10,17 +10,11 @@
""", """,
'category': 'sf', 'category': 'sf',
'website': 'https://www.sf.jikimo.com', 'website': 'https://www.sf.jikimo.com',
'depends': ['sf_plan','jikimo_printing'], 'depends': ['sf_plan'],
'data': [ 'data': [
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'data/stock_route_group.xml',
'views/demand_plan_info.xml',
'views/demand_plan.xml', 'views/demand_plan.xml',
'views/stock_route.xml',
'views/sale_order_views.xml',
'wizard/sf_demand_plan_print_wizard_view.xml', 'wizard/sf_demand_plan_print_wizard_view.xml',
'wizard/sf_release_plan_wizard_views.xml',
'views/menu_view.xml',
], ],
'demo': [ 'demo': [
], ],
@@ -30,7 +24,6 @@
'web.assets_backend': [ 'web.assets_backend': [
'sf_demand_plan/static/src/scss/style.css', 'sf_demand_plan/static/src/scss/style.css',
'sf_demand_plan/static/src/js/print_demand.js', 'sf_demand_plan/static/src/js/print_demand.js',
'sf_demand_plan/static/src/js/custom_button.js',
] ]
}, },
'license': 'LGPL-3', 'license': 'LGPL-3',

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,11 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from . import sf_demand_plan
from . import sf_production_demand_plan from . import sf_production_demand_plan
from . import sale_order 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

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

@@ -10,65 +10,20 @@ class ReSaleOrder(models.Model):
string='与此销售订单相关联的制造订单', string='与此销售订单相关联的制造订单',
groups='mrp.group_mrp_user', store=True) 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): def sale_order_create_line(self, product, item):
ret = super(ReSaleOrder, self).sale_order_create_line(product, item) ret = super(ReSaleOrder, self).sale_order_create_line(product, item)
vals = { vals = {
'sale_order_id': ret.order_id.id, 'sale_order_id': ret.order_id.id,
'sale_order_line_id': ret.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 = 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: if demand_plan.product_id.machining_drawings_name:
filename_url = demand_plan.product_id.machining_drawings_name.rsplit('.', 1)[0] filename_url = demand_plan.product_id.machining_drawings_name.rsplit('.', 1)[0]
wizard_vals = { wizard_vals = {
'demand_plan_id': demand_plan.id,
'model_id': demand_plan.model_id, 'model_id': demand_plan.model_id,
'filename_url': filename_url, 'filename_url': filename_url,
'machining_drawings': product.machining_drawings,
'type': '1', 'type': '1',
} }
self.env['sf.demand.plan.print.wizard'].sudo().create(wizard_vals) self.env['sf.demand.plan.print.wizard'].sudo().create(wizard_vals)
ret.order_id.demand_plan_ids = [(4, demand_plan_info.id)]
return ret 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,265 +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')
check_overdelivery_allowed = False
for line in line_ids:
if line.location_id.id == customer_location_id:
if not self.overdelivery_allowed:
if float_compare(sum_product_uom_qty, self.product_uom_qty,
precision_rounding=line.product_id.uom_id.rounding) == 1:
check_overdelivery_allowed = True
if check_overdelivery_allowed:
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

File diff suppressed because it is too large Load Diff

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,16 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink 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,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_production_demand_plan_for_dispatch,sf.production.demand.plan for dispatch,model_sf_production_demand_plan,sf_base.group_plan_dispatch,1,1,0,0
access_sf_demand_plan_print_wizard,sf.demand.plan.print.wizard,model_sf_demand_plan_print_wizard,base.group_user,1,0,0,0 access_sf_demand_plan_print_wizard,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_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
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 0 1 0
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_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

View File

@@ -1,59 +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"]');
console.log('isStatus30', isStatus30, button);
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

@@ -81,9 +81,4 @@
input,label { input,label {
cursor: pointer; cursor: pointer;
} }
} }
/*.demand_plan_tree th[data-name=planned_start_date] + th::before{*/
/* content: '待执行单据';*/
/* line-height: 38px;*/
/*}*/

View File

@@ -4,17 +4,14 @@
<field name="model">sf.production.demand.plan</field> <field name="model">sf.production.demand.plan</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="需求计划" default_order="sequence desc,id desc" editable="bottom" <tree string="需求计划" default_order="sequence desc,id desc" editable="bottom"
class="demand_plan_tree freeze-columns-before-part_number" create="false" delete="false" class="demand_plan_tree">
js_class="custom_demand_plan_list">
<header> <header>
<button string="打印" name="button_action_print" type="object" <button string="打印" name="button_action_print" type="object"
class="btn-primary"/> class="btn-primary"/>
<button string="下达计划" name="button_batch_release_plan" type="object"
class="btn-primary"
/>
</header> </header>
<field name="sequence" widget="handle"/> <field name="sequence" widget="handle"/>
<field name="id" optional="hide"/> <field name="id" optional="hide"/>
<field name="priority"/>
<field name="status"/> <field name="status"/>
<field name="customer_name"/> <field name="customer_name"/>
<field name="order_remark"/> <field name="order_remark"/>
@@ -23,24 +20,16 @@
<field name="model_id" optional="hide"/> <field name="model_id" optional="hide"/>
<field name="part_name"/> <field name="part_name"/>
<field name="part_number"/> <field name="part_number"/>
<field name="manual_quotation" optional="hide"/>
<field name="is_incoming_material"/> <field name="is_incoming_material"/>
<field name="new_supply_method" attrs="{'readonly': [('status', '!=', '30')]}"/> <field name="supply_method"/>
<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="product_uom_qty"/>
<field name="plan_uom_qty" attrs="{'readonly': [('status', '!=', '30')]}"/>
<field name="deadline_of_delivery"/> <field name="deadline_of_delivery"/>
<field name="inventory_quantity_auto_apply" optional="hide"/> <field name="inventory_quantity_auto_apply"/>
<field name="qty_delivered" optional="hide"/> <field name="qty_delivered"/>
<field name="qty_to_deliver" optional="hide"/> <field name="qty_to_deliver"/>
<field name="model_long"/> <field name="model_long"/>
<field name="blank_type" optional="hide"/> <field name="blank_type"/>
<field name="blank_precision"/>
<field name="embryo_long"/> <field name="embryo_long"/>
<field name="unit_number" optional="hide"/>
<field name="materials_id"/> <field name="materials_id"/>
<field name="model_machining_precision"/> <field name="model_machining_precision"/>
<field name="model_process_parameters_ids" widget="many2many_tags"/> <field name="model_process_parameters_ids" widget="many2many_tags"/>
@@ -49,49 +38,37 @@
<field name="sale_order_id" optional="hide"/> <field name="sale_order_id" optional="hide"/>
<field name="sale_order_line_number" optional="hide"/> <field name="sale_order_line_number" optional="hide"/>
<field name="order_state"/> <field name="order_state"/>
<field name="route_ids" widget="many2many_tags" optional="hide" <field name="route_id" optional="hide"/>
context="{'demand_plan_search_stock_route_id': id}"/>
<field name="contract_date"/> <field name="contract_date"/>
<field name="date_order"/> <field name="date_order"/>
<field name="contract_code"/> <field name="contract_code"/>
<field name="plan_remark" attrs="{'readonly': [('status', 'in', ('60','100'))]}"/> <field name="plan_remark"/>
<field name="priority" decoration-danger="priority == '1'" <field name="processing_time"/>
decoration-warning="priority == '2'"
decoration-info="priority == '3'"
decoration-success="priority == '4'"/>
<field name="material_check" optional="hide"/> <field name="material_check" optional="hide"/>
<!-- <field name="hide_action_open_mrp_production" invisible="1"/>--> <field name="hide_action_open_mrp_production" invisible="1"/>
<!-- <field name="hide_action_purchase_orders" invisible="1"/>--> <field name="hide_action_purchase_orders" invisible="1"/>
<!-- <field name="hide_action_stock_picking" invisible="1"/>--> <field name="hide_action_stock_picking" invisible="1"/>
<!-- <field name="hide_action_view_programming" invisible="1"/>--> <field name="hide_action_view_programming" invisible="1"/>
<!-- <button name="action_open_sale_order" type="object" string="供货方式待确认" class="btn-secondary"--> <button name="action_open_sale_order" type="object" string="供货方式待确认" class="btn-secondary"
<!-- attrs="{'invisible': [('supply_method', '!=', False)]}"/>--> attrs="{'invisible': [('supply_method', '!=', False)]}"/>
<!-- <button name="action_open_mrp_production" type="object" string="待工艺确认" class="btn-secondary"--> <button name="action_open_mrp_production" type="object" string="待工艺确认" class="btn-secondary"
<!-- attrs="{'invisible': [('hide_action_open_mrp_production', '=', False)]}"/>--> attrs="{'invisible': [('hide_action_open_mrp_production', '=', False)]}"/>
<!-- <button name="action_view_purchase_request" type="object" string="采购申请" class="btn-secondary"--> <button name="action_view_purchase_request" type="object" string="采购申请" class="btn-secondary"
<!-- attrs="{'invisible': [('hide_action_purchase_orders', '=', False)]}"/>--> attrs="{'invisible': [('hide_action_purchase_orders', '=', False)]}"/>
<!-- <button name="action_view_stock_picking" type="object" string="调拨单" class="btn-secondary"--> <button name="action_view_stock_picking" type="object" string="调拨单" class="btn-secondary"
<!-- attrs="{'invisible': [('hide_action_stock_picking', '=', False)]}"/>--> attrs="{'invisible': [('hide_action_stock_picking', '=', False)]}"/>
<!-- <button name="action_view_programming" type="object" string="编程单" class="btn-secondary"--> <button name="action_view_programming" type="object" string="编程单" class="btn-secondary"
<!-- attrs="{'invisible': [('hide_action_view_programming', '=', False)]}"/>--> attrs="{'invisible': [('hide_action_view_programming', '=', False)]}"/>
<field name="planned_start_date" attrs="{'readonly': [('status', 'in', ('60','100'))]}"/> <field name="planned_start_date"/>
<field name="actual_start_date"/> <field name="actual_start_date"/>
<field name="actual_end_date"/> <field name="actual_end_date"/>
<field name="processing_time"/>
<field name="create_date" optional="hide" string="创建时间"/> <field name="create_date" optional="hide" string="创建时间"/>
<field name="create_uid" optional="hide" string="创建人"/> <field name="create_uid" optional="hide" string="创建人"/>
<field name="write_date" string="更新时间"/> <field name="write_date" string="更新时间"/>
<field name="write_uid" optional="hide" string="更新人"/> <field name="write_uid" optional="hide" string="更新人"/>
<field name="print_count"/> <field name="print_count"/>
<field name="hide_release_production_order" invisible="1"/> <button name="release_production_order" type="object" string="下达生产" class="btn-primary"
<button string="下达计划" name="button_release_plan" type="object" attrs="{'invisible': ['|',('status', '!=', '50'), ('supply_method', 'not in', ['automation', 'manual'])]}"/>
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"/>
</tree> </tree>
</field> </field>
</record> </record>
@@ -114,8 +91,7 @@
<group expand="0" string="Group By"> <group expand="0" string="Group By">
<filter name="group_by_priority" string="优先级" domain="[]" context="{'group_by': 'priority'}"/> <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_status" string="状态" domain="[]" context="{'group_by': 'status'}"/>
<filter name="group_by_customer_name" string="客户" domain="[]" <filter name="group_by_customer_name" string="客户" domain="[]" context="{'group_by': 'customer_name'}"/>
context="{'group_by': 'customer_name'}"/>
<filter name="group_by_is_incoming_material" string="客供料" domain="[]" <filter name="group_by_is_incoming_material" string="客供料" domain="[]"
context="{'group_by': 'is_incoming_material'}"/> context="{'group_by': 'is_incoming_material'}"/>
<filter name="group_by_supply_method" string="供货方式" domain="[]" <filter name="group_by_supply_method" string="供货方式" domain="[]"
@@ -138,4 +114,12 @@
<field name="view_mode">tree</field> <field name="view_mode">tree</field>
</record> </record>
<menuitem
id="demand_plan_menu"
name="需求计划"
sequence="140"
action="sf_production_demand_plan_action"
parent="sf_plan.sf_production_plan_menu"
/>
</odoo> </odoo>

View File

@@ -1,123 +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"
/>
</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,2 +1 @@
from . import sf_demand_plan_print_wizard from . import sf_demand_plan_print_wizard
from . import sf_release_plan_wizard

View File

@@ -9,6 +9,11 @@ class SfDemandPlanPrintWizard(models.TransientModel):
_name = 'sf.demand.plan.print.wizard' _name = 'sf.demand.plan.print.wizard'
_description = u'打印向导' _description = u'打印向导'
demand_plan_id = fields.Many2one('sf.production.demand.plan', string='需求计划ID')
product_id = fields.Many2one(
comodel_name='product.product',
related='demand_plan_id.product_id',
string='产品', store=True, index=True)
model_id = fields.Char('模型ID') model_id = fields.Char('模型ID')
filename_url = fields.Char('文件名/URL') filename_url = fields.Char('文件名/URL')
type = fields.Selection([ type = fields.Selection([
@@ -20,7 +25,7 @@ class SfDemandPlanPrintWizard(models.TransientModel):
('success', '成功'), ('success', '成功'),
('fail', '失败'), ('fail', '失败'),
], string='状态', default='not_start') ], string='状态', default='not_start')
machining_drawings = fields.Binary('2D加工图纸') machining_drawings = fields.Binary('2D加工图纸', related='product_id.machining_drawings', store=True)
cnc_worksheet = fields.Binary('程序单') cnc_worksheet = fields.Binary('程序单')
@@ -37,19 +42,16 @@ class SfDemandPlanPrintWizard(models.TransientModel):
if pdf_data: if pdf_data:
try: try:
# 执行打印 # 执行打印
# self.env['jikimo.printing'].sudo().print_pdf(pdf_data) self.env['jikimo.printing'].sudo().print_pdf(pdf_data)
record.status = 'success' record.status = 'success'
production_demand_plan_id = self.env['sf.production.demand.plan'].sudo().search( t_part, c_part = record.demand_plan_id.print_count.split('C')
[('model_id', '=', record.model_id)]) t_num = int(t_part[1:])
for production_demand_plan in production_demand_plan_id: c_num = int(c_part)
t_part, c_part = production_demand_plan.print_count.split('C') if record.type == '1':
t_num = int(t_part[1:]) t_num += 1
c_num = int(c_part) elif record.type == '2':
if record.type == '1': c_num += 1
t_num += 1 record.demand_plan_id.print_count = f"T{t_num}C{c_num}"
elif record.type == '2':
c_num += 1
production_demand_plan.print_count = f"T{t_num}C{c_num}"
success_records.append({ success_records.append({
'filename_url': record.filename_url, 'filename_url': record.filename_url,
}) })
@@ -76,14 +78,18 @@ class MrpWorkorder(models.Model):
demand_plan_print = self.env['sf.demand.plan.print.wizard'].sudo().search( demand_plan_print = self.env['sf.demand.plan.print.wizard'].sudo().search(
[('model_id', '=', record.model_id), ('type', '=', '2')]) [('model_id', '=', record.model_id), ('type', '=', '2')])
if demand_plan_print: if demand_plan_print:
demand_plan_print.write( self.env['sf.demand.plan.print.wizard'].sudo().write(
{'cnc_worksheet': record.cnc_worksheet, 'filename_url': record.cnc_worksheet_name}) {'cnc_worksheet': record.cnc_worksheet, 'filename_url': record.cnc_worksheet_name})
else: else:
wizard_vals = { demand_plan = self.env['sf.production.demand.plan'].sudo().search(
'model_id': record.model_id, [('product_id', '=', record.product_id.id)])
'type': '2', if demand_plan:
'cnc_worksheet': record.cnc_worksheet, wizard_vals = {
'filename_url': record.cnc_worksheet_name 'demand_plan_id': demand_plan.id,
} 'model_id': demand_plan.model_id,
self.env['sf.demand.plan.print.wizard'].sudo().create(wizard_vals) '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 return res

View File

@@ -4,12 +4,12 @@
<field name="name">sf.demand.plan.print.wizard.tree</field> <field name="name">sf.demand.plan.print.wizard.tree</field>
<field name="model">sf.demand.plan.print.wizard</field> <field name="model">sf.demand.plan.print.wizard</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="打印" class="print_demand" js_class="print_demand"> <tree string="打印" class="print_demand" js_class="print_demand" >
<field name="model_id"/> <field name="model_id"/>
<field name="filename_url"/> <field name="filename_url"/>
<field name="type"/> <field name="type"/>
<field name="machining_drawings" attrs="{'column_invisible': True }"/> <field name="machining_drawings" attrs="{'column_invisible': True }"/>
<field name="cnc_worksheet" attrs="{'column_invisible': True }"/> <field name="cnc_worksheet" attrs="{'column_invisible': True }" />
<field name="status"/> <field name="status"/>
</tree> </tree>
</field> </field>

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 import re
from odoo import models, fields, api from odoo import models, fields, api
from odoo.exceptions import ValidationError
class ResProductCategory(models.Model): class ResProductCategory(models.Model):
@@ -48,14 +47,11 @@ class ResMrpBomMo(models.Model):
item.subcontractor_name = '' item.subcontractor_name = ''
def bom_create_line_has(self, embryo): 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 = { vals = {
'bom_id': self.id, 'bom_id': self.id,
'product_id': embryo.id, 'product_id': embryo.id,
'product_tmpl_id': embryo.product_tmpl_id.id, 'product_tmpl_id': embryo.product_tmpl_id.id,
'product_qty': product.unit_number, 'product_qty': 1,
'product_uom_id': 1 'product_uom_id': 1
} }
return self.env['mrp.bom.line'].sudo().create(vals) return self.env['mrp.bom.line'].sudo().create(vals)
@@ -126,7 +122,7 @@ class ResMrpBomMo(models.Model):
# 查bom的原材料 # 查bom的原材料
def get_raw_bom(self, product): def get_raw_bom(self, product):
raw_bom = self.env['product.product'].search( 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 return raw_bom

View File

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

View File

@@ -95,8 +95,6 @@
<page string="加工参数"> <page string="加工参数">
<group> <group>
<group string="模型"> <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"> <div class="o_address_format">
<label for="model_long" string="长"/> <label for="model_long" string="长"/>
@@ -106,7 +104,7 @@
<label for="model_height" string="高"/> <label for="model_height" string="高"/>
<field name="model_height" class="o_address_zip"/> <field name="model_height" class="o_address_zip"/>
</div> </div>
<field name="unit_number" readonly="1"/> <field name="blank_type" readonly="1"/>
<field name="model_volume" string="体积[mm³]"/> <field name="model_volume" string="体积[mm³]"/>
<field name="product_model_type_id" string="模型类型"/> <field name="product_model_type_id" string="模型类型"/>
<field name="model_processing_panel" placeholder="例如R,U" string="加工面板" <field name="model_processing_panel" placeholder="例如R,U" string="加工面板"
@@ -513,168 +511,112 @@
</notebook> </notebook>
</page> </page>
<page string="夹具物料参数" attrs="{'invisible': [('categ_type', '!=', '夹具')]}"> <page string="夹具物料参数" attrs="{'invisible': [('categ_type', '!=', '夹具')]}">
<group>
<group> <group>
<group> <field name="brand_id" placeholder="请选择" options="{'no_create': True}"/>
<field name="brand_id" placeholder="请选择" options="{'no_create': True}"/> <field name="multi_mounting_type_id" placeholder="请选择" options="{'no_create': True}"
<field name="multi_mounting_type_id" placeholder="请选择" options="{'no_create': True}" attrs="{'required': [('categ_type', '=', '夹具')]}"/>
attrs="{'required': [('categ_type', '=', '夹具')]}"/> <field name="length" string="长度(mm)"/>
<field name="length" string="度(mm)"/> <field name="width" string="度(mm)"/>
<field name="width" string="度(mm)"/> <field name="height" string="度(mm)"/>
<field name="height" string="高度(mm)"/> <field name="height_tolerance_value"
<field name="height_tolerance_value" attrs="{'invisible': [('fixture_material_type', 'not in', ['磁吸夹具'])]}"/>
attrs="{'invisible': [('fixture_material_type', 'not in', ['磁吸夹具'])]}"/> <field name="diameter"
<field name="diameter" attrs="{'invisible': [('fixture_material_type', 'not in', ['零点卡盘', '零点托盘', '三爪卡盘'])]}"/>
attrs="{'invisible': [('fixture_material_type', 'not in', ['零点卡盘', '零点托盘', '三爪卡盘', '磁吸托盘', '气吸托盘'])]}"/> <field name="weight"/>
<field name="weight"/> <field name="chucking_power_max"
<field name="chucking_power_max" attrs="{'invisible': [('fixture_material_type', '=','磁吸夹具')]}"/>
attrs="{'invisible': [('fixture_material_type', '=','磁吸夹具')]}"/> <field name="carrying_capacity_max"/>
<field name="carrying_capacity_max" <field name="rigidity"/>
attrs="{'invisible': [('fixture_material_type', 'not in', ['磁吸夹具'])]}"/>
<field name="rigidity"/>
</group>
<group>
<!-- 夹持工件尺寸 -->
<label for="gripper_length_min" string="夹持工件最小尺寸"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气动夹具','虎钳夹具','磁吸夹具','转接板(锁板)夹具','三爪卡盘'])]}"/>
<div class="o_address_format"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气动夹具','虎钳夹具','磁吸夹具','转接板(锁板)夹具','三爪卡盘'])]}">
<label for="gripper_length_min" string="长"/>
<field name="gripper_length_min" class="o_address_zip"/>
<span>&amp;nbsp;</span>
<label for="gripper_width_min" string="宽"/>
<field name="gripper_width_min" class="o_address_zip"/>
<span>&amp;nbsp;</span>
<label for="gripper_height_min" string="高"/>
<field name="gripper_height_min" class="o_address_zip"/>
</div>
<label for="gripper_length_max" string="夹持工件最大尺寸"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气动夹具','虎钳夹具','磁吸夹具','转接板(锁板)夹具','三爪卡盘'])]}"/>
<div class="o_address_format"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气动夹具','虎钳夹具','磁吸夹具','转接板(锁板)夹具','三爪卡盘'])]}">
<label for="gripper_length_max" string="长"/>
<field name="gripper_length_max" class="o_address_zip"/>
<span>&amp;nbsp;</span>
<label for="gripper_width_max" string="宽"/>
<field name="gripper_width_max" class="o_address_zip"/>
<span>&amp;nbsp;</span>
<label for="gripper_height_max" string="高"/>
<field name="gripper_height_max" class="o_address_zip"/>
</div>
<field name="gripper_diameter_min" string="夹持工件最小直径(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气动夹具','虎钳夹具','磁吸夹具','转接板(锁板)夹具','三爪卡盘'])]}"/>
<field name="gripper_diameter_max" string="夹持工件最大直径(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气动夹具','虎钳夹具','磁吸夹具','转接板(锁板)夹具','三爪卡盘'])]}"/>
<field name="clamping_diameter" string="装夹直径(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['零点卡盘','零点托盘'])]}"/>
<field name="clamping_num" placeholder="请选择" string="装夹单元数"
attrs="{'invisible': [('fixture_material_type', '!=', '零点卡盘')]}"/>
<field name="repeated_positioning_accuracy" placeholder="请输入重复定位孔精度"
string="重复定位精度(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['零点卡盘','零点托盘'])]}"/>
<field name="orientation_dish_diameter" string="定位盘直径(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['零点卡盘'])]}"/>
<field name="boolean_transposing_hole" string="是否有转位孔"
attrs="{'invisible': [('fixture_material_type', 'not in', ['零点卡盘'])]}"/>
<field name="connector_diameter" placeholder="请选择" string="连接头直径(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['零点托盘'])]}"/>
<field name="way_to_install" placeholder="请选择" string="安装方式"
attrs="{'invisible': [('fixture_material_type', 'not in', ['零点托盘','气吸托盘'])]}"/>
<field name="rated_air_pressure" string="额定气压(Mpa)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气动夹具'])]}"/>
<field name="transverse_groove" string="横向配合槽n(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['虎钳夹具'])]}"/>
<field name="longitudinal_fitting_groove" string="纵向配合槽l(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['虎钳夹具'])]}"/>
<field name="rated_adsorption_force" string="额定吸附力(N/cm²)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['磁吸夹具'])]}"/>
<field name="magnetic_field_height" string="磁场高度(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['磁吸夹具'])]}"/>
<field name="magnetic_pole_plate_grinding_allowance" string="磁极板磨削余量(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['磁吸夹具'])]}"/>
<!-- 磁吸托盘字段 -->
<field name="magnet_tray_length" string="磁吸托盘长度(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['磁吸托盘'])]}"/>
<field name="magnet_tray_width" string="磁吸托盘宽度(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['磁吸托盘'])]}"/>
<field name="magnet_tray_height" string="磁吸托盘厚度(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['磁吸托盘'])]}"/>
<field name="magnet_tray_diameter" string="磁吸托盘直径(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['磁吸托盘'])]}"/>
<field name="magnet_tray_weight" string="磁吸托盘重量(kg)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['磁吸托盘'])]}"/>
<field name="magnet_max_adsorp_length" string="磁吸托盘最大吸附长度(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['磁吸托盘'])]}"/>
<field name="magnet_max_adsorp_width" string="磁吸托盘最大吸附宽度(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['磁吸托盘'])]}"/>
<field name="magnet_max_adsorp_height" string="磁吸托盘最大吸附厚度(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['磁吸托盘'])]}"/>
<field name="magnet_max_adsorp_diameter" string="磁吸托盘最大吸附直径(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['磁吸托盘'])]}"/>
<field name="magnet_max_adsorp_force" string="磁吸托盘最大吸附力(N)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['磁吸托盘'])]}"/>
<field name="magnet_unlocking_method" string="磁吸托盘锁紧方式"
attrs="{'invisible': [('fixture_material_type', 'not in', ['磁吸托盘'])]}"/>
<field name="magnet_flatness" string="磁吸托盘平面精度(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['磁吸托盘'])]}"/>
<field name="magnet_max_load" string="磁吸托盘最大负载(kg)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['磁吸托盘'])]}"/>
<!-- 气吸托盘字段 -->
<field name="air_tray_length" string="气吸托盘长度(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气吸托盘'])]}"/>
<field name="air_tray_width" string="气吸托盘宽度(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气吸托盘'])]}"/>
<field name="air_tray_height" string="气吸托盘高度(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气吸托盘'])]}"/>
<field name="air_tray_diameter" string="气吸托盘直径(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气吸托盘'])]}"/>
<field name="air_tray_weight" string="气吸托盘重量(kg)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气吸托盘'])]}"/>
<field name="air_max_adsorp_length" string="气吸托盘最大吸附长度(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气吸托盘'])]}"/>
<field name="air_max_adsorp_width" string="气吸托盘最大吸附宽度(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气吸托盘'])]}"/>
<field name="air_max_adsorp_height" string="气吸托盘最大吸附高度(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气吸托盘'])]}"/>
<field name="air_max_adsorp_diameter" string="气吸托盘最大吸附直径(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气吸托盘'])]}"/>
<field name="air_max_adsorp_force" string="气吸托盘最大吸附力(N)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气吸托盘'])]}"/>
<field name="air_unlocking_method" string="气吸托盘锁紧方式"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气吸托盘'])]}"/>
<field name="air_flatness" string="气吸托盘平面精度(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气吸托盘'])]}"/>
<field name="air_max_load" string="气吸托盘最大负载(kg)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气吸托盘'])]}"/>
<field name="air_boolean_chip_blowing_function" string="气吸托盘是否有吹屑功能"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气吸托盘'])]}"/>
<field name="air_way_to_install" string="气吸托盘安装方式"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气吸托盘'])]}"/>
<field name="boolean_chip_blowing_function" string="是否有吹屑功能"
attrs="{'invisible': [('fixture_material_type', 'not in', ['零点卡盘','零点托盘','气吸托盘'])]}"/>
<field name="materials_model_id" placeholder="请选择" options="{'no_create': True}"/>
<field name="interface_materials_model_id" placeholder="请选择" string="接口类型"
options="{'no_create': True}"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气动夹具','虎钳夹具','磁吸夹具'])]}"/>
<field name="type_of_drive" placeholder="请选择" string="驱动方式"
attrs="{'invisible': [('fixture_material_type', 'not in', ['零点托盘','气动夹具','虎钳夹具','磁吸夹具','转接板(锁板)夹具','三爪卡盘'])]}"/>
<field name="unlocking_method" string="解锁方式" placeholder="请选择"
attrs="{'invisible': [('fixture_material_type', 'not in', ['零点卡盘','气吸托盘', '磁吸托盘'])]}"/>
<field name="machine_tool_type_id" string="适用机床型号" placeholder="请选择"
options="{'no_create': True}"
attrs="{'invisible': [('fixture_material_type', 'not in', ['零点卡盘'])]}"/>
</group>
</group> </group>
</page> <group>
<label for="gripper_length_min" string="夹持工件最小尺寸"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气动夹具','虎钳夹具','磁吸夹具','转接板(锁板)夹具','三爪卡盘'])]}"/>
<div class="o_address_format"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气动夹具','虎钳夹具','磁吸夹具','转接板(锁板)夹具','三爪卡盘'])]}">
<label for="gripper_length_min" string="长"/>
<field name="gripper_length_min" class="o_address_zip"/>
<span>&amp;nbsp;</span>
<label for="gripper_width_min" string="宽"/>
<field name="gripper_width_min" class="o_address_zip"/>
<span>&amp;nbsp;</span>
<label for="gripper_height_min" string="高"/>
<field name="gripper_height_min" class="o_address_zip"/>
</div>
<label for="gripper_length_max" string="夹持工件最大尺寸"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气动夹具','虎钳夹具','磁吸夹具','转接板(锁板)夹具','三爪卡盘'])]}"/>
<div class="o_address_format"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气动夹具','虎钳夹具','磁吸夹具','转接板(锁板)夹具','三爪卡盘'])]}">
<label for="gripper_length_max" string="长"/>
<field name="gripper_length_max" class="o_address_zip"/>
<span>&amp;nbsp;</span>
<label for="gripper_width_max" string="宽"/>
<field name="gripper_width_max" class="o_address_zip"/>
<span>&amp;nbsp;</span>
<label for="gripper_width_max" string="高"/>
<field name="gripper_width_max" class="o_address_zip"/>
</div>
<field name="gripper_diameter_min" string="夹持工件最小直径(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气动夹具','虎钳夹具','磁吸夹具','转接板(锁板)夹具','三爪卡盘'])]}"/>
<field name="gripper_diameter_max" string="夹持工件最大直径(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气动夹具','虎钳夹具','磁吸夹具','转接板(锁板)夹具','三爪卡盘'])]}"/>
<field name="clamping_diameter" string="装夹直径(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['零点卡盘','零点托盘'])]}"/>
<field name="clamping_num" placeholder="请选择" string="装夹单元数"
attrs="{'invisible': [('fixture_material_type', '!=', '零点卡盘')]}"/>
<field name="repeated_positioning_accuracy" placeholder="请输入重复定位孔精度"
string="重复定位精度(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['零点卡盘','零点托盘'])]}"/>
<field name="orientation_dish_diameter" string="定位盘直径(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['零点卡盘'])]}"/>
<field name="boolean_transposing_hole" string="是否有转位孔"
attrs="{'invisible': [('fixture_material_type', 'not in', ['零点卡盘'])]}"/>
<field name="connector_diameter" placeholder="请选择" string="连接头直径(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['零点托盘'])]}"/>
<field name="way_to_install" placeholder="请选择" string="安装方式"
attrs="{'invisible': [('fixture_material_type', 'not in', ['零点托盘'])]}"/>
<field name="rated_air_pressure" string="额定气压(Mpa)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气动夹具'])]}"/>
<field name="transverse_groove" string="横向配合槽n(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['虎钳夹具'])]}"/>
<field name="longitudinal_fitting_groove" string="纵向配合槽l(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['虎钳夹具'])]}"/>
<field name="rated_adsorption_force" string="额定吸附力(N/cm²)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['磁吸夹具'])]}"/>
<field name="magnetic_field_height" string="磁场高度(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['磁吸夹具'])]}"/>
<field name="magnetic_pole_plate_grinding_allowance" string="磁极板磨削余量(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['磁吸夹具'])]}"/>
<field name="screw_size" string="螺牙大小(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['转接板(锁板)夹具'])]}"/>
<field name="via_hole_diameter" string="过孔直径(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['转接板(锁板)夹具'])]}"/>
<field name="mounting_hole_depth" string="安装孔深度(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['三爪卡盘'])]}"/>
<field name="centering_diameter" string="定心直径(mm)"
attrs="{'invisible': [('fixture_material_type', 'not in', ['三爪卡盘'])]}"/>
<field name="boolean_chip_blowing_function" string="是否有吹屑功能"
attrs="{'invisible': [('fixture_material_type', 'not in', ['零点卡盘','零点托盘'])]}"/>
<field name="materials_model_id" placeholder="请选择" options="{'no_create': True}"/>
<field name="interface_materials_model_id" placeholder="请选择" string="接口类型"
options="{'no_create': True}"
attrs="{'invisible': [('fixture_material_type', 'not in', ['气动夹具','虎钳夹具','磁吸夹具'])]}"/>
<field name="type_of_drive" placeholder="请选择" string="驱动方式"
attrs="{'invisible': [('fixture_material_type', 'not in', ['零点托盘','气动夹具','虎钳夹具','磁吸夹具','转接板(锁板)夹具','三爪卡盘'])]}"/>
<field name="unlocking_method" string="解锁方式" placeholder="请选择"
attrs="{'invisible': [('fixture_material_type', 'not in', ['零点卡盘'])]}"/>
<field name="machine_tool_type_id" string="适用机床型号" placeholder="请选择"
options="{'no_create': True}"
attrs="{'invisible': [('fixture_material_type', 'not in', ['零点卡盘'])]}"/>
</group>
</group>
</page>
</xpath> </xpath>
</field> </field>
</record> </record>

View File

@@ -437,7 +437,7 @@ class Sf_Dashboard_Connect(http.Controller):
('state', 'in', ['ready', 'progress', 'done']) ('state', 'in', ['ready', 'progress', 'done'])
]) ])
plan_data_total_counts = sum(plan_data_total.mapped('qty_production')) plan_data_total_counts = sum(plan_data_total.mapped('qty_produced'))
# # 工单完成量 # # 工单完成量
# plan_data_finish_counts = plan_obj.search_count( # plan_data_finish_counts = plan_obj.search_count(
@@ -489,7 +489,7 @@ class Sf_Dashboard_Connect(http.Controller):
# 工单返工数量 # 工单返工数量
plan_data_rework = work_order_obj.search(work_order_domain + [ plan_data_rework = work_order_obj.search(plan_domain + [
('state', 'in', ['rework']) ('state', 'in', ['rework'])
]) ])
@@ -601,11 +601,8 @@ class Sf_Dashboard_Connect(http.Controller):
line_list = ast.literal_eval(kw['line_list']) line_list = ast.literal_eval(kw['line_list'])
begin_time_str = kw['begin_time'].strip('"') begin_time_str = kw['begin_time'].strip('"')
end_time_str = kw['end_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')
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')
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')
# print('line_list: %s' % line_list) # print('line_list: %s' % line_list)
print('kw', kw) print('kw', kw)
time_unit = kw.get('time_unit', 'day').strip('"') # 默认单位为天 time_unit = kw.get('time_unit', 'day').strip('"') # 默认单位为天
@@ -639,15 +636,6 @@ class Sf_Dashboard_Connect(http.Controller):
if time_unit == 'hour': if time_unit == 'hour':
# 计划量目前只能从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: for line in line_list:
date_field_name = 'date_finished' # 替换为你模型中的实际字段名 date_field_name = 'date_finished' # 替换为你模型中的实际字段名
@@ -690,10 +678,19 @@ class Sf_Dashboard_Connect(http.Controller):
) )
# 使用小时和分钟作为键,确保每个小时的数据有独立的键 # 使用小时和分钟作为键,确保每个小时的数据有独立的键
key = (start_time + timedelta(hours=8)).strftime('%H:%M:%S') # 只取小时:分钟:秒作为键 key = start_time.strftime('%H:%M:%S') # 只取小时:分钟:秒作为键
# time_count_dict[key] = len(orders) # time_count_dict[key] = len(orders)
time_count_dict[key] = sum(interval_orders.mapped('qty_produced')) time_count_dict[key] = sum(interval_orders.mapped('qty_produced'))
# 计划量目前只能从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 time_interval in time_intervals: for time_interval in time_intervals:
start_time, end_time = time_interval start_time, end_time = time_interval
@@ -707,11 +704,9 @@ class Sf_Dashboard_Connect(http.Controller):
interval_plan_orders = plan_order_messages.filtered( interval_plan_orders = plan_order_messages.filtered(
lambda o: o.create_date >= start_time lambda o: o.create_date >= start_time
and o.create_date <= end_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_plan_orders.mapped('res_id'))
interval_orders = request.env['mrp.workorder'].sudo().browse(interval_order_ids)
if line == '业绩总览': if line == '业绩总览':
interval_orders = interval_orders.filtered(lambda o: o.routing_type in ['人工线下加工', 'CNC加工']) interval_orders = interval_orders.filtered(lambda o: o.routing_type in ['人工线下加工', 'CNC加工'])
elif line == '人工线下加工中心': elif line == '人工线下加工中心':
@@ -720,9 +715,9 @@ class Sf_Dashboard_Connect(http.Controller):
interval_orders = interval_orders.filtered(lambda o: o.routing_type == 'CNC加工' and o.production_line_id.name == line) 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') # 只取小时:分钟:秒作为键 key = start_time.strftime('%H:%M:%S') # 只取小时:分钟:秒作为键
# time_count_dict[key] = len(orders) # time_count_dict[key] = len(orders)
plan_count_dict[key] = sum(interval_orders.mapped('qty_production')) plan_count_dict[key] = sum(interval_orders.mapped('qty_produced'))
# order_counts.append() # order_counts.append()
res['data'][line] = { res['data'][line] = {
@@ -862,39 +857,24 @@ class Sf_Dashboard_Connect(http.Controller):
:param kw: :param kw:
:return: :return:
""" """
request.env['stock.warehouse'].browse(request.env.company.id).pbm_loc_id
# res = {'status': 1, 'message': '成功', 'not_done_data': [], 'done_data': []} # res = {'status': 1, 'message': '成功', 'not_done_data': [], 'done_data': []}
res = {'status': 1, 'message': '成功', 'data': {}} res = {'status': 1, 'message': '成功', 'data': {}}
# 解决产品名称取到英文的问题
request.update_context(lang='zh_CN')
plan_obj = request.env['sf.production.plan'].sudo() plan_obj = request.env['sf.production.plan'].sudo()
work_order_obj = request.env['mrp.workorder'].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']) line_list = ast.literal_eval(kw['line_list'])
begin_time_str = kw['begin_time'].strip('"') begin_time_str = kw['begin_time'].strip('"')
end_time_str = kw['end_time'].strip('"') end_time_str = kw['end_time'].strip('"')
begin_time = datetime.strptime(begin_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') end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
# print('line_list: %s' % line_list) # print('line_list: %s' % line_list)
not_done_data = []
done_data = []
final_data = {} final_data = {}
not_done_index = 1
# 获取当前时间并计算24小时前的时间 done_index = 1
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: for line in line_list:
not_done_data = []
done_data = []
not_done_index = 1
done_index = 1
if line == '业绩总览': if line == '业绩总览':
work_order_domain = [('routing_type', 'in', ['人工线下加工', 'CNC加工'])] work_order_domain = [('routing_type', 'in', ['人工线下加工', 'CNC加工'])]
@@ -910,24 +890,21 @@ class Sf_Dashboard_Connect(http.Controller):
# [('production_line_id.name', '=', line), ('state', 'not in', ['finished']), # [('production_line_id.name', '=', line), ('state', 'not in', ['finished']),
# ('production_id.state', 'not in', ['cancel', 'done']), ('active', '=', True) # ('production_id.state', 'not in', ['cancel', 'done']), ('active', '=', True)
# ]) # ])
not_done_orders = work_order_obj.search(work_order_domain + [ not_done_orders = work_order_obj.search(work_order_domain +
('state', 'in', ['ready', 'progress']), [('state', 'in', ['ready', 'progress'])], order='id asc'
('date_planned_start', '>=', time_48_hours_ago),
('date_planned_start', '<=', current_time)
], order='id asc'
) )
# 完成订单 # 完成订单
# 获取当前时间并计算24小时前的时间 # 获取当前时间并计算24小时前的时间
# current_time = datetime.now() current_time = datetime.now()
# time_24_hours_ago = current_time - timedelta(hours=24) time_24_hours_ago = current_time - timedelta(hours=24)
finish_orders = work_order_obj.search(work_order_domain + [ finish_orders = work_order_obj.search(work_order_domain + [
('state', 'in', ['done']), ('state', 'in', ['finished']),
('production_id.state', 'not in', ['cancel']), ('production_id.state', 'not in', ['cancel']),
('date_finished', '>=', time_48_hours_ago) ('date_finished', '>=', time_24_hours_ago)
], order='id asc') ], order='id asc')
# logging.info('完成订单: %s' % finish_orders) # print(finish_orders)
# 获取所有未完成订单的ID列表 # 获取所有未完成订单的ID列表
order_ids = [order.id for order in not_done_orders] order_ids = [order.id for order in not_done_orders]
@@ -963,6 +940,14 @@ class Sf_Dashboard_Connect(http.Controller):
material_match = re.search(material_pattern, blank_name) material_match = re.search(material_pattern, blank_name)
material = material_match.group(1) if material_match else 'No match found' material = material_match.group(1) if material_match else 'No match found'
state_dict = {
'draft': '待排程',
'done': '已排程',
'processing': '生产中',
'finished': '已完成',
'ready': '待加工',
'progress': '生产中',
}
line_dict = { line_dict = {
'sequence': not_done_index, 'sequence': not_done_index,
@@ -978,6 +963,8 @@ class Sf_Dashboard_Connect(http.Controller):
not_done_index += 1 not_done_index += 1
for finish_order in finish_orders: for finish_order in finish_orders:
if not finish_order.actual_end_time:
continue
blank_name = '' blank_name = ''
try: try:
blank_name = finish_order.production_id.move_raw_ids[0].product_id.name blank_name = finish_order.production_id.move_raw_ids[0].product_id.name
@@ -993,13 +980,13 @@ class Sf_Dashboard_Connect(http.Controller):
line_dict = { line_dict = {
'sequence': done_index, 'sequence': done_index,
'workorder_name': finish_order.production_id.name, 'workorder_name': finish_order.name,
'blank_name': blank_name, 'blank_name': blank_name,
'material': material, 'material': material,
'dimensions': dimensions, 'dimensions': dimensions,
'order_qty': finish_order.qty_produced, 'order_qty': order.qty_produced,
'finish_time': finish_order.date_finished.strftime( 'finish_time': finish_order.actual_end_time.strftime(
'%Y-%m-%d %H:%M:%S') if finish_order.date_finished else ' ' '%Y-%m-%d %H:%M:%S') if finish_order.actual_end_time else ' '
} }
done_data.append(line_dict) done_data.append(line_dict)

View File

@@ -11,7 +11,7 @@
'category': 'sf', 'category': 'sf',
'website': 'https://www.sf.jikimo.com', 'website': 'https://www.sf.jikimo.com',
'depends': ['sf_base', 'sf_maintenance', 'web_widget_model_viewer', 'sf_warehouse', 'jikimo_attachment_viewer', 'depends': ['sf_base', 'sf_maintenance', 'web_widget_model_viewer', 'sf_warehouse', 'jikimo_attachment_viewer',
'jikimo_sale_multiple_supply_methods', 'product', 'jikimo_tree_header_buttons_always_visible'], 'jikimo_sale_multiple_supply_methods', 'product'],
'data': [ 'data': [
'data/cron_data.xml', 'data/cron_data.xml',
'data/stock_data.xml', 'data/stock_data.xml',

View File

@@ -18,5 +18,4 @@ from . import quick_easy_order
from . import purchase_order from . import purchase_order
from . import quality_check from . import quality_check
from . import purchase_request_line from . import purchase_request_line
from . import bom
# from . import stock_warehouse_orderpoint # 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

@@ -1709,7 +1709,6 @@ class MrpProduction(models.Model):
vals['procurement_group_id'] = product_group_id[product_id.id] vals['procurement_group_id'] = product_group_id[product_id.id]
else: else:
vals['procurement_group_id'] = is_custemer_group_id[key] vals['procurement_group_id'] = is_custemer_group_id[key]
return super(MrpProduction, self).create(vals_list) return super(MrpProduction, self).create(vals_list)
@api.depends('procurement_group_id.stock_move_ids.created_purchase_line_id.order_id', @api.depends('procurement_group_id.stock_move_ids.created_purchase_line_id.order_id',

View File

@@ -27,15 +27,13 @@ class ResProductMo(models.Model):
categ_type = fields.Selection(string='产品的类别', related='categ_id.type', store=True) categ_type = fields.Selection(string='产品的类别', related='categ_id.type', store=True)
model_name = fields.Char('模型名称') model_name = fields.Char('模型名称')
blank_type = fields.Selection([('圆料', '圆料'), ('方料', '方料')], string='坯料分类') blank_type = fields.Selection([('圆料', '圆料'), ('方料', '方料')], string='坯料分类')
blank_precision = fields.Selection([('精坯', '精坯'), ('粗坯', '粗坯')], string='坯料类型')
model_long = fields.Float('模型长(mm)', digits=(16, 3)) model_long = fields.Float('模型长(mm)', digits=(16, 3))
model_width = fields.Float('模型宽(mm)', digits=(16, 3)) model_width = fields.Float('模型宽(mm)', digits=(16, 3))
model_height = 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_volume = fields.Float('模型体积(m³)')
model_area = fields.Float('模型表面积(m²)') model_area = fields.Float('模型表面积(m²)')
model_machining_precision = fields.Selection(selection=_get_machining_precision, string='加工精度') 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('模型备注说明') model_remark = fields.Char('模型备注说明')
length = fields.Float('长(mm)', digits=(16, 3)) length = fields.Float('长(mm)', digits=(16, 3))
width = fields.Float('宽(mm)', digits=(16, 3)) width = fields.Float('宽(mm)', digits=(16, 3))
@@ -903,17 +901,15 @@ class ResProductMo(models.Model):
vals = { vals = {
'name': product_name, 'name': product_name,
'blank_type': item.get('blank_type'), '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_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_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), '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) * 'model_volume': self.format_float(((item['model_long'] + embryo_redundancy_id.long) *
(item['model_width'] + embryo_redundancy_id.width) * (item['model_width'] + embryo_redundancy_id.width) *
(item['model_height'] + embryo_redundancy_id.height))) if not blank_bool else ( (item['model_height'] + embryo_redundancy_id.height))) if not blank_bool else (
item.get('blank_length') * item.get('blank_width') * item.get('blank_height')), item.get('blank_length') * item.get('blank_width') * item.get('blank_height')),
'product_model_type_id': model_type.id, '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_machining_precision': item['model_machining_precision'],
'model_code': item['barcode'], 'model_code': item['barcode'],
'length': item['model_long'], 'length': item['model_long'],
@@ -961,7 +957,7 @@ class ResProductMo(models.Model):
self.attachment_update(item['quality_standard_name'], copy_product_id.product_tmpl_id.id, self.attachment_update(item['quality_standard_name'], copy_product_id.product_tmpl_id.id,
'quality_standard', item['quality_standard_mimetype']) 'quality_standard', item['quality_standard_mimetype'])
return copy_product_id return copy_product_id
def format_float(self, value): def format_float(self, value):
# 将浮点数转换为字符串 # 将浮点数转换为字符串
value_str = str(value) value_str = str(value)
@@ -1215,51 +1211,6 @@ class ResProductFixture(models.Model):
mounting_hole_depth = fields.Float('安装孔深度(mm)', digits=(16, 2)) mounting_hole_depth = fields.Float('安装孔深度(mm)', digits=(16, 2))
centering_diameter = fields.Float('定心直径(mm)', digits=(16, 2)) centering_diameter = fields.Float('定心直径(mm)', digits=(16, 2))
# ‘磁吸托盘’ 字段
magnet_tray_length = fields.Float('磁吸托盘长度(mm)', digits=(16, 2))
magnet_tray_width = fields.Float('磁吸托盘宽度(mm)', digits=(16, 2))
magnet_tray_height = fields.Float('磁吸托盘厚度(mm)', digits=(16, 2))
magnet_tray_diameter = fields.Float('磁吸托盘直径(mm)', digits=(16, 2))
magnet_tray_weight = fields.Float('磁吸托盘重量(kg)', digits=(16, 2))
magnet_max_adsorp_length = fields.Float('磁吸托盘最大吸附长度(mm)', digits=(16, 2))
magnet_max_adsorp_width = fields.Float('磁吸托盘最大吸附宽度(mm)', digits=(16, 2))
magnet_max_adsorp_height = fields.Float('磁吸托盘最大吸附厚度(mm)', digits=(16, 2))
magnet_max_adsorp_diameter = fields.Float('磁吸托盘最大吸附直径(mm)', digits=(16, 2))
magnet_max_adsorp_force = fields.Float('磁吸托盘最大吸附力(N)', digits=(16, 2))
magnet_unlocking_method = fields.Selection(
[('手动', '手动'), ('气动', '气动'), ('液压', '液压'), ('电动', '电动'), ('其他', '其他')],
string='磁吸托盘锁紧方式'
)
magnet_flatness = fields.Char('磁吸托盘平面精度(mm)', size=20)
magnet_max_load = fields.Float('磁吸托盘最大负载(kg)', digits=(16, 2))
# ‘气吸托盘’ 字段
air_tray_length = fields.Float('气吸托盘长度(mm)', digits=(16, 2))
air_tray_width = fields.Float('气吸托盘宽度(mm)', digits=(16, 2))
air_tray_height = fields.Float('气吸托盘高度(mm)', digits=(16, 2))
air_tray_diameter = fields.Float('气吸托盘直径(mm)', digits=(16, 2))
air_tray_weight = fields.Float('气吸托盘重量(kg)', digits=(16, 2))
air_max_adsorp_length = fields.Float('气吸托盘最大吸附长度(mm)', digits=(16, 2))
air_max_adsorp_width = fields.Float('气吸托盘最大吸附宽度(mm)', digits=(16, 2))
air_max_adsorp_height = fields.Float('气吸托盘最大吸附厚度(mm)', digits=(16, 2))
air_max_adsorp_diameter = fields.Float('气吸托盘最大吸附直径(mm)', digits=(16, 2))
air_max_adsorp_force = fields.Float('气吸托盘最大吸附力(N)', digits=(16, 2))
air_unlocking_method = fields.Selection(
[('手动', '手动'), ('气动', '气动'), ('液压', '液压'), ('电动', '电动'), ('其他', '其他')],
string='气吸托盘锁紧方式'
)
air_flatness = fields.Char('气吸托盘平面精度(mm)', size=20)
air_max_load = fields.Float('气吸托盘最大负载(kg)', digits=(16, 2))
air_boolean_chip_blowing_function = fields.Boolean('气吸托盘是否有吹屑功能')
air_way_to_install = fields.Selection(
[('接口式', '接口式'), ('螺栓固定', '螺栓固定'), ('磁吸式', '磁吸式'), ('其他', '其他')],
string='气吸托盘安装方式'
)
@api.onchange('specification_fixture_id') @api.onchange('specification_fixture_id')
def _onchange_specification_fixture_id(self): def _onchange_specification_fixture_id(self):
if self.specification_fixture_id: if self.specification_fixture_id:

View File

@@ -21,7 +21,6 @@ from odoo.addons.sf_base.commons.common import Common
from odoo.exceptions import UserError from odoo.exceptions import UserError
from io import BytesIO from io import BytesIO
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
from dateutil.relativedelta import relativedelta
class stockWarehouse(models.Model): class stockWarehouse(models.Model):
@@ -96,36 +95,35 @@ class StockRule(models.Model):
precision_rounding=proc[ precision_rounding=proc[
0].product_uom.rounding) > 0) 0].product_uom.rounding) > 0)
list2 = [] list2 = []
for procurement, rule in procurements: for item in procurements:
num = int(procurement.product_qty) num = int(item[0].product_qty)
warehouse_id = rule.warehouse_id product = self.env['product.product'].search(
if not warehouse_id: [("id", '=', item[0].product_id.id)])
warehouse_id = rule.location_dest_id.warehouse_id product_tmpl = self.env['product.template'].search(
manu_rule = rule.route_id.rule_ids.filtered(lambda r: r.action == 'manufacture' and r.warehouse_id == warehouse_id) ["&", ("id", '=', product.product_tmpl_id.id), ('single_manufacturing', "!=", False)])
if product_tmpl:
if procurement.product_id.product_tmpl_id.single_manufacturing and manu_rule:
if num > 1: if num > 1:
for no in range(1, num + 1): for no in range(1, num + 1):
Procurement = namedtuple('Procurement', ['product_id', 'product_qty', Procurement = namedtuple('Procurement', ['product_id', 'product_qty',
'product_uom', 'location_id', 'name', 'origin', 'product_uom', 'location_id', 'name', 'origin',
'company_id', 'company_id',
'values']) 'values'])
s = Procurement(product_id=procurement.product_id, product_qty=1.0, product_uom=procurement.product_uom, s = Procurement(product_id=item[0].product_id, product_qty=1.0, product_uom=item[0].product_uom,
location_id=procurement.location_id, location_id=item[0].location_id,
name=procurement.name, name=item[0].name,
origin=procurement.origin, origin=item[0].origin,
company_id=procurement.company_id, company_id=item[0].company_id,
values=procurement.values, values=item[0].values,
) )
# item1 = list(item) item1 = list(item)
# item1[0] = s item1[0] = s
list2.append((s, rule)) list2.append(tuple(item1))
else: else:
list2.append((procurement, rule)) list2.append(item)
else: else:
list2.append((procurement, rule)) list2.append(item)
for procurement, rule in list2: for procurement, rule in list2:
procure_method = rule.procure_method 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 = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create(
productions_values) 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[ origin_production = production.move_dest_ids and production.move_dest_ids[
0].raw_material_production_id or False 0].raw_material_production_id or False
orderpoint = production.orderpoint_id orderpoint = production.orderpoint_id
@@ -443,7 +452,7 @@ class ProductionLot(models.Model):
@api.model @api.model
def _get_next_serial(self, company, product): def _get_next_serial(self, company, product):
"""Return the next serial number to be attributed to the 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( last_serial = self.env['stock.lot'].search(
[('company_id', '=', company.id), ('product_id', '=', product.id), ('name', 'ilike', product.name)], [('company_id', '=', company.id), ('product_id', '=', product.id), ('name', 'ilike', product.name)],
limit=1, order='name desc') 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 ( return self.env['stock.lot'].generate_lot_names1(product.name, last_serial.name if (
not move_line_id or not move_line_id or
(last_serial and last_serial.name > move_line_id.lot_name)) else move_line_id.lot_name, 2)[1] (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 "%s-%03d" % (product.name, 1)
return False
qr_code_image = fields.Binary(string='二维码', compute='_generate_qr_code') qr_code_image = fields.Binary(string='二维码', compute='_generate_qr_code')
@@ -731,33 +738,6 @@ class StockPicking(models.Model):
production.workorder_ids.write({'back_button_display': False}) production.workorder_ids.write({'back_button_display': False})
return res 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): def create_outcontract_picking(self, workorders, item, sorted_workorders):
production = workorders[0].production_id production = workorders[0].production_id
@@ -1236,20 +1216,6 @@ class ReStockMove(models.Model):
res['lot_id'] = self.subcontract_workorder_id.production_id.move_raw_ids.move_line_ids[0].lot_id.id res['lot_id'] = self.subcontract_workorder_id.production_id.move_raw_ids.move_line_ids[0].lot_id.id
return res 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): class ReStockQuant(models.Model):
_inherit = 'stock.quant' _inherit = 'stock.quant'

View File

@@ -44,8 +44,7 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
if productions: if productions:
# 修改需求计划中的程序工时 # 修改需求计划中的程序工时
demand_plan = request.env['sf.production.demand.plan'].with_user( demand_plan = request.env['sf.production.demand.plan'].with_user(
request.env.ref("base.user_admin")).search( request.env.ref("base.user_admin")).search([('model_id', '=', ret['folder_name'])])
[('model_id', '=', ret['folder_name']), ('new_supply_method', '=', 'custom_made')])
if demand_plan and ret['total_estimated_time']: if demand_plan and ret['total_estimated_time']:
demand_plan.write( demand_plan.write(
{'processing_time': ret['total_estimated_time']}) {'processing_time': ret['total_estimated_time']})

View File

@@ -1,4 +1,4 @@
from . import ftp_operate from . import ftp_operate
from . import res_config_setting from . import res_config_setting
from . import sync_common from . import sync_common
from . import order_price from . import order_price

View File

@@ -9,7 +9,6 @@ from odoo import models
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
from odoo.addons.sf_base.commons.common import Common from odoo.addons.sf_base.commons.common import Common
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@@ -179,7 +178,6 @@ class sfMaterialModel(models.Model):
materials_model.mf_materia_post = item['mf_materia_post'] materials_model.mf_materia_post = item['mf_materia_post']
materials_model.materials_id = materials.id materials_model.materials_id = materials.id
materials_model.need_h = item['need_h'] materials_model.need_h = item['need_h']
materials_model.need_m = item['need_m']
materials_model.density = item['density'] materials_model.density = item['density']
materials_model.active = item['active'] materials_model.active = item['active']
else: else:
@@ -194,7 +192,6 @@ class sfMaterialModel(models.Model):
"active": item['active'], "active": item['active'],
"materials_id": materials.id, "materials_id": materials.id,
"need_h": item['need_h'], "need_h": item['need_h'],
"need_m": item['need_m'],
"mf_materia_post": item['mf_materia_post'], "mf_materia_post": item['mf_materia_post'],
"density": item['density'], "density": item['density'],
}) })
@@ -231,7 +228,6 @@ class sfMaterialModel(models.Model):
"standards_id": self.env['sf.international.standards'].search( "standards_id": self.env['sf.international.standards'].search(
[("name", '=', item['standards_id'])]).id, [("name", '=', item['standards_id'])]).id,
"need_h": item['need_h'], "need_h": item['need_h'],
"need_m": item['need_m'],
"alloy_code": item['alloy_code'], "alloy_code": item['alloy_code'],
"mf_materia_post": item['mf_materia_post'], "mf_materia_post": item['mf_materia_post'],
"density": item['density'], "density": item['density'],
@@ -252,7 +248,6 @@ class sfMaterialModel(models.Model):
materials_model.mf_materia_post = item['mf_materia_post'] materials_model.mf_materia_post = item['mf_materia_post']
materials_model.materials_id = materials.id materials_model.materials_id = materials.id
materials_model.need_h = item['need_h'] materials_model.need_h = item['need_h']
materials_model.need_m = item['need_m']
materials_model.density = item['density'] materials_model.density = item['density']
materials_model.active = item['active'] materials_model.active = item['active']
materials_model.materials_code= item['materials_code'] materials_model.materials_code= item['materials_code']
@@ -1615,10 +1610,6 @@ class SyncfixtureMaterialsBasicParameters(models.Model):
self._write_or_create(all_list.get('adapter_board_yesterday_list'), '转接板(锁板)夹具') self._write_or_create(all_list.get('adapter_board_yesterday_list'), '转接板(锁板)夹具')
if all_list.get('scroll_chuck_all_list'): if all_list.get('scroll_chuck_all_list'):
self._write_or_create(all_list.get('scroll_chuck_yesterday_list'), '三爪卡盘') self._write_or_create(all_list.get('scroll_chuck_yesterday_list'), '三爪卡盘')
if all_list.get('air_blow_tray_all_list'):
self._write_or_create(all_list.get('air_tray_yesterday_list'), '气吸托盘')
if all_list.get('magnet_tray_all_list'):
self._write_or_create(all_list.get('magnet_tray_yesterday_list'), '磁吸托盘')
else: else:
raise ValidationError("夹具型号基本参数认证未通过") raise ValidationError("夹具型号基本参数认证未通过")
@@ -1647,10 +1638,6 @@ class SyncfixtureMaterialsBasicParameters(models.Model):
self._write_or_create(all_list.get('adapter_board_all_list'), '转接板(锁板)夹具') self._write_or_create(all_list.get('adapter_board_all_list'), '转接板(锁板)夹具')
if all_list.get('scroll_chuck_all_list'): if all_list.get('scroll_chuck_all_list'):
self._write_or_create(all_list.get('scroll_chuck_all_list'), '三爪卡盘') self._write_or_create(all_list.get('scroll_chuck_all_list'), '三爪卡盘')
if all_list.get('air_blow_tray_all_list'):
self._write_or_create(all_list.get('air_tray_all_list'), '气吸托盘')
if all_list.get('magnet_tray_all_list'):
self._write_or_create(all_list.get('magnet_tray_all_list'), '磁吸托盘')
else: else:
raise ValidationError("夹具型号基本参数认证未通过") raise ValidationError("夹具型号基本参数认证未通过")
@@ -3243,4 +3230,4 @@ class EmbryoRedundancySync(models.Model):
"height": item['height'], "height": item['height'],
"active": item['active'], "active": item['active'],
"remark": item['remark'], "remark": item['remark'],
}) })

View File

@@ -45759,11 +45759,6 @@ msgstr ""
msgid "热处理" msgid "热处理"
msgstr "" msgstr ""
#. module: sf_base
#: model:ir.model.fields,field_description:sf_base.field_sf_materials_model__need_m
msgid "是否磁吸"
msgstr ""
#. module: sf_base #. module: sf_base
#: model:ir.model.fields,field_description:sf_base.field_sf_materials_model__mf_materia_post #: model:ir.model.fields,field_description:sf_base.field_sf_materials_model__mf_materia_post
msgid "热处理后密度" msgid "热处理后密度"

View File

@@ -22,7 +22,6 @@
'data/report_actions.xml', 'data/report_actions.xml',
'views/view.xml', 'views/view.xml',
'views/quality_cnc_test_view.xml', 'views/quality_cnc_test_view.xml',
'views/stock_picking.xml',
'views/mrp_workorder.xml', 'views/mrp_workorder.xml',
'views/quality_check_view.xml', 'views/quality_check_view.xml',
'views/quality_company.xml', 'views/quality_company.xml',

View File

@@ -92,7 +92,7 @@
<div class="page" style="min-height: 800px; position: relative; padding-bottom: 250px;"> <div class="page" style="min-height: 800px; position: relative; padding-bottom: 250px;">
<table class="table table-sm o_main_table mt-4" style="border: 1px solid black;"> <table class="table table-sm o_main_table mt-4" style="border: 1px solid black;">
<tr> <tr>
<td style="width: 15%; border: 1px solid black;"><strong>产品名称:</strong></td> <td style="width: 15%; border: 1px solid black;"><strong>产品名称:</strong></td>
@@ -113,7 +113,7 @@
<td style="border: 1px solid black;"><span t-field="o.check_qty"/></td> <td style="border: 1px solid black;"><span t-field="o.check_qty"/></td>
</tr> </tr>
</table> </table>
<h4 class="text-center mt-4">检验结果</h4> <h4 class="text-center mt-4">检验结果</h4>
<div class="" style="position: relative;"> <div class="" style="position: relative;">
<table class="table table-sm mt-2" style="border: 1px solid black;"> <table class="table table-sm mt-2" style="border: 1px solid black;">
@@ -182,7 +182,7 @@
</span> </span>
</div> </div>
</div> </div>
<div class="row mt-4"> <div class="row mt-4">
<div class="col-6"> <div class="col-6">
<p><strong>操作员: </strong> <span t-field="o.measure_operator"/></p> <p><strong>操作员: </strong> <span t-field="o.measure_operator"/></p>
@@ -200,11 +200,11 @@
<p></p> <p></p>
</div> </div>
</div> --> </div> -->
<!-- 页脚固定在底部 --> <!-- 页脚固定在底部 -->
<!-- <div style="position: absolute; bottom: 0; left: 0; right: 0;"> -->
<!-- <div style="position: absolute; bottom: 0; left: 0; right: 0;"> --> <t t-call="sf_quality.report_quality_footer"/>
<t t-call="sf_quality.report_quality_footer"/> <!-- </div> -->
<!-- </div> -->
</div> </div>
</t> </t>
</t> </t>
@@ -329,11 +329,9 @@
</div> --> </div> -->
<!-- 页脚固定在底部 --> <!-- 页脚固定在底部 -->
<!-- <t t-if="loop.index == len(docs) - 1">--> <!-- <div style="position: absolute; bottom: 0; left: 0; right: 0;"> -->
<!-- <div style="position: absolute; bottom: 0; left: 0; right: 0;"> --> <t t-call="sf_quality.html_report_quality_footer"/>
<t t-call="sf_quality.html_report_quality_footer"/> <!-- </div> -->
<!-- </div> -->
<!-- </t>-->
</div> </div>
</t> </t>
</t> </t>

View File

@@ -1,185 +1,59 @@
import logging from odoo import api, models
from odoo import api, models, fields
from odoo.exceptions import ValidationError, UserError
class StockPicking(models.Model): class StockPicking(models.Model):
_inherit = 'stock.picking' _inherit = 'stock.picking'
whether_show_quality_check = fields.Boolean('是否显示质量检测按钮', default=True)
def _compute_check(self):
super()._compute_check()
for picking in self:
picking_to_quality = picking.get_picking_to_quality()
if not picking_to_quality:
picking.quality_check_todo = False
picking.whether_show_quality_check = False
break
else:
picking.whether_show_quality_check = True
need_quality_line = picking.get_need_quality_line(picking_to_quality)
if not need_quality_line or all(not line.get('need_done_check_ids') for line in need_quality_line):
picking.quality_check_todo = False
def check_quality(self):
self.ensure_one()
# checkable_products = self.mapped('move_line_ids').mapped('product_id')
# checks = self.check_ids.filtered(lambda check: check.quality_state == 'none' and (
# check.product_id in checkable_products or check.measure_on == 'operation'))
checks = self.env['quality.check']
picking_to_quality = self._get_picking_to_quality()
need_quality_line = self.get_need_quality_line(picking_to_quality)
if need_quality_line and any(line.get('need_done_check_ids') for line in need_quality_line):
for item in need_quality_line:
checks += item.get('need_done_check_ids')
if checks:
return checks.action_open_quality_check_wizard()
return False
def button_validate(self): def button_validate(self):
"""= """
出厂检验报告上传 出厂检验报告上传
""" """
out_quality_checks = self.env['quality.check'].search( out_quality_checks = self.env['quality.check'].search(
[('picking_id', '=', self.id), ('test_type_id.name', '=', '出厂检验报告'), [('picking_id', '=', self.id), ('test_type_id.name', '=', '出厂检验报告')])
('quality_state', '=', 'pass')])
# out_quality_checks 可能存在多个 # out_quality_checks 可能存在多个
if out_quality_checks: if out_quality_checks:
for out_quality_check in out_quality_checks: for out_quality_check in out_quality_checks:
if not out_quality_check.is_factory_report_uploaded: if not out_quality_check.is_factory_report_uploaded:
if out_quality_check and self.state == 'assigned': if out_quality_check and self.state == 'assigned':
out_quality_check.upload_factory_report() out_quality_check.upload_factory_report()
quality_action = self.pinking_checkout_quality()
if quality_action:
return quality_action
res = super(StockPicking, self).button_validate()
return res
def pinking_checkout_quality(self):
""" """
调拨单若关联了质量检查单,验证调拨单时,应校验是否有不合格品,若存在,应弹窗提示: 调拨单若关联了质量检查单,验证调拨单时,应校验是否有不合格品,若存在,应弹窗提示:
“警告存在不合格产品XXXX n 件、YYYYY m件继续调拨请点“确认”否则请取消 “警告存在不合格产品XXXX n 件、YYYYY m件继续调拨请点“确认”否则请取消
""" """
try: context = self.env.context
self.ensure_one() if not context.get('again_validate') and self.quality_check_ids.filtered(lambda qc: qc.quality_state == 'fail'):
context = self.env.context # 回滚事务,为二次确认/取消做准备
if not context.get('pinking_checkout_quality'): self.env.cr.rollback()
picking_to_quality = self._get_picking_to_quality() quality_check_ids = self.quality_check_ids.filtered(lambda qc: qc.quality_state == 'fail')
if not picking_to_quality: return False product_list = list(set([quality_check_id.product_id for quality_check_id in quality_check_ids]))
need_quality_val = self.get_need_quality_line(picking_to_quality) fail_check_text = ''
if any(line.get('fail_check_ids') for line in need_quality_val): for product_id in product_list:
# 回滚事务,为二次确认/取消做准备 check_ids = quality_check_ids.filtered(lambda qc: qc.product_id == product_id)
self.env.cr.rollback() if all(check_id.measure_on == 'move_line' for check_id in check_ids):
# 获取存在失败的 质检单 调拨单明细行 number = sum(check_ids.mapped('qty_line'))
check_list = [item for item in need_quality_val if item.get('fail_check_ids')] else:
fail_check_text = '' number = sum(self.move_ids_without_package.filtered(
for item in check_list: lambda ml: ml.product_id == product_id).mapped('quantity_done'))
move_id, pre_done_qty = item.get('move_id'), item.get('pre_done_qty') if number == 0:
fail_check_text = (f'{fail_check_text}{move_id.product_id.display_name} {pre_done_qty}' number = sum(self.move_ids_without_package.filtered(
if fail_check_text != '' else f'{move_id.product_id.display_name} {pre_done_qty}') lambda ml: ml.product_id == product_id).mapped('reserved_availability'))
return { if number == 0:
'type': 'ir.actions.act_window', number = sum(self.move_ids_without_package.filtered(
'res_model': 'picking.validate.check.wizard', lambda ml: ml.product_id == product_id).mapped('product_uom_qty'))
'name': '质检不合格提示', fail_check_text = (f'{fail_check_text}{product_id.display_name} {number}'
'view_mode': 'form', if fail_check_text != '' else f'{product_id.display_name} {number}')
'target': 'new', return {
'context': { 'type': 'ir.actions.act_window',
'default_picking_id': self.id, 'res_model': 'picking.validate.check.wizard',
'default_fail_check_text': f'警告:存在不合格产品{fail_check_text},继续调拨请点“确认”,否则请取消?', 'name': '质检不合格提示',
'pinking_checkout_quality': True} 'view_mode': 'form',
} 'target': 'new',
else: 'context': {
return False 'default_picking_id': self.id,
except Exception as e: 'default_fail_check_text': f'警告:存在不合格产品{fail_check_text},继续调拨请点“确认”,否则请取消?',
logging.info('pinking_checkout_quality()方法报错:%s' % e) 'again_validate': True}
raise ValidationError('调拨单验证质检单是否合格时报错,请联系管理员处理!!') }
res = super(StockPicking, self).button_validate()
def get_need_quality_line(self, picking_to_quality):
"""
# 对需要进行质检,还没有质检完成的明细行进行统计
# 1、当【质量标准_控制方式】=“产品、作业”,仅校验“预完成数量”>0的产品行对应的质检单必须处理。
2、当【质量标准_控制方式】=“数量”时,仅校验“预完成数量”>0的产品行对应的质检单必须处理
1每一类的“总单数”=【调拨单_需求】时则已处理的质检单“单数”≥“预完成数量”时可执行调拨单验证
2每一类的“总单数”<【调拨单_需求】时则已处理的质检单“单数”≥0时可执行调拨单验证
"""
res = []
for item in picking_to_quality:
need_done_check_ids = self.env['quality.check']
fail_check_ids = self.env['quality.check']
move_id, pre_done_qty, check_ids = item.values()
check_ids_1 = check_ids.filtered(lambda qc: qc.measure_on in ('operation', 'product'))
if check_ids_1:
check_ids_1_done = check_ids_1.filtered(lambda qc: qc.quality_state in ('pass', 'fail'))
check_ids_1_fail = check_ids_1.filtered(lambda qc: qc.quality_state == 'fail')
check_ids_1_none = check_ids_1.filtered(lambda qc: qc.quality_state == 'none')
if check_ids_1 and not check_ids_1_done:
need_done_check_ids += check_ids_1_none
if check_ids_1_fail:
fail_check_ids += check_ids_1_fail
check_ids_2 = check_ids.filtered(lambda qc: qc.measure_on == 'move_line')
if check_ids_2:
check_ids_2_done = check_ids_2.filtered(lambda qc: qc.quality_state in ('pass', 'fail'))
check_ids_2_fail = check_ids_2.filtered(lambda qc: qc.quality_state == 'fail')
check_ids_2_none = check_ids_2.filtered(lambda qc: qc.quality_state == 'none')
# 每一类的“总单数”=【调拨单_需求】时则已处理的质检单“单数”≥“预完成数量”时可执行调拨单验证
if len(check_ids_2) >= move_id.product_uom_qty and len(check_ids_2_done) < pre_done_qty:
need_done_check_ids += check_ids_2_none
# 每一类的“总单数”<【调拨单_需求】时则已处理的质检单“单数”≥0时可执行调拨单验证
elif len(check_ids_2) < move_id.product_uom_qty and len(check_ids_2_done) == 0:
need_done_check_ids += check_ids_2_none
if check_ids_2_fail:
fail_check_ids += check_ids_2_fail
if need_done_check_ids or fail_check_ids:
res.append({'move_id': move_id,
'pre_done_qty': pre_done_qty,
'check_ids': check_ids,
'fail_check_ids': fail_check_ids,
'need_done_check_ids': need_done_check_ids})
return res return res
def get_picking_to_quality(self):
self.ensure_one()
return self._get_picking_to_quality()
def _get_picking_to_quality(self):
"""
对需要质检的明细行进行统计(针对“预完成数量”>0的行)
"""
quality_piking_line_list = []
pre_done_qty_lines = self._get_pinking_pre_done_qty()
for line in pre_done_qty_lines:
move_id, pre_done_qty = line.values()
if pre_done_qty == 0:
continue
product_id = move_id.product_id
check_ids = self.check_ids.filtered(lambda c: c.product_id == product_id)
quality_piking_line_list.append({'move_id': move_id, 'pre_done_qty': pre_done_qty, 'check_ids': check_ids})
return quality_piking_line_list
def _get_pinking_pre_done_qty(self):
"""
return: 明细行 及 预完成数量
1、若调拨单所有明细行的【完成】=0且任意行的【预留】<【需求】,则验证时将会话是否需创建欠单。
---->此时“预完成数量”=【预留】
2、若调拨单任意行的0<【完成】<【需求】,且则验证时将会话是否需创建欠单
---->此时“预完成数量”=【完成】
"""
# if all(move_id.quantity_done == 0 for move_id in self.move_ids_without_package):
# pre_done_qty = [{'move_id': move_id, 'pre_done_qty': move_id.reserved_availability} for move_id in
# self.move_ids_without_package]
# else:
# pre_done_qty = [{'move_id': move_id, 'pre_done_qty': move_id.quantity_done} for move_id in
# self.move_ids_without_package]
pre_done_qty = []
for move_id in self.move_ids_without_package:
if move_id.quantity_done > 0:
pre_done_qty.append({'move_id': move_id, 'pre_done_qty': move_id.quantity_done})
else:
pre_done_qty.append({'move_id': move_id, 'pre_done_qty': move_id.reserved_availability})
return pre_done_qty

View File

@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="stock_picking_view_form_inherit_quality_sf_quality" model="ir.ui.view">
<field name="name">stock.picking.view.form.sf.quality</field>
<field name="model">stock.picking</field>
<field name="inherit_id" ref="quality_control.stock_picking_view_form_inherit_quality"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="inside">
<field name="whether_show_quality_check" invisible="1"/>
</xpath>
<xpath expr="//button[@name='action_open_quality_check_picking'][1]" position="attributes">
<attribute name="attrs">{'invisible': ['|', '|','|', ('check_ids', '=', []), ('quality_check_fail', '=',
True), ('quality_check_todo', '!=', True), ('whether_show_quality_check', '!=', True)]}
</attribute>
</xpath>
<xpath expr="//button[@name='action_open_quality_check_picking'][2]" position="attributes">
<attribute name="attrs">{'invisible': ['|', '|','|', ('check_ids', '=', []), ('quality_check_fail', '=',
True), ('quality_check_todo', '=', True), ('whether_show_quality_check', '!=', True)]}
</attribute>
</xpath>
<xpath expr="//button[@name='action_open_quality_check_picking'][3]" position="attributes">
<attribute name="attrs">{'invisible': ['|', '|',('check_ids', '=', []), ('quality_check_fail', '!=',
True), ('whether_show_quality_check', '!=', True)]}
</attribute>
</xpath>
</field>
</record>
</odoo>

View File

@@ -155,7 +155,7 @@ class ReSaleOrder(models.Model):
'glb_url': item['glb_url'], 'glb_url': item['glb_url'],
'remark': item.get('remark'), 'remark': item.get('remark'),
'embryo_redundancy_id': item.get('embryo_redundancy_id'), 'embryo_redundancy_id': item.get('embryo_redundancy_id'),
'is_incoming_material': True if item.get('incoming_size') else False, 'is_incoming_material': True if item.get('embryo_redundancy_id') else False,
'manual_quotation': item.get('manual_quotation'), 'manual_quotation': item.get('manual_quotation'),
'model_id': item['model_id'], 'model_id': item['model_id'],
'delivery_end_date': item['delivery_end_date'] 'delivery_end_date': item['delivery_end_date']
@@ -287,7 +287,7 @@ class ResaleOrderLine(models.Model):
check_status = fields.Selection(related='order_id.check_status') check_status = fields.Selection(related='order_id.check_status')
remark = fields.Char('备注') remark = fields.Char('备注')
is_incoming_material = fields.Boolean('客供料', store=True) is_incoming_material = fields.Boolean('客供料', compute='_compute_is_incoming_material', store=True)
embryo_redundancy_id = fields.Many2one('sf.embryo.redundancy', '坯料冗余') embryo_redundancy_id = fields.Many2one('sf.embryo.redundancy', '坯料冗余')
manual_quotation = fields.Boolean('人工编程', default=False) manual_quotation = fields.Boolean('人工编程', default=False)
model_url = fields.Char('模型文件地址') model_url = fields.Char('模型文件地址')

View File

@@ -16,7 +16,6 @@
# 'security/sf_stock_security.xml', # 'security/sf_stock_security.xml',
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'wizard/wizard_view.xml', 'wizard/wizard_view.xml',
'views/product.xml',
'views/view.xml', 'views/view.xml',
'views/shelf_location.xml', 'views/shelf_location.xml',
'views/change_stock_move_views.xml', 'views/change_stock_move_views.xml',

View File

@@ -1,3 +1,2 @@
from . import model from . import model
from . import sync_common from . import sync_common
from . import product

View File

@@ -471,7 +471,7 @@ class ShelfLocation(models.Model):
record.display_rfid = record.product_sn_id.rfid if record.product_sn_id else '' record.display_rfid = record.product_sn_id.rfid if record.product_sn_id else ''
except Exception as e: except Exception as e:
record.display_rfid = '' record.display_rfid = ''
@api.depends('product_id') @api.depends('product_id')
def _compute_tool(self): def _compute_tool(self):
"""计算工具 RFID""" """计算工具 RFID"""
@@ -594,7 +594,7 @@ class ShelfLocation(models.Model):
_layer_capacity = _cc_code % record.shelf_id.layer_capacity _layer_capacity = _cc_code % record.shelf_id.layer_capacity
if _layer_capacity == 0: if _layer_capacity == 0:
_layer_capacity = record.shelf_id.layer_capacity _layer_capacity = record.shelf_id.layer_capacity
else: else:
_layer_capacity = _layer_capacity _layer_capacity = _layer_capacity
_layer = _layer+1 _layer = _layer+1
_layer_capacity = f"{_layer_capacity:02d}" _layer_capacity = f"{_layer_capacity:02d}"
@@ -634,7 +634,7 @@ class SfShelfLocationLot(models.Model):
for item in self: for item in self:
if item.qty_num > item.qty: if item.qty_num > item.qty:
raise ValidationError('变更数量不能比库存数量大!!!') raise ValidationError('变更数量不能比库存数量大!!!')
class SfStockMoveLine(models.Model): class SfStockMoveLine(models.Model):
@@ -899,7 +899,7 @@ class SfStockMoveLine(models.Model):
def _compute_current_location_id(self): def _compute_current_location_id(self):
# 批量获取所有相关记录的picking # 批量获取所有相关记录的picking
pickings = self.mapped('picking_id') pickings = self.mapped('picking_id')
# 构建源picking的移库行与目标位置的映射 # 构建源picking的移库行与目标位置的映射
origin_location_map = {} origin_location_map = {}
for picking in pickings: for picking in pickings:
@@ -907,11 +907,11 @@ class SfStockMoveLine(models.Model):
origin_move = picking.move_ids[:1].move_orig_ids[:1] origin_move = picking.move_ids[:1].move_orig_ids[:1]
if not origin_move: if not origin_move:
continue continue
origin_picking = origin_move.picking_id origin_picking = origin_move.picking_id
if not origin_picking: if not origin_picking:
continue continue
# 为每个picking构建lot_id到location的映射 # 为每个picking构建lot_id到location的映射
origin_location_map[picking.id] = { origin_location_map[picking.id] = {
move_line.lot_id.id: move_line.destination_location_id move_line.lot_id.id: move_line.destination_location_id
@@ -919,17 +919,17 @@ class SfStockMoveLine(models.Model):
lambda ml: ml.destination_location_id and ml.lot_id lambda ml: ml.destination_location_id and ml.lot_id
) )
} }
# 批量更新current_location_id # 批量更新current_location_id
for record in self: for record in self:
current_picking = record.picking_id current_picking = record.picking_id
if not current_picking: if not current_picking:
record.current_location_id = False record.current_location_id = False
continue continue
# 获取当前picking对应的lot_location映射 # 获取当前picking对应的lot_location映射
lot_dest_map = origin_location_map.get(current_picking.id, {}) lot_dest_map = origin_location_map.get(current_picking.id, {})
# 查找匹配的lot_id # 查找匹配的lot_id
for move_line in current_picking.move_line_ids: for move_line in current_picking.move_line_ids:
if move_line.lot_id and move_line.lot_id.id in lot_dest_map: if move_line.lot_id and move_line.lot_id.id in lot_dest_map:
@@ -1082,10 +1082,6 @@ class SfStockPicking(models.Model):
重写验证方法,当验证时意味着调拨单已经完成,已经移动到了目标货位,所以需要将当前货位的状态改为空闲 重写验证方法,当验证时意味着调拨单已经完成,已经移动到了目标货位,所以需要将当前货位的状态改为空闲
""" """
res = super(SfStockPicking, self).button_validate() res = super(SfStockPicking, self).button_validate()
if any(ml.state == 'done' for ml in self.move_line_ids):
# 验证产品库存为负库存问题
self.move_ids.product_id.verify_product_repertory(self.location_id)
for line in self.move_line_ids: for line in self.move_line_ids:
if line: if line:
if line.destination_location_id: if line.destination_location_id:

View File

@@ -1,29 +0,0 @@
from odoo import models, fields
from odoo.exceptions import ValidationError
class SfProductCategory(models.Model):
_inherit = 'product.category'
negative_inventory_allowed = fields.Boolean('可负库存', default=True)
class SfProductTemplate(models.Model):
_inherit = 'product.product'
def verify_product_repertory(self, location_id):
"""
验证产品 负库存
"""
if not location_id:
raise ValidationError('当前位置为空!!')
elif len(location_id) != 1:
raise ValidationError(f'存在多个当前位置{[item.name for item in location_id]}')
elif location_id.usage == 'supplier':
return True
for pp in self:
if not pp.categ_id.negative_inventory_allowed:
sq = pp.stock_quant_ids.filtered(lambda sq: sq.quantity < 0 and sq.location_id == location_id)
if sq:
raise ValidationError(
f'产品{pp.name}的产品类型设置为不可负库存,当前操作会导致产品{pp.name}在库存{location_id.name}上的库存数量为负!!!')

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="product_category_form_view_sf_warehouse" model="ir.ui.view">
<field name="name">product.category.property.form.warehouse</field>
<field name="model">product.category</field>
<field name="inherit_id" ref="account.view_category_property_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='account_property']" position="after">
<group name="other">
<group string="其他">
<field name="negative_inventory_allowed"/>
</group>
</group>
</xpath>
</field>
</record>
</odoo>