增加测试数据清理模块
This commit is contained in:
3
jikimo_test_assistant/__init__.py
Normal file
3
jikimo_test_assistant/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from . import models
|
||||||
|
from . import controllers
|
||||||
|
from . import wizards
|
||||||
32
jikimo_test_assistant/__manifest__.py
Normal file
32
jikimo_test_assistant/__manifest__.py
Normal file
@@ -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',
|
||||||
|
}
|
||||||
2
jikimo_test_assistant/controllers/__init__.py
Normal file
2
jikimo_test_assistant/controllers/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from . import main
|
||||||
86
jikimo_test_assistant/controllers/main.py
Normal file
86
jikimo_test_assistant/controllers/main.py
Normal file
@@ -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)
|
||||||
|
|
||||||
1
jikimo_test_assistant/models/__init__.py
Normal file
1
jikimo_test_assistant/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
2
jikimo_test_assistant/security/ir.model.access.csv
Normal file
2
jikimo_test_assistant/security/ir.model.access.csv
Normal file
@@ -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
|
||||||
|
50
jikimo_test_assistant/static/src/js/data_clean_confirm.js
Normal file
50
jikimo_test_assistant/static/src/js/data_clean_confirm.js
Normal file
@@ -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 = "确认清理数据?<br/>"
|
||||||
|
message += "日期:"+ params.date + "以前<br/>"
|
||||||
|
message += "模型:" + params.model_names.join(',')
|
||||||
|
const dialog = new Dialog(parent, {
|
||||||
|
title: "确认",
|
||||||
|
$content: $('<div>').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;
|
||||||
|
});
|
||||||
2
jikimo_test_assistant/wizards/__init__.py
Normal file
2
jikimo_test_assistant/wizards/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from . import jikimo_data_clean_wizard
|
||||||
99
jikimo_test_assistant/wizards/jikimo_data_clean_wizard.py
Normal file
99
jikimo_test_assistant/wizards/jikimo_data_clean_wizard.py
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
47
jikimo_test_assistant/wizards/jikimo_data_clean_wizard.xml
Normal file
47
jikimo_test_assistant/wizards/jikimo_data_clean_wizard.xml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<!-- Form View -->
|
||||||
|
<record id="view_jikimo_data_clean_form" model="ir.ui.view">
|
||||||
|
<field name="name">jikimo.data.clean.wizard.form</field>
|
||||||
|
<field name="model">jikimo.data.clean.wizard</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="业务数据清理">
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<field name="date"/>
|
||||||
|
<field name="model_ids" widget="many2many_tags"/>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
<footer>
|
||||||
|
<button name="action_clean_data_confirm"
|
||||||
|
string="确认清理"
|
||||||
|
type="object"
|
||||||
|
class="btn-primary"/>
|
||||||
|
<button special="cancel"
|
||||||
|
string="取消"
|
||||||
|
class="btn-secondary"/>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Action -->
|
||||||
|
<record id="action_jikimo_data_clean" model="ir.actions.act_window">
|
||||||
|
<field name="name">业务数据清理</field>
|
||||||
|
<field name="res_model">jikimo.data.clean.wizard</field>
|
||||||
|
<field name="view_mode">form</field>
|
||||||
|
<field name="target">new</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Menu -->
|
||||||
|
<menuitem id="menu_test_root"
|
||||||
|
name="测试"
|
||||||
|
parent="base.menu_custom"
|
||||||
|
sequence="100"/>
|
||||||
|
|
||||||
|
<menuitem id="menu_jikimo_data_clean"
|
||||||
|
name="业务数据清理"
|
||||||
|
parent="menu_test_root"
|
||||||
|
action="action_jikimo_data_clean"
|
||||||
|
sequence="10"/>
|
||||||
|
</odoo>
|
||||||
@@ -91,12 +91,14 @@
|
|||||||
<xpath expr="//field[@name='product_id']" position="after">
|
<xpath expr="//field[@name='product_id']" position="after">
|
||||||
<field name="production_id"/>
|
<field name="production_id"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//filter[@name='progress']" position="before">
|
|
||||||
<filter name="waiting" string="待检测" domain="[('quality_state', '=', 'waiting')]"/>
|
|
||||||
</xpath>
|
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
<record id="quality_control.quality_check_action_main" model="ir.actions.act_window">
|
<record id="quality_control.quality_check_action_main" model="ir.actions.act_window">
|
||||||
<field name="context">{'is_web_request': True, 'search_default_waiting': 1}</field>
|
<field name="context">{
|
||||||
|
'is_web_request': True,
|
||||||
|
'search_default_progress': 1,
|
||||||
|
'search_default_passed': 1,
|
||||||
|
'search_default_failed': 1,
|
||||||
|
}</field>
|
||||||
</record>
|
</record>
|
||||||
</odoo>
|
</odoo>
|
||||||
Reference in New Issue
Block a user