diff --git a/jikimo_frontend/__manifest__.py b/jikimo_frontend/__manifest__.py index c3598323..b8b77eb1 100644 --- a/jikimo_frontend/__manifest__.py +++ b/jikimo_frontend/__manifest__.py @@ -21,8 +21,8 @@ 'web.assets_qweb': [ ], 'web.assets_backend': [ - 'jikimo_frontend/static/src/fields/custom_many2many_checkboxes/*', - 'jikimo_frontend/static/src/fields/Many2OneRadioField/*', + # 'jikimo_frontend/static/src/fields/custom_many2many_checkboxes/*', + # 'jikimo_frontend/static/src/fields/Many2OneRadioField/*', # 移除odoo相关标识 'jikimo_frontend/static/src/bye_odoo/*', 'jikimo_frontend/static/src/scss/custom_style.scss', diff --git a/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.css b/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.css deleted file mode 100644 index 72d877a0..00000000 --- a/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.css +++ /dev/null @@ -1,3 +0,0 @@ -.many2one_radio_field { - display: inline-block; -} \ No newline at end of file diff --git a/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.js b/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.js deleted file mode 100644 index 8c2be97f..00000000 --- a/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.js +++ /dev/null @@ -1,53 +0,0 @@ -/** @odoo-module **/ - -import { RadioField } from "@web/views/fields/radio/radio_field"; // 导入单选按钮组件 -import { registry } from "@web/core/registry"; - -export class Many2OneRadioField extends RadioField { - // 你可以重写或者添加一些方法和属性 - // 例如,你可以重写setup方法来添加一些事件监听器或者初始化一些变量 - setup() { - super.setup(); // 调用父类的setup方法 - // 你自己的代码 - } - - onImageClick(event) { - // 放大图片逻辑 - // 获取图片元素 - const img = event.target; - const close = img.nextSibling; - // 实现放大图片逻辑 - // 比如使用 CSS 放大 - img.parentElement.classList.add('zoomed'); - close.classList.add('img_close'); - } - - onCloseClick(event) { - const close = event.target; - const img = close.previousSibling; - img.parentElement.classList.remove('zoomed'); - close.classList.remove('img_close'); - } - - get items() { - return Many2OneRadioField.getItems(this.props.name, this.props.record); - } - - static getItems(fieldName, record) { - switch (record.fields[fieldName].type) { - case "selection": - return record.fields[fieldName].selection; - case "many2one": { - const value = record.preloadedData[fieldName] || []; - return value.map((item) => [item.id, item.display_name, item.image]); - } - default: - return []; - } - } -} - -Many2OneRadioField.template = "jikimo_frontend.Many2OneRadioField"; -// MyCustomWidget.supportedTypes = ['many2many']; - -registry.category("fields").add("many2one_radio", Many2OneRadioField); diff --git a/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.xml b/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.xml deleted file mode 100644 index 3d797eb0..00000000 --- a/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - -
- -
- -
-
-
-
- -
diff --git a/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.css b/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.css deleted file mode 100644 index 20cb4c7e..00000000 --- a/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.css +++ /dev/null @@ -1,100 +0,0 @@ - -.processing-capabilities-grid { - display: grid; - grid-template-columns: repeat(6, 1fr); - gap: 10px; - width: 100%; -} - -.grid-item { - display: flex; - align-items: center; -} - -.item-content { - display: flex; - flex-direction: column; - align-items: center; - text-align: center; -} -/*控制图片大小*/ -.item-icon { - width: 50px; - height: 50px; - margin-bottom: 5px; - margin-top: 15px; -} - -.item-label { - font-size: 12px; - word-break: break-word; -} - -@media (max-width: 1200px) { - .processing-capabilities-grid { - grid-template-columns: repeat(4, 1fr); - } -} - -@media (max-width: 768px) { - .processing-capabilities-grid { - grid-template-columns: repeat(3, 1fr); - } -} - -@media (max-width: 480px) { - .processing-capabilities-grid { - grid-template-columns: repeat(2, 1fr); - } -} -.image-preview-container { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.9); - display: flex; - justify-content: center; - align-items: center; - z-index: 1000; - opacity: 0; - transition: opacity 0.3s ease; -} - -.image-preview-container.show { - opacity: 1; -} - -.image-preview { - max-width: 90%; - max-height: 90%; - object-fit: contain; - box-shadow: 0 0 20px rgba(255, 255, 255, 0.2); - border-radius: 5px; - transform: scale(0.9); - transition: transform 0.3s ease; -} - -.image-preview-container.show .image-preview { - transform: scale(1); -} - -.image-preview-close { - position: absolute; - top: 20px; - right: 30px; - color: #fff; - font-size: 40px; - font-weight: bold; - transition: 0.3s; - cursor: pointer; - opacity: 0.7; -} - -.image-preview-close:hover, -.image-preview-close:focus { - opacity: 1; - text-decoration: none; - cursor: pointer; -} \ No newline at end of file diff --git a/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.js b/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.js deleted file mode 100644 index dee78c5f..00000000 --- a/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.js +++ /dev/null @@ -1,60 +0,0 @@ -/** @odoo-module **/ - -import {Many2ManyCheckboxesField} from "@web/views/fields/many2many_checkboxes/many2many_checkboxes_field"; -import {registry} from "@web/core/registry"; - -export class MyCustomWidget extends Many2ManyCheckboxesField { - setup() { - super.setup(); - } - - onImageClick(event, src) { - event.preventDefault(); - event.stopPropagation(); - - // 创建预览框 - const previewContainer = document.createElement('div'); - previewContainer.className = 'image-preview-container'; - - const previewImg = document.createElement('img'); - previewImg.src = src; - previewImg.className = 'image-preview'; - // 设置放大的预览图片大小 - previewImg.style.width = '600px'; - previewImg.style.height = 'auto'; // 保持宽高比 - - const closeButton = document.createElement('span'); - closeButton.innerHTML = '×'; - closeButton.className = 'image-preview-close'; - - previewContainer.appendChild(previewImg); - previewContainer.appendChild(closeButton); - document.body.appendChild(previewContainer); - - // 添加关闭预览的事件监听器 - const closePreview = () => { - previewContainer.classList.remove('show'); - setTimeout(() => { - document.body.removeChild(previewContainer); - }, 300); - }; - - closeButton.addEventListener('click', closePreview); - - // 点击预览框外部也可以关闭 - previewContainer.addEventListener('click', (e) => { - if (e.target === previewContainer) { - closePreview(); - } - }); - - // 使用 setTimeout 来触发过渡效果 - setTimeout(() => { - previewContainer.classList.add('show'); - }, 10); - } -} - -MyCustomWidget.template = "jikimo_frontend.MyCustomWidget"; - -registry.category("fields").add("custom_many2many_checkboxes", MyCustomWidget); \ No newline at end of file diff --git a/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.xml b/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.xml deleted file mode 100644 index 9bb8797d..00000000 --- a/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - -
- -
- -
- - -
-
-
-
-
-
- -
\ No newline at end of file diff --git a/jikimo_test_assistant/__init__.py b/jikimo_test_assistant/__init__.py new file mode 100644 index 00000000..b6de2276 --- /dev/null +++ b/jikimo_test_assistant/__init__.py @@ -0,0 +1,3 @@ +from . import models +from . import controllers +from . import wizards diff --git a/jikimo_test_assistant/__manifest__.py b/jikimo_test_assistant/__manifest__.py new file mode 100644 index 00000000..f9902300 --- /dev/null +++ b/jikimo_test_assistant/__manifest__.py @@ -0,0 +1,32 @@ +{ + 'name': '机企猫 测试助手', + 'version': '16.0.1.0.0', + 'category': 'Technical', + 'summary': '测试数据初始化工具', + 'description': """ + 用于初始化测试环境数据的工具模块 + """, + 'author': 'Jikimo', + 'website': 'www.jikimo.com', + 'depends': [ + 'base', + 'sale_management', + 'purchase', + 'mrp', + 'stock', + 'account' + ], + 'data': [ + 'security/ir.model.access.csv', + 'wizards/jikimo_data_clean_wizard.xml', + ], + 'assets': { + 'web.assets_backend': [ + 'jikimo_test_assistant/static/src/js/data_clean_confirm.js', + ], + }, + 'installable': True, + 'application': False, + 'auto_install': False, + 'license': 'LGPL-3', +} \ No newline at end of file diff --git a/jikimo_test_assistant/controllers/__init__.py b/jikimo_test_assistant/controllers/__init__.py new file mode 100644 index 00000000..cd4d6a8b --- /dev/null +++ b/jikimo_test_assistant/controllers/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import main \ No newline at end of file diff --git a/jikimo_test_assistant/controllers/main.py b/jikimo_test_assistant/controllers/main.py new file mode 100644 index 00000000..b2ef6810 --- /dev/null +++ b/jikimo_test_assistant/controllers/main.py @@ -0,0 +1,86 @@ +from odoo import http +import logging +import os +import json +import sys + +_logger = logging.getLogger(__name__) + +class Main(http.Controller): + @http.route('/api/pdf2image', type='http', auth='public', methods=['POST'], csrf=False) + def convert_pdf_to_image(self, **kwargs): + """将PDF文件转换为图片文件 + + Returns: + dict: 包含转换后图片url的字典 + """ + res = {} + try: + # 检查poppler是否可用 + # if sys.platform.startswith('win'): + # if not os.environ.get('POPPLER_PATH'): + # return { + # 'code': 400, + # 'msg': '请先配置POPPLER_PATH环境变量' + # } + # else: + # import shutil + # if not shutil.which('pdftoppm'): + # return { + # 'code': 400, + # 'msg': '请先安装poppler-utils' + # } + + # 获取上传的PDF文件 + pdf_file = kwargs.get('file') + if not pdf_file: + res = {'code': 400, 'msg': '未找到上传的PDF文件'} + + # 检查文件类型 + if not pdf_file.filename.lower().endswith('.pdf'): + res = {'code': 400, 'msg': '请上传PDF格式的文件'} + + # 读取PDF文件内容 + pdf_content = pdf_file.read() + + # 使用pdf2image转换 + from pdf2image import convert_from_bytes + import tempfile + + # 转换PDF + with tempfile.TemporaryDirectory() as path: + images = convert_from_bytes(pdf_content) + image_urls = [] + + # 保存每一页为图片 + for i, image in enumerate(images): + image_path = os.path.join(path, f'page_{i+1}.jpg') + image.save(image_path, 'JPEG') + + # 将图片保存到ir.attachment + with open(image_path, 'rb') as img_file: + attachment = http.request.env['ir.attachment'].sudo().create({ + 'name': f'page_{i+1}.jpg', + 'datas': img_file.read(), + 'type': 'binary', + 'access_token': kwargs.get('access_token') or '123' + }) + image_urls.append({ + 'page': i+1, + 'url': f'/web/content/{attachment.id}' + }) + + res = { + 'code': 200, + 'msg': '转换成功', + 'data': image_urls + } + + except Exception as e: + _logger.error('PDF转换失败: %s', str(e)) + res = { + 'code': 500, + 'msg': f'转换失败: {str(e)}' + } + return json.JSONEncoder().encode(res) + diff --git a/jikimo_test_assistant/models/__init__.py b/jikimo_test_assistant/models/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/jikimo_test_assistant/models/__init__.py @@ -0,0 +1 @@ + diff --git a/jikimo_test_assistant/security/ir.model.access.csv b/jikimo_test_assistant/security/ir.model.access.csv new file mode 100644 index 00000000..2e334571 --- /dev/null +++ b/jikimo_test_assistant/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_jikimo_data_clean_wizard,jikimo_test_assistant.jikimo_data_clean_wizard,model_jikimo_data_clean_wizard,base.group_system,1,1,1,1 \ No newline at end of file diff --git a/jikimo_test_assistant/static/src/js/data_clean_confirm.js b/jikimo_test_assistant/static/src/js/data_clean_confirm.js new file mode 100644 index 00000000..a4cd07ec --- /dev/null +++ b/jikimo_test_assistant/static/src/js/data_clean_confirm.js @@ -0,0 +1,50 @@ +odoo.define('jikimo_test_assistant.action_clean_data_confirm', function (require) { + const core = require('web.core'); + const ajax = require('web.ajax'); + const Dialog = require('web.Dialog'); + var rpc = require('web.rpc'); + var _t = core._t; + + async function action_clean_data_confirm(parent, {params}) { + let message = "确认清理数据?
" + message += "日期:"+ params.date + "以前
" + message += "模型:" + params.model_names.join(',') + const dialog = new Dialog(parent, { + title: "确认", + $content: $('
').append(message), + buttons: [ + { text: "确认", classes: 'btn-primary jikimo_button_confirm', close: true, click: () => actionCleanDataConfirm(parent, params) }, + { text: "取消", close: true }, + ], + }); + dialog.open(); + + + async function actionCleanDataConfirm(parent, params) { + rpc.query({ + model: 'jikimo.data.clean.wizard', + method: 'action_clean_data', + args: [params.active_id], + kwargs: { + context: params.context, + } + }).then(res => { + parent.services.action.doAction({ + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'target': 'new', + 'params': { + 'message': '数据清理成功!', + 'type': 'success', + 'sticky': false, + 'next': {'type': 'ir.actions.act_window_close'}, + } + }); + }) + + } + } + + core.action_registry.add('action_clean_data_confirm', action_clean_data_confirm); + return action_clean_data_confirm; +}); diff --git a/jikimo_test_assistant/wizards/__init__.py b/jikimo_test_assistant/wizards/__init__.py new file mode 100644 index 00000000..2dadb00b --- /dev/null +++ b/jikimo_test_assistant/wizards/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import jikimo_data_clean_wizard \ No newline at end of file diff --git a/jikimo_test_assistant/wizards/jikimo_data_clean_wizard.py b/jikimo_test_assistant/wizards/jikimo_data_clean_wizard.py new file mode 100644 index 00000000..f08b114e --- /dev/null +++ b/jikimo_test_assistant/wizards/jikimo_data_clean_wizard.py @@ -0,0 +1,99 @@ +from odoo import models, fields, api +from datetime import datetime + +import logging + +_logger = logging.getLogger(__name__) + +class JikimoDataCleanWizard(models.TransientModel): + _name = 'jikimo.data.clean.wizard' + _description = '业务数据清理' + + date = fields.Date(string='截止日期', required=True, default=fields.Date.context_today) + model_ids = fields.Many2many('ir.model', string='业务模型', domain=[ + ('model', 'in', [ + 'sale.order', # 销售订单 + 'purchase.order', # 采购订单 + 'mrp.production', # 生产订单 + 'stock.picking', # 库存调拨 + 'account.move', # 会计凭证 + ]) + ]) + + def action_clean_data(self): + self.ensure_one() + model_list = self.model_ids.mapped('model') + + # 销售订单清理(排除已交付,已锁定,已取消) + if 'sale.order' in model_list: + self.model_cancel('sale.order', except_states=['delivered', 'done', 'cancel']) + + # 采购订单清理(排除采购订单,已锁定,已取消) + if 'purchase.order' in model_list: + self.model_cancel('purchase.order', except_states=['purchase', 'done', 'cancel']) + + # 生产订单清理(排除返工,报废,完成,已取消) + if 'mrp.production' in model_list: + self.model_cancel('mrp.production', except_states=['rework', 'scrap', 'done', 'cancel']) + + # 工单清理 (排除返工,完成,已取消) + if 'mrp.workorder' in model_list: + self.model_cancel('mrp.production', except_states=['rework', 'done', 'cancel']) + + # 排程单清理 (排除已完成,已取消) + if 'mrp.workorder' in model_list: + self.model_cancel('mrp.production', except_states=['finished', 'cancel']) + + # 工单库存移动 (排除完成,已取消) + if 'stock.move' in model_list: + self.model_cancel('stock.move') + + # 库存调拨清理 (排除完成,已取消) + if 'stock.picking' in model_list: + self.model_cancel('stock.picking') + + # 会计凭证清理 (排除已过账,已取消) + if 'account.move' in model_list: + self.model_cancel('account.move', except_states=['posted', 'cancel']) + + return True + + def model_cancel(self, model_name, state_field='state', to_state='cancel',except_states=('done', 'cancel')): + table = self.env[model_name]._table + if isinstance(except_states, list): + except_states = tuple(except_states) + sql = """ + UPDATE + %s SET %s = '%s' + WHERE + create_date < '%s' + AND state NOT IN %s +""" % (table, state_field, to_state, self.date.strftime('%Y-%m-%d'), except_states) + self.env.cr.execute(sql) + self.env.cr.commit() + + @api.model + def get_confirm_message(self): + date_str = self.date.strftime('%Y-%m-%d') if self.date else '' + model_names = ', '.join([model.name for model in self.model_ids]) + return { + 'date': date_str, + 'model_names': model_names + } + + def action_clean_data_confirm(self): + model_names = self.model_ids.mapped('display_name') + return { + 'type': 'ir.actions.client', + 'tag': 'action_clean_data_confirm', + 'params': { + 'model_names': model_names, + 'date': self.date, + 'active_id': self.id, + 'context': self.env.context + } + } + + + + diff --git a/jikimo_test_assistant/wizards/jikimo_data_clean_wizard.xml b/jikimo_test_assistant/wizards/jikimo_data_clean_wizard.xml new file mode 100644 index 00000000..d297bb3c --- /dev/null +++ b/jikimo_test_assistant/wizards/jikimo_data_clean_wizard.xml @@ -0,0 +1,47 @@ + + + + + jikimo.data.clean.wizard.form + jikimo.data.clean.wizard + +
+ + + + + + +
+
+
+
+
+ + + + 业务数据清理 + jikimo.data.clean.wizard + form + new + + + + + + +
\ No newline at end of file diff --git a/sf_base/static/js/updateTable.js b/sf_base/static/js/updateTable.js index beec2c94..f76ff006 100644 --- a/sf_base/static/js/updateTable.js +++ b/sf_base/static/js/updateTable.js @@ -9,6 +9,7 @@ function getDomData() { table.hide() const thead = customTable.children('thead') const tbody = customTable.children('tbody') + const tfooter = customTable.children('tfoot') const tableData = [] const tbody_child = tbody.children() @@ -16,30 +17,29 @@ function getDomData() { for (let v = 0; v < tbody_child_len; v++) { // 将数据取出来到tableData里面 const data = tbody_child[v].innerText.split('\t') - // console.log('dom data',data) const [index, deep, name, Φ, value] = data - tableData.push({index, deep, name, Φ, value}) + tableData.push({ index, deep, name, Φ, value }) } - const ΦList = [...new Set(tableData.map(_ => _.name))] // ΦList去重 + const ΦList = [...new Set(tableData.map(_ => _.Φ))] // ΦList去重 const newTableData = {} tableData.forEach(_ => { - const key = _.deep + '|' + _.Φ - !newTableData[key] ? newTableData[key] = {i: _.index} : ''; + const key = _.deep + '|' + _.name + !newTableData[key] ? newTableData[key] = { i: _.index } : ''; if (_.Φ) { // 去除没有Φ的脏数据 newTableData[key]['Φ' + _.Φ] = _.value newTableData[key]['Φ' + _.Φ + 'i'] = _.index } }) - // console.log('qwdh',tableData, ΦList, newTableData); + // console.log(tableData, ΦList, newTableData); if (ΦList.filter(_ => _).length == 0) return; - handleThead(thead, ΦList) + handleThead(thead, ΦList, tfooter) - handleTbody(tbody, newTableData, ΦList, table) + handleTbody(tbody, newTableData, ΦList, table ) } // 重新设置表头、 -function handleThead(thead, ΦList) { +function handleThead(thead, ΦList, tfooter) { const dom = thead.children().eq(0).children() const len = dom.length dom.eq(0).attr('rowspan', 2) @@ -47,7 +47,11 @@ function handleThead(thead, ΦList) { len == 5 ? dom.eq(2).attr('rowspan', 2) : '' dom.eq(-2).attr('colspan', ΦList.length) dom.eq(-1).remove() - + if(tfooter && tfooter.length) { + tfooter.children().each(function () { + $(this).children().eq(-1).remove() + }) + } const tr = document.createElement('tr') for (let v = 0; v < ΦList.length; v++) { const th = document.createElement('th') @@ -68,7 +72,6 @@ function handleTbody(tbody, newTableData, ΦList, table) { // b = b.split('=')[1].split('%')[0] // return a - b // }) - // console.log('wqoqw ',ΦList) data.forEach(_ => { i++ const tr = $('') @@ -98,61 +101,6 @@ function handleTbody(tbody, newTableData, ΦList, table) { // // } tbody.append(tr) }) - // $(document).click(function (e) { - // if ($(e.target).attr('coustomTd')) { - // const orginV = $('[coustomInput=1]').children('input').val() - // $('[coustomInput=1]').parent().html(orginV) - // const v = $(e.target).attr('val') - // console.log($(e.target)); - // $(e.target).html('') - // const input = $('
') - // input.children('input').val(v) - // $(e.target).append(input) - // input.children('input').focus() - // input.children('input').select() - // } else if ($(e.target).attr('coustomInput')) { - // - // } else { - // const orginV = $('[coustomInput=1]').children('input').val() - // $('[coustomInput=1]').parent().html(orginV) - // const v = $(e.target).attr('val') - // } - // }) - // $(document).off('change') // 防止重复绑定 -// $(document).on('change', '[coustomInput] input', function () { -// $(this).parents('td').attr('val', $(this).val()); -// var eve1 = new Event('change'); -// var eve2 = new Event('input'); -// var eve3 = new Event('click'); -// const i = $(this).parents('td').attr('col'); -// let patchDom = table.find('tbody').children('tr').eq(i - 1); -// -// if (patchDom.length === 0) { -// console.error('No such row found'); -// return; -// } -// -// patchDom = patchDom.children().eq(-1); -// -// setTimeout(() => { -// if (patchDom.length === 0) { -// console.error('No such cell found'); -// return; -// } -// patchDom[0].dispatchEvent(eve3); // Simulate click event -// -// setTimeout(() => { -// patchDom = patchDom.find('input'); -// if (patchDom.length === 0) { -// console.error('No input found in the target cell'); -// return; -// } -// patchDom.val($(this).val()); -// patchDom[0].dispatchEvent(eve2); -// patchDom[0].dispatchEvent(eve1); -// }, 200); -// }, 500); -// }); } diff --git a/sf_base/views/tool_views.xml b/sf_base/views/tool_views.xml index b2d53392..e0679e55 100644 --- a/sf_base/views/tool_views.xml +++ b/sf_base/views/tool_views.xml @@ -177,12 +177,12 @@ - + + widget="custom_many2many_checkboxes" attrs="{'showExpand': True}"/> diff --git a/sf_manufacturing/models/agv_scheduling.py b/sf_manufacturing/models/agv_scheduling.py index f39c07e7..95813e1b 100644 --- a/sf_manufacturing/models/agv_scheduling.py +++ b/sf_manufacturing/models/agv_scheduling.py @@ -19,12 +19,8 @@ class AgvScheduling(models.Model): _order = 'id desc' name = fields.Char('任务单号', index=True, copy=False) - - def _get_agv_route_type_selection(self): - return self.env['sf.agv.task.route'].fields_get(['route_type'])['route_type']['selection'] - - agv_route_type = fields.Selection(selection=_get_agv_route_type_selection, string='任务类型', required=True) agv_route_id = fields.Many2one('sf.agv.task.route', '任务路线') + agv_route_type = fields.Selection(related='agv_route_id.route_type', string='任务类型', required=True) start_site_id = fields.Many2one('sf.agv.site', '起点接驳站', required=True) end_site_id = fields.Many2one('sf.agv.site', '终点接驳站', tracking=True) site_state = fields.Selection([ diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py index 1af65426..8bec3e65 100644 --- a/sf_manufacturing/models/mrp_production.py +++ b/sf_manufacturing/models/mrp_production.py @@ -1281,6 +1281,7 @@ class MrpProduction(models.Model): 'target': 'new', 'context': { 'default_production_id': self.id, + 'default_is_clamping': True if self.workorder_ids.filtered(lambda wk: wk.routing_type == '装夹预调') else False, 'default_workorder_ids': workorder_ids.ids if workorder_ids.ids != [] else self.workorder_ids.ids, 'default_hidden_workorder_ids': ','.join(map(str, work_id_list)) if work_id_list != [] else '', 'default_reprogramming_num': cloud_programming.get('reprogramming_num') if cloud_programming else '', diff --git a/sf_manufacturing/models/mrp_workorder.py b/sf_manufacturing/models/mrp_workorder.py index ef121e1b..3717cebe 100644 --- a/sf_manufacturing/models/mrp_workorder.py +++ b/sf_manufacturing/models/mrp_workorder.py @@ -69,6 +69,7 @@ class ResMrpWorkOrder(models.Model): delivery_warning = fields.Selection([('normal', '正常'), ('warning', '告警'), ('overdue', '逾期')], string='时效', tracking=True) + date_planned_start = fields.Datetime(tracking=True) @api.depends('processing_panel') def _compute_processing_panel_selection(self): @@ -1858,11 +1859,7 @@ class WorkPieceDelivery(models.Model): feeder_station_destination_id = fields.Many2one('sf.agv.site', '目的接驳站') task_delivery_time = fields.Datetime('任务下发时间') task_completion_time = fields.Datetime('任务完成时间') - - def _get_agv_route_type_selection(self): - return self.env['sf.agv.task.route'].fields_get(['route_type'])['route_type']['selection'] - - type = fields.Selection(selection=_get_agv_route_type_selection, string='类型') + type = fields.Selection(related='route_id.route_type', string='类型') delivery_duration = fields.Float('配送时长', compute='_compute_delivery_duration') status = fields.Selection( [('待下发', '待下发'), ('已下发', '待配送'), ('已配送', '已配送'), ('已取消', '已取消')], string='状态', diff --git a/sf_manufacturing/models/sale_order.py b/sf_manufacturing/models/sale_order.py index 1b94f07f..9f8fbd19 100644 --- a/sf_manufacturing/models/sale_order.py +++ b/sf_manufacturing/models/sale_order.py @@ -183,4 +183,6 @@ class SaleOrderLine(models.Model): for line in self: if vals['supply_method'] == 'automation' and line.manual_quotation: raise UserError('当前(%s)产品为人工编程产品,不能选择自动化产线加工' % ','.join(line.mapped('product_id.name'))) + if vals['supply_method'] == 'purchase' and line.is_incoming_material: + raise UserError('当前(%s)产品为客供料,不能选择外购' % ','.join(line.mapped('product_id.name'))) return super(SaleOrderLine, self).write(vals) diff --git a/sf_manufacturing/models/stock.py b/sf_manufacturing/models/stock.py index 1e0b0849..901b521b 100644 --- a/sf_manufacturing/models/stock.py +++ b/sf_manufacturing/models/stock.py @@ -611,6 +611,18 @@ class StockPicking(models.Model): return sequence_id def button_validate(self): + # 校验“收料入库单、客供料入库单”是否已经分配序列号,如果没有分配则自动分配 + if self.picking_type_id.use_existing_lots is False and self.picking_type_id.use_create_lots is True: + for move in self.move_ids: + if not move.move_line_nosuggest_ids: + move.action_show_details() + else: + # 对已经生成的序列号做唯一性校验,如果重复则重新生成新的序列号 + line_lot_name = [line_id.lot_name for line_id in move.move_line_nosuggest_ids] + lot_ids = self.env['stock.lot'].sudo().search([('name', 'in', line_lot_name)]) + if lot_ids: + move.action_clear_lines_show_details() + move.action_show_details() res = super().button_validate() picking_type_in = self.env.ref('sf_manufacturing.outcontract_picking_in').id if res is True and self.picking_type_id.id == picking_type_in: @@ -844,7 +856,8 @@ class ReStockMove(models.Model): self.next_serial = self._get_tool_next_serial(self.company_id, self.product_id, self.origin) else: self.next_serial = self.env['stock.lot']._get_next_serial(self.company_id, self.product_id) - if self.picking_type_id.sequence_code == 'DL' and not self.move_line_nosuggest_ids: + if (self.picking_type_id.use_existing_lots is False + and self.picking_type_id.use_create_lots is True and not self.move_line_nosuggest_ids): self.action_assign_serial_show_details() elif self.product_id.tracking == "lot": self._put_tool_lot(self.company_id, self.product_id, self.origin) diff --git a/sf_manufacturing/views/mrp_workorder_view.xml b/sf_manufacturing/views/mrp_workorder_view.xml index b1d59405..419eec50 100644 --- a/sf_manufacturing/views/mrp_workorder_view.xml +++ b/sf_manufacturing/views/mrp_workorder_view.xml @@ -785,6 +785,10 @@ + + + + @@ -807,7 +811,7 @@ sf.workpiece.delivery {'search_default_filter_to_be_issued': 1, - 'search_default_filter_issued': 1} + 'search_default_filter_type_to_production_line': 1} tree,form diff --git a/sf_manufacturing/views/sale_order_views.xml b/sf_manufacturing/views/sale_order_views.xml index 353739c1..bfaca560 100644 --- a/sf_manufacturing/views/sale_order_views.xml +++ b/sf_manufacturing/views/sale_order_views.xml @@ -19,7 +19,7 @@ - + diff --git a/sf_manufacturing/wizard/rework_wizard.py b/sf_manufacturing/wizard/rework_wizard.py index a3ac96ed..11827819 100644 --- a/sf_manufacturing/wizard/rework_wizard.py +++ b/sf_manufacturing/wizard/rework_wizard.py @@ -35,6 +35,7 @@ class ReworkWizard(models.TransientModel): is_reprogramming = fields.Boolean(string='申请重新编程', default=False) is_reprogramming_readonly = fields.Boolean(string='申请重新编程(只读)', default=False) is_clamp_measure = fields.Boolean(string='保留装夹测量数据', default=True) + is_clamping = fields.Boolean(string='制造订单是否存在装夹预调工单') reprogramming_num = fields.Integer('重新编程次数', default=0) programming_state = fields.Selection( [('待编程', '待编程'), ('编程中', '编程中'), ('已编程', '已编程'), ('已编程未下发', '已编程未下发'), diff --git a/sf_manufacturing/wizard/rework_wizard_views.xml b/sf_manufacturing/wizard/rework_wizard_views.xml index aad8a692..e6c8dea9 100644 --- a/sf_manufacturing/wizard/rework_wizard_views.xml +++ b/sf_manufacturing/wizard/rework_wizard_views.xml @@ -13,6 +13,7 @@ + -
+
保留装夹测量数据 diff --git a/sf_manufacturing/wizard/workpiece_delivery_wizard.py b/sf_manufacturing/wizard/workpiece_delivery_wizard.py index 6a13fc08..bc4b8210 100644 --- a/sf_manufacturing/wizard/workpiece_delivery_wizard.py +++ b/sf_manufacturing/wizard/workpiece_delivery_wizard.py @@ -54,10 +54,7 @@ class WorkpieceDeliveryWizard(models.TransientModel): } } - def _get_agv_route_type_selection(self): - return self.env['sf.agv.task.route'].fields_get(['route_type'])['route_type']['selection'] - - delivery_type = fields.Selection(selection=_get_agv_route_type_selection, string='类型') + delivery_type = fields.Selection(related='route_id.route_type', string='类型') def dispatch_confirm(self): if len(self.workorder_ids) < 4: diff --git a/sf_message/data/bussiness_node.xml b/sf_message/data/bussiness_node.xml index b429d766..4003df45 100644 --- a/sf_message/data/bussiness_node.xml +++ b/sf_message/data/bussiness_node.xml @@ -156,4 +156,12 @@ product.product + + + + 计划数据异常跟踪 + mrp.workorder + + + \ No newline at end of file diff --git a/sf_message/data/template_data.xml b/sf_message/data/template_data.xml index 11f05a3c..c0e96d99 100644 --- a/sf_message/data/template_data.xml +++ b/sf_message/data/template_data.xml @@ -402,4 +402,19 @@ 事项:有{{num}}个质检单需要处理。 + + + + 计划数据异常跟踪 + + mrp.workorder + + markdown + normal + ### 工单计划数据异常删除: +工单号:{{name}} +异动时间:{{write_date}} + + + \ No newline at end of file diff --git a/sf_message/models/sf_message_workorder.py b/sf_message/models/sf_message_workorder.py index 653562e1..42b03139 100644 --- a/sf_message/models/sf_message_workorder.py +++ b/sf_message/models/sf_message_workorder.py @@ -188,3 +188,10 @@ class SFMessageWork(models.Model): ]) if message_queue_ids: message_queue_ids.write({'message_status': 'cancel'}) + + def write(self, vals): + res = super(SFMessageWork, self).write(vals) + if ('leave_id' in vals and vals['leave_id'] is False or 'date_planned_start' in vals and vals['date_planned_start'] is False) \ + and self.schedule_state != '未排': + self.add_queue('计划数据异常跟踪') + return res diff --git a/sf_mrs_connect/models/sync_common.py b/sf_mrs_connect/models/sync_common.py index 835183b3..9115bc65 100644 --- a/sf_mrs_connect/models/sync_common.py +++ b/sf_mrs_connect/models/sync_common.py @@ -1988,6 +1988,9 @@ class CuttingSpeed(models.Model): }) else: cutting_speed.write({ + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'execution_standard_id': self.env['sf.international.standards'].search( [('code', '=', item['execution_standard_code'])]).id, 'material_name_id': self.env['sf.materials.model'].search( @@ -2040,6 +2043,9 @@ class CuttingSpeed(models.Model): }) else: cutting_speed.write({ + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'execution_standard_id': self.env['sf.international.standards'].search( [('code', '=', item['execution_standard_code'])]).id, 'material_name_id': self.env['sf.materials.model'].search( @@ -2130,6 +2136,9 @@ class CuttingSpeed(models.Model): }) else: feed_per_tooth.write({ + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'materials_type_id': self.env['sf.materials.model'].search( [('materials_no', '=', item['materials_type_code'])]).id, 'cutting_width_depth_id': self.env['sf.cutting.width.depth'].search( @@ -2168,6 +2177,9 @@ class CuttingSpeed(models.Model): }) else: feed_per_tooth.write({ + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'materials_type_id': self.env['sf.materials.model'].search( [('materials_no', '=', item['materials_type_code'])]).id, 'cutting_width_depth_id': self.env['sf.cutting.width.depth'].search( @@ -2454,6 +2466,11 @@ class CuttingToolBasicParameters(models.Model): else: self.search([('code', '=', integral_tool_item['code'])]).write({ 'name': integral_tool_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [( + 'code', '=', + integral_tool_item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'total_length': integral_tool_item['total_length'], 'blade_diameter': integral_tool_item['blade_diameter'], 'blade_length': integral_tool_item['blade_length'], @@ -2516,6 +2533,9 @@ class CuttingToolBasicParameters(models.Model): else: self.search([('code', '=', blade_item['code'])]).write({ 'name': blade_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', blade_item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'length': blade_item['length'], 'thickness': blade_item['thickness'], 'cutting_blade_length': blade_item['cutting_blade_length'], @@ -2573,6 +2593,9 @@ class CuttingToolBasicParameters(models.Model): else: self.search([('code', '=', chuck_item['code'])]).write({ 'name': chuck_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', chuck_item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'er_size_model': chuck_item['size_model'], 'min_clamping_diameter': chuck_item['clamping_diameter_min'], 'max_clamping_diameter': chuck_item['clamping_diameter_max'], @@ -2632,6 +2655,9 @@ class CuttingToolBasicParameters(models.Model): else: self.search([('code', '=', cutter_arbor_item['code'])]).write({ 'name': cutter_arbor_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', cutter_arbor_item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'height': cutter_arbor_item['height'], 'width': cutter_arbor_item['width'], 'total_length': cutter_arbor_item['total_length'], @@ -2697,6 +2723,9 @@ class CuttingToolBasicParameters(models.Model): else: self.search([('code', '=', cutter_head_item['code'])]).write({ 'name': cutter_head_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', cutter_head_item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'install_blade_tip_num': cutter_head_item['number_blade_installed'], 'blade_diameter': cutter_head_item['blade_diameter'], 'cutter_head_diameter': cutter_head_item['cutter_diameter'], @@ -2727,6 +2756,9 @@ class CuttingToolBasicParameters(models.Model): [('code', '=', knife_handle_item['code']), ('active', 'in', [True, False])]) val = { 'name': knife_handle_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', knife_handle_item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'taper_shank_model': knife_handle_item['taper_shank_model'], 'total_length': knife_handle_item['total_length'], 'flange_shank_length': knife_handle_item['flange_length'], @@ -2751,9 +2783,6 @@ class CuttingToolBasicParameters(models.Model): if not knife_handle: val['code'] = knife_handle_item['code'] val['cutting_tool_type'] = '刀柄' - val['standard_library_id'] = self.env['sf.cutting_tool.standard.library'].search( - [('code', '=', knife_handle_item['standard_library_code'].replace("JKM", result[ - 'factory_short_name']))]).id self.create(val) else: self.search([('code', '=', knife_handle_item['code'])]).write(val) @@ -2809,6 +2838,11 @@ class CuttingToolBasicParameters(models.Model): else: integral_tool.write({ 'name': integral_tool_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [( + 'code', '=', + integral_tool_item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'total_length': integral_tool_item['total_length'], 'blade_diameter': integral_tool_item['blade_diameter'], 'blade_length': integral_tool_item['blade_length'], @@ -2871,6 +2905,9 @@ class CuttingToolBasicParameters(models.Model): else: blade.write({ 'name': blade_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', blade_item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'length': blade_item['length'], 'thickness': blade_item['thickness'], 'cutting_blade_length': blade_item['cutting_blade_length'], @@ -2928,6 +2965,9 @@ class CuttingToolBasicParameters(models.Model): else: chuck.write({ 'name': chuck_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', chuck_item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'er_size_model': chuck_item['size_model'], 'min_clamping_diameter': chuck_item['clamping_diameter_min'], 'max_clamping_diameter': chuck_item['clamping_diameter_max'], @@ -2987,6 +3027,9 @@ class CuttingToolBasicParameters(models.Model): else: cutter_arbor.write({ 'name': cutter_arbor_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', cutter_arbor_item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'height': cutter_arbor_item['height'], 'width': cutter_arbor_item['width'], 'total_length': cutter_arbor_item['total_length'], @@ -3053,6 +3096,9 @@ class CuttingToolBasicParameters(models.Model): else: cutter_head.write({ 'name': cutter_head_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', cutter_head_item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'install_blade_tip_num': cutter_head_item['number_blade_installed'], 'blade_diameter': cutter_head_item['blade_diameter'], 'cutter_head_diameter': cutter_head_item['cutter_diameter'], @@ -3114,6 +3160,9 @@ class CuttingToolBasicParameters(models.Model): else: knife_handle.write({ 'name': knife_handle_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', knife_handle_item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'total_length': knife_handle_item['total_length'], 'taper_shank_model': knife_handle_item['taper_shank_model'], 'flange_shank_length': knife_handle_item['flange_length'], diff --git a/sf_quality/models/quality.py b/sf_quality/models/quality.py index 4b6c1774..6bfdf4f1 100644 --- a/sf_quality/models/quality.py +++ b/sf_quality/models/quality.py @@ -85,6 +85,18 @@ class QualityCheck(models.Model): raise ValidationError('请填写【判定结果】里的信息') if self.test_results == '合格': raise ValidationError('请重新选择【判定结果】-【检测结果】') + if self.workorder_id.routing_type != 'CNC加工' and self.workorder_id.individuation_page_PTD is False: + self.workorder_id.production_id.write({'detection_result_ids': [(0, 0, { + 'rework_reason': self.workorder_id.reason, + 'detailed_reason': self.workorder_id.detailed_reason, + 'processing_panel': self.workorder_id.processing_panel, + 'routing_type': self.workorder_id.routing_type, + 'handle_result': '待处理' if (self.workorder_id.test_results in ['返工', '报废'] + or self.workorder_id.is_rework is True) else '', + 'test_results': self.workorder_id.test_results, + 'test_report': self.workorder_id.detection_report})], + 'is_scrap': True if self.workorder_id.test_results == '报废' else False + }) if self.workorder_id.state not in ['done']: self.workorder_id.write( {'test_results': self.test_results, 'reason': self.reason, 'detailed_reason': self.detailed_reason}) diff --git a/sf_quality/views/quality_check_view.xml b/sf_quality/views/quality_check_view.xml index 61309737..00b139de 100644 --- a/sf_quality/views/quality_check_view.xml +++ b/sf_quality/views/quality_check_view.xml @@ -74,6 +74,7 @@ + @@ -89,8 +90,15 @@ - + + { + 'is_web_request': True, + 'search_default_progress': 1, + 'search_default_passed': 1, + 'search_default_failed': 1, + } + \ No newline at end of file diff --git a/sf_sale/static/src/css/purchase_list.css b/sf_sale/static/src/css/purchase_list.css index 2deacb9c..5e2aa86e 100644 --- a/sf_sale/static/src/css/purchase_list.css +++ b/sf_sale/static/src/css/purchase_list.css @@ -1,3 +1,9 @@ .purchase_order_list_name { min-width: 62px !important; +} + +.section_and_note_text span{ + white-space: wrap!important; + overflow: auto!important; + text-overflow: unset!important; } \ No newline at end of file diff --git a/sf_sale/views/sale_order_view.xml b/sf_sale/views/sale_order_view.xml index 1c96bf59..d408e807 100644 --- a/sf_sale/views/sale_order_view.xml +++ b/sf_sale/views/sale_order_view.xml @@ -102,7 +102,7 @@ + string="模型文件" attrs="{'readonly': [('state', 'in', ['draft'])], 'isInList': True}"/> @@ -112,6 +112,7 @@ {'no_create': True} {'is_sale_order_line': True } + section_and_note_text {'readonly': [('state', 'in', ['cancel','sale'])]} @@ -228,6 +229,18 @@ + + sale.order.line.tree.sale.stock.qty.sf + + sale.order + + + + + + + + sale.order.quotation.tree.inherit.sf sale.order diff --git a/sf_stock/models/stock_picking.py b/sf_stock/models/stock_picking.py index f8105097..7b73485b 100644 --- a/sf_stock/models/stock_picking.py +++ b/sf_stock/models/stock_picking.py @@ -18,7 +18,7 @@ class StockPicking(models.Model): @api.depends('name') def _compute_pro_purchase_count(self): for sp in self: - if sp: + if sp.name and sp.name != '/': po_ids = self.env['purchase.order'].sudo().search([ ('origin', 'like', sp.name), ('purchase_type', '=', 'standard')]) if po_ids: @@ -52,7 +52,7 @@ class StockPicking(models.Model): @api.depends('name') def _compute_pro_out_purchase_count(self): for sp in self: - if sp: + if sp.name and sp.name != '/': po_ids = self.env['purchase.order'].sudo().search([ ('origin', 'like', sp.name), ('purchase_type', '=', 'outsourcing')]) if po_ids: diff --git a/sf_warehouse/models/model.py b/sf_warehouse/models/model.py index fb80618a..582d7a59 100644 --- a/sf_warehouse/models/model.py +++ b/sf_warehouse/models/model.py @@ -935,6 +935,17 @@ class SfStockPicking(models.Model): _inherit = 'stock.picking' check_in = fields.Char(string='查询是否为入库单', compute='_check_is_in') + product_uom_qty_sp = fields.Float('需求数量', compute='_compute_product_uom_qty_sp', store=True) + + @api.depends('move_ids_without_package', 'move_ids_without_package.product_uom_qty') + def _compute_product_uom_qty_sp(self): + for sp in self: + if sp.move_ids_without_package: + sp.product_uom_qty_sp = 0 + for move_id in sp.move_ids_without_package: + sp.product_uom_qty_sp += move_id.product_uom_qty + else: + sp.product_uom_qty_sp = 0 def batch_stock_move(self): """ diff --git a/sf_warehouse/views/change_stock_move_views.xml b/sf_warehouse/views/change_stock_move_views.xml index 9e1073c9..8cde3ca3 100644 --- a/sf_warehouse/views/change_stock_move_views.xml +++ b/sf_warehouse/views/change_stock_move_views.xml @@ -156,6 +156,9 @@